编写TLS协议的suricata规则-测试(9001127-9001153)
1.主机B开启弱TLS服务
from __future__ import annotations import argparse import socket import threading def check_tcp_port_available(listen_ip: str, port: int) -> None: probe = socket.socket(socket.AF_INET, socket.SOCK_STREAM) probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: probe.bind((listen_ip, port)) except OSError as exc: raise SystemExit( f"[server] TCP {listen_ip}:{port} is already in use. " f"Run `ss -ltnp | grep :{port}` on 10.10.10.2." ) from exc finally: probe.close() def check_udp_port_available(listen_ip: str, port: int) -> None: probe = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: probe.bind((listen_ip, port)) except OSError as exc: raise SystemExit( f"[server] UDP {listen_ip}:{port} is already in use. " f"Run `ss -lunp | grep :{port}` on 10.10.10.2." ) from exc finally: probe.close() def recv_with_timeout(conn: socket.socket, timeout: float = 1.0) -> bytes: conn.settimeout(timeout) chunks: list[bytes] = [] while True: try: data = conn.recv(4096) except socket.timeout: break if not data: break chunks.append(data) if len(data) < 4096: break return b"".join(chunks) def tls_record(version: bytes, handshake_type: int, body: bytes) -> bytes: handshake = bytes([handshake_type]) + len(body).to_bytes(3, "big") + body return b"\x16" + version + len(handshake).to_bytes(2, "big") + handshake def build_server_hello(version: bytes, cipher_suite: bytes) -> bytes: body = version + (b"\x11" * 32) + b"\x00" + cipher_suite + b"\x00" return tls_record(version, 0x02, body) def build_certificate_record(version: bytes, oid_bytes: bytes) -> bytes: body = b"\x00\x00\x10" + b"\x30\x82" + b"\x01\x22" + b"\x30\x81" + oid_bytes + b"\x05\x00" + (b"\x41" * 24) return tls_record(version, 0x0B, body) def build_server_key_exchange(version: bytes, curve_bytes: bytes) -> bytes: body = b"\x03" + curve_bytes + (b"\x22" * 24) return tls_record(version, 0x0C, body) def build_http_response(body: bytes, content_type: str = "text/plain") -> bytes: headers = [ b"HTTP/1.1 200 OK", b"Connection: close", f"Content-Length: {len(body)}".encode("ascii"), f"Content-Type: {content_type}".encode("ascii"), b"", b"", ] return b"\r\n".join(headers) + body TLS_PROFILES: dict[str, bytes] = { "RC4_MD5_TLS10": build_server_hello(b"\x03\x01", b"\x00\x04"), "RC4_SHA": build_server_hello(b"\x03\x03", b"\x00\x05"), "DH_ANON_RC4": build_server_hello(b"\x03\x03", b"\x00\x18"), "ECDHE_ECDSA_RC4": build_server_hello(b"\x03\x03", b"\xC0\x07"), "ECDHE_RSA_RC4": build_server_hello(b"\x03\x03", b"\xC0\x11"), "DES3_RSA_TLS11": build_server_hello(b"\x03\x02", b"\x00\x0A"), "DES3_DHE_DSS": build_server_hello(b"\x03\x03", b"\x00\x13"), "DES3_DHE_RSA": build_server_hello(b"\x03\x03", b"\x00\x16"), "DES3_ECDHE_ECDSA": build_server_hello(b"\x03\x03", b"\xC0\x08"), "DES3_ECDHE_RSA": build_server_hello(b"\x03\x03", b"\xC0\x12"), "CERT_MD5": build_certificate_record(b"\x03\x03", b"\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x04"), "CERT_SHA1_RSA": build_certificate_record(b"\x03\x03", b"\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x01\x05"), "CERT_ECDSA_SHA1": build_certificate_record(b"\x03\x03", b"\x06\x07\x2A\x86\x48\xCE\x3D\x04\x01"), "WEAK_CURVE": build_server_key_exchange(b"\x03\x03", b"\x00\x10"), "RSA_AES128_SHA": build_server_hello(b"\x03\x03", b"\x00\x2F"), "RSA_AES256_SHA": build_server_hello(b"\x03\x03", b"\x00\x35"), "RSA_AES128_SHA256": build_server_hello(b"\x03\x03", b"\x00\x3C"), "RSA_AES256_SHA256": build_server_hello(b"\x03\x03", b"\x00\x3D"), } def parse_tls_profile(request: bytes) -> str: text = request.decode("latin-1", errors="ignore").strip() if text.startswith("PROFILE "): return text.split(" ", 1)[1].strip().upper() return text.upper() def handle_tls_client(conn: socket.socket, addr: tuple[str, int], tls_port: int) -> None: try: request = recv_with_timeout(conn, 0.8) profile = parse_tls_profile(request) or "RC4_MD5_TLS10" payload = TLS_PROFILES.get(profile, TLS_PROFILES["RC4_MD5_TLS10"]) conn.sendall(payload) print(f"[server:{tls_port}] tls profile={profile} from {addr[0]}:{addr[1]}") except Exception as exc: # pragma: no cover - lab helper print(f"[server:{tls_port}] tls handler error from {addr}: {exc}") finally: try: conn.close() except OSError: pass def parse_http_path(request: bytes) -> str: first_line = request.split(b"\r\n", 1)[0].decode("latin-1", errors="ignore") parts = first_line.split(" ") if len(parts) >= 2: return parts[1] return "/" def handle_http_client(conn: socket.socket, addr: tuple[str, int], http_port: int) -> None: try: request = recv_with_timeout(conn, 1.0) path = parse_http_path(request) if path.startswith("/health"): body = b"ok\n" else: body = b"ok\n" conn.sendall(build_http_response(body)) first_line = request.split(b"\r\n", 1)[0].decode("latin-1", errors="ignore") print(f"[server:{http_port}] {addr[0]}:{addr[1]} {first_line}") except Exception as exc: # pragma: no cover - lab helper print(f"[server:{http_port}] http handler error from {addr}: {exc}") finally: try: conn.close() except OSError: pass def recv_line(conn: socket.socket) -> bytes: data = bytearray() while not data.endswith(b"\n"): chunk = conn.recv(1) if not chunk: break data.extend(chunk) return bytes(data) def handle_starttls_client(conn: socket.socket, addr: tuple[str, int], port: int) -> None: try: conn.settimeout(2.0) conn.sendall(b"220 weaktls-lab.example ESMTP ready\r\n") ehlo = recv_line(conn) if ehlo: conn.sendall(b"250-STARTTLS\r\n250 AUTH PLAIN LOGIN\r\n") auth_line = recv_line(conn) if auth_line: conn.sendall(b"235 2.7.0 accepted for test\r\n") print( f"[server:{port}] {addr[0]}:{addr[1]} " f"ehlo={ehlo.decode('latin-1', errors='ignore').strip()} " f"auth={auth_line.decode('latin-1', errors='ignore').strip()}" ) except Exception as exc: # pragma: no cover - lab helper print(f"[server:{port}] starttls handler error from {addr}: {exc}") finally: try: conn.close() except OSError: pass def serve_tcp(listen_ip: str, port: int, stop_event: threading.Event, handler) -> None: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((listen_ip, port)) sock.listen(128) sock.settimeout(1.0) print(f"[server:{port}] listening on {listen_ip}:{port}") try: while not stop_event.is_set(): try: conn, addr = sock.accept() except socket.timeout: continue thread = threading.Thread(target=handler, args=(conn, addr, port), daemon=True) thread.start() finally: sock.close() def serve_quic(listen_ip: str, port: int, stop_event: threading.Event) -> None: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((listen_ip, port)) sock.settimeout(1.0) version_negotiation = b"\xC0\x00\x00\x00\x00\x01\x08testquic" print(f"[server:{port}/udp] listening on {listen_ip}:{port}") try: while not stop_event.is_set(): try: data, addr = sock.recvfrom(4096) except socket.timeout: continue sock.sendto(version_negotiation, addr) print(f"[server:{port}/udp] {addr[0]}:{addr[1]} {len(data)} bytes") finally: sock.close() def main() -> None: parser = argparse.ArgumentParser(description="Minimal live responder for weak_tls_followon_supplement.rules") parser.add_argument("--listen-ip", default="10.10.10.2") parser.add_argument("--tls-port", type=int, default=18443) parser.add_argument("--http-port", type=int, default=18080) parser.add_argument("--starttls-port", type=int, default=18125) parser.add_argument("--quic-port", type=int, default=18480) args = parser.parse_args() check_tcp_port_available(args.listen_ip, args.tls_port) check_tcp_port_available(args.listen_ip, args.http_port) check_tcp_port_available(args.listen_ip, args.starttls_port) check_udp_port_available(args.listen_ip, args.quic_port) stop_event = threading.Event() threads = [ threading.Thread(target=serve_tcp, args=(args.listen_ip, args.tls_port, stop_event, handle_tls_client), daemon=True), threading.Thread(target=serve_tcp, args=(args.listen_ip, args.http_port, stop_event, handle_http_client), daemon=True), threading.Thread(target=serve_tcp, args=(args.listen_ip, args.starttls_port, stop_event, handle_starttls_client), daemon=True), threading.Thread(target=serve_quic, args=(args.listen_ip, args.quic_port, stop_event), daemon=True), ] for thread in threads: thread.start() try: for thread in threads: thread.join() except KeyboardInterrupt: print("\n[server] stopping") stop_event.set() if __name__ == "__main__": main()
2.主机A发送请求
from __future__ import annotations import argparse import socket import time TLS_PROFILES = [ "RC4_MD5_TLS10", "RC4_SHA", "DH_ANON_RC4", "ECDHE_ECDSA_RC4", "ECDHE_RSA_RC4", "DES3_RSA_TLS11", "DES3_DHE_DSS", "DES3_DHE_RSA", "DES3_ECDHE_ECDSA", "DES3_ECDHE_RSA", "CERT_MD5", "CERT_SHA1_RSA", "CERT_ECDSA_SHA1", "WEAK_CURVE", "RSA_AES128_SHA", "RSA_AES256_SHA", "RSA_AES128_SHA256", "RSA_AES256_SHA256", ] def send_tcp_and_read(host: str, port: int, payload: bytes, timeout: float = 1.5) -> bytes: sock = socket.create_connection((host, port), timeout=3) sock.settimeout(timeout) try: if payload: sock.sendall(payload) chunks: list[bytes] = [] while True: try: data = sock.recv(4096) except socket.timeout: break if not data: break chunks.append(data) if len(data) < 4096: break return b"".join(chunks) finally: sock.close() def http_request(path: str, headers: list[tuple[str, str]] | None = None, body: bytes = b"", method: str = "GET") -> bytes: headers = headers or [] lines = [f"{method} {path} HTTP/1.1", "Host: weaktls-lab.local", "Connection: close"] lower_names = {name.lower() for name, _ in headers} if body and "content-length" not in lower_names: headers.append(("Content-Length", str(len(body)))) for name, value in headers: lines.append(f"{name}: {value}") return ("\r\n".join(lines) + "\r\n\r\n").encode("ascii") + body def send_and_log(name: str, host: str, port: int, payload: bytes) -> None: response = send_tcp_and_read(host, port, payload) head = response[:16].hex() if response else "no-response" print(f"[client-send] {name} -> {host}:{port} {head}") time.sleep(0.15) def send_tls_profiles(server_ip: str, tls_port: int) -> None: for profile in TLS_PROFILES: payload = f"PROFILE {profile}\r\n".encode("ascii") send_and_log(f"tls-{profile.lower()}", server_ip, tls_port, payload) def send_starttls_flow(server_ip: str, starttls_port: int) -> None: sock = socket.create_connection((server_ip, starttls_port), timeout=3) sock.settimeout(1.5) try: banner = sock.recv(4096) print(f"[client-send] starttls-banner <- {banner.splitlines()[0].decode('latin-1', errors='ignore') if banner else 'none'}") sock.sendall(b"EHLO client.example\r\n") capability = sock.recv(4096) print(f"[client-send] starttls-capability <- {capability.decode('latin-1', errors='ignore').strip()}") sock.sendall(b"AUTH PLAIN dGVzdAB0ZXN0AHRlc3Q=\r\n") auth_reply = sock.recv(4096) print(f"[client-send] starttls-auth <- {auth_reply.decode('latin-1', errors='ignore').strip()}") finally: sock.close() time.sleep(0.15) def send_http_leak(server_ip: str, http_port: int) -> None: payload = http_request( "/collect?debug=1", headers=[ ("Authorization", "Basic dGVzdDp0ZXN0"), ("Cookie", "session=SESSIONTOKEN123456; auth=AUTHVALUE123456"), ], ) send_and_log("http-clear-credential", server_ip, http_port, payload) def send_mail_clear_auth(server_ip: str, starttls_port: int) -> None: sock = socket.create_connection((server_ip, starttls_port), timeout=3) sock.settimeout(1.5) try: _ = sock.recv(4096) sock.sendall(b"EHLO second.example\r\n") _ = sock.recv(4096) sock.sendall(b"LOGIN weakuser\r\n") reply = sock.recv(4096) print(f"[client-send] clear-mail-auth <- {reply.decode('latin-1', errors='ignore').strip()}") finally: sock.close() time.sleep(0.15) def send_admin_port_probes(server_ip: str) -> None: ports = [88, 135, 139, 389, 445, 464, 636, 5985] for port in ports: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(0.3) try: sock.connect((server_ip, port)) except OSError: pass finally: sock.close() print(f"[client-send] admin-probe -> {server_ip}:{port}") time.sleep(0.05) def send_quic_probe(server_ip: str, quic_port: int) -> None: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(1.0) try: sock.sendto(b"\xC1\x01\x02\x03clienthello", (server_ip, quic_port)) response, _ = sock.recvfrom(4096) print(f"[client-send] quic-probe <- {response.hex()}") finally: sock.close() time.sleep(0.15) def main() -> None: parser = argparse.ArgumentParser(description="Send live weak TLS supplement traffic to 10.10.10.2") parser.add_argument("--server-ip", default="10.10.10.2") parser.add_argument("--tls-port", type=int, default=18443) parser.add_argument("--http-port", type=int, default=18080) parser.add_argument("--starttls-port", type=int, default=18125) parser.add_argument("--quic-port", type=int, default=18480) args = parser.parse_args() send_tls_profiles(args.server_ip, args.tls_port) send_starttls_flow(args.server_ip, args.starttls_port) send_http_leak(args.server_ip, args.http_port) send_mail_clear_auth(args.server_ip, args.starttls_port) send_admin_port_probes(args.server_ip) send_quic_probe(args.server_ip, args.quic_port) if __name__ == "__main__": main()

浙公网安备 33010602011771号