Last year I wrote how to weaponize CVE-2018-19204. This blog post will continue and elaborate on the finding and analysis of two additional vulnerabilities that were discovered during the process; one leading to an arbitrary write as system where the contents can’t be fully controlled and the other leading to Remote Code Execution as SYSTEM. Both vulnerabilities require you to have the administrator password for PRTG Network Monitor. Often you just get lucky, as the software defaults to
prtgadmin:prtgadmin for the username and password respectively.
The following vulnerabilities were remediated on version 19.3.51 so make sure you have updated to the latest version: https://www.paessler.com/prtg/history/stable#188.8.131.5230
While analysing the previous RCE bug, I developed some habits when testing the PRTG Network Monitor software. One of them was to look at the binaries that were in the “Sensor System” folder. In most cases, the sensors that can be set up from the web interface would call these binaries in order to perform some extra processing. The functionality can range from a simple
GET request against a website to monitoring all sorts of things like databases, SNMP, free space in OneDrive, GitLab build status, Amazon instances, etc.
You get the idea, there’s a lot of tooling in there which, from an attacker perspective, means that there’s a huge attack surface. Now, if we take a peek at the “Sensor System” folder itself, there is one binary that should grab your attention instantly. At least it did for me.
To satisfy my own curiosity I went ahead and started looking and testing different sensors that might make use of this binary. After some searching, I came across the following sensor:
The next step was to inspect how to force the sensor to call PhantomJS and see how parameters were passed to it. As we can see in the following image, we can force the sensor to call PhantomJS with a single option:
When selected, this will trigger a call to PhantomJS. The next step was to see what that call looked like. For this, I used a tool from our beloved sysinternals by Mark Russinovich (TIL he was born in Spain): Process Explorer.
As you can see, in the following Process Explorer capture, the
phantomjs.exe binary is getting called with a set of parameters and, at least one of them we can control:
There’s two things that should catch your eye:
- The destination file
What if we could inject an extra parameter right after our controlled URL
http://sensepost.com/ parameter? Instead of hitting things blindly and brute forcing the answers, let’s analyse whatever the “phloadspeed.js” file is doing by opening it with our editor of choice:
So, if we can inject an extra parameter right after the first one, we’ll be able to arbitrarily place files anywhere, with
NT AUTHORITY\SYSTEM privileges since all sensors execute themselves with such privileges on a default PRTG Network Monitor installation.
The parameters for the script don’t have any identifiers or switches and are purely positional. From the previous vulnerabilities in the PRTG software we have learned that double quotes can sometimes become problematic. Will this be another case? Let’s try it:
Executing the sensor with that payload gives us:
It looks like the injection worked well but that extra slash added at the end is no good for filenames. We can easily bypass it by adding an extra parameter after our
That looks much better. Now, running a quick search on the C drive for the “test.jpg” file, we can find it in:
This means that we are able to write images anywhere as SYSTEM, effectively being able to overwrite almost any file in the disk. But wait, are we? Actually, no. If you try to input a full path such as
C:\Windows\System32\cmd.exe there will be some unexpected behaviour. It is left as an exercise to the reader to follow the same analysis shown in this section in order to learn why that doesn’t work. If you are looking for an easy answer here it is: Initially, I reported a PRTG Network vulnerability where it was possible for sensors to resolve UNC paths, allowing you to get an NetNTLMv2 hash of the machine’s account. This meant that you could use a localhost UNC path in order to bypass any path restrictions. One such payload could be something like:
http://sensepost.com" "\\127.0.0.1\\C$\\PROGRA~2\evilimage.jpg" "/
After finding the previous injection, I was left with a bittersweet taste of: “I got something, but not enough”. So my analysis continued in a similar way as before, this time focusing on other sensors that would have either more inputs and called external binaries.
I re-visited the HTTP Transaction Sensor, but I thought it would have the same countermeasures as the patch implemented for CVE-2018-19204. In the patch for that vulnerability there were two countermeasures applied. One being the fact that double quotes are sanitised and the second one we can see in the following code, inspecting with dnspy:
As you can see in the code snippet of the patch, if we manage to inject a second
writeresult argument, the sensor will error out and just stop execution, preventing exploitation of the vulnerability.
However, the HTTP Transaction Sensor also has the capability to store the results from HTML pages, just like the HTTP Advanced Sensor. If we inspect the code for the Transaction Sensor, we can find the following:
Wait! So there is no check for duplicated arguments, meaning that if we encounter a new way to inject our arguments, we’ll be able to write arbitrary content to an arbitrary directory of our choice. Again, we follow the same process as with the previous vulnerabilities and inspect the arguments given to the sensor with Process Explorer.
We can see in the screenshot that the arguments are not quite what we expected. In fact, there is some information that doesn’t add up with the previous reverse engineering done with dnSpy. What we saw is that there was a
warningwriteresult parameter and not a
writeresult parameter. I wasn’t sure if this was a programming error or added by other processes or pieces of code, but we can ignore this. However, something we cannot ignore that we’ll use during exploitation, is the
_1 string appended to certain arguments, such as
Url_1. Let’s analyse this.
We can see that it’s looking for the aforementioned string and a number in order to parse the rest of the arguments, if not, it will just crash on us. Meaning that we will need to adhere to this format when launching any exploits.
Ok, so we know that the argument to override or inject is
writeresult, and that we need to append a
_1 to any argument we inject in order to make it work. Time for exploitation!
Since double quotes are now sanitised by adding an extra double quote to the arguments (two double quotes within double quotes is the representation of a single double quote while passing arguments in the Windows command line), a different approach was required. Let’s go back to the analysis of the arguments and inspect how these are passed to the binary.
We can control many arguments here but, the most apparent and “clean” approach would be to start our malicious injection in the URL. Since there are only a few characters that could break the syntax, we’ll spoil the solution. In order to break out of the argument, we’ll use the backslash character
\. In this way, if the injection was good, by having the
Url_1 parameter as
http://127.0.0.1:8000/?\" we should be receiving the next parameter (since it’s the last one) in the access logs of our web server running on local host. The question mark will help our URL to still be valid while performing our injection. Let’s try that:
That looks really good! We got the next argument in our access logs. With the help of Process Explorer we can see that the arguments using our injection resulted in the following:
HttpTransactionSensor.exe [...snip...] "-Url_1=http://127.0.0.1:8000/?\" "-Method_1=GET"
This means that the last double quote is left hanging and, because there’s no space between the
\"- characters, it gets treated as the same argument. So, if we manage to manipulate the next parameter’s value, every space we add will be a new parameter. Let’s test this by overwriting the
Url_1 parameter from within the
Method_1 value. The only caveat is that to edit the value of the
Method_1 argument, we’ll need to either use an intercepting proxy or change the source code of the page before saving the settings. I decided to use the developer tools from Google Chrome (F12 to open it):
If we inspect our access logs with the new payload… *drumroll*
Finally, we have managed to bypass the double quote sanitisation and inject an extra argument. If you read this far (as well as the first part), you already know how to get remote code execution. I am not publishing the full PoC though given the prevalence of PRTG exposed to the Internet:
Sanitising input is hard, even for simple double quotes, and especially when arguments are passed onto other sub-processes/applications. In this specific case, it can get even more problematic: a check for duplicated parameters would not be enough, since attackers could inject a new one with a different ordinal number (
_2) as we saw on this post. Therefore, the hardening had to be done at another layer, by preventing special characters that could cause parameters to be joined or split, such as the backslash and double quotes.
Being stubborn pays off.