A self-contained, software-only offensive-security lab that pairs an emulated
NTCIP 1202 traffic-signal controller with an Eclipse SUMO intersection
simulation. Real snmpset commands drive a real SUMO-GUI window, so every
attack is visible end-to-end without ever touching a physical device.
Companion write-up (Turkish): mucahic.com/dossier-08.html
A pure-software workbench for studying how a modern traffic-signal controller can be manipulated over its native management protocol. The lab exposes a realistic NTCIP 1202 / SNMPv2c attack surface and renders the consequences in a SUMO simulation, so the operator can see queues build, gridlock spread and adaptive logic fall for spoofed sensor data.
It is not a production controller and not intended for any device on a public network. Use it to learn, to demo, and to design defenses.
There is no shortage of NTCIP material on GitHub, but every project takes one small slice:
| Project | What it does | What it is not |
|---|---|---|
mnit-rtmc/ntcip |
Rust library, focused on DMS | Not 1202, not a server |
markashook/SharpNTCIP |
C# protocol library | Client only, no emulator |
usdot-fhwa-OPS/V2X-Hub |
NTCIP 1202 SPAT plugin | Talks to a real controller |
mmitss/mmitss-az |
Arizona MMITSS V2I | Needs real signal hardware |
sumoITScontrol |
SUMO signal-control framework | TraCI/TCP only, no SNMP |
Fortiphyd/GRFICSv3 |
OT security lab | Chemical process, not traffic |
This lab fills the gap between them: an emulated controller that speaks NTCIP 1202 on the wire and a visual SUMO target that lets you see what happens after the SET request lands. The whole thing is one pip install and one python command away from a working offensive playground.
┌──────────────┐ UDP/1161 (SNMP v2c) ┌────────────────────────────────┐
│ attacker │ ────────────────────────> │ controller.py │
│ snmpset │ │ ┌─────────────────────────┐ │
└──────────────┘ │ │ pysnmp agent thread │ │
│ │ - public (read) │ │
│ │ - private (write) │ │
│ └────────────┬────────────┘ │
│ │ │
│ ┌────────────▼────────────┐ │
│ │ ControllerState │ │
│ │ (thread-safe dict) │ │
│ └────────────┬────────────┘ │
│ │ │
│ ┌────────────▼────────────┐ │
│ │ TraCI bridge (main) │ │
│ │ every step: │ │
│ │ state -> SUMO TLS │ │
│ └────────────┬────────────┘ │
└───────────────┼────────────────┘
│ TCP / TraCI
▼
┌────────────┐
│ SUMO-GUI │
│ (intersect)│
└────────────┘
A single Python process exposes two doors. One door takes SNMP packets from the attacker; the other door drives the SUMO simulation through TraCI. The two threads share a small in-memory state object protected by a lock. That is literally the only bridge between the SNMP world and the simulation world.
Vendor branch 1.3.6.1.4.1.1206 (NTCIP organization). The lab exposes a
deliberately small subset that captures the attacks in the write-up.
| OID | Field | Type | Notes |
|---|---|---|---|
.1.3.6.1.4.1.1206.4.2.1.1.1.0 |
currentPhase | Integer32 | 1 = NS green, 2 = EW green |
.1.3.6.1.4.1.1206.4.2.1.1.2.0 |
greenTime | Integer32 | seconds |
.1.3.6.1.4.1.1206.4.2.1.1.3.0 |
forceAllRed | Integer32 | 1 = all-red gridlock |
.1.3.6.1.4.1.1206.4.2.1.1.4.0 |
greenLockPhase | Integer32 | 0 / 1 / 2 (green-forever lock) |
.1.3.6.1.4.1.1206.4.2.1.1.5.0 |
preemptionPhase | Integer32 | 0 / 1 / 2 (fake emergency) |
.1.3.6.1.4.1.1206.4.2.1.1.6.0 |
loopSpoofMask | Integer32 | 4-bit (N=1, E=2, S=4, W=8) |
.1.3.6.1.4.1.1206.4.2.1.1.99.0 |
sysBanner | OctetString | identification string |
.1.3.6.1.4.1.1206.4.2.1.1.100.0 |
screenshotRequest | OctetString | write a filename to capture SUMO |
Communities: public (RO) and private (RW). This mirrors NTCIP 1202
defaults that are still found in real deployments.
ntcip-1202-lab/
├── scenario/ SUMO network + routes (4-leg signalized intersection)
├── server/ NTCIP 1202 SNMP agent + TraCI bridge (single process)
├── attacks/ SNMP client, recon, attack CLI, demo orchestrator
├── diagrams/ Generators for the article diagrams (PIL/SVG)
├── README.md
├── LICENSE MIT
└── requirements.txt
captures/ is created at runtime and is gitignored.
python -m pip install -r requirements.txt
eclipse-sumo installs the SUMO binaries (sumo, sumo-gui, netconvert)
into the active Python's Scripts/ directory. controller.py extends
PATH automatically so they are reachable without a manual setup step.
netconvert \
--node-files scenario/network.nod.xml \
--edge-files scenario/network.edg.xml \
--output-file scenario/network.net.xml \
--tls.guess true --tls.default-type static
Terminal 1 — SUMO-GUI + SNMP agent + TraCI bridge:
python server/controller.py --mode full
Terminal 2 — recon and individual attacks:
python attacks/recon.py
python attacks/attack.py eternal-red
python attacks/attack.py green-forever --phase 1
python attacks/attack.py preemption --phase 2
python attacks/attack.py loop-spoof --mask 5
python attacks/attack.py reset
python attacks/attack.py screenshot --name eternal_red.png
Or the orchestrator that runs every attack and captures one screenshot per scene:
python attacks/demo_all.py
The agent talks plain SNMPv2c, so anything that speaks SNMP works against it. The lab uses the same protocol shape as a field controller:
snmpwalk -v2c -c public 127.0.0.1:1161 .1.3.6.1.4.1.1206.4.2.1.1
snmpset -v2c -c private 127.0.0.1:1161 .1.3.6.1.4.1.1206.4.2.1.1.3.0 i 1
The four attacks reflect the categories in the companion write-up.
- Eternal Red —
forceAllRed = 1keeps all directions red. - Green Forever —
greenLockPhase = 1|2pins one axis green. - Preemption Spam —
preemptionPhase = 1|2mimics a stuck emergency-vehicle signal. - Loop Spoofing —
loopSpoofMaskinjects phantom vehicles on selected approaches.
Each of these is one snmpset and gets one SUMO scene in return.
- Rotate the community strings off
public/private. - Put SNMP traffic on a management VLAN with strict L3 ACLs.
- Move to SNMPv3 (auth + priv). NTCIP 1218 already favors this.
- Verify the hardware conflict monitor is calibrated and recently tested.
- Treat vendor remote access as just-in-time, not always-on.
- Lock physical access to detector pull boxes.
Designed for the simulation it ships with. Pointing real snmpset traffic at
production traffic signals without written authorization is illegal in every
jurisdiction the author is aware of. Stay in your own lab.
Mücahit İç (MUCAHIC) — mucahic.com · LinkedIn · GitHub
