VYPR
Moderate severityNVD Advisory· Published Oct 2, 2024· Updated Oct 31, 2024

OpenC3 COSMOS uses clear text storage of password/token (`GHSL-2024-129`)

CVE-2024-47529

Description

OpenC3 COSMOS provides the functionality needed to send commands to and receive data from one or more embedded systems. OpenC3 COSMOS stores 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 vulnerability is fixed in 5.19.0. This only affects Open Source edition, and not OpenC3 COSMOS Enterprise Edition.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
openc3RubyGems
< 5.19.05.19.0
@openc3/tool-commonnpm
< 5.19.05.19.0
openc3PyPI
< 5.19.05.19.0

Affected products

1

Patches

1
b5ab34fe7fa5

Switch to session token based auth

https://github.com/OpenC3/cosmosRyan MeltonSep 30, 2024via ghsa
5 files changed · +47 13
  • openc3-cosmos-cmd-tlm-api/app/controllers/auth_controller.rb+4 2 modified
    @@ -34,11 +34,12 @@ def token_exists
       def verify
         begin
           if OpenC3::AuthModel.verify(params[:token])
    -        head :ok
    +        render :plain => OpenC3::AuthModel.generate_session()
           else
             head :unauthorized
           end
         rescue StandardError => e
    +      OpenC3::Logger.error(e.formatted)
           render :json => { :status => 'error', :message => e.message, 'type' => e.class }, :status => 500
         end
       end
    @@ -48,8 +49,9 @@ def set
           # Set throws an exception if it fails for any reason
           OpenC3::AuthModel.set(params[:token], params[:old_token])
           OpenC3::Logger.info("Password changed", user: username())
    -      head :ok
    +      render :plain => OpenC3::AuthModel.generate_session()
         rescue StandardError => e
    +      OpenC3::Logger.error(e.formatted)
           render :json => { :status => 'error', :message => e.message, 'type' => e.class }, :status => 500
         end
       end
    
  • openc3-cosmos-cmd-tlm-api/app/controllers/users_controller.rb+1 0 modified
    @@ -26,6 +26,7 @@ def active()
         end
     
         def logout()
    +      OpenC3::AuthModel.logout
           head :ok
         end
       end
    
  • openc3-cosmos-init/plugins/packages/openc3-tool-common/src/tools/base/components/Login.vue+7 5 modified
    @@ -140,12 +140,12 @@ export default {
         showReset: function () {
           this.reset = true
         },
    -    login: function () {
    -      localStorage.openc3Token = this.password
    +    login: function (response) {
    +      localStorage.openc3Token = response.data
           const redirect = new URLSearchParams(window.location.search).get(
             'redirect',
           )
    -      if (redirect[0] === '/' && redirect[1] !== '/') {
    +      if (redirect.startsWith('/tools/')) {
             // Valid relative redirect URL
             window.location = decodeURI(redirect)
           } else {
    @@ -161,7 +161,7 @@ export default {
             ...this.options,
           })
             .then((response) => {
    -          this.login()
    +          this.login(response)
             })
             .catch((error) => {
               this.alert = 'Incorrect password'
    @@ -177,7 +177,9 @@ export default {
               token: this.password,
             },
             ...this.options,
    -      }).then(this.login)
    +      }).then((response) => {
    +        this.login(response)
    +      })
         },
       },
     }
    
  • openc3-cosmos-script-runner-api/app/controllers/scripts_controller.rb+4 4 modified
    @@ -56,7 +56,7 @@ def delete_temp
     
       def body
         return unless authorization('script_view')
    -    scope, name = sanitize_params([:scope, :name])
    +    scope, name = sanitize_params([:scope, :name], :allow_forward_slash => true)
         return unless scope
     
         file = Script.body(scope, name)
    @@ -126,15 +126,15 @@ def run
     
       def lock
         return unless authorization('script_edit')
    -    scope, name = sanitize_params([:scope, :name])
    +    scope, name = sanitize_params([:scope, :name], :allow_forward_slash => true)
         return unless scope
         Script.lock(scope, name, username())
         render status: 200
       end
     
       def unlock
         return unless authorization('script_edit')
    -    scope, name = sanitize_params([:scope, :name])
    +    scope, name = sanitize_params([:scope, :name], :allow_forward_slash => true)
         return unless scope
         locked_by = Script.locked?(scope, name)
         Script.unlock(scope, name) if username() == locked_by
    @@ -143,7 +143,7 @@ def unlock
     
       def destroy
         return unless authorization('script_edit')
    -    scope, name = sanitize_params([:scope, :name])
    +    scope, name = sanitize_params([:scope, :name], :allow_forward_slash => true)
         return unless scope
         Script.destroy(scope, name)
         OpenC3::Logger.info("Script destroyed: #{name}", scope: scope, user: username())
    
  • openc3/lib/openc3/models/auth_model.rb+31 2 modified
    @@ -21,15 +21,22 @@
     # if purchased from OpenC3, Inc.
     
     require 'digest'
    +require 'securerandom'
     require 'openc3/utilities/store'
     
     module OpenC3
       class AuthModel
         PRIMARY_KEY = 'OPENC3__TOKEN'
    +    SESSIONS_KEY = 'OPENC3__SESSIONS'
     
         TOKEN_CACHE_TIMEOUT = 5
    +    SESSION_CACHE_TIMEOUT = 5
         @@token_cache = nil
         @@token_cache_time = nil
    +    @@session_cache = nil
    +    @@session_cache_time = nil
    +
    +    MIN_TOKEN_LENGTH = 8
     
         def self.set?(key = PRIMARY_KEY)
           Store.exists(key) == 1
    @@ -38,14 +45,23 @@ def self.set?(key = PRIMARY_KEY)
         def self.verify(token)
           return false if token.nil? or token.empty?
     
    +      time = Time.now
    +      return true if @@session_cache and (time - @@session_cache_time) < SESSION_CACHE_TIMEOUT and @@session_cache[token]
           token_hash = hash(token)
    -      return true if @@token_cache and (Time.now - @@token_cache_time) < TOKEN_CACHE_TIMEOUT and @@token_cache == token_hash
    +      return true if @@token_cache and (time - @@token_cache_time) < TOKEN_CACHE_TIMEOUT and @@token_cache == token_hash
    +
    +      # Check sessions
    +      @@session_cache = Store.hgetall(SESSIONS_KEY)
    +      @@session_cache_time = time
    +      return true if @@session_cache[token]
     
    +      # Check Direct password
           @@token_cache = Store.get(PRIMARY_KEY)
    -      @@token_cache_time = Time.now
    +      @@token_cache_time = time
           return true if @@token_cache == token_hash
     
           # Handle a service password - Generally only used by ScriptRunner
    +      # TODO: Replace this with temporary service tokens
           service_password = ENV['OPENC3_SERVICE_PASSWORD']
           return true if service_password and service_password == token
     
    @@ -54,6 +70,7 @@ def self.verify(token)
     
         def self.set(token, old_token, key = PRIMARY_KEY)
           raise "token must not be nil or empty" if token.nil? or token.empty?
    +      raise "token must be at least 8 characters" if token.length < MIN_TOKEN_LENGTH
     
           if set?(key)
             raise "old_token must not be nil or empty" if old_token.nil? or old_token.empty?
    @@ -62,6 +79,18 @@ def self.set(token, old_token, key = PRIMARY_KEY)
           Store.set(key, hash(token))
         end
     
    +    def self.generate_session
    +      token = SecureRandom.urlsafe_base64(nil, false)
    +      Store.hset(SESSIONS_KEY, token, Time.now.iso8601)
    +      return token
    +    end
    +
    +    def self.logout
    +      Store.del(SESSIONS_KEY)
    +      @@sessions_cache = nil
    +      @@sessions_cache_time = nil
    +    end
    +
         def self.hash(token)
           Digest::SHA2.hexdigest token
         end
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.