Inside the Router: How I Accessed Industrial Routers and Reported the Flaws

Router Vulnerability Hunt, From Google Dorks to Firmware Emulation - The Full Story

Hello, World! ❤️

Today, I have an exciting story about how I exposed admin passwords and gained access to thousands of 3G/4G/5G Industrial Cellular Routers with the help of some old-school vulnerabilities (or rather, misconfigurations).

Before proceeding please note that the following vulnerabilities have been immediately identified and fixed. The manufacturer proactively communicated the vulnerability situation and promptly updated the software to address the vulnerability risks. I confirm that this issue has been resolved by August 2023 without any negative impact. Therefore, the following vulnerability content is for discussion and research purposes only.


Accidental Discovery

A few months back, I shared a search trick on GitHub that allowed you to find thousands of leaked keys and secrets from public repositories. You can check out that search syntax right here. As I was experimenting with it, I realized it could be turned into a Google Dork.

Google Dork is basically a clever search query that you can use on Google to discover hidden, sensitive information on the internet. Here’s the special Google Dork I came up with:

site:*.*:8080 (ext:json OR ext:properties OR ext:sql OR ext:txt OR ext:log OR ext:tmp OR ext:backup OR ext:bak OR ext:enc OR ext:yml OR ext:yaml OR ext:toml OR ext:ini OR ext:config OR ext:conf OR ext:cfg OR ext:env OR ext:envrc OR ext:prod OR ext:secret OR ext:private OR ext:key)

Pretty simple, right? This small piece of code will help me find sensitive files like SQL, config, backups, logs, etc., on a web server running on port 8080, exposed to the world.

As a result, I came across the website http://[REDACTED]:8080/lang/log/system.log. Upon visiting this website, I discovered that it was publicly disclosing system logs, which included internal information.

By deleting system.log from the URL and navigating back to one directory, I noticed a server misconfiguration that allowed me to view the contents or list of files within that particular directory. In this directory, I found an interesting log file named httpd.log.

Upon reviewing its contents, I identified that usernames and encrypted passwords were being logged. 😲

Further, I observed that the application’s login page is accessible via http://[REDACTED]:8080/.

From a quick Google search, I found out that Ursalink is a manufacturer of IoT products in the industrial sector. It was a vendor of remote monitoring, data collection, and automation devices for use in various industrial applications.

I guessed it might be a router login. Upon closer examination of the login page, I came to know that it utilized a JavaScript file called login.js.

What’s the big deal with login.js, you ask? Well, this JavaScript file revealed that the application was encrypting passwords client-side using the CryptoJS library and then sending them to the server.

Upon analyzing the JavaScript code, I determined that the application was using the AES algorithm in CBC Cipher mode, with a hardcoded secret key and initialization vector (IV). Here is a formatted version of the same JavaScript code snippet:

$("#login").click(function() {
var e = $("#username").val(),
n = $("#password").val();
if (0 == e.length) return void $(".error").html(language_class.login.error.username);
if (0 == n.length) return void $(".error").html(language_class.login.error.password);
var o = CryptoJS.enc.Utf8.parse("1111111111111111"),
t = CryptoJS.enc.Utf8.parse("2222222222222222"),
l = CryptoJS.enc.Utf8.parse(n),
i = CryptoJS.AES.encrypt(l, o, {
iv: t,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
n = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(i.ciphertext.toString()

To further investigate, I quickly created a decryption code and successfully decrypted the previously copied password. Here is the decryption code I used:

CryptoJS.enc.Utf8.parse("1111111111111111"), {
iv: CryptoJS.enc.Utf8.parse("2222222222222222"),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7

The decrypted password that I obtained was: electrasystems1

But would this password actually get me inside the application? To validate the severity of this vulnerability, I attempted to log into the application using the username and cleartext password.

Surprise! I successfully gained unauthorized access to the router.

My guess was right, I concluded from the model number that it is an Industrial Cellular Router.

UR75 Industrial Cellular Router

Along with this, I also did port scanning using nmap and found 4 open ports including TCP port 22.

With the help of the same credentials, I managed to log in to the router console using SSH.

To find the owner of the router or the linked organization, I used to get the information associated with the IP address. Through this process, I determined that the IP address is linked with Telus Communications Inc., a Canadian telecom company.

After a little analysis and gathering information from the internet, I concluded that the IP address is assigned by Telus to the SIM card inside the cellular router. Therefore, it is quite difficult to get SIM-owner/router-owner information to inform/notify about this issue.

But I wasn’t done yet!

I wanted to see if other routers were also vulnerable. I queried again with the new Google Dorks "/lang/log/system" ext:log, "URSALINK" "English" "Login" and confirmed that a bunch of routers were also vulnerable.

Google Dork: "/lang/log/system" ext:log
Google Dork: "URSALINK" "English" "Login"

I was not satisfied with the limited results, so I used the Shodan Search Engine to get more such routers using the http.html:rt_title search query filter.

It returned over 5500 results! 🎣 I downloaded all the Shodan results, filtered and exported them into a text file.

Command: shodan parse --fields ip_str,port --separator ":" shodan_results.json.gz > ursalink_ip.txt

Using httpx, exported URLs of all vulnerable router web interfaces by requesting the /lang/log/httpd.log endpoint.

Command: httpx -l ursalink_ip.txt -x HEAD -path "/lang/log/httpd.log" -sc -title -location -cl -ct -csv -o ursalink_vulnerable_hosts.csv

To demonstrate the impact, and verify the vulnerability at scale, I created a Python script to test all of those results, and you can find it in my GitHub repository.

The script allows the testing of a router’s console URL or a list of URLs from a text file and quickly retrieves the admin password. Passed the result of the vulnerable list of URLs to my script and observed cleartext admin credentials.

As a result — most routers were vulnerable, and I collected administrator credentials. I even logged in to a few devices as proof. I identified that the Ursalink Industrial Cellular Router series UR5X, UR32L, UR32, UR35, and UR41 were vulnerable and others may also be vulnerable.

This vulnerability becomes even more severe as some routers allow the sending and receiving of SMS messages. An attacker could exploit this functionality for fraudulent activities, potentially causing financial harm to the router owner.

After logging in, go to General Settings in the System menu, and access the SMS tab for messaging.
SMS received from a Malaysian number

I started investigating the router manufacturer Ursalink. I learned that previously the manufacturer of this device was a Chinese company called Xiamen Ursalink Technology Co., Ltd., but then they merged with Milesight, and now they operate as Milesight IoT Co., Ltd.

I reached out to the company through email at and Help Desk/Support to report this vulnerability.

In my communication with them, I explained to them why system logs, such as system.log and httpd.log, should not be publicly accessible. I recommended the implementation of appropriate access controls to limit their exposure.

Additionally, I stressed the need to avoid the practice of hardcoding keys or IVs within the application code. While I acknowledged that the encryption layer was intended to provide an extra layer of security on top of TLS, I pointed out that its current implementation might not be as effective as intended. This encryption could be easily cracked and introduce unnecessary overhead to both the browser and the application, affecting overall performance. Therefore, I suggested reconsidering the necessity of this layer or exploring alternative, more secure algorithm implementations.

In response to my report, the company thanked me for the detailed report and confirmed that the vulnerability was a known issue. They assured me that the vulnerability had already been resolved/fixed in their latest firmware. They also provided the latest firmware version for verification purposes.


The Art of Firmware Emulation

In this chapter, I won’t be able to share the findings or other confidential details, but I’m excited to share some essential steps with you. If you’ve ever found yourself in a similar situation, dealing with firmware and emulation, this may be the insight you need.

So, I received a firmware file named Using the file command, I found out that it is a zip archive. Running binwalk, I found that this file was encrypted/password-protected and it contained two compressed files: router.tar and upgrade_tool.tar.gz.

  1. router.tar was the big deal here; it held all the crucial firmware components like the kernel and file system.
  2. On the other hand, upgrade_tool.tar.gz wasn’t as important. It contained some scripts to extract the firmware data from router.tar and perform a system upgrade.

I decrypted and extracted some important files, namely filesystem.squashfs, ur35.dtb, and zImage_signed.bin, all from the router.tar. But then, the question was, “How do I use these files?

One approach considered was using binwalk -Me filesystem.squashfs to extract the contents of a Squashfs filesystem for analysis, but since it doesn’t allow program execution within the filesystem, I opted not to proceed with it.

I didn’t have access to the physical device (which happened to be an Industrial Cellular Router). So, I decided to attempt emulating the provided firmware on my Debian Linux (Ubuntu) machine using a tool called QEMU (Quick Emulator), the generic open-source emulator and virtualizer.

Now, emulating router firmware directly on a Linux Debian system isn’t exactly a walk in the park. You see, router firmware is designed for embedded systems with different CPU architectures (like ARM or MIPS) than what a typical Linux Debian system uses (x86 or x86_64). After reading the router’s specifications, I found out that this particular router used a 32-bit ARM architecture.

Emulating one architecture on another can be difficult and requires tools like QEMU, which can be a bit of a hassle to set up.

So, I rolled up my sleeves and installed QEMU for ARM with the command sudo apt install qemu-system-arm. You can choose between qemu-system-arm or qemu-system-aarch64 to simulate a 32-bit ARM machine. QEMU’s ARM system emulation requires you to specify a board model using the -M or -machine option. This is where ur35.dtb file comes into play, the file I extracted earlier from the router.

To access the content of the Device Tree Blob (DTB) file I require a tool that can interpret and extract information from DTB files. The Device Tree Compiler (DTC) is a tool that can convert DTB files to a human-readable Device Tree Source (DTS) format. To get it, just run sudo apt install device-tree-compiler. Using the DTC tool I converted the DTB file to DTS format with the help of the following command: dtc -I dtb -O dts -o ur35.dts ur35.dtb.

A quick look at ur35.dts showed that the model was “Freescale i.MX6 UltraLite 14x14 EVK Board”.

This is what the i.MX6UltraLite Evaluation Kit (EVK) board looks like

Next, I needed to select a machine or board model for QEMU. I found it by running qemu-system-arm -machine help | grep i.MX6, which led me to choose the “mcimx6ul-evk” board.

We’re getting closer!

With everything set up, my ultimate goal was to gain access to the root shell and the router’s administration web panel. This would allow me to analyze how it handled user input (debugging) and find potential vulnerabilities.

Putting all the pieces together, I came up with the following command:

qemu-system-arm -M mcimx6ul-evk -kernel zImage_signed.bin -initrd filesystem.squashfs -append “root=/dev/ram0 init=init” -dtb ur35.dtb -nographic -no-reboot

Let’s break down the command step by step:

  1. qemu-system-arm: This is the QEMU command to run a virtual machine with an ARM-based processor architecture.
  2. -M mcimx6ul-evk: This specifies the machine or board model to emulate. In this case, it’s set to emulate the “mcimx6ul-evk” board.
  3. -kernel zImage_signed.bin: This option specifies the kernel image file to be loaded into the virtual machine. “zImage_signed.bin” is the kernel image that will be used.
  4. -initrd filesystem.squashfs: This option specifies the initial ramdisk (initrd) image file to be loaded into the virtual machine. It contains an initial file system that can be used during the boot process. In this case, it’s “filesystem.squashfs
  5. -append “root=/dev/ram0 init=init”: This option provides a kernel command line that will be passed to the kernel during boot. It specifies two boot parameters: 1. root=/dev/ram0— This sets the root filesystem to be loaded from RAM (/dev/ram0) initially. 2. init=init — It instructs the kernel to execute the traditional init process as the initial user-space program during boot
  6. -dtb ur35.dtb: This option specifies the device tree binary (DTB) file to be used. Device tree files describe the hardware configuration to the kernel, and “ur35.dtb” is the one specified here.
  7. -nographic: This option tells QEMU to operate without a graphical user interface (GUI). It’s useful for text-based or headless operations.
  8. -no-reboot: This option tells QEMU not to automatically restart the virtual machine if it shuts down. This can be helpful when debugging or testing specific scenarios.

I was able to partially emulate the firmware using this command but was still unable to run the server and access the web admin panel on the host machine. I faced numerous challenges and made countless adjustments to my final command.

Now, I won’t bore you with all the twists and turns, but let me tell you, I tinkered with configurations and parameters in my final command until it was just right. And after a few failed attempts (hey, it was my first time emulating firmware), I cracked the code!

A snapshot version of LEDE “Reboot” was running on the system. LEDE (Linux Embedded Development Environment) merged with the OpenWrt project in early 2018. OpenWrt is an open-source project that provides a Linux-based operating system (OS) and firmware for embedded devices, particularly routers and network devices.

I was more interested in web admin. The web admin interface details can typically be found in a configuration file. I began to inspect the router’s configuration file to find relevant details.

After a little digging, I found a configuration file for the uHTTPd web server, which is commonly used for web admin interfaces on OpenWrt-based systems.

I was looking for the controller file like file-export that was handling HTTP requests.

I found those files in /www/cgi-bin/directory.

Those handler files were symbolic links pointing to /usr/libexec/luci2-io

I extracted the luci2-io binary file from the firmware and started analyzing it using Ghidra.

After some debugging and analysis, I found several vulnerabilities, but due to company confidentiality, I can’t share them here.

Lastly, I want to extend my heartfelt thanks to Milesight for their proper coordination and for providing the firmware.

Disclosure Timeline

  • June 22, 2023: Initial notification to the vendor requesting assistance in obtaining the appropriate email address for reporting the security issue.
  • June 26, 2023: Response received from Kevin Huang, Senior Technical Specialist, instructing me to share the vulnerability details via email.
  • June 26, 2023: A detailed report on the vulnerability was shared with the vendor.
  • June 26, 2023: Kevin acknowledged the vulnerability and provided the latest router firmware (v35.3.0.7) for testing to determine its vulnerability status.
  • August 22, 2023: After an extensive email exchange spanning from June 26th to August 22nd, testing was successfully completed. It was concluded that the latest firmware v35.3.0.7 is not vulnerable, and the patch has been implemented correctly. Additional findings submitted.
  • August 31, 2023: Received a response, expressing appreciation for the report and agreeing that the identified issues can be improved in future updates.
  • September 12, 2023: Requested for CVE-ID
  • September 26, 2023: CVE-2023-43261 assigned
  • October 1, 2023: Public disclosure

Thank you for reading.
Keep learning, and stay safe and healthy! 😇

This article, authored by Bipin Jitiya , is part of our company's insights and was originally published on Oct 1, 2023, on Medium .