One Empty Header to Admin: How an Auth Bypass Breaks OpenBullet2

In this article, I explain what OpenBullet2 is used for and walk through the vulnerabilities I was able to find in it.

What is OpenBullet2?

OpenBullet2 is a cross-platform automation suite powered by .NET. It can send requests to a target web app and offers a wide range of tools for working with the results. The software is used for scraping and parsing data, as well as for automated penetration testing.

It is also notably popular among attackers, who use OpenBullet2 for credential stuffing. On hacking forums, you can find numerous configs (ready-made scripts) for attacking different sites.

You can read more in this article by Trend Micro.

Statistics

A number of OpenBullet2 instances are exposed on the internet. They can be found with the following dorks:

  • Shodanhttp.html:"Openbullet2WebClient"
  • ZoomEyehttp.body="Openbullet2WebClient"
  • Fofatitle="Openbullet2WebClient"


Fofa reports 493 reachable instances (346 of them unique).

How to set up a stand

All vulnerabilities were found in version 0.3.2 (the latest at the time of writing).


You can deploy it yourself with:

docker run --name openbullet2 --rm -p 8069:5000 -it openbullet/openbullet2:0.3.2

CVE-2026-25555 — Empty X-Api-Key Header Bypasses Authentication

Description

OpenBullet2 supports two authentication methods: credentials plus JWT, and API-key-based auth. For the second method, you can set an API key in the application settings.


The settings note states: “If empty, API key authentication will be disabled.” In practice, this isn’t true because no such check exists in the code. The header is always validated and compared against the value stored in the configuration file.

Meanwhile, AdminApiKey is initialized to an empty value at deploy time (you can also confirm this in the generated config at /app/UserData/OpenBulletSettings.json).

Because the header is always checked and the configured value is empty, you only need to attach an empty X-Api-Key header to any request to gain admin access to the OpenBullet2 console or to call any endpoint.

Fix

The vulnerability can be addressed with the following measures:

  • Add an explicit flag in the admin panel that controls whether API-key authentication is enabled, and disable key-based authentication by default.
  • Disallow using an empty API key.
  • Disallow setting the API key manually.
  • Add automatic API-key generation following [best practices](https://randomkeygen.com/guides/api-key-best-practices](https://randomkeygen.com/guides/api-key-best-practices).

CVE-2026-25856 — Job Configuration Allows Arbitrary Server-Side C# Execution

Description

To build automation in OpenBullet2, you create a config — a file describing a sequence of actions, such as which requests to send, what to parse from the responses, and so on.


Configs are written in a purpose-built scripting engine called LoliCode. In addition to LoliCode, however, you can also use plain C# with any libraries.


This capability allows arbitrary code execution on the host.

Fix

A possible solution is to restrict the available APIs (reference filtering) via a whitelist, blocking access to the file system, process execution, and so on.

CVE-2026-25855 — FileProxySource Executes Uploaded Script Files Without Restriction

Description

When sending requests, OpenBullet2 supports loading proxies from a .txt file, with one proxy per line. That is usually sufficient, but four years ago, a strange PR from one of the maintainers added support for script files such as .bat, .ps1, and .sh, along with their subsequent interpretation. Support for these files is undocumented, and the author did not explain in the PR why they decided to add them. The intended usage can, however, be inferred from the tests in the code.

While this behavior is undocumented, the standard usage is documented.

The only thing distinguishing the standard .txt load from the one the author wanted to add is the presence of echo at the start of the line. The OpenBullet2 maintainer approved the PR without further questions. The code provides no real benefit and allows arbitrary command execution on the host.

Fix

The functionality is unnecessary; the solution is:

  • Revert the changes introduced in PR #761.

CVE-2026-25559 — Wordlist Upload Endpoint Allows Arbitrary File Read/Write/Delete via Path Traversal

Write

A wordlist is a list of resources that can be loaded for use by configs at runtime. For credential stuffing attacks, for example, these would be combolists (files of credentials in email:pass format).


The upload form expects the user to upload a file, specify its name, and have it saved under /app/UserData/Wordlists/.

The wordlist upload code is here.

Specifically, lines 189–190:

var path = FileUtils.GetFirstAvailableFileName(
    Path.Combine(_baseDir, "Wordlists", file.FileName));


The file name file.FileName is used without sanitization, and Path.Combine has an important quirk: if one of its arguments is an absolute path, all preceding arguments are ignored.


Here is an example of exploitation in which we supply an absolute path:

The vulnerability allows arbitrary code execution — for example, by writing a public key to ~/.ssh/authorized_keys. The GetFirstAvailableFileName function checks whether the file already exists, so we cannot overwrite an existing file. This, however, can be bypassed (see below).

Read

Besides uploading your own file, there is a feature for loading a file from the host. You can, of course, specify any file on the system, and since OpenBullet2 runs as root out of the box, we have the privileges to load a config file or /etc/shadow.


The loading code is here.

After loading the specified file, you need to obtain its ID via the /api/v1/wordlist/all endpoint.

Finally, you can read it using the undocumented /api/v1/wordlist/preview endpoint (I couldn’t find it being called anywhere from the UI).

The vulnerability allows reading any file on the system.

Delete

The vulnerability also allows deleting an arbitrary file; the deletion code is here.


Instead of calling /api/v1/wordlist/preview (which lets you view a file), send a DELETE request to /api/v1/wordlist?id=<ID>&alsoDeleteFile=true.


Below is an example of deleting the application’s configuration file in order to reset the OpenBullet2 password.

The vulnerability can also lead to RCE — for example, by targeting the /etc/shadow or /etc/passwd files.

Fix

Ready-to-use code for fixing the arbitrary-path file upload vulnerability:

public async Task<ActionResult<WordlistFileDto>> Upload(IFormFile file)
{
    // Extract only the file name, stripping any directory separators
    // to prevent path traversal attacks (e.g. "../../etc/passwd")
    var safeFileName = Path.GetFileName(file.FileName);

    // Reject empty names or names containing invalid characters
    if (string.IsNullOrWhiteSpace(safeFileName)
        || safeFileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
    {
        return BadRequest("Invalid file name.");
    }

    // Resolve the absolute path of the allowed directory
    var allowedDir = Path.GetFullPath(Path.Combine(_baseDir, "Wordlists"));

    // Build the full path and resolve it to catch any remaining traversal attempts
    var fullPath = Path.GetFullPath(
        FileUtils.GetFirstAvailableFileName(
            Path.Combine(allowedDir, safeFileName)));

    // Ensure the resolved path is still within the allowed directory.
    // The trailing separator prevents bypasses like "/allowed_dir_evil/"
    if (!fullPath.StartsWith(allowedDir + Path.DirectorySeparatorChar,
                              StringComparison.OrdinalIgnoreCase))
    {
        return BadRequest("Invalid file path.");
    }

    // Use FileMode.CreateNew to atomically create the file,
    // preventing TOCTOU race conditions that could allow overwriting existing files
    await using var fileStream = new FileStream(
        fullPath,
        FileMode.CreateNew,
        FileAccess.Write,
        FileShare.None);

    await file.CopyToAsync(fileStream);

    _logger.LogInformation("Uploaded a wordlist file at {Path}", fullPath);
    return new WordlistFileDto { FilePath = fullPath };
}

CVE-2026-39908 — Job Proxy Source UNC Path Causes NTLMv2 Hash Disclosure

This vulnerability occurs when attempting to load proxies from a URL and leads to NTLMv2 hash disclosure if OpenBullet2 is installed on Windows.


To set up a test environment, download OpenBullet2.Web-win-x64.zip from GitHub, extract it, and run OpenBullet2.Web.exe. Optionally, add the flag --urls "http://0.0.0.0:5000" to bind the app on all interfaces (this was useful for me to connect to the panel from Kali, where Burp is preinstalled).


On Kali, start Responder beforehand so you can later capture the hash. Don’t forget to copy the IP address as well.

Next, open OpenBullet2 and go to Jobs (workflows that run all the logic). In the top-right corner click New and select Multi Run Job.

The job creation menu opens. Set Proxy Mode = ON and, as the file path, enter the path to our server using the IP we copied earlier.

Then save the job and click Start.

OpenBullet2 will attempt to load the proxies, and we will capture the hash.

Fix

Fixed by input sanitization, as already described in the Wordlist Upload section.

PoC

I prepared exploits based on Metasploit, which can be found in the official repository.

Temporary mitigation

The OpenBullet2 developer did not respond to messages requesting fixes for the issues above, so this article is published “as is” after 120 days. To temporarily protect against unauthorized access to OpenBullet2, set a random API key in the settings instead of the empty key enabled by default.

Acknowledgements

Thanks to Wade Sparks of VulnCheck for:

  • helping register the CVEs
  • actively trying to reach the author, including creating a thread for this purpose on the OpenBullet2 forum

Conclusion

This article described and analyzed the vulnerabilities I was able to find in OpenBullet2.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.