"Find, confirm, and document real RCEs — not just hints."
This project automates two phases:
- Phase 1 – Static Detection (shellhunter.py): scans the codebase, applies heuristics/signatures and produces areport.jsonwith candidates.
- Phase 2 – Dynamic Verification (verify_rce.py): maps Phase-1 file paths to live URLs, fires payloads (including obfuscated variants), detects visible evidence, blind timing, classifies PHP errors, and generates timestamped reports, logs, and artifacts.
C:.
|   config.yml
|   endpoints.txt
|   requeriments.txt
|   rules.yml
|   shellhunter.py
|   verify_rce.py
|
\---whitelists
        ignore_hashes.txt
        ignore_paths.txt
python shellhunter.py -h
usage: shellhunter.py [-h] -r ROOTS [ROOTS ...] [--rules RULES] [-o OUT] [--csv CSV] [--ext EXT [EXT ...]] [--exclude [EXCLUDE ...]]
                      [--whitelist-paths WHITELIST_PATHS] [--whitelist-hashes WHITELIST_HASHES] [--threshold THRESHOLD] [--max-size-mb MAX_SIZE_MB]
                      [--max-read-kb MAX_READ_KB] [--workers WORKERS] [--keep-all] [--verbose] [--log-file LOG_FILE] [--stream-findings]
ShellHunter — local webshell scanner (no-RCE) with a progress bar and single status line
options:
  -h, --help            show this help message and exit
  -r ROOTS [ROOTS ...], --roots ROOTS [ROOTS ...]
                        Root directories to scan
  --rules RULES         YAML rules file
  -o OUT, --out OUT     JSON output
  --csv CSV             CSV output
  --ext EXT [EXT ...]   Included extensions
  --exclude [EXCLUDE ...]
                        Exclusion regexes
  --whitelist-paths WHITELIST_PATHS
                        File with regexes (one per line) to ignore paths
  --whitelist-hashes WHITELIST_HASHES
                        File with partial SHA256 hashes (one per line) to ignore
  --threshold THRESHOLD
                        Score threshold for HIGH
  --max-size-mb MAX_SIZE_MB
                        Max file size
  --max-read-kb MAX_READ_KB
                        Bytes to read per file (for speed)
  --workers WORKERS     Threads (I/O bound)
  --keep-all            Keep even score=0
  --verbose             DEBUG logging to console
  --log-file LOG_FILE   Write log to file
  --stream-findings     Print each finding (FOUND/ERROR) on separate lines
python verify_rce.py -h
usage: verify_rce.py [-h] [--report REPORT] [--map MAP_PATH] [--urls [URLS ...]] [--params [PARAMS ...]] [--methods [METHODS ...]] [--timeout TIMEOUT]
                     [--workers WORKERS] [--out OUT] [--csv CSV] [--verbose] [--headers HEADERS] [--cookie COOKIE] [--risk-min RISK_MIN]
                     [--no-time-based] [--only-http-200] [--aggressive] [--aggr-budget AGGR_BUDGET] [--aggr-max-auth AGGR_MAX_AUTH]
                     [--aggr-max-pass AGGR_MAX_PASS] [--extra-kv EXTRA_KV] [--alt-evidence ALT_EVIDENCE] [--no-datetime] [--log LOG]
                     [--positives-out POSITIVES_OUT] [--positives-csv POSITIVES_CSV] [--save-bodies SAVE_BODIES] [--save-bodies-all]
Verify possible webshell RCE via HTTP requests
options:
  -h, --help            show this help message and exit
  --report REPORT       Phase-1 JSON (report.json)
  --map MAP_PATH        config.yml with mappings root→url_prefix
  --urls [URLS ...]     Additional URLs to test (optional)
  --params [PARAMS ...]
                        Parameter names to try (override)
  --methods [METHODS ...]
                        HTTP methods to try (default GET and POST). Example: --methods GET
  --timeout TIMEOUT
  --workers WORKERS
  --out OUT
  --csv CSV
  --verbose
  --headers HEADERS     Extra headers as JSON (e.g. '{"X-Token":"abc"}')
  --cookie COOKIE       Literal cookie string (e.g. PHPSESSID=abc; other=1)
  --risk-min RISK_MIN   HIGH or MEDIUM (default MEDIUM)
  --no-time-based       Disable timing-based checks
  --only-http-200       Ignore endpoints whose initial status is not 200 (unless name looks like a known shell)
  --aggressive          Enable extra variants (auth/json/multipart + secondary toggles and obfuscated techniques)
  --aggr-budget AGGR_BUDGET
                        Max requests per endpoint in aggressive mode (default 500)
  --aggr-max-auth AGGR_MAX_AUTH
                        Max auth param names to try (default 6)
  --aggr-max-pass AGGR_MAX_PASS
                        Max passwords per auth name (default 8)
  --extra-kv EXTRA_KV   Extra pairs k=v separated by comma (e.g., func=system,ajax=1)
  --alt-evidence ALT_EVIDENCE
                        Extra strings that count as visible evidence if present (comma-separated)
  --no-datetime         Do not append timestamp to output filenames
  --log LOG             Log file (use {ts} to place the timestamp explicitly)
  --positives-out POSITIVES_OUT
                        JSON with positives only (default generated next to --out)
  --positives-csv POSITIVES_CSV
                        CSV with positives only (default generated next to --csv)
  --save-bodies SAVE_BODIES
                        Directory to save HTTP response bodies (positives by default)
  --save-bodies-all     Also save bodies for NO-RCE/ERR (may grow large)
- Python 3.9+
pip install -r requeriments.txt
# or manually:
pip install requests pyyaml tqdm coloramaMinimal example:
mappings:
  - root: "C:\\xampp\\htdocs\\your_patch"
    url_prefix: "http://localhost:8080/your_patch"- root: absolute filesystem path where Phase 1 found the files.
- url_prefix: HTTP prefix from which those same files are served.
You can define multiple mappings for multiple docroots/vhosts.
Run the static scan to build the report:
python shellhunter.py --rules rules.yml --out report.json --verbosereport.json should include entries like:
{
  "findings": [
    {"path": "C:\\xampp\\htdocs\\your_patch\\webshell.php", "risk": "HIGH"},
    {"path": "C:\\...\\testshell.php", "risk": "MEDIUM"}
  ]
}Use whitelists/ignore_paths.txt and whitelists/ignore_hashes.txt to filter Phase-1 false positives.
Confirms real RCE by interacting with endpoints.
python verify_rce.py \
  --report report.json \
  --map config.yml \
  --verbose# Windows:
python verify_rce.py --urls $(type endpoints.txt) --verbose
# Linux/Mac:
python verify_rce.py --urls $(cat endpoints.txt) --verbosepython verify_rce.py \
  --report report.json --map config.yml \
  --methods GET POST --timeout 10 --workers 16 \
  --aggressive --aggr-budget 500 \
  --alt-evidence "SAFE_TEST_OBF_HOT,SAFE_TEST_INCLUDE_USER" \
  --save-bodies out/bodies --save-bodies-all \
  --out out/rce_verified.json --csv out/rce_verified.csv \
  --log out/verify.log \
  --verbose- Visible evidence: appearance of the canary token (e.g., SHCANARY_abcd1234) or any alt-evidence you define.
- Blind timing: ~5s delay over baseline (Linux: sleep 5; Windows:ping -n 6 127.0.0.1).
- Informative PHP errors: classified and included in evidence (e.g., php_error:php8_incompat,php_error:parse_error) — great to explain "why it didn't pop."
By default, filenames receive _{YYYYMMDD_HHMMSS} (disable with --no-datetime).
- Full JSON: rce_verified_{ts}.json
- Full CSV: rce_verified_{ts}.csv
- Positives only:
- JSON: rce_verified_{ts}_positives.json
- CSV: rce_verified_{ts}_positives.csv
 
- JSON: 
- Log: rce_verified_{ts}.log(or path from--log)
- Bodies (HTML): folder from --save-bodies
By default, saves positives only.
With --save-bodies-all, it also saves NO-RCE and ERR (watch disk usage).
| Option | Description | 
|---|---|
| --report PATH | Phase-1 JSON with findings[].pathandrisk. | 
| --map PATH | config.ymlwithmappings[].root→url_prefix(requires pyyaml). | 
| --urls URL ... | List of URLs to verify manually (skip Phase 1). | 
| --risk-min LEVEL | Filter Phase-1 candidates: HIGHorMEDIUM(defaultMEDIUM). | 
| Option | Description | 
|---|---|
| --params P ... | Override params to try. Default: cmd,c,exec,command,run,shell,q,s,action,do,code,payload,p,x,a,b,u,file,include,f,user,page,path,tpl,template,module,data,tmpl. | 
| --methods GET POST | HTTP methods to use (default: both). | 
| --no-time-based | Disable ~5s timing test. | 
| --aggressive | Extra variants: auth (pass/password/key... + small wordlist), multipart/json, secondary flags (func=system, ajax=1, etc.), base64, and include php://input. | 
| --aggr-budget N | Max requests per endpoint in aggressive mode (default: 500). | 
| --aggr-max-auth N | Number of auth param names to try (default: 6). | 
| --aggr-max-pass N | Passwords per auth name (default: 8). | 
| --extra-kv "k1=v1,k2=v2" | Extra k/v pairs added to every request (helpful to "unlock" branches). | 
| --alt-evidence "A,B,C" | Strings that count as visible evidence if present in the response (including baseline). Useful for test pages with known banners. | 
| Option | Description | 
|---|---|
| --headers JSON | Extra headers (e.g., '{"X-Token":"abc"}'). | 
| --cookie "k=v; a=b" | Literal cookies. | 
| --timeout N | Per-request timeout (default: 10s). | 
| --workers N | Concurrency (default: 16). | 
| --only-http-200 | Ignore endpoints whose initial status is not 200 (except if the name looks like a known shell: r57, c99, wso, …). | 
| Option | Description | 
|---|---|
| --out PATH | Full JSON (timestamp added unless --no-datetime). | 
| --csv PATH | Full CSV (timestamp added unless --no-datetime). | 
| --positives-out PATH | Positives-only JSON (default: <out>_positives.json). | 
| --positives-csv PATH | Positives-only CSV (default: <csv>_positives.csv). | 
| --log PATH | Log path (default: <out_basename>.log). Accepts{ts}. | 
| --save-bodies DIR | Folder to save HTML bodies. Accepts {ts}. | 
| --save-bodies-all | Save bodies for NO-RCEandERRas well. | 
| --no-datetime | Don't append timestamp to filenames. | 
| --verbose | Verbose mode (per-URL summary + hints). | 
url,rce,method,param,os,evidence,status_code,elapsed,error
http://.../webshell.php,True,GET,cmd,win,token:SHCANARY_xxxx visible,200,0.08,
...
Each entry includes everything from the CSV plus resp_text (when captured) — handy if you enabled --save-bodies.
- 
Nail the mapping ( config.yml). If you see a lot of 403/404/301/302, yoururl_prefix/base path likely doesn't match. The verifier prints hints automatically.
- 
Cookies/Headers: use --cookie/--headersfor sessions or tokens.
- 
Param-specific shells: some shells require a specific name ( cmd,p, etc.). Use--params cmdto force it.
- 
alt-evidence: if your test pages print a unique banner (e.g., SAFE_TEST_INCLUDE_USER), pass it via--alt-evidence "SAFE_TEST_INCLUDE_USER". If it appears in the baseline (no params), it still counts.
- 
PHP errors: php_error:*in evidence often explains non-exploitation (PHP 8 incompat, disabled functions, …).
- 
Blind timing: when a WAF strips echo, blind timing may be your only confirmation (delay ~5s over baseline). 
python verify_rce.py --urls http://localhost:8080/your_patch/webshell.php --verbosepython verify_rce.py \
  --report report.json --map config.yml \
  --cookie "PHPSESSID=abc123; role=admin" \
  --headers '{"X-Auth":"mytoken"}' \
  --verbosepython verify_rce.py \
  --urls http://localhost:8080/your_patch/shell.php \
  --params cmd \
  --aggressive --aggr-budget 300 \
  --verbosepython verify_rce.py \
  --report report.json --map config.yml \
  --save-bodies out/bodies --save-bodies-all \
  --verbose"Everything is 403/404/301/302"
Check config.yml (url_prefix), use --cookie/--headers to authenticate, and verify the server actually serves that path.
"r57/c99 doesn't run on PHP 8"
You'll see php_error:php8_incompat. Try --aggressive and --extra-kv to "unlock" branches; if the code is broken, there won't be RCE.
"My sample prints something without params, but no token"
Add that string with --alt-evidence "MY_BANNER". Baseline matches count too.
"Blind timing doesn't trigger"
Ensure timing is enabled (don't pass --no-time-based). Very low baseline latency might require repeated attempts.
"I need to see the exact server reply"
Use --save-bodies (and maybe --save-bodies-all). Positives also include resp_text in JSON.
This tool is for authorized security testing only. Make sure you have explicit written permission. Misuse is the operator's responsibility.
- Proxy support and basic/NTLM auth.
- Tech-specific payloads (ASP/JSP).
- WAF-evasion payload mutation engine.
- Add Rules
Inspired by classic signatures (r57, c99, wso…) and static scanning tools. Thanks to the FOSS projects that made this possible.
-- by dEEpEst _ Hack Tools Dark Community --
