
Fridgep0T: Low-interaction honeypot for supermarket refrigeration controllers
«Farò sicuramente dei danni, ma mi rivolgo a tutte queste casalinghe che
以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。
«Farò sicuramente dei danni, ma mi rivolgo a tutte queste casalinghe che vedono su Internet la borsa che gli piace e che costa solo 6 euro al mese, il frigorifero che costa solo 19 euro al mese, quel materasso che ne costa 22 al mese. Quando arriva la fine del mese, arrivano pure i Carabinieri […]»
"I will surely do some damage, but I'm talking to all those housewives who see online the bag they like for only 6 euros a month, the fridge for only 19 euros a month, that mattress for 22 a month. When the end of the month comes, the Carabinieri come too […]"
— Renato Zero, interview with Fanpage.it (our translation)
He knew it before any of us: where there are fridges, sooner or later the Carabinieri show up.
A honeypot for supermarket refrigeration controllers.
In October and November 2026, attackers broke into internet-facing refrigeration supervisors across Europe using default passwords and old, unpatched firmware, then forced defrost cycles. fridgep0t pretends to be one of those controllers. It records who connects, what passwords they try and which public exploits they use, and it never gives them anything real.
It is a single static binary with no runtime dependencies, built around three choices: a per-deployment identity so instances cannot be catalogued together, persona fidelity anchored to public sources, and supply-chain hardening.
Per-deployment identity. A decoy that ships one hard-coded store name, MAC address and CA subject looks identical in every deployment to anyone doing honeypot detection. Here the store name, hostname, MAC, firmware string and its Last-Modified time are generated per start, and so are the certificate's keys and validity window (set TLS_DIR to keep the certificate across restarts, as the container image does). The certificate subjects are not: they are fixed per persona, like the model-wide CN a real unit carries. Set IDENTITY_SEED to keep a stable identity across restarts without sharing it with any other deployment; leave it unset for a fresh identity each start. The generated store name follows the language of DEVICE_TZ (or STORE_LANG), and the MAC carries the vendor's IEEE-registered OUI (see FINGERPRINTS.md).
HTTPS is available for every persona (HTTPS_PORT). The certificate issuer is filled in only for danfoss, the one unit whose issuer details are documented; the others carry a bare CN rather than a guessed organisation.
A believable CVE-2021-45420 for the Dixell persona. Answering the XWEB upload paths with an empty 200 is something any automated checker sees through. fridgep0t simulates the write-then-read handshake the public exploit and its Nuclei template actually verify: a POST to an upload CGI "stores" the file in memory, a following GET /logo/<name> returns it, and cal_dir.cgi lists it. Nothing touches disk; stored uploads are capped and logged as control events.
Persona Pretends to be Serves
danfoss Danfoss AK-SM 800A system manager StoreView login, the XML API at /html/xml.cgi and /xml.cgi, HTTP+HTTPS
dixell Copeland/Dixell XWEB EVO supervisor Login page, cgi-bin/login.lua flow, the XWEB 500 CGI paths
aksc355 Danfoss AK-SC 355 (legacy) Login page with the documented Forgot-Credentials / MAC-disclosure flow
carel Carel pCOWeb thttpd server, HTTP Basic auth, /config/ pages incl. the cleartext credential disclosure
eliwell Eliwell TelevisGo Login page (Administrator / 0 default), welcome/version payload
rdm RDM Data Manager Login page with the documented 1234 default, CGI login/status
These six cover every vendor family named in the post-attack exposure reports (Danfoss, Dixell/Copeland XWEB, Carel, Eliwell, RDM).
Any persona can also run an optional Modbus TCP service with fake case temperatures, setpoints and a defrost register (see "Modbus TCP" below). Off unless MODBUS_PORT is set.
The aksc355 persona is deliberately shallow. It targets the other legacy model named in the CERT-FR alert (title: "Danfoss série AK-SM et AK-SC"), but we do not have a copy of the real AK-SC 355 web interface, so it is anchored only to publicly documented behaviour: the firmware band ("3.091 or higher"), the default accounts, and the "Forgot Credentials -> Reset Password" screen that discloses the unit's MAC and a reset code. It is enough to catch a scanner hunting specifically for AK-SC 355 units; it will not survive deep interaction. For a deployment you care about, capture a real unit's pages and replace the markup, and set SERVER_HEADER to the web server string you observe — we do not ship one, because a wrong value is a worse tell than none.
Requests that match a public exploit get an exploit field in the log:
Personas are built only from public sources (vendor manuals, CVE and vendor advisories, exposure-report signatures) and original markup — never captured firmware. Fidelity therefore varies with how much is publicly documented:
Where a real device detail cannot be cited it is left configurable and empty rather than guessed, because a wrong fingerprint is a worse tell than a missing one. For a deployment you care about, capture a real unit's pages and set SERVER_HEADER to the value you observe.
danfoss (nginx-style headers, keep-alive on the login page)
carel (thttpd 2.25b's response headers and error pages; answers in the request's HTTP version)
HTTPS_PORT adds a TLS listener for any persona. It speaks TLS 1.2 only, as the real units do, and presents a leaf certificate signed by a self-signed CA, both generated in-process and backdated. A failed or timed-out handshake is logged as tls_error.
MODBUS_PORT adds a Modbus TCP listener for any persona, answering any unit id.
cargo build --release --locked PERSONA=danfoss HTTP_PORT=8080 ./target/release/fridgep0t
A binary built on your workstation embeds your home directory in panic locations. Deploy the container build, which has no such paths, or build with RUSTFLAGS="--remap-path-prefix=$HOME=~" (a [build] rustflags entry in your user-level ~/.cargo/config.toml makes that permanent).
Open http://localhost:8080 and every request shows up as a JSON line on stdout and in events.jsonl. Run the tests: unit tests for the parsers and state, and an integration suite that starts the binary per persona and attacks it over HTTP, HTTPS and Modbus, including the timeout, connection-cap and shutdown paths:
cargo test --release --locked
CI runs build, test, clippy and fmt on stable, build and test on the minimum supported Rust (1.75), and cargo audit on the locked dependencies. It also runs every week, and its actions are pinned by commit SHA.
SIGTERM or Ctrl-C stops the decoy; it writes out the queued log events first, for at most two seconds.
Container. The Containerfile builds the binary and ships it on distroless nonroot, listening on 8080 (HTTP) and 8443 (HTTPS), logging to /logs/events.jsonl and keeping its certificate in /logs/tls:
podman build -t fridgep0t .
podman run -d --name fridgep0t --restart=always
--read-only --cap-drop=ALL --security-opt no-new-privileges
-p 80:8080 -p 443:8443 -v fridgep0t-logs:/logs:U
-e PERSONA=danfoss -e IDENTITY_SEED=<your-own-random-string>
fridgep0t
Fresh server. Paste cloud-init.yaml into the "User data" box of a new Ubuntu 24.04 server after setting PERSONA, FRIDGEP0T_REPO and FRIDGEP0T_REF in it. It installs the firewall, builds the image from your pinned commit, generates a random IDENTITY_SEED for that host and starts the container with the flags above. Only danfoss gets the HTTPS port there. To expose Modbus, follow the comment in the file.
Variable Default Meaning
PERSONA danfoss danfoss, dixell, aksc355 (or aksc), carel (pcoweb), eliwell (televisgo), rdm (datamanager)
BIND 0.0.0.0 Address to listen on
HTTP_PORT 8080 HTTP listener
HTTPS_PORT 0 (off) HTTPS listener (certificate generated in-process)
MODBUS_PORT 0 (off) Modbus TCP listener
LOG_PATH events.jsonl Where events are appended
DEVICE_TZ Europe/Rome Time zone the decoy's clock reports
IDENTITY_SEED none (random) Fixed seed for a stable per-deployment identity
TLS_DIR none (fresh each start) Directory that keeps the generated certificate across restarts (/logs/tls in the container image)
STORE_NAME generated Override the generated store name
STORE_LANG from DEVICE_TZ Language of the generated store banner: it, de, fr, es, nl, en
FIRMWARE generated Override the generated firmware string
MAC_OUI per persona (IEEE) Override the vendor OUI (xx:xx:xx) of the generated MAC
SERVER_HEADER persona default Override the Server header for any persona (see Fidelity tiers)
Reading the log
The log rotates at 256 MB, keeping events.jsonl.1 to events.jsonl.4. summary.py turns a log into a short report:
python3 -I summary.py --hours 24 --ignore <your-own-ip> < events.jsonl
It prints totals (events, sources, logins tried and accepted, exploit attempts, Modbus sources, control writes, defrost attempts), any gaps in the log, the top exploits, IPs, credentials and paths, and one line per defrost attempt.
Every event is one JSON object with ts (UTC), persona, proto (http, https, modbus, tcp or system), src_ip, src_port and event, plus the fields of that event. Attacker-supplied fields are cut to 512 characters and a line to 8 KiB ("truncated": true marks a line cut harder).
Never runs commands, reads files, or forwards traffic. Exploit payloads are logged and dropped; "uploaded" files live in a capped in-memory map.
Request bodies, logged fields and connections are capped, including a per-source limit so one attacker cannot crowd out others. Idle sockets time out; any connection is cut after 30 seconds however it trickles.
HTTP is parsed strictly: only HTTP/1.0 and 1.1, well-formed header lines, exactly one Host on HTTP/1.1, a numeric Content-Length that agrees across repeats and fits the body cap, no Transfer-Encoding. Anything ambiguous is logged as bad_request, answered with the persona's 400 (or 413) error page and the connection closed, so a body can never be read as the next request.
Over-long log lines are truncated; summary.py neutralises terminal escape codes from attacker input.
Logging never stalls a connection: one writer thread drains a bounded queue; if the queue fills, the newest events are dropped and a log_dropped event reports how many. Shutdown waits at most two seconds for the writer to drain.
One source cannot roll everyone else's events out of the size-bounded log: each source IP may log 600 events a minute, counted separately for routine events and for logins and commands, so a flood of requests cannot bury the command that follows it. Past the cap one log_suppressed event is written, then nothing until the minute is up. summary.py reports both kinds of gap.
Log fields are attacker-controlled. Treat them as untrusted when you process them.
The limits are fixed in the code, not configurable, because they are the safety net:
Shallow interaction: a logged-in attacker gets a handful of API commands, not the full app. Only danfoss shows anything in the browser after login: a table of the eight cases.
Public code can be fingerprinted. The generated identity helps, but change the persona markup for a deployment you care about.
Cloud IPs look suspicious. Some scanners flag industrial devices on cloud hosting as likely honeypots; a non-cloud address is more convincing.
To do
Open work, roughly in priority order. See FINGERPRINTS.md for the current sourced state of each persona.
First-party fingerprints (needs a Shodan/Censys account):
Thanks to Nik0l4 for help with the Danfoss fingerprint (login page, firmware band and the code-of-the-day algorithm).
BSD-4-Clause. Use and modification are free, but any publication, report, presentation or other material that mentions features or use of this software must acknowledge the copyright holder (clause 3 of LICENSE), and the notices must travel with copies. CITATION.cff gives a ready-made citation.
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。