Java code auditing
TL;DR: Map every HTTP route to a handler, then trace request fields to dangerous sinks —
Runtime.exec,ObjectInputStream, JNDI, SpEL/OGNL, JDBC. Decompile JARs with CFR / Procyon / jadx when source is unavailable.
What it is
Auditing a Java web app means understanding the framework’s routing + binding layer, then running a source-to-sink hunt. The same primitives appear across Spring, Struts, JAX-RS, Vert.x, Micronaut — only the annotations change. The interesting bug classes are RCE-via-deserialisation, expression injection, JNDI lookup, SQL/HQL, XXE, SSRF and path traversal.
Preconditions / where it applies
- Java source (Maven/Gradle) or JAR/WAR archives
- Decompilers — CFR (
cfr-0.152.jar), Procyon, jadx for Android/Kotlin, Recaf for live IL editing - Familiarity with
web.xml, Spring@Controller/@RestController, JAX-RS@Path, OSGi bundles for application servers
Technique
- Map routes. For Spring: grep
@RequestMapping|@GetMapping|@PostMapping|@PutMapping|@DeleteMapping|@RequestParam|@PathVariable|@RequestBody. For Struts:struts.xmlaction mappings. For JAX-RS:@Pathon resource classes. For raw servlets:web.xml<servlet-mapping>+HttpServlet.doGet/doPost. Build a route → method table. - Identify binders / DTOs.
@RequestBody Foo dtois a Jackson/Gson sink — type confusion lives here.@ModelAttributebinds query/form into POJOs (Spring4Shell pattern). Hidden fields likeclass.classLoader.URLs[0]may be writable. - Decompile if no source.
1 2
java -jar cfr.jar app.war --outputdir out/ jadx -d out app.jar
Triage
WEB-INF/lib/*.jarfor vulnerable versions (Log4j 2.x, Commons-Collections 3.x, Spring < 5.3.x). - Hunt sinks. Use the catalogue in dangerous-java-sinks. Top priorities:
ObjectInputStream.readObject— see java-deserialization-auditInitialContext.lookup(userControlled)— JNDI / Log4Shell-class bugsparser.parseExpression(input).getValue()— see expression-injectionStatement.executeQuery("..." + input)— SQLiRuntime.exec/ProcessBuilderwith concatDocumentBuilderFactorywithoutdisallow-doctype-decl— XXE
- Auth + filter analysis. Walk every
Filterchain and Spring SecuritySecurityFilterChainconfig. Look forpermitAll()on dangerous routes, wildcard antMatchers, missing CSRF for state-changing endpoints, andauthorizeRequests().anyRequest().permitAll()left over from scaffolding. - Trust deserialisers. Jackson with
enableDefaultTyping()or@JsonTypeInfo(use=Id.CLASS)+ no allowlist is a known sink. XStream pre-1.4.18 is gadget-laden. SnakeYAMLnew Yaml().loaddefaults to unsafe constructor. - Static analysis. Run SpotBugs + FindSecBugs, Semgrep
java.lang.security, CodeQLjava-security-and-quality. Treat results as starting points — confirm reachability manually.
1
2
3
4
# decompile + grep
unzip app.war -d app/
find app/WEB-INF/lib -name '*.jar' -exec sh -c 'mkdir -p src/${1##*/}; cd src/${1##*/}; jar xf "$0"; java -jar ~/tools/cfr.jar . > out.java 2>/dev/null' {} \;
grep -RnE 'readObject|InitialContext\.lookup|parseExpression|Runtime\.exec|ProcessBuilder' src/
Detection and defence
- Apply JEP 290
ObjectInputFilterglobally; denyorg.apache.commons.collections.*etc. - Disable Jackson default-typing or use
PolymorphicTypeValidatorwith allowlist - Spring Security — explicit
denyAlldefault and per-route grants - Use parameterised JDBC / Criteria / JPA Named Queries; ban string-concat HQL
- Centralise SSRF guard at the HTTP-client factory level
References
- HackTricks — Java audit checklist — review patterns
- Spring Security reference — filter-chain semantics
- FindSecBugs sink catalogue — sink → bug-class map
- ysoserial — gadget chains for deserialisation testing