Hacking a .NET API in the real world
I have an interesting story to share with you about the fragility of modern web applications. I have been struggling with how to share this in a way to protect the innocent while still telling the story that can educate and enlighten you about real world red teams.
So I’ll generalize a bit. Please bear with me, but stick to it. By the end of this article, you’ll learn the value of understanding how .NET applications and APIs are built and how the basics of reverse engineering can open you up to a world of exploitable vulnerabilities you might never have thought of before.
One of the more interesting engagements I worked on this year included infiltrating a complex SaaS application written in .NET that was eventually brought down due to a non-linear kill chain that jumped a whole bunch of different microservices.
There is no way traditional vulnerability scanning tools would have been able to find this. In fact, it was a combination of pure, bloody luck and genius reverse engineering that gave me the foothold I needed to exploit the objective.
So let’s go back to the beginning of how I first found my footing that led me to a cornucopia of interesting code to exploit.
It all seemed harmless at first. A report generation endpoint in an API allowed the user to request a report based on a date range. The response was a unique GUID that could be sent to another endpoint to later retrieve and download the report in PDF format when it was ready.
Whenever I see an endpoint that accepts user input, I try to corrupt the data by injecting malicious payloads that can trigger the API. Usually, when I see a GUID, I check to see if it might be predictable and something I could eventually guess. I’ve talked about attacking predictable GUIDs before. But in this case, I could immediately see that this was a v4 UUID, which meant I couldn’t exploit that weakness.
So I turned to a custom polyglot dictionary of “interesting” inputs I usually use on .NET applications to see if I could fuzz the endpoint to see how it would react to weird inputs.
Side note: If you’re wondering how I knew it was a .NET endpoint, check out my article on discovering the programming language of an API. During initial recon, I found that the target was running a modern .NET stack.
So with my polyglot glossary in hand, I loaded up Burp Repeater with a successful request for the report and fired it off. Sure enough, I could see the endpoint returning a 200 HTTP status code along with a PDF file stream text. I replaced the valid GUID entry in the URL path with an invalid one and fired it again. It returned a 404 HTTP response code. Meaningful. Seems like normal behavior.
So I sent the request to Burp Intruder. I uploaded my dictionary and set an injection point to replace the GUID with potentially malicious payloads and let Intruder do its thing.
And wow… did it work.
Within minutes I was downloading PDFs left and right.
WTF was going on? Where did all these PDFs come from?
I took a closer look at the successful responses. These were not valid PDF files at all. In reality, they were just file streams. And one of those files was the contents of /etc/passwd.
I had stumbled upon a local file inclusion (LFI) vulnerability in the report endpoint. As I investigated further, I realized that the developers had written basic code to prevent directory traversal for common paths, but did not account for encoded/omitted UNIX paths.
So I had a couple of new discoveries that I didn’t have from the first recon. The first was the LFI vulnerability, and the second was that the target was not running .NET on Windows as I had first assumed.
Together, these findings gave me the critical links in my kill chain to hack into this .NET web application.
Leverages LFI to obtain API artifacts
Having the ability to download files directly from the target API is extremely useful. While typical network pentesters love to leverage LFI to find privilege escalation points at this stage, my goal is to access API artifacts as an app pentester.
And that’s exactly what I did.
At this point I already knew we had some kind of Unix environment. Since the technology stack is .NET based, I could mostly rely on the target running some form of Linux. And there’s a good chance this is running in some sort of constrained container since most .NET apps running on Unix run dotnet core in a containerized environment.
To confirm the operating system, I grabbed the /etc/os-release file. Sure enough. It was a Debian-based operating system.
I then tried to get hold of .dockerenv file in the root of the system. It returned an empty file. But not a 404 status code. It was helpful. The mere existence of the file is a leading indicator that we are in a docker container.
Knowing that it is a .NET app running in docker, I wanted to start investigating the file system for the .NET assemblies of the API. But how could I find them when I don’t know their name?
Finding an API’s .NET collection name
It ends up that .NET developers are an interesting clan. They follow many tests directly online without understanding the impact on their applications. There are way too many “experts” sharing how to containerize dotnet apps in a way that makes them predictable and in no way hardened for the real world.
Don’t believe me? Start by reading the guide directly from Microsoft to get an idea.
By following the Microsoft Learn guide, you can learn a lot about how .NET apps are typically containerized. You really should try it and see for yourself.
Anyway, in my case I knew you could usually find artifacts like
/app/appsettings.Development.json. Sure enough. Both were there.
This already gave me a positive signal that this API was not sufficiently hardened. Developers should NEVER leave development artifacts in a production release.
The files also had some interesting findings, like a connection string to a backend message bus that would come into play later in the engagement when I wanted to pivot deeper into the infrastructure. But that’s a story for another day.
Anyway, I wasn’t lucky enough to get information that would lead to the collection name of the API. But I had an idea where the collections were stored in the containerized microservice.
So I had to be a little creative.
This is where a bit of luck came in. Sometimes finding error code paths can lead to more information about an API. In my case, while interrogating the API, I managed to break it in a way that caused an exception that led to a rare edge case that leaked the assembly name in a stack trace.
You’ve probably seen this before. The developer catches an exception and dumps too much information in the output. In this case, I got the name of the failing function, the class it was in, and the company name. And it’s consistent with Microsoft’s guidance on how to name managed code DLLs.
It is usually in the format
And sure enough, fortunately, that naming convention ended up matching the target and I was able to use the LFI vulnerability to download the main API assembly artifact from inside the
Reverse engineering of the .NET API assembly
Once I had the DLL locally, I was well positioned to decompile the assembly back to human-readable source code. There are many ways in which this can be achieved. GUI tools like RedGate’s Reflector or JetBrain’s dotPeek can do this quite well.
I prefer to use tools I can use on the command line and script as part of automation. I also like to use tools that work across platforms since I hack from systems running Windows, Linux and even macOS.
My decompiler is ILSpyCmd.
Decompiling the target API
For obvious reasons, I can’t show you the actual commands and output from my apps test engagement. So instead, for the rest of this article, while I explain what I did, I’ll demonstrate the methodology against the Damn Vulnerable C# application (API only). This vulnerable .NET API written in C# mimics some of the same behaviors I found in this real-world engagement.
The DVCSA assembly is named dvcsharp-core-api.dll. To decompile it the same way I did in the commit itself, I used the following command:
ilspycmd --nested-directories -p -o src dvcsharp-core-api.dll
Here is a bit of an explanation of the parameters used:
- – nested directory : Use nested directories for the namespace
- – p : Decompiles the assembly into a compilable project and generates an appropriate .csproj file.
- -o src : Sets the output directory to src
This basically decompiles the target assembly into the destination directory called src, creates a project file, and creates one source file (.cs) per type found, all in nicely nested directories based on the namespace discovered.
And with that… we now have the source code for the .NET API.
Check the .NET code for vulnerabilities
Now that we have the API in a source code format, we can do a quick rough audit for any suspicious and/or dangerous exploitable features that may exist in the codebase. I use graudit for this purpose and cover it in far more detail in my article on tracking API exploitability using code review and taint analysis.
To use graudit with .NET code, this command worked fine:
graudit -d dotnet -L .
During my involvement, I found a vulnerability that led to a command injection vulnerability on the API microservice that allowed me to manipulate the communication between the API microservice and the backend event processor. Regarding DVCSA here, we can quickly see a potential SQL injection (SQLi) vulnerability.
If we open the offending line in a text editor like we (thanks to graudit’s
-L param) we can quickly see that the API route
/search is vulnerable to SQLi i search term parameter.
Whew. What a whirlwind ride hacking a vulnerable .NET API!
In this article, we walked through how I was able to exploit a local file inclusion vulnerability in a target .NET API to download the main API assembly artifacts. We then decompiled the assembly back to human-readable source code and did a quick audit for suspicious and dangerous exploitable features. Finally, we found and demonstrated an example of a vulnerable function in the codebase that led to an injection vulnerability on the API microservice that could be easily exploited.
Knowing how .NET works was very helpful. Understanding how to reverse engineer the API allows us to weaponize the artifacts to dig deeper and find more interesting vulnerabilities. It is these skills that continue to prove to me that human-powered pentesting will almost always trump any vulnerability scanner sold on the market.
What do you think? Does this story match what you are currently seeing in the field? Let me know.
Are you still pretty new to API hacking and want to learn more about this kind of stuff? Then make sure you download my free eBook on The Ultimate Guide to API Hacking Resources.
The post Hacking a .NET API in the real world appeared first on Dana Epp’s blog.
*** This is a Security Bloggers Network syndicated blog from Dana Epp’s blog written by Dana Epp. Read the original post at: