57 lines
2.6 KiB
Python
57 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
"""Harmless path checks against the local tunnel to the decoy; no payload execution."""
|
||
|
|
import argparse
|
||
|
|
import http.client
|
||
|
|
import ipaddress
|
||
|
|
import urllib.parse
|
||
|
|
|
||
|
|
PATHS = (
|
||
|
|
'/reports', '/reports/', '/admin', '/admin/', '/dashboard', '/evidence', '/evidence/',
|
||
|
|
'/logs', '/logs/', '/logs/events.jsonl', '/events.jsonl', '/artifacts/',
|
||
|
|
'/artifacts/' + '0' * 64 + '.bin', '/http/events.jsonl', '/network/events.jsonl',
|
||
|
|
'/var/lib/decoy/http/events.jsonl', '/var/lib/decoy/network/events.jsonl',
|
||
|
|
'/opt/decoy/honeypot.py', '/honeypot.py', '/evidence.py', '/vm_backend.py', '/guest.py',
|
||
|
|
'/network_capture.py', '/ui/sources.json', '/README.md',
|
||
|
|
'/../var/lib/decoy/http/events.jsonl', '/../../var/lib/decoy/http/events.jsonl',
|
||
|
|
'/%2e%2e/%2e%2e/var/lib/decoy/http/events.jsonl',
|
||
|
|
'/%2e%2e%2f%2e%2e%2fvar%2flib%2fdecoy%2fhttp%2fevents.jsonl',
|
||
|
|
'/%252e%252e%252fvar%252flib%252fdecoy%252fhttp%252fevents.jsonl',
|
||
|
|
'/..%5c..%5cvar%5clib%5cdecoy%5chttp%5cevents.jsonl',
|
||
|
|
'//var/lib/decoy/http/events.jsonl', '/logs/../events.jsonl', '/logs%00/events.jsonl',
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
||
|
|
parser.add_argument('--url', required=True, help='HTTP loopback tunnel URL')
|
||
|
|
args = parser.parse_args()
|
||
|
|
url = urllib.parse.urlsplit(args.url)
|
||
|
|
if url.scheme != 'http' or url.username or url.password or url.query or url.fragment:
|
||
|
|
parser.error('use an HTTP loopback tunnel URL without credentials or query')
|
||
|
|
try:
|
||
|
|
loopback = ipaddress.ip_address(url.hostname).is_loopback
|
||
|
|
except ValueError:
|
||
|
|
loopback = url.hostname == 'localhost'
|
||
|
|
if not loopback or url.path not in ('', '/'):
|
||
|
|
parser.error('test is restricted to the loopback tunnel root')
|
||
|
|
def request(path):
|
||
|
|
conn = http.client.HTTPConnection(url.hostname, url.port or 80, timeout=25)
|
||
|
|
try:
|
||
|
|
conn.request('GET', path)
|
||
|
|
response = conn.getresponse()
|
||
|
|
body = response.read(131073)
|
||
|
|
assert len(body) <= 131072, 'unexpected large response for ' + path
|
||
|
|
return response.status, body
|
||
|
|
finally:
|
||
|
|
conn.close()
|
||
|
|
status, body = request('/')
|
||
|
|
assert status == 200 and b'<html' in body.lower(), 'decoy UI unavailable'
|
||
|
|
for path in PATHS:
|
||
|
|
status, body = request(path)
|
||
|
|
assert status == 404, f'private path returned {status}: {path}'
|
||
|
|
assert body == b'Not Found', 'unexpected content at private path: ' + path
|
||
|
|
print(f'PASS: NetScaler UI available; {len(PATHS)} private/report/evidence/source/traversal paths return plain404')
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
main()
|