Compare commits

...

4 Commits

Author SHA1 Message Date
b6b5e1c17c fixed ip parsing and logs page 2026-02-14 01:46:47 -05:00
9d16df6053 Updated home with table of contents 2026-02-14 01:20:49 -05:00
adab1e9746 Initial commit 2026-02-14 00:27:48 -05:00
3af2826c95 Initial commit 2026-02-14 00:27:10 -05:00
3 changed files with 64 additions and 24 deletions

15
LICENSE Normal file
View File

@@ -0,0 +1,15 @@
ISC License
Copyright (c) 2026, acidvegas <acid.vegas@acid.vegas>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -1,4 +1,4 @@
# malserv Test Results
# BadTest Results
Tested against `badtests.py` on port 60666 using **curl 8.5.0**, **urllib.request**, **http.client**, **requests**, and **aiohttp**.

View File

@@ -166,6 +166,13 @@ h1{color:#f85149;font-size:24px;margin-bottom:4px}
h1 span{font-size:14px;color:#484f58;font-weight:normal}
h2{color:#58a6ff;font-size:18px;margin:30px 0 12px;padding-bottom:6px;border-bottom:1px solid #21262d}
.desc{color:#8b949e;font-size:13px;margin-bottom:20px}
.toc{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:16px 20px;margin:20px 0}
.toc-title{color:#58a6ff;font-size:15px;margin-bottom:8px;font-weight:bold}
.toc ul{list-style:none;columns:2;column-gap:24px}
.toc li{padding:2px 0}
.toc a{color:#c9d1d9;text-decoration:none;font-size:13px}
.toc a:hover{color:#58a6ff;text-decoration:underline}
.toc .count{color:#484f58;font-size:12px}
.card{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:16px;margin-bottom:16px}
.card:hover{border-color:#58a6ff}
.card h3{margin:0 0 4px}
@@ -181,6 +188,8 @@ code{color:#e6edf3}
.special a{color:#f85149;font-weight:bold;text-decoration:none}
.special a:hover{text-decoration:underline}
footer{margin-top:40px;padding-top:16px;border-top:1px solid #21262d;color:#484f58;font-size:12px}
footer a{color:#484f58;text-decoration:none}
footer a:hover{color:#58a6ff;text-decoration:underline}
</style>
</head><body>
<h1>☠ badtests <span>— malicious HTTP response server</span></h1>
@@ -188,11 +197,21 @@ footer{margin-top:40px;padding-top:16px;border-top:1px solid #21262d;color:#484f
Click any endpoint to hit it directly.</p>
<div class="special">📋 <a href="/logs">/logs</a> — View all visitor IPs, user agents, headers, and timestamps</div>
'''
# Build table of contents
h += '<div class="toc"><div class="toc-title">Table of Contents</div><ul>\n'
for cat_name, prefix in CATEGORIES:
tests_in_cat = [t for t in TESTS if t[0].startswith(prefix)]
if not tests_in_cat:
continue
h += f'<h2>{html.escape(cat_name)}</h2>\n'
anchor = cat_name.lower().replace(' ', '-').replace('/', '-')
h += f'<li><a href="#{html.escape(anchor)}">{html.escape(cat_name)}</a> <span class="count">({len(tests_in_cat)})</span></li>\n'
h += '</ul></div>\n'
for cat_name, prefix in CATEGORIES:
tests_in_cat = [t for t in TESTS if t[0].startswith(prefix)]
if not tests_in_cat:
continue
anchor = cat_name.lower().replace(' ', '-').replace('/', '-')
h += f'<h2 id="{html.escape(anchor)}">{html.escape(cat_name)}</h2>\n'
for path, title, desc in tests_in_cat:
crit = '🚨' in desc
warn = '⚠️' in desc
@@ -209,7 +228,7 @@ Click any endpoint to hit it directly.</p>
'''
h += f'''<footer>
☠ badtests — {len(TESTS)} endpoints on port {PORT}<br>
python3 badtests.py to start
made by <a href="https://git.supernets.org/acidvegas/badtests">acidvegas</a>
</footer>
</body></html>'''
return h.encode()
@@ -233,8 +252,8 @@ JS_DOWNLOAD = b'''<!DOCTYPE html><html><head><title>Download bomb</title></head>
<h1>Triggering 200 simultaneous downloads...</h1>
<script>
for(let i=0;i<200;i++){let a=document.createElement('a');
a.href=URL.createObjectURL(new Blob(['MALSERV_PAYLOAD_'+i+'_'+Math.random()]));
a.download='malserv_payload_'+i+'.txt';document.body.appendChild(a);a.click();}
a.href=URL.createObjectURL(new Blob(['BADTESTS_PAYLOAD_'+i+'_'+Math.random()]));
a.download='badtests_payload_'+i+'.txt';document.body.appendChild(a);a.click();}
</script></body></html>'''
JS_CLIPBOARD = b'''<!DOCTYPE html><html><head><title>Clipboard hijack</title></head><body>
@@ -548,22 +567,22 @@ add('MIME Types',navigator.mimeTypes.length);
LOGS_STYLE = '''<!DOCTYPE html><html><head><meta charset="utf-8">
<title>☠ badtests — visitor logs</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#0d1117;color:#c9d1d9;font-family:monospace;font-size:13px;padding:20px 40px}
h1{color:#f85149;margin-bottom:4px}
.sub{color:#484f58;margin-bottom:20px;font-size:12px}
table{border-collapse:collapse;width:100%}
th{text-align:left;color:#58a6ff;padding:8px;border-bottom:2px solid #30363d;position:sticky;top:0;background:#0d1117}
td{padding:6px 8px;border-bottom:1px solid #161b22;max-width:400px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
tr:hover td{background:#161b22}
.ip{color:#7ee787}.path{color:#d2a8ff}.method{color:#d29922}.time{color:#484f58}
details{cursor:pointer}
details pre{background:#161b22;padding:8px;margin-top:4px;border-radius:4px;white-space:pre-wrap;max-height:200px;overflow:auto}
*{{margin:0;padding:0;box-sizing:border-box}}
body{{background:#0d1117;color:#c9d1d9;font-family:monospace;font-size:13px;padding:20px 40px}}
h1{{color:#f85149;margin-bottom:4px}}
.sub{{color:#484f58;margin-bottom:20px;font-size:12px}}
table{{border-collapse:collapse;width:100%}}
th{{text-align:left;color:#58a6ff;padding:8px;border-bottom:2px solid #30363d;position:sticky;top:0;background:#0d1117}}
td{{padding:6px 8px;border-bottom:1px solid #161b22;max-width:400px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}
tr:hover td{{background:#161b22}}
.ip{{color:#7ee787}}.path{{color:#d2a8ff}}.method{{color:#d29922}}.time{{color:#484f58}}
details{{cursor:pointer}}
details pre{{background:#161b22;padding:8px;margin-top:4px;border-radius:4px;white-space:pre-wrap;max-height:200px;overflow:auto}}
</style></head><body>
<h1>☠ visitor logs</h1>
<p class="sub">Recording all requests. %d entries so far. <a href="/logs" style="color:#58a6ff">Refresh</a> | <a href="/" style="color:#58a6ff">Home</a></p>
<p class="sub">Recording all requests. {count} entries so far. <a href="/logs" style="color:#58a6ff">Refresh</a> | <a href="/" style="color:#58a6ff">Home</a></p>
<table><tr><th>Time</th><th>IP</th><th>Method</th><th>Path</th><th>User-Agent</th><th>Headers</th></tr>
%s
{rows}
</table></body></html>'''
# --- MIME sniff test page (wraps the payload endpoint) ---
@@ -581,6 +600,12 @@ class Handler(http.server.BaseHTTPRequestHandler):
_index = build_index()
_routes = {t[0]: t for t in TESTS}
def _get_client_ip(self):
'''Get real client IP from reverse proxy headers, fallback to socket address.'''
return (self.headers.get('X-Real-IP')
or (self.headers.get('X-Forwarded-For', '').split(',')[0].strip())
or self.client_address[0])
def do_GET(self):
p = self.path.split('?')[0]
self._log_visit()
@@ -607,7 +632,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
headers[k] = self.headers[k]
entry = {
'time': time.strftime('%Y-%m-%d %H:%M:%S'),
'ip': self.client_address[0],
'ip': self._get_client_ip(),
'method': self.command,
'path': self.path,
'ua': self.headers.get('User-Agent', ''),
@@ -630,7 +655,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
<td>{html.escape(e['ua'][:80])}</td>
<td><details><summary>{len(e['headers'])} headers</summary><pre>{html.escape(hdrs_str)}</pre></details></td>
</tr>'''
page = (LOGS_STYLE % (len(VISITOR_LOG), rows)).encode()
page = LOGS_STYLE.format(count=len(VISITOR_LOG), rows=rows).encode()
self._r(200, {'Content-Type': 'text/html; charset=utf-8'}, page)
def _dispatch(self, path):
@@ -721,7 +746,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
elif p == '/html-multiline-title':
self._r(200, {'Content-Type': 'text/html'}, b'<html><head><title>Line 1\nLine 2\nLine 3\nLine 4\nLine 5</title></head><body><h1>Check your tab title</h1></body></html>')
elif p == '/html-irc-title':
self._r(200, {'Content-Type': 'text/html'}, b'<html><head><title>Normal Title\r\nQUIT :Hacked by malserv\r\nPRIVMSG #channel :pwned</title></head><body><h1>IRC Injection via HTML title</h1></body></html>')
self._r(200, {'Content-Type': 'text/html'}, b'<html><head><title>Normal Title\r\nQUIT :Hacked by badtests\r\nPRIVMSG #channel :pwned</title></head><body><h1>IRC Injection via HTML title</h1></body></html>')
elif p == '/html-unicode':
chars = ''.join(chr(random.randint(0x1000, 0x3000)) for _ in range(500))
self._r(200, {'Content-Type': 'text/html; charset=utf-8'}, f'<html><head><title>{chars}</title></head><body><h1>{chars}</h1></body></html>'.encode())
@@ -730,7 +755,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
# --- Terminal abuses ---
elif p == '/ansi-chaos':
a = '\033[2J\033[H'
a += '\033]0;HACKED BY MALSERV\007'
a += '\033]0;HACKED BY BADTESTS\007'
a += '\033[?25l'
for i in range(80):
row = random.randint(1, 50)
@@ -817,11 +842,11 @@ document.querySelector('text:last-of-type').textContent = 'JS EXECUTED SUCCESSFU
}, b'<html><body><h1>HSTS Bomb</h1><p>Your browser now thinks this site requires HTTPS for 31 years.</p><p>This header also applies to ALL subdomains and requests preload list inclusion.</p><p>To undo in Chrome: chrome://net-internals/#hsts</p></body></html>')
# --- Misc ---
elif p == '/misc-ipv4':
ip = self.client_address[0]
ip = self._get_client_ip()
body = f'<html><head><title>Your IP: {ip}</title></head><body style="background:#0d1117;color:#c9d1d9;font-family:monospace;padding:40px"><h1 style="color:#7ee787">{ip}</h1></body></html>'
self._r(200, {'Content-Type': 'text/html'}, body.encode())
elif p == '/misc-ipv6':
ip = self.client_address[0]
ip = self._get_client_ip()
body = f'<html><head><title>Your IP: {ip}</title></head><body style="background:#0d1117;color:#c9d1d9;font-family:monospace;padding:40px"><h1 style="color:#7ee787">{ip}</h1><p>For true IPv6, connect to [::1]:{PORT}</p></body></html>'
self._r(200, {'Content-Type': 'text/html'}, body.encode())
elif p == '/fingerprint':