Markdown Menace: Discovering an LFI Vulnerability on a Blogging Platform
On February 25, 2022, an Akamai researcher in conjunction with a CredShields researcher were able to find a local file inclusion (LFI) vulnerability in Hashnode, a blogging tool known among the developer community
The team ethically disclosed and worked with Hashnode to provide a solution
The LFI originates in a Bulk Markdown Import feature that can be manipulated to provide attackers with unimpeded ability to download local files from Hashnode’s server
This access allowed the researchers to bypass CDN proxy and obtain SSH keys, IP and network information, and potentially other information that attackers could potentially abuse
Akamai has monitored more than 5 million LFI attacks over the past six months — a 141% increase over the previous six months
LFI attacks are an attack vector that could cause major damage to an organization, as a threat actor could obtain information about the network for future reconnaissance, breach, and lateral movement
Sometimes error messages aren’t the end of the line — they’re the beginning.
Some of my favorite research projects are the ones that happen by accident, especially ones that turn out to be indicative of a major problem. How we found the vulnerability was a great example of the differences between the developer vs. security community. Hashnode is a tool for developers — a place intended to teach and learn — and as security people, we know that anything good can be leveraged for nefarious means. In this case, something as seemingly innocuous as an error message led to showcasing a significant threat that security professionals are facing today.
While playing around with Hashnode, my mate and I discovered a big red flag in their Bulk Markdown Import feature, and like any researcher would, we wanted to see just how far it could go. We not only found the server’s private SSH key, but even bypassed their CDN by leveraging LFI, and were able to find the server’s IP — which could wreak all kinds of havoc.
The good news in this specific instance is Hashnode had their SSH allowlisted, so we wouldn’t be able to do anything with the key had we tried, and they were quick to respond when we disclosed what we had found. However, with the degree to which LFI attacks have grown over the past six months, it’s important to tell the story of how we found it so other security professionals can be on the lookout for these bugs too.
So how did we get from bulk importing to leaking the server’s IP? I’ll break it down here.
The saga begins: LFI
There we were, experimenting with the Bulk Markdown Import feature of Hashnode. One of the best parts of getting a new tool is playing around, right? We were looking for an effective way to move large quantities of our blog to the new platform, and this seemed to be the best option. The Bulk Markdown Import required us to provide the system with a markdown file, a well-known format for web writers, which seemed innocent enough. Who could have thought we would stumble upon a way to use the largest web attack vector we’ve seen in the past six months?
Just how, you ask? My mate Aditya noticed that if the markdown file had a reference to an image file, the markdown parser would look for the file internally and throw an error (as it failed to locate the file). So if we had the following markdown content:
The parser will throw the following error:
The markdown parser started looking for “blog.jpg” wherever it stored the markdown file, and upon failing (obviously) threw this verbose error. Does this mean if markdown contained a reference to an internal file, would it be successfully fetched? You bet it did!
A classic passwd payload was enough to trick the markdown parser into looking for a local file and upload it to Hashnode's CDN, as intended. Our malicious markdown looked like this:
This file was parsed without any hiccups. In response, we got a URL where the fetched file is uploaded (as the server assumed it was a legit image file):
The next logical step was to attempt to retrieve sensitive files, including the private SSH key of the user. And that’s exactly what we did. Luckily, the private key was at the default location, i.e., /home/user/.ssh/id_rsa. We tried logging into the server in an attempt to see if it was even possible (this would have been possible only if the corresponding public key was present in the “authorized_keys” file on the server, which had a very slim chance). But we hit a roadblock at this point. Hashnode uses a CDN, which meant we were not able to connect to any port open on the server, as the origin IP was hidden behind the CDN’s proxy. But we did not give up.
Finding the server’s IP
This was not an overly complex task, but something about which we had no idea beforehand. Zach (who runs an awesome infosec Discord server) suggested taking a look at the /proc/net/tcp file, which might reveal some information regarding the IP addresses associated with the server — and that’s where we struck gold.
The mysterious /proc/net/tcp
So what is this file? /proc/net/tcp contains information regarding all the network connections and corresponding interfaces in hexadecimal form. Below is what the contents of this file look like:
Understanding what each entry meant was its own hell. Thanks to this kernel.org document, it was clear what we were looking at. Below is a screencap from the URL:
We focused on the local addresses, as they were IPs assigned to the server. Each local address entry was the hex value of the decimal value of the IP, but in reverse. So if the entry is AABBCCDD and the corresponding IP is a.b.c.d then AA=d, BB=c, and so on.
From the /proc/net/tcp, we retrieved three IPs. One was 127.0.0.1, which was expected. Another one was 10.x.x.x (redacted), which we figured was the internal IP address. Lastly, one translated into 192.x.x.x (redacted), and this one piqued our interest, as it was owned by a cloud service provider. We tried establishing an SSH connection through netcat, and voilà:
We were able to establish an SSH connection. We did not proceed further, and we reported our findings to the Hashnode team.
Let’s talk about LFI
As a member of the SOCC, I see millions of LFI attacks on a daily basis, while SQL injections are less prevalent and more focused in approach. We all know how pervasive SQL injections are, so why is this vector outshining them now? The easy answer is simplicity. Hashnode had done everything right in this case — and to their credit, they were able to stop us from going any further than we did. As you read, this was found accidentally and was done as a bit of fun.
If an attacker went after an organization with malicious intentions, this could be a relatively simple way of gaining access to some very sensitive information … like the origin IP address. A successful SQL injection exploitation might require a complex payload — but with a single payload consisting of few characters in an LFI attack, you can do some serious damage.
As security professionals, we know better than anyone that even the most seemingly benign “feature” can be exploited. Staying vigilant is half the battle, and that includes before and after the code is released. What was staggering to me about this experience was the sheer volume of LFI attacks. It’s one thing to see them on the other side, but to be doing one, even accidentally, made it clear how easily an attacker could gain a true foothold in a network using LFI.
End of the line
Protecting sensitive information is a recurring and widely known concern in the security community. As researchers, we know all too well how information can be used maliciously (I mean, come on … it’s our job). Considering the size of the threat vector that is LFI, it’s something that developers and security professionals alike need to be aware of.
To wrap things up:
It’s fair to say a large number of verbose error messages are still out there spitting information that threat actors leverage to move ahead. Developers need to be keen on what they leave for everyone to see.
Local file inclusion in markdown importing and parsing might be something that’s exploitable in numerous applications out there and ready to be pwned — it’s just a matter of time.
Linux files are a treasure trove of crucial information that can help you further increase the impact of your initial findings. As we saw how /proc/net/tcp gave us the public IP of a server of Hashnode’s cloud provider, other files might also prove helpful.
Aditya Dixit’s Twitter: twitter.com/zombie007o
Aditya’s blog: https://blog.dixitaditya.com/pwning-a-server-using-markdown
Zach Hanson’s Discord server: discord.gg/cbt2RBD
kernel.org documentation: kernel.org/doc/Documentation/networking/proc_net_tcp.txt