Most "connect your device to AWS IoT Core in five minutes" guides get you a blinking dashboard and a good feeling. This one does that too, and then keeps going, because the interesting part starts right after the tutorial ends. I wire up a Raspberry Pi as an industrial edge gateway, send real telemetry, and then show what happens when someone copies the device's certificate. Spoiler: the certificate was never the thing protecting you.
Lab Setup and Theorie
Onboard Device
Create Endpoint
First a couple of variables, then ask AWS for the device data endpoint:

Create a Thing
The Thing is just the device's identity record in the registry.

Create Certificate
This is the X.509 certificate and private key the Pi uses to prove who it is over mutual TLS.

Policy
Here is the part everyone skims past. A lot of "easily connect your device to AWS IoT Core" tutorials hand you a policy that is completely open, something that allows everything on everything. For this example I create exactly that kind of policy on purpose and call it what it is, vulnerable:
aws iot create-policy --policy-name "$POLICY_NAME" --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"iot:*","Resource":"*"}]}'Attach the Policy to the Device
aws iot attach-policy --policy-name "$POLICY_NAME" --target "$CERT_ARN"
aws iot attach-thing-principal --thing-name "$THING_NAME" --principal "$CERT_ARN"Download the certificate
I did all of this from CloudShell, so I have to download the certificates to move them onto the device.

After downloading the private key and the device cert I copy them onto the device, in my case the Raspberry Pi.
Connect to IoT Core
sudo apt update && sudo apt install -y python3-venv git
python3 -m venv ~/iot-venv
source ~/iot-venv/bin/activate
pip install --upgrade pip
pip install awsiotsdk
git clone https://github.com/aws/aws-iot-device-sdk-python-v2.git
cd ~/aws-iot-device-sdk-python-v2/samples/mqttSend Data
python3 mqtt5_x509.py --endpoint YOUR-AWS-DEVICE-DATA-ENDPOINT.ats.iot.eu-central-1.amazonaws.com --cert ~/certs/device.pem.crt --key ~/certs/private.pem.key --client_id pi4-edge-01 --topic dt/pi4-edge-01/telemetry --count 5In the AWS console go to Test, MQTT test client, and subscribe to all topics.

And there they are, the messages our edge device is sending.
Sending Telemetry Data
I wrote a small script that ships telemetry like CPU temperature and memory usage up to AWS. You can send whatever data you want here.

To make the attack easy to see later, I built a dashboard in AWS CloudWatch.

The Attack
From device health to process data, and why the data here is simulated
Up to this point the Raspberry Pi has been reporting its own health, CPU temperature and memory. In that first dashboard I could watch those values arrive through IoT Core, the Rules Engine and CloudWatch. That was on purpose. The point of step one is not the sensor, it is the chain: one device, one certificate, one policy, mutual TLS, and the question of who is allowed to speak in whose name. The Pi was the simplest possible sender to build that chain cleanly.
But device health is not the story OT security is about. What a control room actually watches is process values, pressure, temperatures, valve positions. And I cannot pull those from real hardware yet, the actual OT rig, a CybICS gas pressure setup on a custom PCB, is not here. Instead of waiting, I drop a simulator in between. It sends realistic process telemetry for a gas pressure reduction station, inlet and outlet pressure, gas temperature, valve position, flow, over the exact same path and the same JSON schema the real source will use later. When the hardware shows up, the Modbus to MQTT bridge just replaces the simulator, and the topic, the rule, the dashboard and the alarms all stay the same. So I am really defining the telemetry contract that the real plant will fill later.

One honest note, because an OT audience will ask right away. These numbers are synthetic, fixed setpoints with a bit of gaussian noise, plus a scripted fault scenario. It is deliberately not a physical process model. For what I am showing here that changes nothing. The attack does not depend on the values being real. Taking over an identity, reading along, forging telemetry, all of that works the same on simulated data as on real data, because the gap is not in the data, it is in the policy that decides who is allowed to write it.
So the station gets its own dashboard:

And that brings us to the actual topic. This dashboard is the truth the control room relies on. Next I look at how much that truth is worth when the device delivering it carries an over broad policy.
Attack Story
The break-in that barely counts as one
Picture one of those pressure reduction stations along a gas distribution line. No control room on site, no guard, just a metal cabinet behind a chain link fence at the edge of town that a technician unlocks maybe twice a year. Inside sits the process itself, pressure control, valves and so on, and next to it a small Linux gateway that collects the readings and ships them to the cloud. In my lab that gateway is a Raspberry Pi 4.
The attacker does not need to "hack" anything in the Hollywood sense. They just need to get there, and in OT that is often the weakest link, unmanned sites, a standard lock a screwdriver opens, a cabinet in the field nobody looks at for days. Whoever stands in front of it has the device literally in their hands.
From there everything is offline and quiet. They pull the SD card, or boot the device into a maintenance mode, and read the filesystem. What they are after is two files:
device.pem.crt, the station's certificateprivate.pem.key, its private key
That is the device's entire identity, in plain text on the disk. No password cracking, no exploit, copying is enough. They put the card back, close the cabinet, and on the outside nothing happened. The station reports again as if nobody was ever there, and to the operator in the control room it looks like a brief hiccup at most.
Now they have those two files. And with them, they are the station. Not at the cabinet anymore, but from any device anywhere in the world, for as long as it takes someone to revoke that certificate. And in practice that "device" is not a laptop at home, it is real infrastructure, rented or compromised servers somewhere out on the net, disposable and anonymous. They never have to go near the plant again, and they do not have to attack AWS either. They are now a legitimate device with a valid identity that the cloud happily accepts.

And that puts the real question on the table, the one that is not decided at the cabinet but in AWS: what is this identity actually allowed to do? That is where it gets interesting.
The attack script
The script connects with the stolen device certificate as a seemingly legitimate station to AWS IoT Core. It reads along with the whole fleet's telemetry, learns the normal state from the live stream, and then feeds in forged values that match it exactly. On the server it sits there quietly as run.py, nothing that would stand out at a glance.
Recon: what does "normal" even look like
The attacker sits on some compromised server and starts the script with the stolen certificate. To AWS IoT Core this is a completely normal, valid device connection, no exploit, no alarm. But instead of only speaking as "its" station, they subscribe to scada/#. Thanks to the over broad policy this one certificate is allowed to read every station's topics.
This is the first step of any serious attack, reconnaissance. In seconds they have enumerated the fleet, which stations even exist, station-07 and station-12, and more importantly read their normal state live: outlet pressure around 4 bar, inlet pressure about 16 bar, gas temperature about 10 °C, valve about 45 %, flow between 1.150 and 1.220 m³/h. They do not have to guess anything. That normal picture is exactly what they need in the next step to feed in fakes that are statistically indistinguishable from the real feed.

From this server they never need to set foot in the plant again. They can forge any station's values remotely, including stations they have never been near. And if they were on site, they could simply unplug the real device, let their server send in its place, and calmly mess with the plant while the control room sees quiet, normal values the whole time. How far all of this reaches comes down to one single thing, and it is not at the cabinet, it is in AWS: the policy attached to that certificate.
Taking over: the real device goes quiet and the cloud does not notice
Now the theory gets concrete. In the real world the attacker would have unplugged the Linux gateway at the station, in the lab I just stopped the sender on the Pi. From this moment on the real station-12 is silent, no reading leaves the device anymore.
Meanwhile the script runs on the compromised server, and it does two things in order. First it listens for a moment and learns what "normal" looks like for station-12.
A few seconds are enough. Then it takes over the identity. It connects with the stolen certificate as station-12 and replays exactly that learned normal picture, with a little noise so it looks alive.
Station-12 keeps running as if nothing happened, outlet pressure around 4 bar, the gauge green, the pipeline overview completely unremarkable. Except these values no longer come from the field, they come from a rented server somewhere on the net. The real device is off.
That is the gap, and it is not staged. The device can be provably switched off, and the control room still shows a healthy, live station. AWS does not flag it, because the only thing it checks is a valid certificate, and that is present. The cloud cannot tell the real station from its copy. For the operator there is nothing to see, no alarm, no hint. They are looking at a station that, in that moment, no longer exists.

And it goes further: the attacker decides what the control room believes
Up to now the takeover was invisible, the attacker just kept replaying the learned normal picture. But they can just as easily set any value they want. Instead of the calm 4 bar the script now sends a constant 6.5 bar:

On the dashboard station-12's curve shoots up and breaks through the red safe max line at 5 bar. An overpressure that does not exist at the real station, because it is switched off. And yet there it is in the control room, as if it came straight from the field.
That proves the second half of the point: the attacker does not just read the telemetry, they decide it. The quiet playing along turns into an active intervention. They can hide a real danger, showing calm values while the plant is actually going out of bounds, or they can invent one, a false alarm that triggers an unnecessary shutdown and eats away at trust in the system. Both directions run through the same gap, and both look like perfectly normal readings to the operator.
An attentive defender would even have a small clue here. The forged value sits perfectly constant for seconds, while real readings always jitter a little. A careful attacker would therefore forge with matching noise, the way the learn phase does. The core stays the same though: the values obey the attacker, not the plant.
What matters is what did not happen here. No exploit, no break-in to AWS, no bug in the service being abused. A legitimate identity is legitimately accepted, just from the wrong side. Whether this is even possible comes down to a single place, and it is in AWS: the policy attached to that certificate. That is what we tackle next.
The Fix
Flipping the fix on
In the console the actual step is quick, attach the hardened policy to the station's certificate and take the old, broad one back off.


One detail that is easy to trip over, and worth its own line: AWS IoT evaluates all policies attached to a certificate together. As long as the broad pi4-lab-vulnerable is still hanging next to it, its "allow everything" keeps winning, even with the scoped policy already attached. So the hardening only takes effect once the old policy is actually detached. After that the certificate carries just the one fenced in rule.

The real stations do not notice any of this. THey still connect under their own name and send on their own topic, which is exactly what the new policy allows. Normal operation keeps running.
The re-test: same attack, now into the void
Now the same script, from the same compromised server, with the same stolen certificate. Only the policy has been swapped.
- Recon, reading
scada/#: fails right at the connection. The attacker's client ID is not a valid Thing, andscada/#is not covered by the new policy. Instead of the fleet telemetry all that comes back is a connection error, not authorized. - Forging its own station, station-12: still works. And that is the honest part I am not going to hide.
- Forging a foreign station, station-07: rejected as well. The station-12 certificate is not allowed to write to any topic that does not belong to its own station. I will look at that hands on in the next article.

What the policy fixes, and what it does not
The scoped policy shrinks the damage dramatically, but there is one truth it does not remove: a certificate stolen from station-12 is station-12. It can keep forging its own station, because as far as AWS is concerned it is that station. The policy cannot and should not take that away. You cover that remainder on three other levels:
- Revoke and rotate the certificate. The moment a device is suspected of being compromised, its certificate is deactivated or revoked and a new one is rolled out. From then on the stolen identity is worthless.
- Keep the key in hardware. If the private key lives in a secure element or TPM instead of as a file on the disk, it cannot be copied in the first place. That kills the whole attack right at the start.
- Watch behavior. Device Defender and the connection logs see what the values alone do not. The same identity suddenly connecting from a strange cloud IP, or two connections fighting over the same client ID, are exactly the signals that give this attack away.
And that closes the loop back to the start. The certificate was never the security boundary, it is only the identity, and identities can be copied. The boundary is the policy that decides what that identity is allowed to do. One line of iot:* on *turns a single compromised field device into a fleet wide problem. A policy bound to the device's own Thing turns it into a single device problem. Same theft, two completely different worlds, decided not at the cabinet in the field but in a text file in AWS.
Closing thoughts
I used a gas pressure reduction station because it makes the stakes easy to picture, but nothing in this story is actually about gas. Take the domain away and what is left is a pattern, a field device with its certificate sitting as a plain file on disk, a cloud backend that trusts whatever shows up holding that certificate, and a policy that quietly decides how far one stolen identity reaches. That shape turns up everywhere OT meets the cloud.
Swap the pipeline for a rail network and almost nothing changes. Trackside boxes reporting track occupancy, point positions, heater status on the switches, vibration data for predictive maintenance, more and more of it ends up on an IP path to some central or cloud system. The process is different and the failure modes are a lot more serious, but the question is identical: what is the identity behind that telemetry allowed to do, and what happens the day someone copies it. Water and wastewater, district heating, power distribution, building automation, EV charging, a factory line, it is the same trust chain with a different label on the front.
That is the whole reason to start this small. The lab is one Pi and one certificate, but the lesson scales straight up the criticality ladder. Authentication only proves who is speaking. Authorization, the policy, is the boundary that decides what they get to touch. As long as the identity is a file you can copy, that boundary is the only thing standing between one compromised field device and everything behind it. The more safety critical the process gets, the less comfortable that should make you, and the more the real answer moves toward hardware backed keys, revocation and watching behavior instead of trusting the certificate on its own.