RCE detection and confirmation toolkit that tests URLs or captured HTTP requests for command injection, SSTI, blind and OOB paths, returning tiered verdicts with proof.

executed means the target executed the input. negative means the probes reached it.
Version 3.12.1 · MIT · Python 3.8+ · zero third-party dependencies
RCEKit is an RCE detection & confirmation toolkit for authorised penetration testing, red teaming and security research. Point it at a target you are allowed to test — a URL or a captured HTTP request — and every finding comes back with the tier it earned.
Every executed rests on a value RCEKit generated at random for that probe and
that reflection cannot produce. It comes back through one of 3 channels: a
computed result present in the response and absent from a payload-free
control; an out-of-band callback carrying a token only the target ever held;
or a value the target stored and RCEKit fetched back in a second request —
the token file has it write to a path it serves, the product write's
one-liner computes when the target interprets the file it stored, and the product
include writes through the inclusion sink itself and then fetches back. Weaker signals
keep their own tiers and are never promoted into it. And a run that could not
test something never reports it as clean.
RCEKit confirms RCE through multiple methods under one CLI. Below it is pointed at real production software — publicly-documented CVEs, and builds that demonstrate a method without reproducing one — with every verdict differenced against a payload-free control:
| Target | Advisory | RCE class | --methods | Verdict | Bench case | Recording |
|---|---|---|---|---|---|---|
| Webmin 1.910 | CVE-2019-15107 | OS command injection (results-based) | reflected | executed | yes | GIF |
| Apache Struts2 | S2-001 | Expression injection (OGNL) | eval | executed | yes | GIF |
| Apache Solr 8.11.0 (Log4j 2.14.1) | CVE-2021-44228 | Expression-lookup (Log4Shell/JNDI) | lookup | lookup-sink | yes | GIF |
| Webmin 1.910 | CVE-2019-15107 | Blind command injection (no output) | time | timing-sink | yes | GIF |
| Webmin 1.910 | CVE-2019-15107 | OS command injection (self-OOB read-back) | file | executed | yes | — |
| OpenTSDB 2.4.1 | CVE-2023-25826 | Blind command injection (gnuplot) | oob | executed | yes | — |
| OpenTSDB 2.4.1 | CVE-2023-25826 | Blind command injection (gnuplot) | time | timing-sink | as a control | — |
| Apache HugeGraph 1.2.0 | — | Expression injection (Gremlin/Groovy) | eval | executed | yes | — |
| Apache HugeGraph 1.2.0 | — | OS command injection | reflected | executed | yes | — |
| Spring Boot on fastjson 1.2.83 | — | Deserialization sink | deser | deserialization-sink | yes | — |
| Apache Tomcat 8.5.19 | CVE-2017-12615 | Write primitive (PUT a JSP) | write | executed | yes | — |
| mongo-express 0.53.0 on MongoDB 3.4 | — | Predicate sink, nothing rendered (Mongo $where) | boolean | evaluation-sink | yes | — |
| GitList 0.6.0 | CVE-2018-1000533 | Argument injection (no-shell argv sink) | argv | executed | yes | — |
| PHP 7.1.3 inclusion sink | — | File inclusion (arbitrary read) | include | file-read | yes | — |
| PHP 7.1.3 inclusion sink | — | File inclusion reaching execution (bare include sink) | include | executed | yes | — |
| ThinkPHP 6.0.12 inclusion sink | — | File inclusion reaching execution (loader appends .php) | include | executed | yes | — |
3 of those columns say how much weight the row carries, and they are the ones worth reading before the rest.
Verdict is what the run reported against that build. Measured, not what the method could reach in principle.
Bench case says whether tests/bench/ reproduces the row —
bringing the target up under Docker and checking the verdict and its negative
control. All 16 rows do; python tests/bench/runner.py --all was last green at
3.0.1 (2026-10-01), 10/10 cases in 55m48s. That is a point-in-time claim:
the benchmark runs on a cadence, not on every change.
The suite holds 17 cases now, and the 7 added last were each run on their own
rather than as part of an --all sweep: mongo-express-where-boolean passed in
29.9s, webmin-cve-2019-15107-enumerated in 4m32s,
webmin-cve-2019-15107-time in 21m21s, gitlist-cve-2018-1000533-argv in
18.3s, and the three include cases in one cold sweep —
php-inclusion-file-read in 2.2s, php-inclusion-include-execute in 2.0s and
thinkphp-lang-include-execute in 2.7s. So the figure above still describes the last full sweep, which is the
claim it was making.
The controls are not in this table and two of them are the point. On a Webmin
1.910 and an OpenTSDB 2.4.1 that are genuinely vulnerable, time reaches its
own proven tier and still does not reach executed — a ceiling holding against
real software rather than against a fixture. The OpenTSDB one is the row
reading as a control above; the Webmin one now has a case of its own, where
time is the subject and the control injects into a parameter on the same
request that no shell reads — so the slope has to be attributable to the sink
rather than to the host. The full 17, controls included, are in
tests/bench/README.md.
Advisory is empty where the verdict does not depend on the patch. Both
HugeGraph rows and the fastjson row are — deliberately: HugeGraph 1.3.0 answers
the arithmetic exactly as 1.2.0 does — its Gremlin API evaluates Groovy
unauthenticated by design, which was checked by pulling the patched image and
running it — and fastjson resolving an Inet4Address is documented autoType
behaviour. Those rows prove a method against real software, which is worth
recording; calling them CVE reproductions would be the overclaim this table
exists to avoid.
Each control is the row's real test. Struts2 probed with reflected comes back
negative, because S2-001 re-evaluates OGNL and there is no shell behind it.
Webmin's time signal is held at timing-sink on a target where it happens to
be right. And Solr probed with oob comes back negative although it is
exploitable -- oob builds shell commands and a ${jndi:...} sink runs none
of them, which is the gap lookup exists to close, measured rather than
asserted.
The Log4Shell row says lookup-sink, not executed: what the callback proves
is that the sink resolved a URI RCEKit chose. Reaching RCE needs a server that
answers the lookup with a loadable class, and at the default risk tier only
jndi:dns:// goes out -- a name lookup, with no connection past it for such a
server to answer on.
reflected — OS command injection, Webmin CVE-2019-15107 → executed
eval — OGNL expression injection, Apache Struts2 S2-001 → executed