Talos Vulnerability Report


Peplink Smart Reader web interface /cgi-bin/upload_config.cgi data integrity vulnerability

April 17, 2024
CVE Number



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);
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);
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      }
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…");
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.

Exploit Proof of Concept

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.