Skip to main content

External control of file name or path

Need

Secure handling of file uploads

Context

  • Usage of Dart 2.0 for building high-performance, cross-platform applications
  • Usage of Shelf for managing and organizing data in a Node.js application
  • Usage of shelf_io for managing input/output operations in a Shelf application

Description

Non compliant code

import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;

void main() async {
var server = await io.serve(handler, 'localhost', 8080);

print('Serving at http://${server.address.host}:${server.port}');
}

shelf.Response handler(shelf.Request request) {
if (request.url.path == 'upload') {
var filePath = request.headers['filePath'];
var fileContent = request.headers['fileContent'];

var file = File(filePath);
file.writeAsStringSync(fileContent);

return shelf.Response.ok('File uploaded successfully');
}

return shelf.Response.notFound('Not Found');
}

In the above code, we have a simple HTTP server built using the shelf framework. The server listens for a request at the /upload endpoint and then takes the filePath and fileContent from the headers of the request.

The filePath is used to create a new file on the server's filesystem, and the fileContent is written to that file.

The vulnerability here lies in the fact that the server trusts the filePath header from the request. An attacker could manipulate the filePath to overwrite any file on the server's filesystem, or to write new files in sensitive locations.

For example, an attacker could set filePath to /etc/passwd to overwrite the server's password file, or to /var/www/html/evil.html to serve malicious content from the server's web root.

This is a classic example of the 'External Control of File Name or Path' vulnerability, where the application fails to properly validate input that is used to construct file names or paths.

Steps

  • Validate the file path provided in the request to ensure it is within the expected directory.
  • Do not allow absolute paths or paths that contain '..' to prevent directory traversal attacks.
  • Consider using a library or built-in function to safely join paths.
  • Generate a server-defined filename instead of using the one provided in the request.
  • Ensure that the application has the least privilege on the file system, especially on the directories where the files are stored.

Compliant code

import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as io;

void main() async {
var server = await io.serve(handler, 'localhost', 8080);

print('Serving at http://${server.address.host}:${server.port}');
}

shelf.Response handler(shelf.Request request) {
if (request.url.path == 'upload') {
var filePath = request.headers['filePath'];
var fileContent = request.headers['fileContent'];

// Validate the file path
if (filePath.contains('..') || path.isAbsolute(filePath)) {
return shelf.Response.forbidden('Invalid file path');
}

// Safely join paths
var safeFilePath = path.join('/expected/directory', filePath);

// Generate a server-defined filename
var fileName = path.basename(safeFilePath);
var serverDefinedFilePath = path.join('/expected/directory', fileName);

var file = File(serverDefinedFilePath);
file.writeAsStringSync(fileContent);

return shelf.Response.ok('File uploaded successfully');
}

return shelf.Response.notFound('Not Found');
}

The updated code includes several changes to address the vulnerability:

  1. File Path Validation: The code now checks if the provided file path contains '..' or is an absolute path. If either condition is true, the server responds with a 'Forbidden' status, preventing potential directory traversal attacks.

  2. Safe Path Joining: The path.join function from the path package is used to safely join the expected directory with the provided file path. This ensures that the file is always saved within the expected directory, even if the provided file path tries to navigate to a different directory.

  3. Server-Defined Filename: Instead of using the filename provided in the request, the server now generates its own filename using the path.basename function. This prevents an attacker from controlling the file name or path.

  4. Least Privilege: Although not shown in the code, it is recommended to ensure that the application has the least privilege on the file system, especially on the directories where the files are stored. This can be done through proper configuration of the server and file system permissions.

References