Summary
The get-unit-log AJAX action accepts a logfile parameter that is concatenated directly into a filesystem path with no traversal check. Any authenticated user can read arbitrary files on the server that are readable by the web user (www-data), including repomanager's configuration, GPG signing-key passphrase, and source code.
Specifically, the parameter is passed through Validate::string(), which only escapes HTML (htmlspecialchars(stripslashes(trim()))) and leaves ../ completely untouched.
Steps
- Navigate to the Status Page and scroll to "Service Units"
- Load up Burpsuite (or any web interceptor like Devtools)
- Turn on intercept
- Click any "View" button in Service Units
- On Burpsuite/Interceptor, replace the name of logfile with a path (ex: &logfile=../../../../../../etc/passwd)
- Send request
Vulnerable file: www/controllers/ajax/status/service.php
Line 7: Does not filter "." or "/", so "../" passes through unchanged.
if ($action == 'get-unit-log' and !empty($_POST['unit']) and !empty($_POST['logfile'])) {
$logfile = \Controllers\Utils\Validate::string($_POST['logfile']);
Line 21: Attacker-controlled value is appended with no basename()/realpath() check.
$logfile = SERVICE_LOGS_DIR . '/' . $logDir . '/' . $logfile;
Line 29
`$content = file_get_contents($logfile);`
Line 36: file contents returned to the client.
`response(HTTP_OK, $content);`
PoC
For example, since we know start somewhere like /var/lib/repomanager/logs/service/notifications/, we can estimate where to find certain files.
Replacing the prior log name with a path retrieves all users through /etc/passwd
Fetching the path towards the signing key (.gnupg/passphrase)
Remediation/Recommendations
The proper way to solve this vuln is to actually strip any directory component from the user-supplied value, and/or verify the resolved path stays inside the intended directory. Validate::string() is an HTML-output escaper for XSS and is not suitable for validating
filesystem paths.
BEFORE (vulnerable) — www/controllers/ajax/status/service.php, line 7:
$logfile = \Controllers\Utils\Validate::string($_POST['logfile']);
AFTER (fixed):
$logfile = basename(\Controllers\Utils\Validate::string($_POST['logfile']));
basename() function in PHP is used to extract the filename from a given file path or URL, stripping away the directory structure. We can also use the realpath() function as an alternative to basename().
Impact
Arbitrary file disclosure/leak as the web user (www-data) by any authenticated account. Confirmed exposure includes:
- app.yaml - OIDC/SSO client secret on configured instances
- .gnupg/passphrase - passphrase protecting the repository GPG signing key
- application source code and system files such as /etc/passwd
This enables privilege escalation (a low-privilege user can harvest secrets belonging to the application and administrators/find more leaks within the source code)
Contact
Contact me at simonlee.dev2@gmail.com. Thank you very much!
Summary
The
get-unit-logAJAX action accepts alogfileparameter that is concatenated directly into a filesystem path with no traversal check. Any authenticated user can read arbitrary files on the server that are readable by the web user (www-data), including repomanager's configuration, GPG signing-key passphrase, and source code.Specifically, the parameter is passed through
Validate::string(), which only escapes HTML (htmlspecialchars(stripslashes(trim()))) and leaves../completely untouched.Steps
Vulnerable file: www/controllers/ajax/status/service.php
Line 7: Does not filter "." or "/", so "../" passes through unchanged.
Line 21: Attacker-controlled value is appended with no basename()/realpath() check.
Line 29
Line 36: file contents returned to the client.
PoC
For example, since we know start somewhere like /var/lib/repomanager/logs/service/notifications/, we can estimate where to find certain files.
Replacing the prior log name with a path retrieves all users through /etc/passwd
Fetching the path towards the signing key (.gnupg/passphrase)
Remediation/Recommendations
The proper way to solve this vuln is to actually strip any directory component from the user-supplied value, and/or verify the resolved path stays inside the intended directory. Validate::string() is an HTML-output escaper for XSS and is not suitable for validating
filesystem paths.
BEFORE (vulnerable) — www/controllers/ajax/status/service.php, line 7:
AFTER (fixed):
basename() function in PHP is used to extract the filename from a given file path or URL, stripping away the directory structure. We can also use the realpath() function as an alternative to basename().
Impact
Arbitrary file disclosure/leak as the web user (www-data) by any authenticated account. Confirmed exposure includes:
This enables privilege escalation (a low-privilege user can harvest secrets belonging to the application and administrators/find more leaks within the source code)
Contact
Contact me at simonlee.dev2@gmail.com. Thank you very much!