Skip to content
lupingQAQPublic

About

Autonomous agent that mines Java deserialization gadget chains from any JAR directory. Static bytecode analysis + dynamic JVM probes + dual-model adversarial auditing → weaponized PoC. Solving the last mile.

Topics

Resources

Contributing

Security policy

Stars

30 stars

Watchers

0 watching

Forks

Repository files navigation

⚡ JGD

JavaGadgetDigger — Solving the Last Mile of Deserialization Vulnerability Discovery

Version Python License Java

🌐 中文版

v0.4.0 (2026-10-28): security-left-shift release. JGD can now be inserted into GitHub Actions, GitLab CI, Jenkins, pre-commit, and Nexus repository events, with SARIF, baseline diffing, and deterministic fail gates.

v0.3.0 (2026-10-28): integration, performance, and correctness release. JGD is now callable by other code-audit agents through JSON/SARIF, MCP, and A2A; the bytecode index and dynamic probes are faster; and the deserialization verifier no longer has LLM gatekeeping or build-time false positives.

v0.4.0 focus: CI/CD gates

GitHub Actions

Root action.yml is a composite action:

  • installs JGD on the runner;
  • runs jgd audit with JSON/SARIF/Markdown;
  • writes gate.json and optional run.json;
  • supports baseline, fail-on, dynamic probes, and optional ECJ;
  • exposes exit-code, sarif-file, gate-file, and run-dir;
  • publishes a job summary and fails the job on policy findings.

Use it directly:

- uses: lupingQAQ/JGD@v0.4.0
  with:
    target: build/libs
    fail-on: high
    offline: "true"
- uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: jgd-report/report.sarif

Nexus webhook

jgd serve webhook receives Nexus component/asset events, authenticates with Bearer token and/or HMAC-SHA256, and hands .jar artifacts to the normal JGD service:

jgd serve webhook \
  --host 0.0.0.0 --port 8090 \
  --token "$JGD_WEBHOOK_TOKEN" \
  --secret "$JGD_WEBHOOK_SECRET" \
  --nexus-base-url https://nexus.example.com/repository/maven-releases \
  --profile standard --fail-on high

The receiver only downloads URLs under the configured Nexus base URL, only accepts .jar files, enforces a 512 MB limit, and rejects local paths outside --local-root. Details: ci/README.md.

Baseline-gated pull requests

First run establishes a baseline:

jgd audit --target build/libs --profile standard --out .jgd/baseline

Later PR runs fail only on new findings:

jgd audit --target build/libs --profile standard \
  --baseline .jgd/baseline/run.json --fail-on high --out jgd-report

Every run writes gate.json with blockingIds, new, considered, and failed so CI can render the decision without parsing prose.

Other insertion points

v0.3.0 focus

1. Agent-callable

  • CLI contract with machine-readable JSON, SARIF 2.1.0, Markdown, and stable exit codes (0/10/20/30/40).
  • MCP stdio tools: jgd_audit, jgd_poc, jgd_get_run, jgd_list_findings, jgd_cancel.
  • A2A JSON-RPC/SSE: agent card, SendMessage, SendStreamingMessage, GetTask, ListTasks, CancelTask, optional Bearer auth.
  • The JGD mining loop stays inside JGD. External agents supply target, authorization, profile, and budget; they consume findings/artifacts rather than reimplementing deserialization analysis.
  • Deterministic verifier/terminator owns the verdict; LLM output is advisory only and cannot mark a chain confirmed.

2. Faster

  • SQLite bytecode-as-a-database index: 828-JAR warm query 0.038s vs 2.56s legacy (~67x); cold build is incremental and skips unchanged JARs.
  • Dynamic probes batch by target classpath, so a batch of bridges shares one JVM (DynamicProbe.probe_many).
  • Probe classpaths use the whole target rather than only the bridge JAR; Spring Boot BOOT-INF/lib/*.jar and BOOT-INF/classes are safely extracted under the run directory.
  • Incremental index counters now report full target coverage on warm runs.

3. Correctness fixes

  • CC3 ChainedTransformer now uses the real Transformer[] constructor and the standard getMethod -> invoke -> exec sequence.
  • New hmz: HashMap carrier defers the bridge trigger to HashMap.readObject(); build/serialization no longer count as deserialization closure.
  • Removed LLM acceptance gatekeeping from conductor/PoC verdicts.
  • Nested fat JARs (Spring Boot BOOT-INF/lib) are indexed recursively.
  • Version gates that cannot be verified return UNVERIFIED, not APPLICABLE.
  • Compression-ratio limits apply to .class entries only, avoiding false rejects on legitimate resources.

What is JGD?

An autonomous agent that takes any JAR directory and produces:

  1. Known public chains — four-state determination (PRESENT / VERSION / ASSEMBLE / FIRE)
  2. Novel unpublished chains — discovered via static + dynamic analysis with adversarial LLM auditing
  3. Weaponized PoCs — serialized payloads with RCE closure demo (benign marker file)

All decisions are internalized: JARs in → chains out, zero intermediate user input.

┌─────────────────────────────────────────────────────────────────┐
│  Input: any JAR directory          Output: chains + PoCs        │
│                                                                 │
│  ┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐   │
│  │  Known   │──▶│  Bridge  │──▶│  Chain   │──▶│   PoC    │   │
│  │  Chains  │   │  Discovery│  │  Pairing │   │  Weapon  │   │
│  └──────────┘   └──────────┘   └──────────┘   └──────────┘   │
│       │               │               │               │          │
│   signature       bytecode         dynamic         benign      │
│   + version       + CHA graph      + contract      payload     │
│   gates           + dispatch       synthesis       + fire      │
│                   edges            + 5 carriers    test       │
└─────────────────────────────────────────────────────────────────┘

Chain catalog (research history)

These are historical research candidates, not a claim about any current target. Run jgd audit for target-specific evidence. A chain is only CHAIN_CONFIRMED when the deterministic verifier observes fired_at_deser=true.

T1 — Novel Entry (historical adversarial-audit result)

Chain Bridge Class Carrier JDK Status
objlongpair-hashmap org.apache.activemq.artemis.api.core.ObjLongPair HashMap rehash 17+ ✅ RCE_CLOSED
HashMap.readObject() → rehash → hash(key)
  → ObjLongPair.hashCode() → Objects.hash(first, second)
    → Arrays.hashCode() → first.hashCode()     [Object field — isInstance always true]
      → EqualsBean.hashCode() → beanHashCode()
        → ObjectBean.toString() → ToStringBean.toString()
          → Templates.getOutputProperties() → TemplatesImpl.newTransformer()
            → TemplatesImpl.defineClass() → payload static block → RCE

Novelty: Entry-side confirmed T1 by adversarial audit — absent from ysoserial, GadgetInspector, and all public CVE writeups. Orthogonal to all known ROME entry paradigms (direct ROME key / BAVE / HotSwappableTargetSource / XString).

T2 — New Bridge Classes (ds confirmed CONFIRM, novel=True)

Vavr family (7 chains, ds T2 novel=True)

Chain Bridge Class Carrier JDK
tuple1-8-hashmap io.vavr.Tuple1…Tuple8 HashMap rehash 11
either$left-hashmap io.vavr.control.Either$Left HashMap rehash 11
either$right-hashmap io.vavr.control.Either$Right HashMap rehash 11
option$some-hashmap io.vavr.control.Option$Some HashMap rehash 11
validation$valid-hashmap io.vavr.control.Validation$Valid HashMap rehash 11
validation$invalid-hashmap io.vavr.control.Validation$Invalid HashMap rehash 11
hasharraymappedtrie$leafsingleton io.vavr.HashArrayMappedTrie$LeafSingleton HashMap rehash 11
HashMap.readObject → HashMap.hash → Tuple3.hashCode
  → Tuple.hash → Objects.hashCode → _1.hashCode()       [Object field, attacker-controlled]
    → EqualsBean.hashCode → beanHashCode → ObjectBean.toString
      → ToStringBean.toString → Templates.getOutputProperties
        → TemplatesImpl.newTransformer → defineClass → RCE

Spring AOP family (6 chains, ds T2/T3 novel=True)

Chain Bridge Class Carrier JDK
composablepointcut-hashmap ComposablePointcut HashMap rehash 11
methodmatchers$unionmethodmatcher MethodMatchers$UnionMethodMatcher HashMap rehash 11
methodmatchers$intersectionmethodmatcher MethodMatchers$IntersectionMethodMatcher HashMap rehash 11
singletontargetsource-bave SingletonTargetSource BAVE toString 11
hotswappabletargetsource-bave HotSwappableTargetSource BAVE toString 11
defaultintroductionadvisor-bave DefaultIntroductionAdvisor BAVE toString 11

Guava family (3 chains, ds T2 novel=True)

Chain Bridge Class Carrier JDK
functions$formapwithdefault com.google.common.base.Functions$ForMapWithDefault HashMap rehash 11
predicates$isequaltopredicate com.google.common.base.Predicates$IsEqualToPredicate HashMap rehash 11
present com.google.common.base.Present HashMap rehash 11

Other libraries

Chain Bridge Class Library Carrier JDK
mutableobj-bave cn.hutool.core.lang.mutable.MutableObj hutool-core BAVE ≤11
antlr4-pair-bave org.antlr.v4.runtime.misc.Pair antlr4-runtime BAVE ≤11
clojure-proxy-hashmap clojure.inspector.proxy$…AbstractTableModel$ff19274a clojure HashMap 11
jacksoninject$value-bave JacksonInject$Value jackson-annotations BAVE 11
objectidgenerator$idkey-bave ObjectIdGenerator$IdKey jackson-databind BAVE 11
tolerantmap-hashmap org.snakeyaml.engine.v2.common.TolerantMap snakeyaml HashMap 11
scala-objectref-bave scala.runtime.ObjectRef scala-library BAVE 11

T3 — Variants

Chain Bridge Class Library Notes
ewah-hashmap EWAHCompressedBitmap JavaEWAH (Lucene/ES) Shallow dispatch
federation-hashmap* FederationConfiguration etc. (4) Artemis Config class family
📊 Full dispatch stacks (click to expand)

ObjLongPair (T1, JDK 17):

HashMap.put → HashMap.hash
  → ObjLongPair.hashCode(ObjLongPair.java:55)
    → Objects.hash → Arrays.hashCode
      → EqualsBean.hashCode → EqualsBean.beanHashCode
        → ObjectBean.toString → ToStringBean.toString → Method.invoke
          → TemplatesImpl.defineClass → payload static block

MutableObj (T2, JDK 11):

BAVE.readObject → val.toString()
  → MutableObj.toString → value.toString()
    → ObjectBean.toString → ToStringBean.toString
      → Templates.getOutputProperties → newTransformer → defineClass → RCE

🚀 Quick Start

Hardened static scanner (bytecode index, Plan 1)

# 1) Build the known-chain DB (current builder: 115 chains / 168 version gates)
python -m jgd db build

# 2) Static audit: one-pass bytecode index + JSON/SARIF report
python -m jgd audit --target /path/to/jars --profile standard \
  --format json,sarif,md --out runs/demo

# 3) Environment preflight
python -m jgd doctor

The scanner does not execute target code. Dynamic probes and PoC closure run only through an explicit sandbox boundary; the current preferred boundary is the dedicated Linux VM (docs/VM-SANDBOX.md). PoC and LLM advisory default on in policy; LLM output is advisory and cannot own a terminal verdict.

Sandboxed dynamic probes

# Inside the isolated Linux VM (host sandbox = the VM, not Windows)
python -m jgd audit --target /opt/app/lib --profile standard \
  --dynamic --sandbox host --unsafe-host --format json,sarif,md --out runs/app

# Optional container/WSL2 routes (requires Docker or a rebooted WSL2)
python -m jgd audit --target tests/fixtures --profile standard \
  --dynamic --sandbox docker --format json --out runs/lab

Dynamic probes batch classes that share a target classpath into one JVM, so the per-bridge JVM startup cost is paid once per classpath batch. Spring Boot BOOT-INF/lib JARs and BOOT-INF/classes are extracted under the run for dynamic loading; the probe classpath is the whole target, not only the bridge JAR.

Deterministic PoC

# Must run inside the isolated VM; --unsafe-host is mandatory
python -m jgd poc --target /opt/app/lib \
  --bridge cn.hutool.core.bean.DynaBean \
  --trigger hash --ecj /path/to/ecj.jar \
  --out runs/poc --unsafe-host

See docs/POC.md for the verified Hutool DynaBean + CC3 deserialization closure and the Honest scope caveat.

MCP and A2A

# MCP stdio (tools: jgd_audit, jgd_poc, jgd_get_run, jgd_list_findings, jgd_cancel)
python -m jgd serve mcp

# A2A v1.0-style JSON-RPC + SSE + agent card
python -m jgd serve a2a --host 127.0.0.1 --port 8080
# card: http://127.0.0.1:8080/.well-known/agent-card.json

A2A sets Bearer auth when --token is provided. The full JGD mining loop remains inside JGD; the adapters only start/inspect tasks.

Integration contract

Entry point Contract
CLI jgd audit, jgd poc, jgd index, jgd db, jgd doctor, jgd serve
JSON run.json with schema version, coverage, findings, errors, target fingerprint
SARIF SARIF 2.1.0 for code-audit platforms
MCP stdio JSON-RPC tools for audit, PoC, status, findings, cancel
A2A agent card + JSON-RPC/SSE task lifecycle with optional Bearer auth

External agents should pass a target directory, policy/profile, and budget. They should not reimplement bridge discovery, chain pairing, dynamic verification, or PoC termination; those remain deterministic JGD internals.

Legacy research CLI (still available)

# Install optional RAG dependency (Python 3.10+, JDK 11 & 17 for dynamic stages)
pip install chromadb  # optional, for RAG persistence

# Point at any JAR directory
python3 audit_target.py --target /path/to/jars --name "your-product"

TUI Mode (interactive visualization)

python3 tui.py                    # English
python3 tui.py --lang zh          # 中文

Key Bindings (TUI)

Key Action
↑ ↓ / j k Navigate chains
Enter Toggle detail view
t Switch language (EN/CN)
q Quit

📁 Project Structure

jgd/
├── audit_target.py          # Product CLI entry point
├── tui.py                   # Interactive TUI
├── jgd/            # Core agent modules (25)
│   ├── verify_agent.py      # Bridge discovery + ds adversarial audit (incremental checkpoint)
│   ├── chain_complete.py    # Chain pairing + exhaustion proof (per-item evidence save)
│   ├── poc_gen.py           # Weaponized PoC generation (heq/jackson/map-dispatch tails)
│   ├── known_chains.py      # Public chain determination (SQLite-backed)
│   ├── build_chain_db.py    # Chain DB builder (current: 115 chains / 168 gates)
│   ├── novel_chains.py      # Novel chain auto-tiering (GLM propose + DS verify)
│   ├── matrix_agent.py      # Multi-JDK probe orchestration
│   ├── bcdisasm.py          # Pure-Python bytecode disassembler
│   ├── bridge_fix.py        # Operand-stack symbolic execution
│   ├── chroma_store.py      # RAG with corpus-scoped collections
│   ├── llm.py               # Dual-model (GLM × DeepSeek)
│   ├── scope.py             # Corpus fingerprint isolation
│   ├── profiler.py          # Node-level performance profiling
│   ├── conductor.py         # Acceptance-gated terminal verdict
│   └── ...
├── examples/
│   ├── chains.json          # All discovered chains (machine-readable)
│   └── chains.md            # Human-readable chain catalog
├── tests/
│   └── test_smoke.py        # Third-party reproducible test suite
├── ARCHITECTURE.md          # 25-module graph + design decisions
├── CHANGELOG.md             # Design decision history (R6-R52)
├── SECURITY.md              # Authorized-use policy + disclosure
├── CONTRIBUTING.md          # Five development principles
└── LICENSE                  # MIT

🔬 How It Works

1. Public Chain Determination (guaranteed)

  • SQLite database with per-chain: family, source, CVE, trigger method, sink type, JDK range, conditions, jar version gates. The current builder emits 115 chains / 168 gates; jgd db build writes it and jgd audit reports db_status/db_chains explicitly.
  • Class signature matching (jar-scoped) + JDK internal class detection
  • Version gates with per-jar multi-version verdicts (APPLICABLE / BLOCKED / UNVERIFIED per jar)
  • Dynamic assembly + fire verification using target's own JARs

2. Novel Chain Discovery (domain-exhaustive)

  • Static: Operand-stack symbolic execution detects receiver-bridges + argument-bridges + Map-dispatch bridges (via=mapget)
  • Graph: Real-time corpus-fingerprinted call graph + CHA dispatch edges + JDK builtin sink seeds
  • Dynamic: Batch-parallel JVM probes (5 carriers × field-contract synthesis × multi-JDK)
  • Observability: Marker reachability + exception stack frames + marker caller-stack + MAPDISPATCH signals
  • Incremental checkpoint: verify/chain/poc all support resume-on-same-fingerprint
  • Adaptive scaling: dynamic probes and ds audits scale with INTERESTING candidate count

3. Adversarial Auditing

  • Finder ≠ Verifier: GLM selects candidates, DeepSeek audits independently
  • Deterministic verifier/terminator: terminal verdicts are owned by evidence, coverage, budget, and policy; LLM output is advisory only. The old LLM acceptance gate is removed.
  • "Claims ≠ Evidence" standard: Every fix requires measurement evidence

4. Weaponized PoC Generation

  • Chains auto-derived from mining artifacts (derive_chains)
  • Field-type-aware bridge assembly (make_bridge with Object/interface-Proxy/Map-injection)
  • Tail candidates: ROME (toString/hashCode), Jackson (POJONode), CC3, heq (equals carrier)
  • Complete loop until RCE closure or candidate exhaustion
  • Benign payload: writes /tmp marker file only

5. Novel Chain Auto-Tiering (default pipeline stage 5)

  • RCE_DEMO_FIRED chains with non-known-family bridges are automatically tiered
  • GLM proposes tier (T1/T2/T3) + novelty rationale → DeepSeek cross-verifies (can reject/downgrade)
  • Anti-forgery guard: tier results only trusted when the stage ran successfully in current run

🧪 Real-world validation

bench/realworld.py runs the hardened CLI against an authorized corpus and records coverage, known-chain hits, dynamic probes, and artifacts.

Lab corpus (Tomcat/Commons deps, 30 JARs, 2026-10-26):

Mode Duration Classes Bridges Known chains Dynamic probes
static standard 11.9s 3389 492 25 0
+ dynamic host (trusted lab) 18.2s 3389 492 25 10 (6 DESER_OK, 4 NOT_SERIALIZABLE)

Commercial-product testing requires an authorized corpus; do not point this at third-party products without permission. Dynamic probes confirm class load/serialization round-trip, not a full gadget chain; full chain pairing is the next hardening step.

Recent high-star open-source products (static only, 2026-10-27; no target code executed):

Product JARs Classes Bridges Known chains Time
Nacos 3.2.4 15 78,393 15,169 55 786s
SkyWalking 11.0.0 283 65,346 16,794 38 400s
Jenkins 2.587 77 29,339 4,535 47 145s
RocketMQ 5.5.1 160 31,126 3,575 43 108s
Hutool 5.8.48 1 1,663 172 25 7.9s

Details, sources, and caveats: docs/REAL-WORLD-VALIDATION.md and bench/results/. Known-chain presence is not exploitability; bridge candidates need dynamic/contract verification.

Dynamic probes inside the Linux VM (2026-10-28):

Product Time Probes Result
Hutool 5.8.48 23.8s 10 4 DESER_OK, incl. cn/hutool/core/bean/DynaBean
RocketMQ 5.5.1 166.6s 10 8 NOT_SERIALIZABLE, 2 dependency errors
Jenkins 2.587 166.7s 10 6 NOT_SERIALIZABLE, 4 NPE
Nacos 3.2.4 835.9s 10 10 NOT_SERIALIZABLE (shaded ASM)
SkyWalking 11.0.0 549.3s cold / 217.6s warm 10 10 DESER_OK (bundled Nacos API classes), no PoC closure

DESER_OK is a carrier round-trip, not chain closure. VM setup: docs/VM-SANDBOX.md. Auto-PoC on the Hutool + CC3 lab classpath closed in 43.1s with coverage.poc=2; see docs/POC.md.

⚡ Performance

Metric Value
Static index warm query (828 synthetic JARs) 0.04s vs 2.29s old = 57× (this host, 2026-10-26)
Static index cold build (828 synthetic JARs) 17.98s one-time (5-class fixture; real corpora will differ)
Full pipeline (31 jars) ~90s end-to-end (research baseline, not the hardened scanner)
Pair throughput 1500+ pairs / 9 min (batch + 4 workers, research baseline)
PoC generation (3 chains) 20.3s (2.7× speedup with parallel, research baseline)
Memory per JVM ≤ 256 MB (-Xmx256m, 28% headroom, research baseline)
OOM count across full corpus 0 (research baseline)

The 828-JAR number is a warm-query measurement on a synthetic corpus of five-class JARs; it proves the archive-I/O/query path, not end-to-end chain mining. Cold build is reported separately and must be measured on the real commercial corpus in Plan 5.

🛡️ Honest Capability Boundaries

Claim Status
Public chains: guaranteed detection Yes — mechanical (finite signature set)
Novel chains: domain-exhaustive Yes — within declared scope (ledger-verifiable)
Novel chains: universally exhaustive No — undecidable (Rice's theorem)
PoC: always complete output Yes — tail candidate loop until RCE closure
Carrier families: all classic triggers Yes — toString/hashCode/equals/compare/compareTo

📖 Documentation

⚠️ Disclaimer

For lawful security research, education, and authorized penetration testing only. All PoC payloads write a benign marker file (/tmp/jgd_poc_fired) — no destructive action.

📄 License

MIT — Copyright (c) 2026 JGD Contributors

About

Autonomous agent that mines Java deserialization gadget chains from any JAR directory. Static bytecode analysis + dynamic JVM probes + dual-model adversarial auditing → weaponized PoC. Solving the last mile.

Topics

Resources

Contributing

Security policy

Stars

30 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages