CitrixPot: NetScaler / Citrix ADC Gateway honeypot (initial public release)
This commit is contained in:
commit
4491b42714
54 files changed
+4532
No files matched your search
@@ -0,0 +1,100 @@
|
||||
"""Offline tests: fixture sockets only; no external connections."""
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import socket
|
||||
import struct
|
||||
import tempfile
|
||||
import threading
|
||||
from unittest.mock import patch
|
||||
from types import SimpleNamespace
|
||||
|
||||
import network_capture as net
|
||||
from evidence import Evidence
|
||||
|
||||
|
||||
def run():
|
||||
for address in ['127.0.0.1', '10.0.0.1', '169.254.169.254', '192.168.1.1', '224.0.0.1', '0.0.0.0', '100.64.0.1', '192.0.2.1', '::1']:
|
||||
try:
|
||||
net.public_ip(address)
|
||||
raise AssertionError(address)
|
||||
except ValueError:
|
||||
pass
|
||||
assert net.public_ip('8.8.8.8') == '8.8.8.8'
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
net.STOP = pathlib.Path(tmp) / 'STOP'
|
||||
peers = pathlib.Path(tmp) / 'peers.json'
|
||||
peers.write_text(json.dumps(['10.0.0.1', '8.8.8.8', 'bad', '127.0.0.1', 134744072]))
|
||||
peers.chmod(0o600)
|
||||
actual_stat = os.stat(peers)
|
||||
# Root ownership is mocked only for this unit check on unprivileged Macs.
|
||||
with patch.object(net.os, 'fstat', return_value=SimpleNamespace(
|
||||
st_mode=actual_stat.st_mode, st_uid=0, st_mtime=actual_stat.st_mtime)):
|
||||
assert net.allowed_peers(peers) == {'8.8.8.8'}
|
||||
evidence = Evidence(pathlib.Path(tmp) / 'evidence')
|
||||
server = net.Server(('127.0.0.1', 0), net.Broker, evidence=evidence, peers=str(pathlib.Path(tmp) / 'absent'))
|
||||
worker = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
worker.start()
|
||||
original_connect = socket.create_connection
|
||||
outgoing = []
|
||||
def fixture_connect(destination, timeout=None, **kwargs):
|
||||
assert destination == ('8.8.8.8', 80), destination
|
||||
outgoing.append(destination)
|
||||
broker_end, fixture_end = socket.socketpair()
|
||||
def echo():
|
||||
with fixture_end:
|
||||
while True:
|
||||
data = fixture_end.recv(16384)
|
||||
if not data:
|
||||
break
|
||||
fixture_end.sendall(data)
|
||||
threading.Thread(target=echo, daemon=True).start()
|
||||
return broker_end
|
||||
with patch.object(net.socket, 'create_connection', fixture_connect):
|
||||
with original_connect(server.server_address, timeout=3) as client:
|
||||
net.send_message(client, {'op': 'connect', 'ip': '127.0.0.1', 'port': 80})
|
||||
assert not net.read_message(client)['ok']
|
||||
with original_connect(server.server_address, timeout=3) as client:
|
||||
net.send_message(client, {'op': 'connect', 'ip': '8.8.8.8', 'port': 4444})
|
||||
assert not net.read_message(client)['ok']
|
||||
with original_connect(server.server_address, timeout=3) as client:
|
||||
net.send_message(client, {'op': 'connect', 'ip': '8.8.8.8', 'port': 80})
|
||||
assert net.read_message(client)['ok']
|
||||
payload = b'\x00malware-fixture\xff\r\n'
|
||||
client.sendall(payload)
|
||||
data = b''
|
||||
while len(data) < len(payload):
|
||||
data += client.recv(1024)
|
||||
assert data == payload
|
||||
client.shutdown(socket.SHUT_WR)
|
||||
assert client.recv(1) == b''
|
||||
assert len(outgoing) == 1
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
rows = [json.loads(line) for line in (evidence.directory / 'events.jsonl').read_text().splitlines()]
|
||||
chunks = [r for r in rows if r['event'] == 'network_chunk']
|
||||
assert {r['direction'] for r in chunks} == {'guest_to_remote', 'remote_to_guest'}
|
||||
for row in chunks:
|
||||
assert (evidence.directory / row['artifact']).read_bytes() == payload
|
||||
assert row['time_ns'] and row['connection_id']
|
||||
# DNS parser rejects malformed/compressed questions; AAAA returns no answer.
|
||||
assert net.dns_reply(b'bad', ('unused', 0)) is None
|
||||
question = struct.pack('!6H', 7, 0x100, 1, 0, 0, 0) + b'\x07example\x03com\0' + struct.pack('!HH', 28, 1)
|
||||
reply = net.dns_reply(question, ('unused', 0))
|
||||
assert struct.unpack('!6H', reply[:12])[3] == 0
|
||||
# Capturing failure closes streams before further forward and persists STOP.
|
||||
evidence.quota = evidence.used
|
||||
failing = net.Server(('127.0.0.1', 0), net.Broker, evidence=evidence, peers='absent')
|
||||
threading.Thread(target=failing.serve_forever, daemon=True).start()
|
||||
with original_connect(failing.server_address, timeout=3) as client:
|
||||
net.send_message(client, {'op': 'connect', 'ip': '8.8.8.8', 'port': 80})
|
||||
assert client.recv(1) == b''
|
||||
assert net.STOP.exists() and failing.failed.is_set()
|
||||
failing.shutdown()
|
||||
failing.server_close()
|
||||
evidence.close()
|
||||
print('network capture offline checks passed')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
Reference in new issue
Block a user