Self-hosted service
vulhub/java-chains avatar
vulhub/java-chains

Java-Chains: a payload generation workbench for Java deserialization research

Java Vulnerability Exploitation Platform

2,173 stars174 forksShellLicense varies

At a glance

What is it?
A web interface for building Java deserialization gadget chains, with JNDI, FakeMySQL and JRMP listeners running in the same container, packaged as Docker Compose, a Docker image or a plain jar.
Who is it for?
Java-Chains is genuinely useful for the narrow job it names: turning a known gadget into a working payload without hand-assembling byte arrays. The bundled JNDI, FakeMySQL and JRMP listeners remove the usual half hour of setting up side services, and the jar route means you can run it on a laptop with no container runtime.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 51 days ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the web interface actually covers

The project's own summary is that Java-Chains is a Java payload generation platform for security researchers, and that its web interface supports common Java payload generation plus JNDI, FakeMySQL and JRMP testing. That is an unusually accurate one-paragraph description, and it is worth reading closely, because it defines the scope. This is not a scanner, not a fuzzing tool and not an exploit delivery framework. It is a place where you construct a payload and watch what a gadget does.

The README links a docs site at java-chains.github.io for the guide, and a separate repository, chains-plugin-demo, for plugin development. So the architecture has an extension seam: the core generates payloads, and plugins add capability. The beta7 release notes mention MCP support being added along with fixes to known problems, which suggests the tool has started exposing its functions to automation rather than only through the browser UI.

The acknowledgements list is unusually long and reads like a map of the Java security tooling world. ysoserial and marshalsec are the two names most Java developers will recognise, and the rest, including MemShellParty, ysomap, JYso, JNDIMap, class-obf, mysql-fake-server, java-memshell-generator and java-echo-generator, are the tools a practitioner would otherwise have to install one at a time. Bundling their reach behind one interface is the product.

Eight ports, and what each one is for

The container publishes eight ports and the README tabulates what each is for, which is the fastest way to understand why the image is larger than a simple generator. Two entries are the web interface on 8011 and a plain HTTP service on 50000. Three belong to JNDI: an HTTP service on 58080, an LDAP service on 50389 and an RMI service on 50388. Then there is FakeMySQL on 3308 and a JRMP Listener on 13999, plus a raw TCP service on 11527.

That list is the argument for the container over the jar. When you are testing whether a deserialization gadget can reach out over LDAP or RMI, you need a listener on the other end, and a half-finished JDBC connection is often enough to trigger it. Shipping those listeners in the image means one container rather than four.

The plain `docker run` form shows the full port list, which is worth keeping as a reference even if you use Compose:

bash
docker run -d \
  --name java-chains \
  --restart=unless-stopped \
  -p 8011:8011 \
  -p 58080:58080 \
  -p 50389:50389 \
  -p 50388:50388 \
  -p 3308:3308 \
  -p 13999:13999 \
  -p 50000:50000 \
  -p 11527:11527 \
  -e CHAINS_AUTH=true \
  -e CHAINS_PASS= \
  javachains/javachains:2.0.0-beta7

Authentication is on by default, and the password is generated

Leaving an unauthenticated payload generator on a network port would be careless, and the project does not do it. `CHAINS_AUTH=true` in the example, and the Compose file defaults to `${CHAINS_AUTH:-true}`. Setting `CHAINS_PASS` to an empty value, as the example does, does not disable the login: the README states that an empty value causes a random password to be generated at startup and printed on an `Auth` line in the logs.

So the documented way in is to start the container and read the credential out of the log:

bash
docker compose up -d
docker logs -f java-chains | grep -i auth

Turning authentication off takes two variables, not one. `CHAINS_AUTH=false` also needs `CHAINS_ALLOW_AUTH_DISABLED=true`, which the Compose file describes as an explicit acknowledgement. That double switch is a small design decision that says something about how the authors think about this software: it is expected to be reachable, sometimes from a VM you are sharing.

The Compose file also carries a `CHAINS_SHOW_PUBLIC_IP` flag that is off by default and documented as querying a public IP echo service at startup. Worth knowing about, since it is the one option that reaches outside your host without you asking for it.

Why the chains-config directory has to come from the tarball

The Compose instructions carry a warning that is easy to skim past and expensive to ignore. You are told to extract the release package and place its `chains-config` directory next to `docker-compose.yml`, and the following comment explains why: if `chains-config` is empty, the container will still start, but it cannot reach the image's bundled configuration and plugins.

The mechanics are visible in the Compose file. It bind mounts `./chains-config` over `/chains/chains-config`, and the inline comment spells out the consequence, that an empty host directory still accepts writes because the entrypoint chowns it, but hides the image's baked-in `chains-config`. So the failure mode is quiet. The UI comes up, you start clicking through it, and the parts that depend on bundled plugins or presets are simply not there.

The Dockerfile explains where the content comes from. It copies `java-chains.jar` and the `chains-config/` directory into `/chains` with explicit ownership, creates the runtime-writable subdirectories for FakeMySQL captures, plugins, presets and plugin-data, and chowns the tree to a dedicated `appuser` in an `appgroup`. Base image is `eclipse-temurin:8u432-b06-jdk-jammy`, and the entrypoint starts as root to repair bind mount permissions before dropping to the unprivileged user. That is a more careful container than most research tools ship.

JDK 8 is required, and the reason is specific

The jar route is three commands and one hard requirement. The README asks for OpenJDK, Temurin or Zulu JDK 8, and adds a parenthetical that Oracle JDK 8 is not recommended for BCEL chains. That is not a generic version preference, it points at a specific interaction between the JDK build and the BCEL library that many gadget chains depend on.

bash
tar -xzf java-chains-2.0.0-beta7.tar.gz
cd java-chains-2.0.0-beta7   # or unpack layout with java-chains.jar + chains-config/
java -jar java-chains.jar

The same JDK 8 assumption is baked into the image, which starts the jar with a fixed heap configuration and the G1 collector:

dockerfile
CMD ["java", "-jar", "-Xms512m", "-Xmx2g", "-XX:+UseG1GC", "/chains/java-chains.jar"]

The `-Xms512m` and `-Xmx2g` defaults are worth noting if you are running this inside a constrained VM. Two gigabytes of ceiling is fine on a workstation and awkward on a small cloud instance, and nothing in the README suggests the value is configurable.

Where the version numbers disagree

The published history has three tags in quick succession: v2.0.0-beta5 on 2026-08-07, v2.0.0-beta7 on 2026-08-09, and v2.0.0 on 2026-08-22. The repository's last push is 2026-08-22, the same day as the final tag, so the codebase is current.

Against that, both the README's `docker run` example and the `docker-compose.yml` image line still name `javachains/javachains:2.0.0-beta7`, and the jar instructions unpack `java-chains-2.0.0-beta7.tar.gz`. Both states are in the repository at once: a stable v2.0.0 tag exists, and the shipped quick start still points at a beta. The tag names say beta7 was the last pre-release, and the release notes for the final tag say only that bugs were fixed, so the gap looks like a documentation lag rather than a deliberate choice to hold back the image.

Practical consequence: do not assume the quick start gives you the latest tag, and check whether a `2.0.0` image tag exists before assuming it does. The two release bodies are similarly terse, one in Chinese noting fixes plus the new MCP function, the other simply saying bugs were fixed, so the changelog will not tell you what changed between beta7 and final.

There is a second loose end in the same place. No license is recorded for this project anywhere in the repository, and the tree contains no LICENSE file, though it does hold a `CHANGELOG.md` and a `docs/` directory alongside the compose file and Dockerfile. For a security research tool that lists twenty acknowledged upstream projects, the absence of a stated license is worth asking about before you fork it.

Editorial conclusion

Java-Chains is genuinely useful for the narrow job it names: turning a known gadget into a working payload without hand-assembling byte arrays. The bundled JNDI, FakeMySQL and JRMP listeners remove the usual half hour of setting up side services, and the jar route means you can run it on a laptop with no container runtime. Two things the repository does not settle are worth carrying into your decision. The published release is v2.0.0 from 2026-08-22, yet the README and `docker-compose.yml` both still pin the image to `2.0.0-beta7`, so pull the tag you actually want rather than copying the compose file blindly. And the repository carries no LICENSE file while its metadata records no license either, which is a real question for anyone planning to build on it. Start with the compose route to see the interface, then move to the jar when you need plugin work.

Frequently asked questions

Which JDK version does Java-Chains need?

The README asks for OpenJDK, Temurin or Zulu JDK 8, and specifically advises against Oracle JDK 8 for BCEL chains. The container image is built on a Temurin 8 JDK, so matching that distribution locally avoids surprises.

How do I get the login password for the web interface?

Leave `CHAINS_PASS` empty and a random password is generated at startup, then read it from the `Auth` line in the container logs with `docker logs -f java-chains | grep -i auth`. Disabling authentication entirely takes two settings, `CHAINS_AUTH=false` plus `CHAINS_ALLOW_AUTH_DISABLED=true`.

Why does the Compose setup tell me to copy chains-config first?

The compose file bind mounts a host `./chains-config` over the directory baked into the image, so an empty host directory hides the bundled configuration and plugins. The container still starts and still accepts writes, which makes the failure quiet, so copy the directory out of the release tarball.

What are the extra ports used for besides the web interface?

Port 8011 is the web interface, and the rest are outbound-style services a payload needs to reach: JNDI over HTTP, LDAP and RMI, a FakeMySQL server, a JRMP listener, plus a plain HTTP and a raw TCP service. Shipping them in one image avoids standing up separate listeners during research.

Official sources

  1. Issues
  2. README
  3. Releases
  4. vulhub/java-chains on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/vulhub-java-chains.svg)](https://hysenlabs.com/projects/vulhub-java-chains)