Spring Boot Actuator - Using misconfigurations to your advantage: paths, bypasses, techniques

by Damian Strobel at 30.09.2025
Spring Boot Actuator - Using misconfigurations to your advantage: paths, bypasses, techniques

Summary: This article shows how misconfigured Spring Boot Actuator endpoints can be exploited in penetration tests or bug bounty programs. It covers discovery methods beyond /actuator/, the use of special headers (e.g., X-Forwarded-For), path traversal and semicolon bypasses, and access to critical endpoints such as mappings, metrics, httptrace, or heapdump. Practical examples are used to explain how attackers can use these methods to obtain sensitive information or even take over sessions. At the same time, it becomes clear that Actuator is still a worthwhile target if you search specifically for it. For administrators and developers, this means that Actuator endpoints must be consistently secured, only necessary interfaces should be enabled, and logging/monitoring must be established.

Introduction

Spring Boot is a widely used Java framework for building APIs and web applications. It is particularly prevalent in the enterprise space. Companies such as Salesforce, AT&T, Amazon, Porsche, Daimler, Zoom and MANY others use it for their tools and software.

From the perspective of a penetration tester, red teamer, or bug bounty hunter, Spring Boot is interesting because it includes a module known as the Actuator. In essence, Actuator provides monitoring and management endpoints that can disclose debug information. If you want general background on Actuator, there are plenty of articles available online. This article focuses specifically on Spring Boot Actuator and how - when present - it can be leveraged during penetration tests or bug bounty engagements.

In general, Actuator is something you encounter relatively rarely in the context of bug bounty hunting: they are harder to discover today, new Spring Boot versions ship with reasonably secure defaults, and many assets are protected from unauthorized access by WAFs (special thanks to Akamai and Cloudflare for continuously making my life more difficult ;)).

Recently I received several messages asking how I still find so many Actuators. Since the questions kept coming and I repeatedly intended to write something that might help others, I thought: I’ll put a write-up together.

Other resources and posts

Over time there have been many posts—often with a particular focus—that discuss Spring Boot or its submodules. All of the entries listed below are worth reading.

General coverage of Actuators and misconfigurations: wiz.io

The Spring Boot Jolokia module as an attack surface: blog.wss.sh

You can certainly find additional interesting write-ups online. The WIZ article in particular is well worth reading because it explains several basic concepts that I will not repeat here - instead I will try to go deeper. If you decide to read this article, I expect that you already have a rough understanding of Actuator!

Finding Actuator - paths and variations

Most people look for Actuator primarily under /actuator/. There are several points to keep in mind:

  • the relevant endpoints (env, mappings, heapdump, …) do NOT have to be available under “/actuator/
  • just because “/actuator/” is not available doesn’t mean that e.g. “/actuator/env” (or other endpoints) are also unavailable
  • points 1) and 2) can occur together

On point 1):

System administrators may expose endpoints directly at the asset root:

GET /env HTTP/2

Actuator can also be present within a subdirectory:

GET /att-admin/env HTTP/2 
GET /att-admin/actuator/env HTTP/2
GET /att-admin/info/env HTTP/2

What do we learn from this? Fuzzing is necessary. I don’t recommend blind fuzzing with SecLists - by the time you find something you'll likely have been blocked many times. Read the HTML, inspect JavaScript files, and consider the domain/subdomain. For example:

Imagine a subdomain like: vadt.management.domain.com - the Actuator was discovered under “vadt”. There are also cases where “management” is worth testing.

Beyond that, use your creativity. After a few years you’ll develop a good intuition about what to test and should add any successful finds to your wordlist.

On point 2):

In many cases the Actuator index page is not accessible because it is blocked or for other reasons. It is therefore often more productive to search for inner endpoints directly. A solid tactic is to query for health rather than env or heapdump. You can of course try env, but if that endpoint is blocked you may miss the presence of an Actuator entirely because you could not recognize it.

On point 3):

It becomes frustrating when both scenarios from points 1) and 2) apply:

We have a non-trivial path where the Actuator lives and env is not reachable. The HTTP 404 error, however, hints at the Actuator’s presence. You might only realize you should investigate further when you actually request the health endpoint:

Accessing Actuator via special HTTP headers

If you cannot find an Actuator using ordinary requests, it often makes sense to try whether special HTTP headers grant access. In particular, the following two headers have a high success rate:

X-Forwarded-For: 127.0.0.1
X-Original-URL: /actuator/env 

Here is a real example from a bug bounty program:

What do you see? The mere presence of X-Forwarded-For: 127.0.0.1 was enough to gain access to the Actuator. What else do you notice? The Actuator was not located under “actuator/” but under “api/”. Also, I did not use health or env in this case - I recommend trying configprops as well.

The other header - X-Original-URL - is less common but still worth testing occasionally. Its behavior is reasonably well documented so I won’t go into details here.

Additionally: There are other headers worth testing. I’ll leave it to you to discover which ones are relevant.

Accessing mappings

As mentioned earlier, finding an Actuator can be difficult due to external factors (WAF, server rules, etc.). Sometimes you find an Actuator but cannot access env or heapdump. Gateway techniques and others may not work. In such cases, check whether mappings is accessible:

GET /actuator/mappings HTTP/2

If it is available you can at least attempt to understand the application’s routes from an attacker’s perspective and construct a meaningful, impactful attack. The mappings endpoint essentially lists all registered endpoints and their parameters within the application. A typical HTTP response looks like this:

Concretely, you might find something like this:

All that remains is to call the exposed route, e.g. adminDashBoard/getUsers. With some luck you’ll retrieve - as the name implies - all users. In the example shown, that was indeed the case.

In the worst case the routes are authenticated and you cannot proceed. In the best case you receive a 404, 403, 400, or 500 HTTP status - and then it’s worthwhile to fuzz for parameters, for example:

GET /adminDashBoard/getUsers?FUZZ=a HTTP/2

The interesting point is that without the mappings information you would almost never discover this route. These are typically terms not present in wordlists. In the concrete example the route was also case-sensitive.

No mappings? metrics as an alternative

If mappings is not available, check the metrics endpoint:

GET /actuator/metrics HTTP/2

It responds with available metric names that can be queried in subsequent requests:

{"names":["application.ready.time","application.started.time","disk.free","disk.total","executor.active","executor.completed","executor.pool.core","executor.pool.max","executor.pool.size","executor.queue.remaining","executor.queued","http.client.requests","http.client.requests.active","http.server.requests","http.server.requests.active","jvm.buffer.count","jvm.buffer.memory.used","jvm.buffer.total.capacity","jvm.classes.loaded","jvm.classes.unloaded","jvm.compilation.time","jvm.gc.live.data.size","jvm.gc.max.data.size","jvm.gc.memory.allocated","jvm.gc.memory.promoted","jvm.gc.overhead","jvm.gc.pause","jvm.info","jvm.memory.committed","jvm.memory.max","jvm.memory.usage.after.gc","jvm.memory.used","jvm.threads.daemon","jvm.threads.live","jvm.threads.peak","jvm.threads.started","jvm.threads.states","logback.events","process.cpu.time","process.cpu.usage","process.files.max","process.files.open","process.start.time","process.uptime","spring.cloud.gateway.requests","spring.cloud.gateway.routes.count","system.cpu.count","system.cpu.usage","system.load.average.1m"]}

Most of these are mundane metadata.

Relevant ones include:

spring.cloud.gateway.requests
http.server.requests
http.client.requests

They are used as follows:

GET /actuator/metrics/{metric-name} HTTP/2

When queried (with less verbosity) they can reveal recent requests the server or client has made or received.

spring.cloud.gateway.requests

It may be worth trying vhost fuzzing using these values. These hosts are also highly relevant in SSRF contexts.

http.server.requests

This is essentially a lower-detail replacement for mappings - it provides less information but is still useful. From here, start testing the discovered routes.

http.client.requests

WIZ covered this in the article referenced above - the implications are similar to those for the other metrics. These metrics provide useful information that can indirectly lead you forward.

When everything fails - path traversals and bypasses

In general, simple and double URL encoding can sometimes help to access “blocked” Actuator endpoints. To be honest, I have NEVER seen this work in real life. The same goes for mixed encodings, UTF-8 overlong encodings, and similar exotic encodings. I’m happy to be proven wrong and would love to see someone demonstrate Actuator + encoding in real life.

What has helped me often:

Path traversals

There are two string patterns I repeatedly use and which are part of my toolkit (not only for Actuators - HINT!!): ..; and ..

Technical details aside - Orange Tsai described these techniques years ago:

https://i.blackhat.com/us-18/Wed-August-8/us-18-Orange-Tsai-Breaking-Parser-Logic-Take-Your-Path-Normalization-Off-And-Pop-0days-Out-2.pdf

Must read! You will also find more resources online.

How does this often look in practice for Actuator endpoints? For example:

GET /..;/actuator/env HTTP/2
GET /static../actuator/env HTTP/2
GET /actuator/health/..;/env HTTP/2

Here are some of the previously mentioned techniques combined with path traversals:

Alternative: bypass using “;

Simple and often effective: adding a semicolon after relevant terms - you’ll need to test this. Causes for success are usually poor rewrite rules, blocklists, or misconfigured rules.

GET /;/actuator/env HTTP/2
GET /actuator;/env HTTP/2
GET /actuator/env; HTTP/2

This approach has indeed been found in bug bounty programs - and fairly often.

In practice I’ve also encountered the following (which worked, unlike some of the previously listed variants):

GET /actuator/env;.. HTTP/2

Path traversals and these simple bypasses often combine well - but you won’t get far without fuzzing. Example from a known bug bounty program:

Session takeover via the httptrace endpoint

The httptrace endpoint can be quite interesting when present:

GET /actuator/httptrace HTTP/2

There is a risk that user sessions, headers with valid values, and cookies may be leaked:

In my case I could take over an administrator’s session and thereby access PII of millions of users. If administrator sessions are not available, you can often capture a normal user’s session and thereby access that user’s data/PII. For impact, it can be useful to build a small tool that polls the endpoint every few seconds, stores sessions/cookies/headers, and reuses them to exfiltrate PII - provided this is permitted by the program or client. At this point, a friendly reminder: read the program’s policy carefully.

Other useful endpoints: logfile & gateway

Actuator often exposes the gateway/routes endpoint. If you have write access there, SSRF is realistic; in older Spring Boot versions this could even lead to RCE. See the WIZ article referenced earlier for details.

Also of interest: logfile

This endpoint must be enabled by the administrator. It returns logs for certain events. I’ve rarely had luck here because I almost never had write access to change debug levels and force more frequent log writes. General documentation can be found HERE. For changing log levels at runtime, see this article.

Bonus: CVE-2022-22978

In 2022 a CVE was published that allowed an authentication bypass. I have not found it in years - I mention it here pro forma because it could still be useful in internal networks that are significantly behind on updates. A request exploiting it would look approximately like this and could allow access to Actuator:

GET /actuator/%0Aenv HTTP/1.1

Heapdump downloaded - what now?

The situation becomes particularly interesting if you manage to download a heapdump. This is usually done via:

GET /actuator/heapdump HTTP/2

The topic has been covered in several articles. A short recommendation from me: Use VisualVM to open and analyze heapdumps. In roughly 90% of cases you’ll find passwords and PII readily accessible.

OQL makes finding these items quicker - here is a short example I use:

select {o: s,val:s.value.toString()} from java.lang.String s
where
/secret|passwd|password|token|api|key|auth|AWS_|kube|redis|kubernetes|k8s|grafana|jira|docker|app_|ssh|credential|confluence|elastic|solr|beats|logstash|slack|Basic |Bearer |django|jdbc|ftp|sftp|odbc|mysql|couchdb|neo4j|leveldb/.test(s.value.toString())

If you have worked with heapdumps you might say: “Unfortunately that often returns no results.” That’s true - the reason is that Strings are often stored internally as byte[] rather than char[], which makes simple string searches ineffective. OQL can still help, for example:

select {
    object: s,
    value: (function(){
        var bytes = s.value;
        var coder = s.coder;
        var chars = [];
        if (coder == 0) {
            for (var i = 0; i < bytes.length; i++) chars.push(String.fromCharCode(bytes[i]&0xff));
        } else if (coder == 1) {
            for (var i = 0; i < bytes.length; i+=2) chars.push(String.fromCharCode((bytes[i]<<8&0xff00)|(bytes[i+1]&0xff)));
        } else {
            return "";
        }
        return chars.join('');
    })()
}
from java.lang.String s
where (function(){
        var bytes = s.value;
        var coder = s.coder;
        var chars = [];
        if (coder == 0) {
            for (var i = 0; i < bytes.length; i++) chars.push(String.fromCharCode(bytes[i]&0xff));
        } else if (coder == 1) {
            for (var i = 0; i < bytes.length; i+=2) chars.push(String.fromCharCode((bytes[i]<<8&0xff00)|(bytes[i+1]&0xff)));
        } else {
            return false;
        }
        return /SUPERSECRET/.test(chars.join(''));
})()

Remediation

It’s useful to know what’s possible, but that knowledge is not very helpful to a sysadmin unless it comes with concrete mitigation steps. A few recommendations to avoid dealing with people like me:

  • Disable any Actuator endpoints that are not required - even though that may be impractical, do this in production environments and, where feasible, in test/dev environments as well. If disabling is not possible, place proper authentication (e.g. an htaccess) in front of them and test thoroughly (or have them tested).
  • Use management.endpoints.web.exposure.include / exclude to expose only what is absolutely necessary (for example, only the health endpoint).
  • A strictly implemented IP whitelist can help.
  • Avoid attempting to block access to endpoints using complex regular expressions on the server or WAF level - unless you are very confident in what you are doing.

Conclusion

I wrote this article because I was told that Actuators are now hard to find and rare. With this write-up I want to disagree: they still exist - you just need to put in a little more effort.

I presented several methods here - not all possible techniques. You should never test these methods in isolation. Combined, they reveal what you are looking for. In the end, creativity is the key!

With that said:

GET /;/bye/..;/actuator/heapdump;.. HTTP/2
Host: dsecured.com
X-Forwarded-For: 127.0.0.1

And if you are asking yourself if I automated this (and more): Yes, I did - feel free to check out Argos eASM.

Empower your security with us. Today.

Let's get to know each other and discuss your needs. We are looking forward to hearing from you.

Encrypted via PGP

You can also send us an encrypted email.

Download public key