Secure handling of file uploads
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.
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.