Traccar Unauthenticated LFI v5.8-v6.8.1
Sometimes you search endlessly and find nothing. Other times, the gold just drops into your lap. This is a story about how we accidentally found a pretty impactful vulnerability.

As a part of our penetration tests, we almost always run a vulnerability scan. Most results can be pretty boring, but in this one test we spotted something interesting.
Nessus found unauthenticated LFI in traccar???
internal monologue: has no one ever ran a vulnerability scan against this application?

When we tried to reproduce the issue in our lab, we quickly realised only Windows installs were vulnerable. Time to figure out why.

Analysis
After reading some Jetty documentation, we found what looks to be the vulnerable code here.
We've added some annotations to help explain what's going on.
--- snip ---
@Override
// pathinContext is the path of the request e.g. /index.html or /css/style.css
public Resource getResource(String pathInContext) {
// overrideResource is the base directory from which we search
// by default this is ./override in versions 6.1 - 6.8.1
// in versions prior v5.8 - 6.0 this isn't set by default/is disabled
// It's intended to allow loading custom assets so you
// can customise your traccar instance without changing code
// e.g. ./override/logo.png
if (overrideResource != null) {
try {
// addPath is from Jetty code.
Resource override = overrideResource.addPath(pathInContext);
if (override.exists()) {
return override;
}
The user provided path is passed directly into Jetty's Resource.addPath
method so we'll need to take a look at Jetty's codebase.
Starting with Jetty documentation, path traversal attempts look like they should be blocked?


The PathResource
implementation of addPath
uses URIUtil.canonicalPath
to check if the path is safe and will return null
if it's dangerous.

Finally following the code here we see additional source code comments indicating that path traversal shouldn't be possible.

Let's mock up a quick test.
--- Testing: Backslashes ---
Path: '..\..\..\blah'
URIUtil.canonicalPath() result: '..\..\..\blah'
--- Testing: Forward slashes with filename ---
Path: '../../../blah'
URIUtil.canonicalPath() result: NULL
oops
That doesn't look good?
Reporting it to the Jetty Team
While we're not deeply familiar with Jetty, the docs and source comments suggest addPath
is intended to block path traversal attempts. So we raised it first with the Jetty team to see what they'd say.
We got this response back pretty quickly:
The code that tracar has is vulnerable, not Jetty.
The https://github.com/traccar/traccar/blob/bc2faa140a2a5ba63968b95cd8ddba7578e1b07f/src/main/java/org/traccar/web/DefaultOverrideServlet.java code skips the entire ResourceService and ContextHandler layers.
This also means it skips the entire AliasChecker facility built into Jetty to prevent this kind of access.
File this against tracar.

Moving on.
Making it Impactful
It's not very interesting to just grab random files from Windows so let's see if we can make this more impactful.
The traccar configuration file can be configured to contain multiple passwords. LDAP passwords seem particularly interesting.


This could be quite bad. An unauthenticated attacker could:
- Exfiltrate AD credentials if they are configured.
- Use them to try to connect to corporate VPNs etc. or access other domain connected hosts.
Hopefully there's not too many instances exposed to the internet.

I haven't dared send a cheeky GET request to any of the exposed hosts but you can tell some of these hosts run Windows as they also expose other Windows services AND the jetty version header matches vulnerable versions of Traccar.
For example, version 6.8.1 of traccar runs jetty 11.0.25.

Versions v6.1 (April 11 2024) - v6.8.1 (July 8 2025) are affected in default installations on Windows.
Versions v5.8 (May 31 2023) - v6.0 (April 7 2024) are affected in non-default configurations if override is enabled on Windows.
Looking Elsewhere
If there's one, there's normally more.
With a quick search on Github we found a smaller project called mediatoad which had some familiar looking code.
@Override
public Resource getResource(final String pathInContext) {
try {
final String path = StringUtils.removeStartIgnoreCase(pathInContext, "/" + C.STATIC_FILES_PATH_PREFIX);
final Matcher m = CACHE_BUST_PATTERN.matcher(path);
if (m.matches()) {
return this.rootRes.addPath(m.group(1));
}
return this.rootRes.addPath(path);
}
Fortunately in this case, mediatoad is only vulnerable when started with the --webroot
argument which is intended for use in local development scenarios.

There's almost certainly more examples out there but that's all we've got time for today.