OTForge · Meridian Petrochemical, simulated chemical batch plant
TRITON/TRISIS: defending a safety instrumented system
An attack that pivots through a default credentialed IP camera and writes a Modbus coil to a Triconex safety controller. Detection here has to be scoped by destination address, because the safety system and the process controller are indistinguishable on the wire.
Why a safety system is a different target
A Safety Instrumented System is the last independent layer between an unsafe process and an explosion, a toxic release, or a loss of life. Stuxnet and Industroyer manipulated the process, opening a valve or tripping a breaker. TRITON went after the system whose entire job is to stop the process from hurting anyone once something has already gone wrong.
XENOTIME (also tracked as TEMP.Veles) is the group behind the 2017 TRITON/TRISIS malware, exposed by FireEye/Mandiant and Dragos after an incident at a Saudi petrochemical facility. A Schneider Electric Triconex SIS unexpectedly tripped and shut the process down. Initially blamed on a mechanical fault, investigators instead found the attackers had reached an engineering workstation running the legitimate TriStation software, reverse engineered the proprietary protocol, and were pushing unauthorized logic to the safety controller. A bug in the attackers' own code, not any defensive control, caused the fail safe trip that led to the malware being discovered at all.
Researchers called it the first malware family with the potential to directly cause loss of life rather than only financial or operational damage. Targeting a SIS is an escalation in intent regardless of whether the attack succeeds.
The pivot
The attacker started with no route into the plant. The way in was an internet facing IP camera at
10.200.50.15 still running its factory password. That camera was
dual homed, sitting on both the internet facing DMZ subnet and the OT field
subnet at once, so compromising it created a live bridge between the public internet and the
safety critical network.
An SSH local port forward tunnel through the camera mapped local port 5060 to the SIS's Modbus
port at 10.200.10.11:502, and forwarded a Modbus function code 5 (Write Single Coil)
to the Triconex controller. Coil 0 on that device is wired to emergency shutdown relay K1, the
trip output that closes feed isolation valve SDV-101 on high high reactor pressure. Writing that
coil off is an attempt to defeat the plant's last defense against an overpressure event.
nc -zv 127.0.0.1 5060.
What the sensors could, and couldn't, see
The Zeek Modbus log recorded the write coming from 10.200.10.201: the camera's
OT-side interface, not the attacker subnet where Kali actually lives. Because the packet
left the camera on its OT interface, every sensor in the plant saw the connection as originating
from a device that legitimately had a path to the field zone. Suricata and Zeek sit on the OT
network and cannot see back through the encrypted tunnel. That is not a flaw in either tool, it
is a direct consequence of how a port forward pivot works.
A firewall rule blocking the attacker subnet would never have fired. The traffic never used that path. This is the practical argument for detection that is scoped to what is being protected rather than to where attacks are expected to come from.
Scoping the rule by destination
In the DNP3 lab, a protocol keyword (dnp3_func:3) was enough to separate malicious
from routine. Here it isn't, because the SIS and the process controller speak the identical
Modbus wire format on the identical port, and coil writes to the process controller are perfectly
normal.
SID 9000002 (preloaded) is scoped to $HOME_NET and matches only
the raw function code byte 0x05 at offset 7, so it fires on any Write Single Coil to
any device, including routine BPCS writes during normal operation.
SID 9100001 (the lab rule) uses the same byte match but is scoped to destination IP
10.200.10.11, so it fires only on a coil write aimed at the safety controller and
stays silent on legitimate writes to the process controller. The generic rule fired because a
coil write crossed the wire at all; the host scoped rule fired because that write targeted the
one device that should never receive an unauthorized command.
Defense in depth around the safety system
- Close the foothold. The camera is still dual homed and still running default credentials. No Suricata rule solves that, credential and asset management does.
- True segmentation. A device bridging the DMZ and the OT field network should not exist; the safety system in particular warrants its own conduit.
- Change management on the SIS. Logic changes should require documented authorization and independent review, so an unauthorized push stands out against a controlled baseline.
- Firmware integrity verification. A memory implant of the kind TRITON used is invisible to a network sensor sitting only on the conduit.
What I took from it
Independence on paper is not independence in practice. IEC 61511 requires an SIS to be independent from the Basic Process Control System: separate logic solver, separate I/O, ideally separate networking. Here they shared a subnet, firewall rules, and Modbus port 502, so nothing on the network distinguished the safety controller from the process controller it exists to protect. That is precisely why the rule had to be scoped by destination address: when two devices speak the same protocol on the same port, only the destination separates a write to the safety system from a routine process command.
The reject action is a verified mitigation, it stopped the write before it touched the trip relay. But it is a compensating control, not a fix. A protocol that cannot protect its own messages has to be wrapped in layers, so the last line of defense is never one keyword away from being switched off.
Lab write up from industrial control systems security coursework at UTSA. All addresses and devices belong to a simulated training environment.