Our Blog

Being Stubborn Pays Off pt. 2 – Tale of two 0days on PRTG Network Monitor

Reading time ~12 min


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#

CVE-2019-11074 (DoS)

Initial Analysis

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.

Excerpt of applications that can be monitored through PRTG Network Monitor

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.

“Ghosts from the past”

Look at that, PhantomJS! Basically, this is software that acts as a headless browser and uses JavaScript-like files in order to perform tasks such as taking screenshots from a website and saving the output.

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:

Sensor disclosing the use of PhantomJS

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:

Choosing PhantomJS as the engine for the sensor

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:

Inspecting the parameters sent to phantomjs.exe

There’s two things that should catch your eye:

  • The JavaScript file used by phantomjs.exe: “phloadspeed.js”
  • 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:

Excerpt of contents from phloadspeed.js

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:

Extra double quotes in the URL

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 test.jpg argument:

Second try at injecting an argument

That looks much better. Now, running a quick search on the C drive for the “test.jpg” file, we can find it in:

Writing to the SysWOW64

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" "\\\\C$\\PROGRA~2\evilimage.jpg" "/

File created in an arbitrary location via UNC path

CVE-2019-11073 (RCE)

Initial Analysis

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:

Patch for CVE-2018-19204

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:

No check for extra arguments

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.

Arguments passed to the HTTPTransactionSensor.exe binary

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.

Routine at the start of the sensor

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 warningwriteresult, not 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.

Arguments passed to the HTTPTransactionSensor.exe 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\" 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:

Breaking the arguments for HTTP Transaction Sensor

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=\" "-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):

Editing the value of the Method_1 argument by inspecting the page

If we inspect our access logs with the new payload… *drumroll*

Successful extra parameter injection

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:

32k exposed PRTG Network Monitor instances.


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 (_1, _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.