etc~ scripts and random things |
| git clone https://github.com/djnnvx/etc.git |
| log | files | refs | archive |
gen_corpus.py (16532B)
1 #!/usr/bin/env python3 2 """Generate seed corpus for TTYD fuzzing harnesses. 3 4 Creates structured binary seeds in corpus/{websocket_auth,http_parsing}/. 5 Run from the script directory or pass the output base dir as argv[1]. 6 7 Formats: 8 websocket_auth: [flags:1][cred_len:2 BE][cred][path_len:1][path] 9 [origin_len:1][origin][host_len:1][host][ws_message] 10 flags: bit0=has_cred, bit1=auth_header, bit2=check_origin, 11 bit3=url_arg, bit4=writable 12 13 http_parsing: [auth_mode:1][cred_len:2 BE][cred][path_len:2 BE][path][auth_header_value] 14 """ 15 16 import os 17 import sys 18 import struct 19 20 base_dir = sys.argv[1] if len(sys.argv) > 1 else os.path.dirname(os.path.abspath(__file__)) 21 22 23 def write_seeds(subdir, seeds): 24 d = os.path.join(base_dir, "corpus", subdir) 25 os.makedirs(d, exist_ok=True) 26 for name, data in seeds.items(): 27 with open(os.path.join(d, name), "wb") as f: 28 f.write(data) 29 print(f"[+] Generated {len(seeds)} seeds for {subdir}") 30 31 32 # ── helpers ────────────────────────────────────────────────────────────────── 33 34 def ws_seed(flags, cred, path, origin, host, ws_msg): 35 """websocket_auth format: 36 [flags:1][cred_len:2 BE][cred][path_len:1][path] 37 [origin_len:1][origin][host_len:1][host][ws_message]""" 38 return (struct.pack(">B", flags) 39 + struct.pack(">H", len(cred)) + cred 40 + struct.pack(">B", len(path)) + path 41 + struct.pack(">B", len(origin)) + origin 42 + struct.pack(">B", len(host)) + host 43 + ws_msg) 44 45 46 def http_seed(mode, cred, path, auth=b""): 47 """http_parsing format: [mode:1][cred_len:2 BE][cred][path_len:2 BE][path][auth]""" 48 return (struct.pack(">B", mode) 49 + struct.pack(">H", len(cred)) + cred 50 + struct.pack(">H", len(path)) + path 51 + auth) 52 53 54 # ── websocket_auth seeds ───────────────────────────────────────────────────── 55 # Flags: 0x01=has_cred, 0x02=auth_header, 0x04=check_origin, 0x08=url_arg, 0x10=writable 56 57 WS = b"/ws" 58 ORIGIN = b"http://localhost:7681" 59 HOST = b"localhost:7681" 60 JSON_AUTH = b'{"columns":80,"rows":24,"AuthToken":"admin"}' 61 JSON_DIMS = b'{"columns":80,"rows":24}' 62 63 ws_seeds = { 64 # ── No auth, basic JSON_DATA command ── 65 "noauth_json": ws_seed(0x10, b"", WS, b"", b"", JSON_DIMS), 66 "noauth_json_large": ws_seed(0x10, b"", WS, b"", b"", b'{"columns":200,"rows":50}'), 67 "noauth_json_empty": ws_seed(0x10, b"", WS, b"", b"", b"{}"), 68 "noauth_json_malformed":ws_seed(0x10, b"", WS, b"", b"", b"{invalid json}"), 69 70 # ── With credential — JSON_DATA auth flow ── 71 "auth_valid": ws_seed(0x11, b"admin", WS, b"", b"", JSON_AUTH), 72 "auth_invalid": ws_seed(0x11, b"admin", WS, b"", b"", 73 b'{"columns":80,"rows":24,"AuthToken":"wrong"}'), 74 "auth_missing_token": ws_seed(0x11, b"admin", WS, b"", b"", JSON_DIMS), 75 "auth_no_dims": ws_seed(0x11, b"admin", WS, b"", b"", b'{"AuthToken":"admin"}'), 76 "auth_empty_cred": ws_seed(0x01, b"", WS, b"", b"", JSON_DIMS), 77 "auth_token_null": ws_seed(0x11, b"admin", WS, b"", b"", 78 b'{"columns":80,"rows":24,"AuthToken":null}'), 79 "auth_token_bool": ws_seed(0x11, b"admin", WS, b"", b"", 80 b'{"columns":80,"rows":24,"AuthToken":true}'), 81 "auth_long_cred": ws_seed(0x11, b"A" * 200, WS, b"", b"", 82 b'{"AuthToken":"' + b"A" * 200 + b'"}'), 83 84 # ── Custom auth header (bit 1) ── 85 "custom_auth": ws_seed(0x12, b"someuser", WS, b"", b"", JSON_DIMS), 86 "custom_auth_empty": ws_seed(0x02, b"", WS, b"", b"", JSON_DIMS), 87 88 # ── INPUT command ('0' prefix) ── 89 "input_hello": ws_seed(0x10, b"", WS, b"", b"", b"0hello world"), 90 "input_ctrl_c": ws_seed(0x10, b"", WS, b"", b"", b"0\x03"), 91 "input_long": ws_seed(0x10, b"", WS, b"", b"", b"0" + b"A" * 4096), 92 "input_not_writable": ws_seed(0x00, b"", WS, b"", b"", b"0hello"), 93 94 # ── RESIZE command ('1' prefix) ── 95 "resize_normal": ws_seed(0x10, b"", WS, b"", b"", 96 b'1{"columns":120,"rows":40}'), 97 "resize_huge": ws_seed(0x10, b"", WS, b"", b"", 98 b'1{"columns":65535,"rows":65535}'), 99 "resize_negative": ws_seed(0x10, b"", WS, b"", b"", 100 b'1{"columns":-1,"rows":-1}'), 101 102 # ── PAUSE/RESUME commands ── 103 "pause": ws_seed(0x10, b"", WS, b"", b"", b"2"), 104 "resume": ws_seed(0x10, b"", WS, b"", b"", b"3"), 105 106 # ── Unknown command ── 107 "unknown_cmd": ws_seed(0x10, b"", WS, b"", b"", b"Xunknown"), 108 109 # ── Origin checking (bit 2) ── 110 "origin_match": ws_seed(0x14, b"", WS, ORIGIN, HOST, JSON_DIMS), 111 "origin_mismatch": ws_seed(0x14, b"", WS, b"http://evil.com", HOST, JSON_DIMS), 112 "origin_no_host": ws_seed(0x14, b"", WS, ORIGIN, b"", JSON_DIMS), 113 "origin_no_origin": ws_seed(0x14, b"", WS, b"", HOST, JSON_DIMS), 114 "origin_long": ws_seed(0x14, b"", WS, 115 b"http://" + b"A" * 200 + b":9999", 116 b"A" * 200 + b":9999", JSON_DIMS), 117 "origin_port_80": ws_seed(0x14, b"", WS, 118 b"http://localhost:80", b"localhost", JSON_DIMS), 119 "origin_port_443": ws_seed(0x14, b"", WS, 120 b"https://localhost:443", b"localhost", JSON_DIMS), 121 122 # ── URL args (bit 3) ── 123 "url_arg_enabled": ws_seed(0x18, b"", WS, b"", b"", b"0echo test"), 124 "url_arg_long": ws_seed(0x18, b"", WS, b"", b"", b"0" + b"X" * 250), 125 126 # ── Path variations ── 127 "path_wrong": ws_seed(0x10, b"", b"/wrong", b"", b"", JSON_DIMS), 128 "path_empty": ws_seed(0x10, b"", b"", b"", b"", JSON_DIMS), 129 "path_traversal": ws_seed(0x10, b"", b"/../ws", b"", b"", JSON_DIMS), 130 "path_long": ws_seed(0x10, b"", b"/" + b"A" * 200, b"", b"", JSON_DIMS), 131 "path_null": ws_seed(0x10, b"", b"/ws\x00extra", b"", b"", JSON_DIMS), 132 133 # ── Auth + origin combined ── 134 "auth_origin_valid": ws_seed(0x15, b"admin", WS, ORIGIN, HOST, JSON_AUTH), 135 "auth_origin_invalid": ws_seed(0x15, b"admin", WS, b"http://evil.com", HOST, JSON_AUTH), 136 137 # ── Empty / minimal ── 138 "empty_msg": ws_seed(0x10, b"", WS, b"", b"", b""), 139 "minimal": ws_seed(0x00, b"", b"", b"", b"", b""), 140 141 # ── JSON edge cases ── 142 "json_deep_nested": ws_seed(0x10, b"", WS, b"", b"", 143 b'{"a":' * 50 + b"1" + b"}" * 50), 144 "json_huge_token": ws_seed(0x11, b"admin", WS, b"", b"", 145 b'{"columns":80,"rows":24,"AuthToken":"' + b"A" * 5000 + b'"}'), 146 "json_truncated": ws_seed(0x10, b"", WS, b"", b"", b'{"columns":'), 147 "json_extra_ws": ws_seed(0x10, b"", WS, b"", b"", 148 b'{ "columns" : 80 , "rows" : 24 }'), 149 "json_uint16_overflow": ws_seed(0x10, b"", WS, b"", b"", 150 b'{"columns":65536,"rows":65536}'), 151 152 # ── Authenticated commands (need JSON_DATA first to auth) ── 153 # Note: without auth, non-JSON_DATA commands with credential set get rejected 154 "unauth_input": ws_seed(0x11, b"admin", WS, b"", b"", b"0hello"), 155 "unauth_resize": ws_seed(0x11, b"admin", WS, b"", b"", 156 b'1{"columns":80,"rows":24}'), 157 } 158 159 160 # ── http_parsing seeds ─────────────────────────────────────────────────────── 161 162 B64_CRED = b"YWRtaW46cGFzcw==" 163 164 http_seeds = { 165 # No auth, various paths 166 "noauth_root": http_seed(0, b"", b"/"), 167 "noauth_token": http_seed(0, b"", b"/token"), 168 "noauth_ws": http_seed(0, b"", b"/ws"), 169 "noauth_parent": http_seed(0, b"", b"/base-path"), 170 "noauth_404": http_seed(0, b"", b"/nonexistent"), 171 "noauth_empty_path": http_seed(0, b"", b""), 172 # Path traversal 173 "path_traversal": http_seed(0, b"", b"/../../../etc/passwd"), 174 "path_double_slash": http_seed(0, b"", b"//etc/passwd"), 175 "path_dotdot": http_seed(0, b"", b"/./../../etc/shadow"), 176 "path_null": http_seed(0, b"", b"/\x00hidden"), 177 "path_encoded": http_seed(0, b"", b"/%2e%2e/%2e%2e/etc/passwd"), 178 "path_fmt": http_seed(0, b"", b"/%s%s%s%n"), 179 "path_crlf": http_seed(0, b"", b"/\r\nInjected: header"), 180 "path_long": http_seed(0, b"", b"/" + b"A" * 200), 181 # Basic auth — valid 182 "basic_valid": http_seed(1, B64_CRED, b"/", b"Basic " + B64_CRED), 183 "basic_token_path": http_seed(1, B64_CRED, b"/token", b"Basic " + B64_CRED), 184 # Basic auth — invalid 185 "basic_mismatch": http_seed(1, B64_CRED, b"/", b"Basic INVALIDBASE64=="), 186 "basic_empty": http_seed(1, B64_CRED, b"/", b"Basic "), 187 "basic_no_header": http_seed(1, B64_CRED, b"/", b""), 188 "basic_wrong_scheme": http_seed(1, B64_CRED, b"/", b"Bearer token123"), 189 "basic_lowercase": http_seed(1, B64_CRED, b"/", b"basic " + B64_CRED), 190 "basic_uppercase": http_seed(1, B64_CRED, b"/", b"BASIC " + B64_CRED), 191 "basic_dbl_space": http_seed(1, B64_CRED, b"/", b"Basic " + B64_CRED), 192 "basic_leading_sp": http_seed(1, B64_CRED, b"/", b" Basic " + B64_CRED), 193 "basic_no_cred": http_seed(1, b"", b"/", b"Basic " + B64_CRED), 194 # Boundary (256-byte buf in check_auth) 195 "basic_long_auth": http_seed(1, b"x:y", b"/", b"Basic " + b"A" * 300), 196 "basic_boundary_254": http_seed(1, b"x:y", b"/", b"Basic " + b"A" * 248), 197 "basic_boundary_256": http_seed(1, b"x:y", b"/", b"Basic " + b"A" * 250), 198 # Custom header auth 199 "custom_with_value": http_seed(2, b"", b"/", b"some-token-value"), 200 "custom_empty": http_seed(2, b"", b"/"), 201 "custom_token_path": http_seed(2, b"", b"/token", b"admin"), 202 # Format string in auth 203 "auth_fmt": http_seed(1, b"cred", b"/", b"Basic %s%s%s%n%n"), 204 } 205 206 207 # ── ws_fragments seeds ──────────────────────────────────────────────────────── 208 # Format: [num_frags:1][cmd:1][payload...] 209 210 def frags_seed(num_frags, cmd, payload): 211 return bytes([num_frags & 0x07, cmd]) + payload 212 213 CMD_JSON = ord('{') 214 CMD_INPUT = ord('0') 215 CMD_RESIZE= ord('1') 216 CMD_PAUSE = ord('2') 217 218 ws_frags_seeds = { 219 "single_json": frags_seed(1, CMD_JSON, b'{"columns":80,"rows":24}'), 220 "two_json": frags_seed(2, CMD_JSON, b'{"columns":80,"rows":24}'), 221 "four_json": frags_seed(4, CMD_JSON, b'{"columns":200,"rows":50}abcdef'), 222 "single_resize": frags_seed(1, CMD_RESIZE,b'{"columns":120,"rows":40}'), 223 "three_resize": frags_seed(3, CMD_RESIZE,b'{"columns":120,"rows":40}padding'), 224 "single_input": frags_seed(1, CMD_INPUT, b'hello world'), 225 "two_input": frags_seed(2, CMD_INPUT, b'hello world long payload here'), 226 "eight_input": frags_seed(8, CMD_INPUT, b'A' * 64), 227 "single_pause": frags_seed(1, CMD_PAUSE, b''), 228 "large_single": frags_seed(1, CMD_INPUT, b'X' * 512), 229 "malformed_json": frags_seed(2, CMD_JSON, b'{invalid'), 230 "empty_payload": frags_seed(1, CMD_JSON, b'{}'), 231 } 232 233 # ── prefs_message seeds ─────────────────────────────────────────────────────── 234 # Format: [prefs_len:2 BE][prefs_json][cmd_len:2 BE][command] 235 236 def prefs_seed(prefs, command): 237 return (struct.pack(">H", len(prefs)) + prefs + 238 struct.pack(">H", len(command)) + command) 239 240 prefs_seeds = { 241 "normal": prefs_seed(b'{}', b'bash'), 242 "prefs_medium": prefs_seed(b'{"theme":"light"}', b'bash'), 243 "prefs_boundary": prefs_seed(b'{"x":"' + b'A'*4080 + b'"}', b'bash'), 244 "prefs_overflow": prefs_seed(b'A' * 4097, b'bash'), 245 "prefs_overflow2": prefs_seed(b'A' * 8192, b'bash'), 246 "cmd_medium": prefs_seed(b'{}', b'/bin/bash'), 247 "cmd_boundary": prefs_seed(b'{}', b'/bin/' + b'A'*3965), 248 "cmd_overflow": prefs_seed(b'{}', b'A' * 4000), 249 "both_large": prefs_seed(b'A' * 3000, b'B' * 3000), 250 "empty_prefs": prefs_seed(b'', b'bash'), 251 "empty_cmd": prefs_seed(b'{}', b''), 252 "fmt_string": prefs_seed(b'%s%s%n%n', b'%s%n'), 253 } 254 255 # ── json_recv seeds ─────────────────────────────────────────────────────────── 256 # Format: [num_msgs:1][cmd:1][len:2 BE][payload...] × N 257 258 def recv_msg(cmd, payload): 259 return bytes([cmd]) + struct.pack(">H", len(payload)) + payload 260 261 def recv_seed(*messages): 262 n = min(len(messages), 4) 263 return bytes([n & 0x03]) + b''.join(messages[:n]) 264 265 JSON_DATA = ord('{') 266 RESIZE = ord('1') 267 INPUT_CMD = ord('0') 268 PAUSE_CMD = ord('2') 269 RESUME_CMD= ord('3') 270 271 json_recv_seeds = { 272 # Single JSON_DATA 273 "json_only": recv_seed(recv_msg(JSON_DATA, b'{"columns":80,"rows":24}')), 274 "json_empty": recv_seed(recv_msg(JSON_DATA, b'{}')), 275 "json_malformed": recv_seed(recv_msg(JSON_DATA, b'{invalid}')), 276 "json_huge_cols": recv_seed(recv_msg(JSON_DATA, b'{"columns":65535,"rows":65535}')), 277 "json_neg": recv_seed(recv_msg(JSON_DATA, b'{"columns":-1,"rows":-1}')), 278 "json_overflow16": recv_seed(recv_msg(JSON_DATA, b'{"columns":65536,"rows":65536}')), 279 "json_deep": recv_seed(recv_msg(JSON_DATA, b'{"a":' * 30 + b'1' + b'}' * 30)), 280 "json_long_str": recv_seed(recv_msg(JSON_DATA, b'{"columns":80,"rows":24,"x":"' + b'A'*500 + b'"}')), 281 282 # JSON_DATA then RESIZE 283 "json_then_resize": recv_seed( 284 recv_msg(JSON_DATA, b'{"columns":80,"rows":24}'), 285 recv_msg(RESIZE, b'{"columns":120,"rows":40}'), 286 ), 287 "json_then_resize_bad": recv_seed( 288 recv_msg(JSON_DATA, b'{"columns":80,"rows":24}'), 289 recv_msg(RESIZE, b'{bad json'), 290 ), 291 292 # JSON_DATA then INPUT 293 "json_then_input": recv_seed( 294 recv_msg(JSON_DATA, b'{"columns":80,"rows":24}'), 295 recv_msg(INPUT_CMD, b'ls -la\n'), 296 ), 297 298 # JSON_DATA then PAUSE/RESUME 299 "json_pause_resume":recv_seed( 300 recv_msg(JSON_DATA, b'{"columns":80,"rows":24}'), 301 recv_msg(PAUSE_CMD, b''), 302 recv_msg(RESUME_CMD, b''), 303 ), 304 305 # Four messages 306 "full_sequence": recv_seed( 307 recv_msg(JSON_DATA, b'{"columns":80,"rows":24}'), 308 recv_msg(RESIZE, b'{"columns":160,"rows":50}'), 309 recv_msg(INPUT_CMD, b'echo hello\n'), 310 recv_msg(PAUSE_CMD, b''), 311 ), 312 313 # RESIZE before JSON_DATA (pss->process == NULL → no-op in RESIZE) 314 "resize_first": recv_seed(recv_msg(RESIZE, b'{"columns":80,"rows":24}')), 315 316 # Unknown command 317 "unknown_cmd": recv_seed(recv_msg(ord('Z'), b'some data')), 318 "unknown_two": recv_seed( 319 recv_msg(JSON_DATA, b'{"columns":80,"rows":24}'), 320 recv_msg(ord('X'), b'garbage'), 321 ), 322 } 323 324 325 # ── main ───────────────────────────────────────────────────────────────────── 326 327 if __name__ == "__main__": 328 write_seeds("websocket_auth", ws_seeds) 329 write_seeds("http_parsing", http_seeds) 330 write_seeds("ws_fragments", ws_frags_seeds) 331 write_seeds("prefs_message", prefs_seeds) 332 write_seeds("json_recv", json_recv_seeds) 333 334 total = len(ws_seeds) + len(http_seeds) + len(ws_frags_seeds) + len(prefs_seeds) + len(json_recv_seeds) 335 print(f"[+] Total corpus: {total} files")