Key Takeaways:
- XBOW identified an unauthenticated, Critical-severity Remote Code Execution vulnerability in a public web application belonging to the U.S. Election Assistance Commission (EAC), the independent, bipartisan federal commission that focuses on election administration.
- The vulnerable application was a resource/logistics calculator, not a voting machine or voter registration system.
- The finding highlights that election security extends across the entire ecosystem of administrative and planning tools that election bodies rely on, not just the systems that cast and count ballots.
- The flaw allowed an unauthenticated attacker to execute code as root inside the backend container, with access to locally protected resources and outbound network connectivity, a privileged foothold reachable via a crafted unauthenticated web request.
- Reported to the EAC under its published Vulnerability Disclosure Policy, validated by their team, and fixed ahead of the November 3, 2026 federal midterm election.
- The finding demonstrates the value of security testing that persists through hypothesis, testing, retesting, and validation rather than giving up on an unpromising target or a misleading HTTP 500.
My name is Ariel Garcia. I’m a Security Researcher and part of the Public-XBOW Team.
One of the things we do in Public-XBOW is run XBOW against real-world bug bounty programs and vulnerability disclosure programs as part of our QA process.
Benchmarks are useful, but production applications are different. They use different technology stacks, authentication systems, architectures, APIs, and business logic. More importantly, they fail in ways that are difficult to reproduce in a synthetic benchmark, no matter how realistic it looks.
Testing real applications helps us understand how XBOW behaves outside controlled environments: whether it can make sense of an unfamiliar application, identify useful signals, correct a wrong assumption, and follow a hypothesis far enough to determine whether there is actually a vulnerability behind it.
That is why Public-XBOW exists. We use real-world programs to test more complex cases and technologies while also helping organizations improve their security. XBOW performs the testing autonomously, and when it identifies something potentially interesting, we humans verify the finding and determine whether it meets the relevant program’s reporting criteria.
Why the EAC?
For this round of testing, we wanted to spend some time looking at U.S. government applications, so we eventually selected the U.S. Election Assistance Commission (EAC).
The EAC is an independent, bipartisan federal commission focused on election administration. Its responsibilities include developing guidance, maintaining voluntary voting system guidelines, accrediting testing laboratories, certifying voting systems, and supporting election officials.
There was also a timing component to our decision. The next regularly scheduled U.S. federal general election is November 3, 2026, so we thought it was a good opportunity to contribute by finding vulnerabilities that could be reported and remediated before the midterm election.
The EAC also has a published Vulnerability Disclosure Policy covering eac.gov and its subdomains. The policy provides a clear path for good-faith security research and coordinated disclosure.
During this work, among other findings, XBOW identified an unauthenticated Remote Code Execution vulnerability in one of the EAC’s public web applications. We immediately reported the vulnerability to the EAC, and their team quickly validated and remediated it.
In this blog post, we walk through how XBOW autonomously discovered this vulnerability, from the initial attack surface exploration through the reasoning and validation steps that ultimately confirmed RCE.
Looking for RCE in a nearly empty surface
This XBOW run had a specific objective: look for Remote Code Execution in the assigned EAC application.
The starting point was the /ulteac/ backend. Initial reconnaissance confirmed a Python and Django application behind Gunicorn, but there was very little application behavior to work with. The endpoint returned a minimal Django response:
Hello, world. You're at the polls index.
Benign variations did not reveal additional functionality, and a POST reached Django's CSRF protection rather than any interesting application logic. For an RCE-focused test, this was not much of an attack surface.
XBOW therefore had a choice: keep guessing backend paths and inputs, or learn how the actual application interacted with this backend. It chose the latter.
The public application was a Next.js SPA. That meant its frontend code had to contain enough information to tell the browser which backend actions existed and what data they expected. XBOW shifted from probing the nearly empty backend index to reconstructing that client-to-server contract from the frontend.


At this point, XBOW had identified the technology stack, but nothing yet suggested a path to code execution. The backend index simply did not expose enough application logic, so XBOW changed direction.

The frontend knew more than the backend index
Rather than guessing backend endpoints, XBOW moved to the public frontend.
The application was a Next.js SPA, so its JavaScript needed to know where to send the actual calculator requests. XBOW downloaded the public application bundle and inspected it locally. That revealed the API base and, more importantly, the /ulteac/run-simulation endpoint used by the frontend to submit calculator simulations. The same client code also exposed the structure of the form data sent to that endpoint.

This was the first important pivot in the investigation:
If the backend index tells us almost nothing, reconstruct the backend contract from the client that legitimately uses it.

XBOW then built a normal simulation request from what it had learned in the JavaScript. The first attempt was wrong. The backend returned HTTP 400 with a structured validation error stating that a required field was missing.

Instead of broadening the testing or treating the response as a dead end, XBOW went back to the frontend source and looked specifically for how that missing field was populated.

It found the relevant form parameters, updated the request, and tried again. The application returned HTTP 200 with a successful simulation result.

The successful request gave XBOW two things: a better understanding of the application protocol and a known-good baseline. From this point on, vulnerability testing could change one meaningful part of a legitimate application request instead of guessing at arbitrary JSON structures.
The interesting part was the Time Study feature
While reconstructing the form, XBOW found that individual simulation steps could use different methods for determining processing time. One of those modes was called Time Study.
Instead of entering a fixed value, users could upload a CSV containing processing-time observations. By following the client-side data flow, XBOW found that the application validated the uploaded rows, extracted values from the Minutes or minutes column, and then called JSON.stringify(...) before assigning the result to a nested data field.

That meant the Python backend received the Time Study values as a serialized representation rather than as an ordinary JSON array. Somewhere downstream, that string needed to become usable simulation data.
That did not prove a vulnerability. There are safe ways to parse structured data, but it raised a much more specific question: Was the backend parsing that client-controlled string as data, or evaluating it as a Python expression?
At this stage, XBOW had a hypothesis, not an RCE.

Testing the hypothesis without breaking the expected data shape
XBOW modified only the Time Study value in an otherwise legitimate simulation request. The probe was designed to introduce a controlled delay if Python evaluation occurred, while still resolving to the type of numeric Time Study data the application expected.
That’s important: XBOW was not treating the field as a generic command-injection sink. The test was built around what it had already learned about the application and preserved the expected shape of the value after the side effect.

XBOW preserved the normal /run-simulation request structure and changed the Time Study subOptions.data value to test its Python evaluation hypothesis. The reproduction-specific portion of the timing expression is redacted.
Then came an interesting result. The application returned HTTP 500.
At first glance, that looks like failure. But the response took approximately 4.1 seconds, and a backend-processing-time header also reported approximately 4.1 seconds of upstream processing.

The status code said the simulation had failed, but the timing said something had happened before it failed.
A slow response by itself would have been weak evidence. Networks fluctuate, proxies queue requests, and applications occasionally stall. Here, however, the delay was reflected in the backend processing measurement and closely matched the controlled delay requested by XBOW. That strongly suggested that the supplied expression had executed inside the backend.
The result was promising, but one delayed response was not enough to call it RCE. XBOW moved into a separate validation phase designed specifically for blind code execution.

From an interesting 500 to confirmed RCE
Because the application did not return command output, the vulnerability had to be confirmed through a side channel.
XBOW first established normal response latency, then repeated the test using multiple controlled timing intervals. If random application latency were responsible, the measurements should have been noisy and only weakly related to the requested delays. Instead, the first series showed a strong linear relationship between requested delay and observed response time.

XBOW then repeated the verification rather than relying on one timing series. The second pass produced an effective 1:1 relationship between requested execution time and measured response delay, with an R² of 1.000.

At that point, the evidence was no longer simply “this input makes the server slow.” Attacker-controlled execution time was directly controlling backend execution time.
The blind RCE was confirmed.
This is one of my favorite parts of the trace because the HTTP status never became helpful. The server continued returning an error. XBOW had to recognize that the vulnerability was occurring before the error response was generated, then build a validation strategy around that observation instead of trying to force the application into returning a cleaner 200 response.
From blind execution to demonstrated impact
Confirming the blind RCE answered one question: could attacker-controlled input cause code to execute on the backend?
XBOW's Impact Assessment then took over to answer the next question: what could that execution actually do?
The first attempt was to retrieve minimal command output through the application's error response. XBOW tested harmless system commands, but the server continued returning the same generic HTTP 500 page. Code was executing, but its output was not coming back in the response.
Instead of stopping there, Impact Assessment reused the timing primitive. This time, rather than using delays simply to prove execution, XBOW used them as predicates about the execution environment. A true condition introduced a controlled delay, while a false condition returned at approximately baseline speed.
That let XBOW ask increasingly concrete questions without retrieving sensitive data: was the backend running Linux? Was the executing user root? Could that user read a root-restricted local resource? Was the process running inside a container?

The results were clear. The baseline request completed in roughly 0.13 seconds, while the positive timing control took roughly 1.7 seconds. The root predicate took 1.83 seconds, while the inverse not root predicate returned in 0.13 seconds. A second, independent check using whoami later produced the same result.

The same technique demonstrated Linux execution, a containerized environment, and access to a local path normally restricted to root.
XBOW then tested one more capability: outbound connectivity. XBOW caused the backend to make a single request to controlled infrastructure using a unique marker. The request itself still resulted in the familiar HTTP 500, but the external server recorded the corresponding GET request from the vulnerable environment.

At that point, the impact was no longer just a blind timing primitive. XBOW had demonstrated unauthenticated code execution as root inside the backend Linux container, access to locally protected resources, and outbound network connectivity.
Those results explain the Critical severity. An unauthenticated Internet user could cross from a normal calculator request into attacker-controlled code execution, inherit the privileges of the backend’s root process inside its container, access locally protected resources, and initiate outbound network connections.
What began as an unusual Time Study input had become a direct path from an unauthenticated web request to privileged code execution in the application’s backend environment.
Why this finding is important
There is an obvious security outcome here: a Critical unauthenticated RCE was reported and fixed. But from the Public-XBOW perspective, the path to the finding is equally useful.
XBOW started with an unhelpful backend route, so it inspected the frontend. Its first simulation request was incomplete, so it used the validation error to correct its model and returned to static analysis. Once it had a successful baseline, it followed the unusual Time Study data transformation and formed a specific Python evaluation hypothesis.
The first RCE test still appeared to fail because the application returned HTTP 500, but the backend timing contradicted that result. XBOW recognized the discrepancy, switched to repeated timing validation, and confirmed that server-side execution controlled the observed delay.
That is much closer to how real vulnerability research works than blindly testing a collection of payloads.
It is also why testing XBOW against production applications is useful for us. It allows us to assess whether it can build and revise a model of an unfamiliar application well enough to understand why a particular test is worth running.
In this case, that reasoning led from a nearly empty Django view to an unauthenticated Critical RCE.
Thanks
We want to thank the EAC security team for their handling of this vulnerability report and for their collaboration throughout the reporting and remediation process.
For Public-XBOW, this is the outcome we want from this kind of work: use real applications to push XBOW beyond controlled benchmarks, have humans review and responsibly disclose the findings, and leave the organization more secure than when the testing started.






