HIGH path traversalloopback

Path Traversal in Loopback

How Path Traversal Manifests in Loopback

Path traversal vulnerabilities in Loopback applications typically emerge through improper handling of file system operations combined with user-controlled input. In Loopback's architecture, these vulnerabilities often appear in custom remote methods, middleware, or when using Loopback's file upload capabilities.

The most common pattern involves using fs module operations where the file path is constructed from user input without proper validation. For example, a Loopback controller might expose a method to download user files:

import {get} from '@loopback/rest';
import fs from 'fs';
import path from 'path';

export class FileController {
  @get('/download', {
    responses: {
      '200': {
        description: 'Download file',
        content: {
          'application/octet-stream': {
            'x-ts-type': Buffer,
          },
        },
      },
    },
  })
  async downloadFile(@param.query.string('filename') filename: string) {
    const filePath = path.join(__dirname, '../uploads/', filename);
    return fs.promises.readFile(filePath);
  }
}

This code is vulnerable because an attacker can supply filename=../../etc/passwd to traverse outside the intended directory. The path.join() function normalizes the path, allowing ../ sequences to escape the uploads directory.

Loopback's file upload system can also introduce path traversal risks. When using @loopback/rest's file upload handling, developers might inadvertently expose file paths:

import {post, Request} from '@loopback/rest';
import {UploadFile} from '@loopback/rest/dist/lib/upload-file.decorator';

export class UploadController {
  @post('/upload', {
    responses: {
      '200': {
        description: 'File uploaded successfully',
      },
    },
  })
  async uploadFile(
    @requestBody.file() request: Request,
    @UploadFile() upload: UploadFile,
  ) {
    const filePath = upload.file.path; // User-controlled path
    // Later, this path might be used without validation
    return {message: 'Uploaded', path: filePath};
  }
}

Another Loopback-specific scenario involves dynamic model resolution. Loopback's model discovery can be exploited if model paths are constructed from user input:

import {get} from '@loopback/rest';

export class ModelController {
  @get('/model/{modelName}')
  async getModel(@param.path.string('modelName') modelName: string) {
    const modelPath = `./models/${modelName}.json`;
    return require(modelPath); // Path traversal possible
  }
}

Loopback's middleware system can also be a vector. Custom middleware that processes file paths without validation can introduce traversal vulnerabilities:

export class StaticFileMiddleware implements MiddlewareFn {
  async handle(context: MiddlewareContext) {
    const {request} = context;
    const filePath = path.join(__dirname, 'public', request.path);
    if (fs.existsSync(filePath)) {
      return fs.createReadStream(filePath);
    }
  }
}

Loopback-Specific Detection

Detecting path traversal in Loopback applications requires both static code analysis and runtime scanning. Static analysis should focus on identifying patterns where file paths are constructed from user input.

Using middleBrick's scanner, you can identify path traversal vulnerabilities in your Loopback APIs without any configuration. The scanner examines your API endpoints for common traversal patterns:

npm install -g middlebrick
middlebrick scan https://your-loopback-app.com/api

The scanner tests for traversal attempts using various payloads like ../../etc/passwd, ../ sequences, and URL-encoded variants. It also checks for improper use of path.join(), path.resolve(), and other path manipulation functions.

For Loopback applications, middleBrick specifically looks for:

  • Remote methods that accept file paths as parameters
  • Custom middleware that processes file system operations
  • Dynamic model loading based on user input
  • File upload handlers that expose file paths
  • Static file serving implementations

The scanner provides detailed findings with severity levels and remediation guidance. For example, it might report:

Path Traversal Vulnerability in FileController.downloadFile
Severity: High
Endpoint: GET /download
Issue: User-controlled filename parameter allows directory traversal
Recommendation: Validate filename against whitelist or use path normalization

middleBrick's continuous monitoring (Pro plan) can automatically scan your Loopback APIs on a schedule, alerting you to new vulnerabilities as they're introduced during development.

Loopback-Specific Remediation

Remediating path traversal in Loopback applications involves validating and sanitizing file paths before use. The most effective approach combines input validation with safe path handling.

For file download endpoints, implement strict validation:

import {get} from '@loopback/rest';
import fs from 'fs';
import path from 'path';

export class SecureFileController {
  @get('/download', {
    responses: {
      '200': {
        description: 'Download file',
        content: {
          'application/octet-stream': {
            'x-ts-type': Buffer,
          },
        },
      },
    },
  })
  async downloadFile(@param.query.string('filename') filename: string) {
    // Validate filename - only allow alphanumeric, hyphen, underscore, dot
    if (!/^[a-zA-Z0-9._-]+$/.test(filename)) {
      throw new HttpErrors.BadRequest('Invalid filename');
    }

    const uploadDir = path.join(__dirname, '../uploads/');
    const filePath = path.resolve(uploadDir, filename);
    
    // Ensure file is within the uploads directory
    if (!filePath.startsWith(uploadDir)) {
      throw new HttpErrors.Forbidden('Path traversal attempt detected');
    }

    if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
      throw new HttpErrors.NotFound('File not found');
    }

    return fs.promises.readFile(filePath);
  }
}

For file uploads, use Loopback's built-in file handling with additional validation:

import {post} from '@loopback/rest';
import {UploadFile} from '@loopback/rest/dist/lib/upload-file.decorator';

export class SecureUploadController {
  @post('/upload', {
    responses: {
      '200': {
        description: 'File uploaded successfully',
      },
    },
  })
  async uploadFile(
    @requestBody.file() request: Request,
    @UploadFile() upload: UploadFile,
  ) {
    const uploadDir = path.join(__dirname, '../uploads/');
    const fileName = upload.file.originalName;
    
    // Validate file name
    if (!/^[a-zA-Z0-9._-]+$/.test(fileName)) {
      throw new HttpErrors.BadRequest('Invalid filename');
    }

    const targetPath = path.join(uploadDir, fileName);
    
    // Ensure target path is within upload directory
    if (!targetPath.startsWith(uploadDir)) {
      throw new HttpErrors.Forbidden('Invalid file path');
    }

    // Save file securely
    await fs.promises.mkdir(uploadDir, {recursive: true});
    await fs.promises.rename(upload.file.path, targetPath);

    return {message: 'Uploaded successfully', path: targetPath};
  }
}

For dynamic model loading, use a whitelist approach:

import {get} from '@loopback/rest';

const VALID_MODELS = ['user', 'product', 'order'];

export class ModelController {
  @get('/model/{modelName}')
  async getModel(@param.path.string('modelName') modelName: string) {
    if (!VALID_MODELS.includes(modelName)) {
      throw new HttpErrors.NotFound('Model not found');
    }

    const modelPath = path.join(__dirname, 'models', `${modelName}.json`);
    return require(modelPath);
  }
}

For static file serving, use Loopback's built-in static middleware with a restricted root:

import {Application} from '@loopback/core';
import {RestApplication} from '@loopback/rest';
import path from 'path';

export class SecureApp extends RestApplication {
  constructor() {
    super();
    
    // Serve static files from a restricted directory
    this.static('/', path.join(__dirname, 'public'));
  }
}

Related CWEs: inputValidation

CWE IDNameSeverity
CWE-20Improper Input Validation HIGH
CWE-22Path Traversal HIGH
CWE-74Injection CRITICAL
CWE-77Command Injection CRITICAL
CWE-78OS Command Injection CRITICAL
CWE-79Cross-site Scripting (XSS) HIGH
CWE-89SQL Injection CRITICAL
CWE-90LDAP Injection HIGH
CWE-91XML Injection HIGH
CWE-94Code Injection CRITICAL

Frequently Asked Questions

How does middleBrick detect path traversal in Loopback applications?
middleBrick uses black-box scanning to test your Loopback API endpoints for path traversal vulnerabilities. It sends requests with traversal payloads like '../../etc/passwd' and analyzes the responses for signs of successful traversal. The scanner also examines your OpenAPI spec to identify endpoints that handle file paths or have file-related parameters, then tests those specifically. No source code access or credentials are required - it works by interacting with your running API just like an attacker would.
Can path traversal lead to remote code execution in Loopback?
Yes, path traversal can potentially lead to remote code execution if an attacker can access and modify server-side files. For example, if traversal allows reading server configuration files, it might expose database credentials. More critically, if traversal allows writing to directories like 'node_modules' or modifying server scripts, an attacker could inject malicious code. This is why proper validation and ensuring the application runs with minimal privileges is essential for Loopback applications.