Why does an empty json request crash my home router web server?

Tati
5 min readFeb 9, 2021

In this post I want to explain how it works a vulnerability I found inside my home router.

To be honest, I cannot explain a lot of the procedures about how I found the vulnerability, because, it was simply coincidence, I was messing a little with the endpoints of the web server of my home router, until I send random characters inside a POST request as a json, the router just stopped receiving requests. Obviously, I did the report to the bug bounty program of my router’s manufacturer.

But, why is this happening? Well, to find out what could be happening here, we can emulate the firmware inside a computer, so we can read the console output, if we can find something. So I set up a tool called FAT(Firmware Analysis Toolkit https://github.com/attify/firmware-analysis-toolkit) that it’s basically a much more easier way to interact with other tool called firmadyne(https://github.com/firmadyne/firmadyne), when I did this, I can simply run `./fat.py firmware.bin` and start emulating the firmware inside my laptop.

Okay, now that we have our firmware emulated, let’s do the same request to it.

Looks like the process received a SIGSEGV signal from the kernel, and the process httpd created a crash report with all that information. A SIGSEGV signal is sended when the process accessed an unauthorized or invalid memory address, and as we can read at the top, it says “invalid read access from 7c800000”, so at some point, the process requested access to that memory address, and because its invalid, the kernel sended that signal to the process.

If we read a little more, we can also see that it gives the values of the CPU registers(A CPU register, it’s basically spaces of memory inside the CPU, that have a faster access time) in the moment the exception occurred. A register value that can help us to discover where the error occurred is the program counter or PC, this register is responsible for storing the actual memory address of where the CPU is fetching instructions.

A CPU reads a program from top to down, and the program counter helps to have the count of the actual position in the program. So, when the CPU runs an instruction, the CPU increments the program counter by the size of the last executed instruction, and executes the next instruction pointed by the program counter. Obviously there are instructions that can change this value, like jump’s or branches.

The value of the program counter is 0x2b10d07c, so this means that the exception occurred in that address. But we have a problem, this address is not actually part of the program, because if we try to go to that address in ghidra, it won’t find it. So, where is this address coming from? Well, this address is pointing inside a function, of a dynamically linked library, so this is probably an external function, for example a libc function. But we have another interesting register that can help us find where the exception occurred, the return address or RA register.

The RA register it’s used to save the return address of a function. When we call a function, the function needs to return control to the caller, or change the PC to the caller, but how can we know where to return? Well, this is the purpose of the RA register, before jumping into a function, we save where we want to return when the function ends, normally just after where we called the function, and then we jump to the function, and when the function finishes it jumps to the address in the RA register.

The value of the RA register is 0x0044d15 as we can see this address is part of the binary, so we can see what’s inside this address using ghidra.

Because this is the return address, actually the exception occurred 2 instructions before, and as we can see, there is a JALR instruction, that receives as parameter the register t9, and the register t9 has as value the address of the function memcmp. The JALR instruction jumps, or changes the value of the program counter, to the specified address.

Let’s have a look into the decompiler view, and see what it shows.

First of all, I want to say that ghidra didn’t decompile with that accuracy with the types and names, I was doing reverse engineering of this executable some days, so now you are reading the pretty version of it.

Okay, from the start we can see a function that gets a json and two buffers as parameters, the function its called sign_data_extract, so we can guess that this function its the responsible of retrieving the values of the sign and data keys of the json we send to the endpoint.

What the function does, is walk through the json string, comparing 8 bytes from the string at the specified position, until that 8 bytes are equal to “data”:”, if this is true, it saves the pointer to the position where the value of data starts, and assigns it to the startOfDataSection buffer.

We can make a flow graph to understand it better.

As we can see, we have a bug here. The program starts the i (we can call it index) variable at 0, and then keeps incrementing I until we find what we want inside the json, or the index is greater than 4096. The problem with this, is if we put a json with a length less than 4096, the program is going to read out of the bounds of the json, so at one moment, it’s going to throw a segmentation fault error.

As we can see, the index variable keeps incrementing, until at some point, reads in an invalid memory address.

A way to fix this, is by checking the length of the json string, and prevent the index variable becoming bigger than the length of the string, with this we prevent the program reading in unauthorized addresses.

As a conclusion I can say, never trust the user input and always validate the input and check the length of what you are reading.

My twitter account: https://twitter.com/SmasherTati

--

--