Tutorial: grade Conpot with UHBS (quick + full)¶
Status: Informative · evaluation proof
Target: mushorg/conpot — ICS honeypot, Modbus on TCP 5020
Published artifacts: quick/ · full/ · trust notes: METHODOLOGY.md
0. Prerequisites¶
git clone https://github.com/mziqudhd92/uhbs-standard.git
cd uhbs-standard
docker build -t uhbs:4.0.1 .
docker build -f Dockerfile.full -t uhbs:4.0.1-full .
1. Clone Conpot¶
mkdir -p .local/labs
git clone https://github.com/mushorg/conpot.git .local/labs/conpot
cd .local/labs/conpot
git rev-parse HEAD
# published proof used: b82b7a7fae8866c648f7be37f7033481a1b93482
2. Build and start the honeypot¶
Upstream’s image currently ships with setuptools ≥ 82, which drops pkg_resources that Conpot’s fs dependency still imports. Use the lab wrapper:
# from UHBS repo root
docker build -t conpot:lab .local/labs/conpot
docker build -f docs/conformance/labs/conpot/Dockerfile.lab -t conpot:lab-fixed .
docker network create uhbs-lab 2>/dev/null || true
docker rm -f conpot-lab 2>/dev/null || true
docker run -d --name conpot-lab --network uhbs-lab -p 502:5020 -p 8800:8800 conpot:lab-fixed
# Wait until Modbus is actually listening (starts ~20s after container create)
until docker logs conpot-lab 2>&1 | grep -q 'Modbus server started'; do sleep 1; done
docker logs conpot-lab 2>&1 | grep -i 'Modbus server'
# Smoke Modbus TCP (host :502 → container :5020)
python3 - <<'PY'
import socket, struct
s = socket.create_connection(("127.0.0.1", 502), timeout=5)
# Read Holding Registers FC03 — default template often returns exception 0x02
s.sendall(struct.pack(">HHHBBHH", 1, 0, 6, 1, 3, 0, 1))
s.settimeout(3)
print(s.recv(64).hex())
s.close()
PY
Inside uhbs-lab, grade against host conpot-lab port 5020 (not host-mapped 502).
3. Quick run (smoke grade)¶
Lab asset: ../../labs/conpot/ics_modbus_quick.yaml
Do not use SSH/Telnet profiles for Conpot Modbus — UHBS will refuse protocol conflicts.
mkdir -p docs/conformance/reports/conpot/quick
docker run --rm \
--network uhbs-lab \
-v "$PWD:/work" \
-v "$PWD/.local/labs/conpot:/honeypot:ro" \
-w /work \
-e UHBS_QUICK=1 \
-e UHBS_AIRGAP_ATTESTED=1 \
uhbs:4.0.1 \
lab \
--tps /work/docs/conformance/labs/conpot/ics_modbus_quick.yaml \
--protocol modbus \
--class ICS-SCADA \
--target conpot-lab \
--port 5020 \
--source-root /honeypot \
--phases profile,static,sandbox,dynamic,score \
--modules A,B,C,D,E,F \
--quick \
--skip-sast-tools \
--out /work/docs/conformance/reports/conpot/quick \
--environment "Quick Docker lab: Conpot Modbus :5020, UHBS_QUICK=1, SAST skipped"
Published quick result: UHQS 44.55 · Grade F · δ_C 0.5625
See quick/SCORECARD.txt.
4. Seed telemetry for the full run¶
mkdir -p .local/labs/conpot-telemetry
python3 - <<'PY'
import socket, struct
for i in range(20):
s = socket.create_connection(("127.0.0.1", 502), timeout=3)
# FC01 Read Coils — matches default template block starting at address 1
s.sendall(struct.pack(">HHHBBHH", i + 1, 0, 6, 1, 1, 1, 1))
s.settimeout(2)
try:
s.recv(256)
except Exception:
pass
s.close()
print("seeded")
PY
docker cp conpot-lab:/var/log/conpot/. .local/labs/conpot-telemetry/
printf '%s\n' '# UHBS egress gateway canary — no HIT lines means clean' \
> .local/labs/conpot-telemetry/egress-gateway.log
5. Full run (claim-grade Docker lab)¶
Assets:
../../labs/conpot/ics_modbus_full.yaml— 1000-sample TPS../../labs/conpot/inventory.yaml
Note: Conpot’s default Modbus template uses mode=serial with a 100 ms delay. High Module E concurrency (e.g. 25×200) can leave the listener in CLOSE_WAIT and hang the grader. Published full run uses concurrency 5 / requests 50.
mkdir -p docs/conformance/reports/conpot/full
# Prefer a fresh Conpot if you already ran a heavy load test
docker rm -f conpot-lab
docker run -d --name conpot-lab --network uhbs-lab -p 502:5020 -p 8800:8800 conpot:lab-fixed
until docker logs conpot-lab 2>&1 | grep -q 'Modbus server started'; do sleep 1; done
# re-seed telemetry (section 4) if you wiped the container
docker run --rm \
--network uhbs-lab \
-v "$PWD:/work" \
-v "$PWD/.local/labs/conpot:/honeypot:ro" \
-v "$PWD/.local/labs/conpot-telemetry:/telemetry:ro" \
-w /work \
-e PYTHONUNBUFFERED=1 \
-e UHBS_AIRGAP_ATTESTED=1 \
-e UHBS_EGRESS_GATEWAY_LOG=/telemetry/egress-gateway.log \
uhbs:4.0.1-full \
lab \
--inventory /work/docs/conformance/labs/conpot/inventory.yaml \
--target conpot \
--phases profile,static,sandbox,dynamic,score \
--modules A,B,C,D,E,F \
--concurrency 5 \
--requests 50 \
--out /work/docs/conformance/reports/conpot/full \
--environment "Full Docker lab: Conpot Modbus :5020 + 1000-sample A3 + SAST + telemetry"
Published full result: UHQS 55.4 · Grade D · δ_C 0.81
See full/SCORECARD.txt.
6. Verify¶
cat docs/conformance/reports/conpot/quick/SCORECARD.txt
cat docs/conformance/reports/conpot/full/SCORECARD.txt
uhbs validate-scorecard docs/conformance/fixtures/conpot-ics-scada.scorecard.json --strict
ls docs/conformance/reports/conpot/full/static/
7. How to read the grade¶
| Signal | Meaning |
|---|---|
Listener on :5020 |
Liveness OK — necessary but not sufficient |
| Module A ≈ 79.0 | FC03 holding-register probe fails on default coil/input map (exception 0x02); timing n=1000; 0–100 CheckResult scale |
| Module C = 55 (full) | Text conpot.log without STIX/OTel/ECS |
| Module E = 100 | Connect-storm at moderated load; not a claim about heavy ICS traffic |
| δ_C < 1 | Safety Gate not cleared (no remote shell exec surface on this decoy) |
| UHQS 55.4 / D | Above F band; still below beta Production Baseline (UHQS > 80 + gate) |
8. Cleanup¶
9. Pitfalls¶
pkg_resources/ setuptools — useconpot:lab-fixed(Dockerfile.labpinssetuptools<81).- Port mapping — host
502≠ container5020; grade on the Docker network at 5020. - SSH profiles — never pair Modbus with
low_interaction_ssh; use ICS TPS +--protocol modbus. - Multi-protocol template — HTTP
:8800, S7, SNMP, etc. are not scored in this report. - Module E load — keep concurrency low unless you have verified Conpot stays healthy.
Back to Conpot hub · all reports.