How we found a zero-day with fuzzing (CVE-2026-30451)

How we found a zero-day with fuzzing (CVE-2026-30451)

Note: this write-up is intended for a wide audience. If you want a more technical explanation I highly suggest checking out my team-mate’s write-up.

What is fuzzing?

Fuzzing is when you input random data into a target program and see if you can find any unintended behaviour such as crashes. Fuzzing has been around for a long time, with some of the earliest examples being fuzzing UNIX.

So what does fuzzing look like nowadays? Most modern-day fuzzers use something called grey-box fuzzing with evolution. This means that the fuzzer doesn’t adapt to the source code (like in white-box fuzzing), but does not go in completely blind (like in black-box fuzzing). The fuzzer adapts the data that is input into the program based on feedback from the previous data. The most important feedback used for this is coverage information. The goal of the fuzzer is to maximize this coverage number, which in essence means it tries to run as much of the code, in as many ways as possible.

Starting our research

We were looking for a project for our course ‘offensive technologies’ when one of my teammates proposed the idea of ‘just trying to find a zero-day’. I have to admit, at first I was not convinced. We had only a couple of weeks for this project, and this seemed like a huge effort. But after some convincing, we settled on this idea.

We decided to look into fuzzing, as this seemed like a nice way to find vulnerabilities. This is when we found out about Google’s project OSS_FUZZ where they fuzz big open-source projects. Well… we simply can’t compete with that? Right?

Maybe we can. If we carefully limit the scope of our project to not include projects within this fuzzing program, we might still be able to find good exploits. Thus we limited our scope to projects that:

  • Have fewer than 1,000 GitHub stars
  • Are written in C, C++ or Zig (all memory unsafe languages)
  • Have been updated recently (we still want to find relevant vulnerabilities)
  • Are parsers of some kind (easier to fuzz)

With that, it was time to find our projects on GitHub! We found the following projects:

With our projects selected, it was time to start our research.

Let the fuzzing begin!

We are fuzzing Zig and C++, so we also need two different setups. But, in both cases we used AFL++, which has implementations for both languages. For this research our C++ related research is the most interesting, especially since Zig is simply just not used as much as C++.

To start fuzzing, you first have to set up a ‘fuzzing harness’. This is basically a wrapper around the function you want to fuzz. Let’s take a look at how this was done in mXparser:

#include <iostream>
#include <string>
#include "org/mariuszgromada/math/mxparser/Expression.hpp"

int main() {
    // Confirm non-commercial use
    org::mariuszgromada::math::mxparser::License::iConfirmNonCommercialUse("John Doe");

    std::string input;
    std::getline(std::cin, input);

    org::mariuszgromada::math::mxparser::Expression e(input);
    e.calculate();

    return 0;
}

As you can see, the ‘wrapper’ is very simple. It simply accepts the license, to prevent the mXparser program from sending warnings, and then inputs the data generated by the fuzzer into the Expression() function.

We created similar wrappers for all the programs mentioned, and for some of the programs we created multiple for different functions. After doing this, it was time to start fuzzing!

In the above video you see what we saw on the screen during the fuzzing. We found a bunch of crashes, most being related to stack overflows. We found most crashes in mXparser, and thus decided to limit the rest of our research to this program.

Researching mXparser

The output of a fuzzer shows the exact input that caused the unintended behaviour. We tried running the input on mXparser, and it did indeed cause an error.

Now we found a crash, and could reproduce it at will, it was time to find out if there were any other programs using the mXparser library that might be vulnerable. We used GitHub searching tricks to find all projects using C++ and which imported the mXparser library.

Unfortunately, we found out that most programs using mXparser used the Java version. The C++ version was apparently a recent addition to the repository, and thus might have not gotten the chance to grow past a few niche projects.

There was only one option: fuzz the Java version as well. For that purpose we used Jazzer. After a relatively short time of fuzzing, and my terminal crashing a few times, we struck gold. We found a command that would crash the program:

h(x)=0=h(0(h(0)))h(x) = 0 = h(0(h(0)))

After investigating we understood that we had found a so-called uncontrolled recursion vulnerability (CWE-674). Let’s dive into how it works!

Recursion is when a function is (partly) defined by itself. In coding this means that a function calls itself. This is very useful, but when recursion isn’t limited, it can consume all resources of the computing machine until it either crashes or it is handled by some kind of mechanic.

MXparser has such a mechanic, limiting the amount of recursion that can occur to a maximum of 200 with a “recursion counter”. Great! So why were we able to crash the program anyway? Well, there was a critical flaw in the implementation of this recursion counter. I like the following image, made by a teammate of mine, as it clearly shows the issue:

Diagram showing how recursion branches bypass the recursion counter

When doing a recursion for more than 2 functions (like the command we showed before), the counter basically gets split, allowing for twice as much recursion for each step. This leads to recursively more system resources being used.

From vulnerability to exploit

Now that we had our vulnerability mapped out, it was time to get back to finding projects using this library on GitHub. Now looking for Java projects, we found 5 pages full of projects, with the most interesting project being CalcMod. CalcMod is a Minecraft plugin that allows users to do calculations with commands within the game. It has around 1.97 million downloads on its Modrinth page. But, was it vulnerable?

As it turned out… yes it was! Using these commands we could crash a test Minecraft server with the CalcMod plugin installed:

/calc custom add h h([x]h([x]))
/calc h(1)

This is basically a straight port from the vulnerability we found in the Java library into CalcMod calculations. The reason this works so well is because there is no validation of user-input.

The impact of this vulnerability is Denial of Service. Any (unprivileged) user can take down a Minecraft server using this plugin. Since the plugin has been downloaded so often, this might be thousands of servers, of which some will most likely be publicly available.

Responsible disclosure

With a vulnerability found, we needed to start the responsible disclosure procedure. We contacted the mXparser and CalcMod maintainers, and gave them ample time to issue a patch. As of the release of this post, both CalcMod and mXparser have issued a patch to resolve this vulnerability. To anyone using this library or plugin, please update your versions.

After informing the maintainers, we applied for a CVE, a unique identifier assigned to vulnerabilities by the American government organisation MITRE. After waiting for approximately 3 months we received confirmation and our assigned CVE-2026-30451.

What I learned

I had a fun time throughout this project. Even though it was fairly short (only 4 weeks, of which 2 weeks were fully free for the research), I learned a lot. Most importantly it taught me to take moonshots from time to time. I had to be convinced to do this as our topic, as I had some doubts about the feasibility.

The other important thing I learned is the power of fuzzing. It can be setup rather quickly, and can be incredibly effective, especially on projects that have not been fuzzed before. In future research I will certainly consider fuzzing, and I would like to set up fuzzing for software of the company I work at.

Finally, I learned about how to ethically disclose a vulnerability. Though sometimes challenging, it’s very rewarding to see patches issued for issues you found. I would love to do this more often!

Thank you for reading this write up! I hope you liked it and maybe learned something from it.