Compare commits

..

No commits in common. "72b7eca12eb12afa2377afc13806220cf74e56c1" and "8bb546c689ebb43a6505dc1315a86e67742ac6a0" have entirely different histories.

4 changed files with 12 additions and 91 deletions

View File

@ -712,12 +712,6 @@ class SecuBoxWAF:
if ROUTES_FILE.exists():
try:
self.routes = json.loads(ROUTES_FILE.read_text())
sfx = set()
for _h in self.routes:
_p = _h.split('.')
if len(_p) >= 2 and not _p[-1].isdigit():
sfx.add('.'.join(_p[-2:]))
self.local_suffixes = sfx
ctx.log.info(f"Loaded {len(self.routes)} routes")
except Exception as e:
ctx.log.error(f"Failed to load routes: {e}")
@ -926,12 +920,12 @@ class SecuBoxWAF:
ctx.log.warn(f"BAN FAILED for {ip} ({reason}) : LAPI off + cscli unavailable")
def requestheaders(self, flow: http.HTTPFlow):
# #605 — mitmproxy 11 opens the upstream connection before request(),
# so routing must happen here. ALSO: in --mode regular mitmproxy is a
# forward proxy that would relay ANY Host, so internet scanners abused
# it as an open proxy (~70% error churn + self-loops). Serve ONLY our
# own vhosts: mapped (routes), our domains (-> nginx catch-all), or our
# own IPs; refuse everything else with 421 and never open an upstream.
# #603 — mitmproxy 11 opens the upstream connection BETWEEN the
# requestheaders and request hooks, so upstream redirection must
# happen here. Doing it in request() (below) is too late: the
# socket is already connected to the original Host, so routed
# vhosts went to their public DNS IP instead of the internal
# backend. Setting flow.server_conn.address here fixes routing.
try:
host = flow.request.pretty_host
if host in self.routes:
@ -944,32 +938,9 @@ class SecuBoxWAF:
except Exception:
pass
flow.request.headers['Host'] = orig
return
if host in SELF_HOSTS or self._is_local_host(host):
flow.request.host = '192.168.1.200'
flow.request.port = 9080
try:
flow.server_conn.address = ('192.168.1.200', 9080)
except Exception:
pass
return
self.stats['blocked'] = self.stats.get('blocked', 0) + 1
flow.response = http.Response.make(
421,
b'<h1>421 Misdirected Request</h1><p>SecuBox WAF does not proxy this host.</p>',
{'Content-Type': 'text/html', 'X-SecuBox-WAF': 'unmapped-host'},
)
except Exception as e:
ctx.log.warn(f'[requestheaders-route] {e}')
def _is_local_host(self, host: str) -> bool:
# #605 — is `host` one of our own (registrable) domains? Derived from
# the routed hosts in load_routes (self.local_suffixes).
sfx = getattr(self, 'local_suffixes', None)
if not sfx:
return False
return any(host == s or host.endswith('.' + s) for s in sfx)
def request(self, flow: http.HTTPFlow):
# Connection close (Phase 6.J leak fix, ref #496) — prevents mitmproxy
# from accumulating idle keep-alive sockets to upstream backends.

View File

@ -1,15 +1,3 @@
secubox-mitmproxy (1.0.6-1~bookworm1) bookworm; urgency=medium
* fix(waf): refuse unmapped hosts — close the open forward-proxy (#605). In
--mode regular the addon relayed any Host, so HAProxy's default_backend
made the WAF an open proxy; internet scanners drove ~72% backend-error
churn + self-loops. The `requestheaders` hook now serves ONLY our vhosts
(mapped, our domains via routes-derived `local_suffixes` → nginx catch-all,
or our own IPs) and returns 421 for everything else with no upstream
connect. Verified live: 0 external server-connects, 0 loop-508s.
-- Gerald KERMA <devel@cybermind.fr> Mon, 15 Jun 2026 16:30:00 +0200
secubox-mitmproxy (1.0.5-1~bookworm1) bookworm; urgency=medium
* fix(waf): mitmproxy-11 upstream routing (#603). The addon only redirected

View File

@ -1,12 +1,3 @@
secubox-waf (1.2.4-1~bookworm1) bookworm; urgency=medium
* fix(waf): refuse unmapped hosts in the addon copy — close the open
forward-proxy (#605, synced with secubox-mitmproxy). 421 for any host not
in routes / our domains / our IPs; no upstream connect. Kills the ~72%
scanner-driven error churn and the self-loop 508s.
-- Gerald KERMA <devel@cybermind.fr> Mon, 15 Jun 2026 16:30:00 +0200
secubox-waf (1.2.3-1~bookworm1) bookworm; urgency=medium
* fix(waf): mitmproxy-11 upstream routing — `requestheaders` hook in the

View File

@ -594,12 +594,6 @@ class SecuBoxWAF:
if ROUTES_FILE.exists():
try:
self.routes = json.loads(ROUTES_FILE.read_text())
sfx = set()
for _h in self.routes:
_p = _h.split('.')
if len(_p) >= 2 and not _p[-1].isdigit():
sfx.add('.'.join(_p[-2:]))
self.local_suffixes = sfx
ctx.log.info(f"Loaded {len(self.routes)} routes")
except Exception as e:
ctx.log.error(f"Failed to load routes: {e}")
@ -782,12 +776,12 @@ class SecuBoxWAF:
ctx.log.warn(f"BAN FAILED for {ip} ({reason})")
def requestheaders(self, flow: http.HTTPFlow):
# #605 — mitmproxy 11 opens the upstream connection before request(),
# so routing must happen here. ALSO: in --mode regular mitmproxy is a
# forward proxy that would relay ANY Host, so internet scanners abused
# it as an open proxy (~70% error churn + self-loops). Serve ONLY our
# own vhosts: mapped (routes), our domains (-> nginx catch-all), or our
# own IPs; refuse everything else with 421 and never open an upstream.
# #603 — mitmproxy 11 opens the upstream connection BETWEEN the
# requestheaders and request hooks, so upstream redirection must
# happen here. Doing it in request() (below) is too late: the
# socket is already connected to the original Host, so routed
# vhosts went to their public DNS IP instead of the internal
# backend. Setting flow.server_conn.address here fixes routing.
try:
host = flow.request.pretty_host
if host in self.routes:
@ -800,32 +794,9 @@ class SecuBoxWAF:
except Exception:
pass
flow.request.headers['Host'] = orig
return
if host in SELF_HOSTS or self._is_local_host(host):
flow.request.host = '192.168.1.200'
flow.request.port = 9080
try:
flow.server_conn.address = ('192.168.1.200', 9080)
except Exception:
pass
return
self.stats['blocked'] = self.stats.get('blocked', 0) + 1
flow.response = http.Response.make(
421,
b'<h1>421 Misdirected Request</h1><p>SecuBox WAF does not proxy this host.</p>',
{'Content-Type': 'text/html', 'X-SecuBox-WAF': 'unmapped-host'},
)
except Exception as e:
ctx.log.warn(f'[requestheaders-route] {e}')
def _is_local_host(self, host: str) -> bool:
# #605 — is `host` one of our own (registrable) domains? Derived from
# the routed hosts in load_routes (self.local_suffixes).
sfx = getattr(self, 'local_suffixes', None)
if not sfx:
return False
return any(host == s or host.endswith('.' + s) for s in sfx)
def request(self, flow: http.HTTPFlow):
# Connection close (Phase 6.J leak fix, ref #496) — prevents mitmproxy
# from accumulating idle keep-alive sockets to upstream backends.