CVE-2023-45744
A data integrity vulnerability exists in the web interface /cgi-bin/upload_config.cgi functionality of Peplink Smart Reader v1.2.0 (in QEMU). A specially crafted HTTP request can lead to configuration modification. An attacker can make an unauthenticated HTTP request to trigger this vulnerability.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
Peplink Smart Reader v1.2.0 (in QEMU)
Smart Reader - https://www.peplinkworks.com/Smart-Reader.asp
8.3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:H/A:H
CWE-284 - Improper Access Control
The Peplink Smart Reader is the access-control hardware associated with the PepXIM Time-Logging and Security System. It is used to manage access to buildings, workstations and public transit, as well as for employee time management.
The Peplink Smart Reader exposes a web server on ports 80 and 443 intended for local configuration and control of the card reader. This web server exposes an unauthenticated endpoint at /cgi-bin/upload_config.cgi
that lets a user place a new configuration file onto the device. The configuration file must be formatted appropriately. In this case, that entails creating a tar archive of the file, compressing it with gzip and then enciphering the file with a hard-coded XOR key of \x32
. Submitting the file to the endpoint results in the configuration file being staged. If an authenticated user applies the changes, the new configuration file takes effect. Most notably, changes to this configuration file can change the administrative user’s password for the website, locking them out of the device and granting future authenticated access to the attacker.
The upload_config.cgi
endpoint is handled by the function located at offset 0x4332a0
of /web/cgi-bin/upload_config.cgi
, which we refer to as upload_config_handler
. The relevant portion of a decompilation of this function is included below, for reference.
004332a0 int32_t upload_config_handler()
004332a0 // [1] Construct initial response header
004332d8 unlink("/tmp/config.txt");
00433300 char response_content_type[0x80] = "text/html";
0043332c puts("HTTP/1.0 200 OK");
00433348 printf("Content-type: %s\r\n\r\n", &response_content_type);
00433360 char* request_content_type;
00433360 cgiGetenv(&request_content_type, "CONTENT_TYPE");
00433374 char* boundary;
00433374 cgiGetBoundary(&boundary, request_content_type);
0043338c char* request_content_length;
0043338c cgiGetenv(&request_content_length, "CONTENT_LENGTH");
004333b4 int32_t i = atoi(request_content_length);
004333b4
004333b0 // [2] Extract the encoded archive file being supplied by the user into `/tmp/config.tmp.bin`
004333b0 void* config_file_content = malloc(0xa000);
004333d0 struct FILE* input_fd = *(uint32_t*)stdin;
004333cc sub_4327e0(boundary, "/tmp/config.tmp.bin");
004333d4 if (i > 0)
004333d4 {
00433410 do
00433410 {
004333e4 int32_t n = 0xa000;
004333e8 if (i < 0xa001)
004333e0 {
004333e8 n = i;
004333e8 }
004333f8 int32_t num_bytes = fread(config_file_content, 1, n, input_fd);
0043340c i = (i - num_bytes);
00433408 sub_4329dc(config_file_content, num_bytes);
00433408 } while (i > 0);
00433410 }
00433418 sub_432d14();
00433428 free(config_file_content);
00433428
0043343c // [3] Conduct validation checks on the encoded and decoded file
0043343c if (file_content_len("/tmp/config.tmp.bin") < 0x15)
00433438 {
00433558 ... // Handle 'Not a configuration file'
004335d4 }
00433448 decipher_config_file("/tmp/config.tmp.bin");
00433458 if (check_is_valid_configuration_file(/tmp/config.txt) == 0)
00433458 {
0043351c ... // Handle 'Not valid configuration file'
00433548 }
This initial chunk of the function is responsible for receiving, parsing and decoding the file, storing it into /tmp/config.txt
and finally validating the contents. Note that at no point in this handler function is any form of authentication completed. The validation of the configuration file is relatively straightforward: the encoded file must be larger than 0x14 bytes in size and must, at a minimum, match the AP_MODEL and AP_VARIANT of the configuration file to the target device. Given the existence of TALOS-2023-XXXX, constructing a valid configuration file for an arbitrary Smart Reader is very simple.
00433470 // [4] Make a back up of the existing configuration if it doesn't already exist
00433470 int32_t $v0_4 = fopen("/etc/masterconfig.old", "r");
00433478 if ($v0_4 == 0)
00433478 {
004335f4 sprintf(&response_content_type, "cp -p %s %s", "/etc/masterconfig", "/etc/masterconfig.old", 0x49c5d0);
00433604 system(&response_content_type);
00433608 }
00433488 else
00433488 {
00433488 fclose($v0_4);
00433488 }
00433488
004334a8 // [5] Copy the decoded file into `/etc/masterconfig`
004334a8 sprintf(&response_content_type, "cp -p %s %s", "/tmp/config.txt", "/etc/masterconfig", 0x49c5d0);
004334b8 system(&response_content_type);
004334cc puts("<script>window.location = 'setup…");
004334cc
004334e0 // [6] Lastly, signal that a configuration change has been made but needs to be applied
004334e0 ftouch("/tmp/prompt_activate");
0043350c return 0;
0043350c }
At [5]
the validated and decoded configuration file is copied into the staging /etc/masterconfig
file and a signal (at [6]
) is set so that any future requests to the web server will prompt the authenticated user to save the staged changes. At this point the secondary (benign) user can opt to discard the changes, or apply the changes. If the secondary user applies the changes, the attacker-controlled configuration file will be applied, potentially locking the secondary user out of the device.
curl -v -F config_file=@config.bin http://$TARGET/cgi-bin/upload_config.cgi
The vendor links to new firmware versions at the end of their advisory: https://forum.peplink.com/t/peplink-security-advisory-smart-reader-firmware-1-2-0-cve-2023-43491-cve-2023-45209-cve-2023-39367-cve-2023-45744-cve-2023-40146/47256
2023-11-30 - Vendor Disclosure
2024-04-17 - Vendor Patch Release
2024-04-17 - Public Release
Discovered by Matt Wiseman of Cisco Talos.