Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
IT Security NachrichtenIT Security News Hourly Summary 2026-09-22 08h : 8 posts(22.09.2026 um 08:00 Uhr)
IT Security NachrichtenDeutsche Telekom startet internationale Reise-eSIM T-Travel(22.09.2026 um 07:41 Uhr)
IT Security NachrichtenDrei ergänzende Microsoft-365-Apps werden im Dezember eingestellt(22.09.2026 um 07:42 Uhr)
IT Security NachrichtenRechnungshof: EU nicht genug gegen Cyberangriffe gewappnet(22.09.2026 um 07:42 Uhr)
IT NachrichtenThis UCD expert is building advanced quantum sensing tech(22.09.2026 um 08:00 Uhr)
IT NachrichtenHow to watch BJK Cup Finals 2026: Free Streams & Schedule(22.09.2026 um 08:00 Uhr)
IT Security NachrichtenIT Security News Hourly Summary 2026-09-22 08h : 8 posts(22.09.2026 um 08:00 Uhr)
IT Security NachrichtenDeutsche Telekom startet internationale Reise-eSIM T-Travel(22.09.2026 um 07:41 Uhr)
IT Security NachrichtenDrei ergänzende Microsoft-365-Apps werden im Dezember eingestellt(22.09.2026 um 07:42 Uhr)
IT Security NachrichtenRechnungshof: EU nicht genug gegen Cyberangriffe gewappnet(22.09.2026 um 07:42 Uhr)
IT NachrichtenThis UCD expert is building advanced quantum sensing tech(22.09.2026 um 08:00 Uhr)
IT NachrichtenHow to watch BJK Cup Finals 2026: Free Streams & Schedule(22.09.2026 um 08:00 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

I shipped an unauthenticated RCE in my own port scanner — here's the whole chain, and how I killed it

I built a web front end for an Nmap-based port scanner: a FastAPI backend, a React dashboard, background scan jobs, a plugin system, the works. It ran fine. Then I sat down and actually audited it like an attacker would — and the thing was …

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

I built a web front end for an Nmap-based port scanner: a FastAPI backend, a React

dashboard, background scan jobs, a plugin system, the works. It ran fine. Then I

sat down and actually audited it like an attacker would — and the thing was a

textbook unauthenticated remote-code-execution box.



This is the full chain, why each link existed, and the exact fixes. Every bug

here is one you can ship in any tool that shells out to a subprocess, so the

lessons transfer well beyond this project.



Repo (hardened): https://github.com/DipesThapa/PortScanner




Framing note: this is my own project, audited and fixed by me. No third-party

systems were touched. Scanners are dual-use — only ever point one at hosts you

own or are authorised to test.






The stack in one breath





  • POST /api/scans accepts a JSON body (target, ports, scripts, extra nmap args).

  • A JobManager runs the scan in a background thread, shelling out to nmap.

  • A "deep-dive" feature runs follow-up tools (nmap, nuclei, testssl.sh)
    against an allowlist.

  • A React dashboard talks to the API and a /ws/status WebSocket.



Four features, four attack surfaces. Here's how they combined.





Link 0: there was no authentication at all



The foundation of the whole chain: every route and the WebSocket were open.

No API key, no session, nothing. The Dockerfile bound 0.0.0.0:8000 and ran as

root. So everything below is reachable by anyone who can hit the port.




app = FastAPI(...)
api_router = APIRouter() # no dependencies
# ... every scan/upload/deepdive route hangs off this open router






If your service does anything more privileged than serve static files, "we'll

add auth later" is how you end up here. Auth is link zero.





Link 1: Nmap argument injection (no shell required)



The scan target flowed from the JSON body straight into the Nmap argv:




def build_nmap_command(target, start_port, end_port, ...):
command = ["nmap", "--reason", "-p", port_range, "-oX", "-"]
# ...flags...
command.append(target) # <-- attacker-controlled, last positional
return command






It's subprocess.run(command, shell=False), so there's no classic shell

injection. People stop worrying at that point. They shouldn't — you don't need

a shell to abuse a tool as powerful as Nmap.
If target is

--script=/path/to/evil.nse, it's no longer a target; it's a flag. Nmap will

happily load and run that NSE (Lua) script. Nmap can also write files

(-oN /etc/cron.d/x), read files via scripts, and more. extra_args was passed

through verbatim too, so you didn't even need the trick.



shell=False protects you from the shell. It does nothing about the program

you're actually invoking interpreting its own arguments.





Link 2: uploaded scripts that authorised themselves



The deep-dive runner executed only allowlisted commands — good instinct. But

there was an upload endpoint:




async def save_script(self, name, content):
script_path = scripts_dir / Path(name).name # traversal handled, ok
self._write_script(script_path, content) # writes + chmod +x
return str(script_path)

def _load_allowlist(self, initial):
# ...
for item in scripts_dir.glob("*"):
if item.is_file():
allowed.add(str(item.absolute())) # <-- upload allowlists itself






Read those two together: an uploaded file is written, made executable, and then

added to the very allowlist that's supposed to gate execution. The allowlist

was checking a lock whose key it handed out for free. An allowlist that any input

can extend is not an allowlist.





The chain



No auth (link 0) + write-an-executable-and-allowlist-it (link 2) + get Nmap to

load an arbitrary NSE file (link 1) = an unauthenticated network attacker runs

code on the host. Each piece looked defensible in isolation. Chained, it's game

over. That's the thing about appsec: bugs compose.





The bonus one I didn't see coming: XXE



While wiring up CI I ran Bandit and it flagged

the XML parser:




import xml.etree.ElementTree as ET
root = ET.fromstring(xml_output) # B314: untrusted XML






Nmap emits XML, and I parse it. But that XML describes the host you scanned

which can be attacker-controlled. A hostile service can shape responses so the

resulting Nmap XML carries an XXE payload (file:///etc/passwd, entity

expansion, SSRF). The stdlib parser resolves external entities. Static analysis

caught a bug my manual review walked straight past.





The fixes



Authentication on everything. An X-API-Key dependency on the whole API

router; the WebSocket validates a token. Key comes from PORTSCANNER_API_KEY or

is generated to web_runs/.api_key (0600) on first boot — never anonymous.

Constant-time comparison.




api_router = APIRouter(dependencies=[Depends(require_api_key)])






Validate input; refuse flag-shaped values. Targets must match a hostname/IP/

CIDR pattern and cannot start with -. Dangerous flags (--script, -oN,

--datadir, …) are rejected in extra_args. And defence in depth: a --

sentinel before the target so Nmap stops parsing options.




command.append("--")      # everything after this is a positional, never a flag
command.append(target)






Stop uploads from self-authorising. Uploaded scripts are no longer added to

the allowlist; an operator must add them explicitly via env. Upload is disabled

entirely unless PORTSCANNER_ENABLE_SCRIPT_UPLOAD=1.



Kill the XXE with defusedxml.




from defusedxml.ElementTree import fromstring as _safe_fromstring
root = _safe_fromstring(xml_output) # entities/DTDs refused






The rest: redact secrets (passwords, tokens, secret://) before logs hit the

DB; run the container as non-root with cap_net_raw scoped to the Nmap binary

only; pin dependencies. Then I locked it all in with a tests/test_security.py

suite (auth, injection, XXE, redaction) and CI: pytest, Bandit, pip-audit, and

CodeQL on every push.






Five things worth stealing





  1. Auth is link zero. Everything else is only as safe as "who can reach it."


  2. shell=False ≠ safe. The invoked program parses its own args. Put --
    before positionals and validate anything flag-shaped.


  3. An allowlist that inputs can extend isn't one. Authorisation and the data
    being authorised must not share a writer.


  4. Untrusted XML is untrusted even when you generate it — if it's derived
    from something an attacker controls, parse it with defusedxml.


  5. Automated scanners see what tired eyes miss. Bandit/CodeQL/pip-audit in CI
    caught a real bug I'd read past. Cheap, run them.



Full before/after is in the repo, including the threat model

and CI config. If you spot something I still got wrong, open an issue — that's

the point of publishing it.



https://github.com/DipesThapa/PortScanner

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten I shipped an unauthenticated RCE in my own port scanner — here's the whole chain, and how I killed it

Thematisch verwandte Begriffe: shipped, unauthenticated, port, scanner · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-61647 | NotebookLM MCP is an MCP server and HTTP service for interacting with Go…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick