04. 11. 2025 William Calliari Contribution, Icinga Web 2

Reconstructing protected or hidden custom variables in Icinga DB Web

Recently Icinga DB Web had a new security release, fixing a vulnerability where protected or hidden custom variables could be inferred by any user with object visibility by abusing comparative filters on those hidden variables. This led to a 5.3/10 rated Information Disclosure Vulnerability, documented in the CVE-2025-61789. In this blog post I will go through the discovery and the inner workings of the exploit.

Setting the scene

Every company has their own specific needs when it comes to monitoring their environments. From specific metrics they are interested in, to thresholds, workdays, team composition and shifts, and company structure there is a lot more going into effective monitoring than meets the eye.

Icinga’s approach to this complexity is to provide a framework to specify templates and custom data fields, that lets you model the monitoring environment to your needs. It can be used to store thresholds, check or notification parameters, or flags to search objects by. But most importantly it can be used to store passwords or API tokens needed for the check.

To make the monitoring results visible to the user who do not manage the infrastructure, Icinga Web 2 allows users to only access the monitoring, or in this case the Icinga DB view and not the director with the configuration. Icinga DB Web further allows to hide specific custom variables, like the aforementioned passwords and API tokens. These can be hidden by giving the user a role that sets the restriction icingadb/protect/variables to give a list of patterns the user is not allowed to access, e.g. *pass*,api-token. To execute the exploit, an attacker must have at least viewing permissions on the attacked host or service and needs to know the name of the variable, which could not be known in hidden variables, but can often still be inferred from the check or other properties of the object.

The discovery

While I was investigating Icinga DB Web for integration into NetEye, I happened to work on understanding the restriction, while helping a colleague to look over a PR to support the customization of the search params. There I made the first connection. So purely out of curiosity on what would happen I looked up one of the passwords for an object I had created to test the restriction and ran the following query against Icinga DB Web: host.name=test-host&host.vars.password=<password>. To my surprise the object actually was returned correctly. Weird. So I tried changing the password and it disappeared. Ok, so apparently the protected variables are only protected AFTER the filters have already run, giving us access to some information on them.

If it was just an equality check that in and of itself is not the biggest of problems, a normal user wouldn’t know the password to run this query and if you already know it, you don’t need the query anymore. The worst outcome of this behavior is the possibility to brute-force the password in Icinga DB Web instead of the service directly, in some cases circumventing the service’s brute-force protection. But we are only marginally improving our options, but fundamentally working in the same time-complexity.

Improving our attack

So, to actually be able to exploit this vulnerability, we need some way to improve the time complexity, getting more information for each request. But since we cannot see the actual password, the only states we can observe is either true or false, 1 or 0, depending on if the object is still present. So is there no way to actually exploit this small oversight?

Well, I wouldn’t be writing this blog post if that were the case. A quick look into the UI of Icinga DB Web and we find = is not the only operator we can apply to the variables, other than equality, we can also check for greater than or lesser i.e. the lexicographical ordering of the words.

For the case of this example, let’s assume the password can be found in a dictionary. We all know that the words there are sorted a-z. So if we wanted to find it in the fastest way possible, we could start asking: “does the password start with ””. After we have asked that at most 26 times, we are bound to have found the initial letter of the password, effectively reducing the amount of words we have to look for by 96%. And it does not stop there. Now we can do the same procedure again for the second letter of the password. Another 26 questions later, we removed another 96% of the remaining words, with 0.15% of words remaining. Continuing this strategy, we can find the password in the dictionary in at most 26 times the number of characters in the password.

We can decrease this even further if we simply ask “is the character the password starts with greater than ?”. Then we can start in the middle and with each request exclude half of the remaining letters. With that we need at most 5 queries to find a character.

We can use the same strategy for Icinga DB Web. For that we take the known prefix (the part of the password that we already know) and add the character we want to test afterwards, looking for the host with the query host.name=test-host&host.vars.password=(known prefix)(character) and with that perform a binary search for the next character. This strategy lets us reduce the time complexity from O(A^n) to O(n * log2 A). As an example, if we had a strong random password with upper and lowercase letters, digits and special characters we have in total A=100 options per character, the difference would look as follows: Randomly guessing a password with 16 characters would take you at most 100^16 = 10^32 tries (yes, this is not a typo). Even with 10,000 requests per second it would take over 10^32 / 10^4 / 60 / 60 / 24 / 365 ≈ 3.17e20 years (or 24 billion times the age of the universe) to crack. On the other hand, with our improved strategy, we could reduce the amount of queries to at most 16 * ceil(log2(100)) = 16 * 7 = 112 (since log2(100) ≈ 6.64). With the same 10,000 requests per second it would take just 0.01 seconds until the password is broken. In a realistic scenario, this will likely take much longer, since we need to perform our requests strictly sequentially, reducing the amount of queries we can perform per second, but probably still in less than a minute. To, at last, know if you have actually found the whole password, simply make the query from the beginning using the equality operator.

Of course, passwords are just one example, but this attack can be used on any variables protected like that, including API tokens, internal IDs or other sensitive information stored like that. The credentials could then also be used to pivot to other services to use the access to trigger exploits there.

The aftermath

Once I had found the vulnerability and verified it actually worked, I immediately reported it to security@icinga.com. They investigated it and came to the same conclusion as me that this was a severe problem. Their investigations also uncovered a similar problem in Icinga 2, where the fix was released with versions 2.15.1, 2.14.7 and 2.13.13, while the fix for Icinga DB Web was released in versions 1.1.4 and 1.2.3. All versions were released on October 16th 2025. I encourage all users that run versions lower than that to update their systems.

I collaborated with the people from Icinga2 on the fix for the vulnerability and hope I can do that in the future as well to keep all Icinga users safe. This has been a fun experience and a valuable lesson in how seemingly small bugs can be abused to obtain additional data.

William Calliari

William Calliari

Author

William Calliari

Leave a Reply

Your email address will not be published. Required fields are marked *

Archive