Series MapLesson 20 / 35
Deepen PracticeOrdered learning track

Learn Java Security Cryptography Integrity Part 020 Injection Ssrf Deserialization And Parser Security

14 min read2755 words
PrevNext
Lesson 2035 lesson track2029 Deepen Practice

title: Learn Java Security, Cryptography and Integrity - Part 020 description: Injection, SSRF, deserialization, parser, XML, template, command, and interpreter-boundary security for Java production systems. series: learn-java-security-cryptography-integrity seriesTitle: Learn Java Security, Cryptography and Integrity order: 20 partTitle: Injection, SSRF, Deserialization & Parser Security tags:

  • java
  • security
  • injection
  • ssrf
  • deserialization
  • parser-security
  • xxe
  • secure-coding
  • integrity date: 2026-06-30

Part 020 — Injection, SSRF, Deserialization & Parser Security

Target: setelah part ini, kamu mampu mengenali dan memperbaiki class of bugs yang muncul ketika untrusted input melintasi interpreter boundary: SQL/JPQL/LDAP/OS command/template injection, SSRF, unsafe Java deserialization, XML parser abuse, expression language execution, path/protocol confusion, dan parser resource exhaustion.

Part ini bukan pengulangan JDBC, ORM, JSON, XML, atau REST yang sudah pernah dibahas. Kita hanya membahas sudut security:

Apa yang terjadi ketika data dari attacker dipakai sebagai instruction,
selector, locator, class name, protocol, expression, parser directive,
atau resource identifier?

Core invariant:

Untrusted input boleh menjadi data. Untrusted input tidak boleh menjadi code, query grammar, object type, host target, file path, parser directive, template expression, shell fragment, or classpath behavior.

Referensi utama:


1. Kaufman Deconstruction: Interpreter Boundary Skill Map

CapabilityPertanyaan korektifOutput engineering
Boundary recognitionApakah input akan dibaca oleh interpreter/parser lain?Interpreter map.
Query safetyApakah data dipisahkan dari query grammar?Parameterized queries.
Dynamic selectionApakah user memilih column, sort, class, host, template, or file?Allowlist mapping.
SSRF defenseApakah server melakukan request ke destination yang dikontrol user?Egress policy + URL validator.
Deserialization safetyApakah object graph/classpath bisa dikontrol attacker?No native serialization for untrusted input.
Parser hardeningApakah parser bisa fetch external resources or expand entities?Secure parser config.
Resource limitsApakah input bisa memicu CPU/memory/network explosion?Size/depth/time limits.
Review heuristicsApakah code smell terdeteksi cepat?Secure review checklist.
Test strategyApakah negative payloads menjadi regression tests?Misuse test suite.

Mental shortcut:

If input crosses from "value" into "language", stop and redesign.

2. The Interpreter Boundary Model

Sistem Java modern sering hanya terlihat seperti Java code, padahal melewati banyak interpreter:

Each interpreter has:

syntax + semantics + escaping rules + resource behavior + side effects

Secure engineering means making input data-only, not instruction-bearing.


3. Injection Taxonomy for Java Systems

ClassUnsafe patternSafer pattern
SQL injectionString concatenated SQL.PreparedStatement, bind variables.
JPQL/HQL injectionConcatenated query string.Named parameters, Criteria API, allowlisted sort fields.
NoSQL injectionUser-controlled JSON query operators.Typed query builder, schema validation.
LDAP injectionConcatenated LDAP filters.Escape filter values and allowlist attributes.
OS command injectionShell command string with user input.Avoid shell; ProcessBuilder with fixed executable and args.
Template injectionUser controls template expression.No untrusted templates; strict template mode.
Expression injectionUser controls SpEL/OGNL/MVEL expression.No expression evaluation from users.
XPath injectionConcatenated XPath.Parameterized/compiled safe approach or allowlist.
Header injectionUser input in response header.Validate CR/LF, structured APIs.
Log injectionUser input controls log lines.Structured logging and normalization.
Regex DoSUser controls complex regex or catastrophic input.Allowlisted regex, timeouts, safe patterns.
SSRFUser controls URL/server request target.Destination allowlist + DNS/IP validation + egress deny.
DeserializationUser controls serialized object graph.Avoid native deserialization; filter only as defense-in-depth.
XXEXML parser loads external entity.Disable DTD/external entities.

4. SQL Injection: Not Just Login Bypass

Vulnerable:

String sql = "select * from users where email = '" + email + "'";
try (Statement st = connection.createStatement()) {
    ResultSet rs = st.executeQuery(sql);
}

Safe:

String sql = "select * from users where email = ?";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
    ps.setString(1, email);
    ResultSet rs = ps.executeQuery();
}

But parameterization does not solve dynamic identifiers:

// Wrong: column name cannot be a bind variable.
String sql = "select * from cases order by " + request.getSort();

Use allowlist mapping:

private static final Map<String, String> SORT_COLUMNS = Map.of(
        "createdAt", "created_at",
        "risk", "risk_score",
        "status", "status"
);

public String buildOrderBy(String requestedSort) {
    String column = SORT_COLUMNS.get(requestedSort);
    if (column == null) {
        throw new BadRequestException("Unsupported sort field");
    }
    return " order by " + column + " desc";
}

Important distinction:

Values -> bind parameters.
Identifiers/grammar -> allowlist mapping.

5. JPQL/HQL and ORM Injection

Vulnerable:

String q = "select c from Case c where c.ownerEmail = '" + email + "'";
return entityManager.createQuery(q, Case.class).getResultList();

Safe:

return entityManager
        .createQuery("select c from Case c where c.ownerEmail = :email", Case.class)
        .setParameter("email", email)
        .getResultList();

Dynamic filtering and sorting still need a grammar boundary:

public enum CaseSort {
    CREATED_AT("createdAt"),
    STATUS("status"),
    RISK("riskScore");

    private final String property;

    CaseSort(String property) {
        this.property = property;
    }

    public String property() {
        return property;
    }
}

Do not pass arbitrary property names from client into criteria builders.


6. Command Injection

Vulnerable:

String command = "convert " + inputFile + " " + outputFile;
Runtime.getRuntime().exec(command);

Problems:

  • shell parsing;
  • whitespace/special character behavior;
  • path injection;
  • option injection;
  • environment variable influence;
  • executable search path ambiguity.

Safer pattern:

Path input = safeUploadPath(requestedInputId);
Path output = safeOutputPath(requestedOutputId);

ProcessBuilder pb = new ProcessBuilder(
        "/usr/bin/convert",
        input.toString(),
        output.toString()
);
pb.redirectErrorStream(true);
Process process = pb.start();
boolean finished = process.waitFor(10, TimeUnit.SECONDS);
if (!finished) {
    process.destroyForcibly();
    throw new ProcessingTimeoutException();
}

Still review:

  • executable absolute path;
  • no shell (sh -c) unless impossible;
  • allowlisted operation;
  • fixed flags;
  • file paths resolved under safe directory;
  • resource timeout;
  • output capture limit;
  • non-root runtime;
  • seccomp/container constraints for risky processors.

7. Option Injection

Even without shell, user input can become command option.

new ProcessBuilder("tar", "-xf", userProvidedFile).start();

If userProvidedFile is --checkpoint-action=exec=..., some tools may treat it as option.

Defenses:

  • place -- before user-controlled positional arguments where tool supports it;
  • reject filenames beginning with -;
  • use internal libraries instead of shell tools;
  • run processor in sandbox;
  • allowlist file IDs instead of accepting file paths/names.

Example:

new ProcessBuilder("tar", "-xf", "--", safeFile.toString()).start();

8. LDAP Injection

Vulnerable:

String filter = "(&(objectClass=user)(mail=" + email + "))";

Attack input can modify filter semantics.

Safer:

  • use framework escaping utilities;
  • bind values if supported by API;
  • allowlist attribute names;
  • do not expose arbitrary LDAP filters.

Pseudo-pattern:

String safeEmail = ldapEscape(email);
String filter = "(&(objectClass=user)(mail=" + safeEmail + "))";

Important:

Escaping is context-specific. HTML escaping does not protect LDAP. SQL escaping does not protect shell. URL encoding does not protect XML.

9. Template and Expression Injection

Dangerous pattern:

String template = request.getParameter("template");
return templateEngine.render(template, model);

If user controls template source, many engines expose expression features, method calls, object traversal, or sandbox escape risks.

Safer:

User selects template_id from allowlist.
Server loads immutable template by id.
User controls only data model values.
Output is encoded for target context.

Expression injection example risk:

ExpressionParser parser = new SpelExpressionParser();
Object value = parser.parseExpression(userInput).getValue(context);

Do not evaluate user-provided expressions unless you are building a carefully sandboxed rules product with language design, audit, resource limits, and least-privilege execution.


10. SSRF Mental Model

SSRF happens when attacker influences server-side network request target.

SSRF impact:

  • access cloud metadata service;
  • scan internal network;
  • hit internal admin panels;
  • bypass firewall because request originates from trusted backend;
  • exploit internal services;
  • exfiltrate data through callbacks;
  • force expensive downloads;
  • hit Redis/Elasticsearch/etc if protocol handling is loose.

Core invariant:

User input must not directly determine outbound URL destination. Server-side egress must be allowlisted and network-enforced.


11. SSRF Defense Layers

LayerControl
Business designPrefer file IDs/resources, not arbitrary URLs.
URL parserParse once using strict URI rules.
SchemeAllow https only unless exception.
Host allowlistExact known hostnames or verified tenant domains.
DNS validationResolve and validate IP ranges.
Rebinding defenseRevalidate after resolution/connect; avoid trusting one DNS lookup forever.
IP blockingDeny private, loopback, link-local, multicast, metadata IPs.
Redirect handlingDisable redirects or revalidate every redirect target.
Egress firewallNetwork denies internal/metadata destinations.
Timeout/sizeLimit connect/read time and response size.
Response handlingDo not return arbitrary fetched content blindly.
AuditLog normalized destination and decision.

Best defense is architectural:

Instead of /fetch?url=<any>
Use /import/provider/{providerId}/resource/{resourceId}
where provider endpoint is configured server-side.

12. SSRF URL Validator Skeleton

This is not a complete universal validator. It demonstrates the shape of the control.

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

public final class OutboundDestinationPolicy {
    private static final Set<String> ALLOWED_HOSTS = Set.of(
            "files.partner.example",
            "api.partner.example"
    );

    public URI validate(String rawUrl) {
        URI uri;
        try {
            uri = URI.create(rawUrl);
        } catch (IllegalArgumentException ex) {
            throw new BadRequestException("Invalid URL");
        }

        if (!"https".equalsIgnoreCase(uri.getScheme())) {
            throw new BadRequestException("Only HTTPS is allowed");
        }
        if (uri.getUserInfo() != null) {
            throw new BadRequestException("Userinfo is not allowed");
        }
        if (uri.getHost() == null) {
            throw new BadRequestException("Host is required");
        }

        String host = IDN.toASCII(uri.getHost()).toLowerCase(Locale.ROOT);
        if (!ALLOWED_HOSTS.contains(host)) {
            throw new BadRequestException("Destination is not allowed");
        }

        validateResolvedAddresses(host);
        return uri;
    }

    private void validateResolvedAddresses(String host) {
        try {
            for (InetAddress address : InetAddress.getAllByName(host)) {
                if (isForbidden(address)) {
                    throw new BadRequestException("Forbidden destination address");
                }
            }
        } catch (UnknownHostException ex) {
            throw new BadRequestException("Unknown host");
        }
    }

    private boolean isForbidden(InetAddress address) {
        return address.isAnyLocalAddress()
                || address.isLoopbackAddress()
                || address.isLinkLocalAddress()
                || address.isSiteLocalAddress()
                || address.isMulticastAddress();
    }
}

Production hardening also needs:

  • IPv6 special ranges;
  • cloud metadata IP/domain blocks;
  • redirect revalidation;
  • DNS rebinding defense;
  • proxy enforcement;
  • egress network policy;
  • per-partner certificate/TLS policy;
  • response content limits.

13. SSRF with Java HTTP Client

HttpClient client = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(3))
        .followRedirects(HttpClient.Redirect.NEVER)
        .build();

URI target = destinationPolicy.validate(request.url());

HttpRequest httpRequest = HttpRequest.newBuilder(target)
        .timeout(Duration.ofSeconds(5))
        .GET()
        .build();

HttpResponse<InputStream> response = client.send(
        httpRequest,
        HttpResponse.BodyHandlers.ofInputStream()
);

Do not use followRedirects(ALWAYS) unless every redirect target is revalidated.

Also avoid returning fetched body directly to caller. If importing files, process with size/type checks and store under controlled object storage.


14. Deserialization: Why Java Native Serialization Is Dangerous

Java deserialization can execute code paths while rebuilding object graphs. The risk comes from:

  • attacker-controlled classes in stream;
  • gadget chains on classpath;
  • readObject, readResolve, finalize, comparator, collection, proxy behavior;
  • dependency libraries that introduce exploitable gadgets;
  • resource exhaustion via huge graphs;
  • partial mitigations that allow too much.

Core rule:

Do not deserialize untrusted Java native serialized data. Replace with explicit data formats and schema validation.

Risky API smell:

ObjectInputStream in = new ObjectInputStream(request.getInputStream());
Object obj = in.readObject();

Even if you cast later, damage may happen during readObject().


15. Safer Alternatives to Native Deserialization

NeedPrefer
API payloadJSON with DTO schema and validation.
Internal binary schemaProtocol Buffers, Avro, FlatBuffers, or similar typed schema.
Cache serializationFramework serializer with allowlisted classes and no polymorphic arbitrary types.
Message queueVersioned event schema.
Session storageServer-side structured session record.
Plugin dataExplicit DSL or capability-scoped config.

Security-specific requirement:

Payload schema must constrain shape, size, type, recursion depth, and unknown fields policy.

16. ObjectInputFilter as Defense-in-Depth

If legacy code cannot remove native serialization immediately, use ObjectInputFilter with allowlist and limits.

Example shape:

ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
        "maxdepth=5;maxrefs=1000;maxbytes=1048576;"
      + "com.example.safe.dto.*;java.base/*;!*"
);

try (ObjectInputStream in = new ObjectInputStream(inputStream)) {
    in.setObjectInputFilter(filter);
    Object value = in.readObject();
    if (!(value instanceof ExpectedMessage message)) {
        throw new InvalidPayloadException();
    }
    handle(message);
}

Important limitations:

  • Filters reduce risk; they do not make arbitrary untrusted deserialization safe.
  • Allowlisting packages can still be too broad.
  • Dependencies can change gadget surface.
  • Resource limits must be strict.
  • Filters must be applied before reading objects.
  • Use context-specific filters, not one permissive global filter.

17. Context-Specific Deserialization Filters

Context-specific filters allow different boundaries to have different rules:

Message queue payload -> only com.example.events.*
Cache payload -> only com.example.cache.*
Admin import -> no native serialization at all

Good filters are based on expected object graph, not developer convenience.

Decision record template:

Boundary: legacy RMI endpoint / partner file import / cache restore
Reason native serialization remains: ...
Allowed classes: ...
Max depth/refs/bytes: ...
Classpath risk reviewed: ...
Migration plan: ...
Owner: ...
Expiration date for exception: ...

18. Polymorphic JSON Deserialization Risk

Even non-native serialization can be dangerous if type metadata lets attacker select classes.

Dangerous conceptual pattern:

{
  "@class": "com.some.LibraryGadget",
  "...": "..."
}

Safer:

  • avoid default typing for untrusted data;
  • deserialize into final DTOs or records;
  • explicitly allow known subtypes;
  • reject unknown fields for high-risk boundaries;
  • keep domain entities separate from wire DTOs;
  • apply size/depth limits;
  • avoid binding directly into classes with side-effect setters.

Principle:

Attacker may choose values, not implementation classes.

19. XML Parser Security and XXE

XXE occurs when XML parser resolves external entities controlled by input.

Example dangerous XML:

<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<foo>&xxe;</foo>

Risks:

  • local file disclosure;
  • SSRF through XML entity resolution;
  • denial of service through entity expansion;
  • network calls from parser;
  • data exfiltration via error messages.

Core invariant:

XML parser must not load external entities, external DTDs, or arbitrary schemas from untrusted input.


20. Secure DOM Parser Configuration

Configuration varies by parser, but the goal is explicit: disallow DTD and external entities.

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;

public DocumentBuilderFactory secureDocumentBuilderFactory() throws Exception {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
    factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
    factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
    factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
    factory.setXIncludeAware(false);
    factory.setExpandEntityReferences(false);
    factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
    factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
    factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
    return factory;
}

Notes:

  • Different parsers may support features differently.
  • Failing closed is important; do not ignore ParserConfigurationException.
  • Validate with malicious payload tests.
  • Control schema loading explicitly.

21. Secure SAX Parser Configuration

import javax.xml.XMLConstants;
import javax.xml.parsers.SAXParserFactory;

public SAXParserFactory secureSaxParserFactory() throws Exception {
    SAXParserFactory factory = SAXParserFactory.newInstance();
    factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
    factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
    factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
    factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
    factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
    factory.setXIncludeAware(false);
    return factory;
}

If a parser cannot be hardened, do not use it for untrusted XML.


22. XPath Injection

Vulnerable:

String xpath = "/users/user[email='" + email + "']";

Safer approaches:

  • avoid XPath over untrusted user predicates where possible;
  • pre-parse and filter in Java over structured objects;
  • use variable binding if available;
  • allowlist path fragments;
  • never expose arbitrary XPath query language to ordinary users.

Same rule:

Values are data. XPath grammar is not user-controlled.

23. Parser Resource Exhaustion

Parser abuse is not always about code execution. It can be DoS:

  • huge JSON arrays;
  • deeply nested objects;
  • repeated fields;
  • giant XML documents;
  • zip bombs;
  • regex catastrophic backtracking;
  • multipart upload storms;
  • YAML anchors/aliases;
  • decompression bombs.

Controls:

  • request body size limit;
  • per-field length limit;
  • nesting depth limit;
  • streaming parser for large payloads;
  • decompressed size cap;
  • upload scanning and quarantine;
  • timeout;
  • rate limiting;
  • memory budget;
  • reject unknown polymorphic shapes.

24. Regex DoS

Vulnerable regex:

Pattern.compile("(a+)+$").matcher(input).matches();

Certain inputs can cause catastrophic backtracking.

Guidelines:

  • avoid nested quantifiers;
  • prefer simple allowlist regex;
  • cap input length before regex;
  • do not let users define arbitrary regex for shared services;
  • use timeout-capable matching library if necessary;
  • test evil inputs.

Example:

private static final Pattern SAFE_CODE = Pattern.compile("^[A-Z0-9_-]{1,40}$");

public void validateCode(String code) {
    if (code == null || !SAFE_CODE.matcher(code).matches()) {
        throw new BadRequestException("Invalid code");
    }
}

25. File Path and Protocol Confusion

Vulnerable:

Path path = Paths.get("/data/uploads/" + request.getParameter("file"));
return Files.readString(path);

Safer:

private static final Path ROOT = Path.of("/data/uploads").toAbsolutePath().normalize();

public Path resolveUpload(String fileName) {
    if (!fileName.matches("^[A-Za-z0-9._-]{1,100}$")) {
        throw new BadRequestException("Invalid file name");
    }
    Path resolved = ROOT.resolve(fileName).normalize();
    if (!resolved.startsWith(ROOT)) {
        throw new BadRequestException("Path traversal rejected");
    }
    return resolved;
}

Do not accept:

  • absolute paths;
  • ../ traversal;
  • symlink escape;
  • file:// URLs;
  • UNC paths;
  • platform-specific separators;
  • user-chosen storage bucket/key without tenant scoping.

26. Header and Response Splitting

Vulnerable:

response.setHeader("Location", request.getParameter("next"));

Risks:

  • open redirect;
  • CRLF header injection;
  • cache poisoning;
  • token leakage;
  • response splitting.

Safer:

  • use allowlisted relative paths;
  • reject CR/LF;
  • construct URLs with URI builder;
  • never put untrusted data in security headers;
  • validate redirect targets server-side.

27. Log Injection

Vulnerable:

log.info("Login failed for user=" + username);

If username contains newlines or structured log delimiters, attacker can forge log lines.

Safer structured logging:

log.info("login_failed user_hash={} reason={}", hash(username), reasonCode);

Also:

  • normalize control characters;
  • do not log secrets;
  • include event type;
  • use immutable audit logs for sensitive events;
  • separate debug logs from audit records.

28. Review Heuristics: Dangerous Java APIs and Patterns

Search for:

createStatement(
createQuery(" +
Runtime.getRuntime().exec
ProcessBuilder(
SpelExpressionParser
ScriptEngineManager
ObjectInputStream
readObject(
XMLInputFactory.newInstance
DocumentBuilderFactory.newInstance
SAXParserFactory.newInstance
TransformerFactory.newInstance
URI.create(request
new URL(request
setFollowRedirects(true)
@JsonTypeInfo
activateDefaultTyping
Class.forName(request
Method.invoke
Paths.get(request
Pattern.compile(request

Not every occurrence is a vulnerability. Every occurrence deserves boundary reasoning.


29. Secure Boundary Design Pattern

Use this sequence for every interpreter boundary:

Concrete examples:

BoundaryNormalizeValidateMapBind/Execute
SQL sorttrimenum valueenum to columnconcatenate trusted column only.
HTTP fetchURI parsescheme/host/IPprovider IDHTTP client with no redirects.
XMLcontent-type/sizeDTD disabledschema versionhardened parser.
Commandfile IDowner/tenantstorage pathProcessBuilder args.
Templatetemplate IDallowlistimmutable templaterender data only.

30. Negative Test Payloads

Use controlled tests. Do not run exploit tooling against systems you do not own.

SQL:

' OR '1'='1
x' UNION SELECT ...

SSRF:

http://127.0.0.1:8080/admin
http://169.254.169.254/latest/meta-data/
http://[::1]/
https://app.example.com.evil.org/

XML:

<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]><foo>&xxe;</foo>

Path:

../../etc/passwd
..%2f..%2fetc%2fpasswd

Regex:

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!

Deserialization:

Do not include weaponized gadget payloads in normal unit tests.
Instead test that ObjectInputStream boundary is absent or filter rejects unexpected classes.

31. Java/Spring Test Examples

SQL sort allowlist:

@Test
void rejectsUnsupportedSortField() {
    assertThrows(BadRequestException.class,
            () -> queryBuilder.buildOrderBy("created_at desc; drop table cases"));
}

SSRF destination:

@ParameterizedTest
@ValueSource(strings = {
        "http://127.0.0.1:8080/admin",
        "http://169.254.169.254/latest/meta-data/",
        "ftp://files.partner.example/a",
        "https://files.partner.example.evil.org/a"
})
void rejectsForbiddenOutboundDestinations(String url) {
    assertThrows(BadRequestException.class, () -> policy.validate(url));
}

XML XXE:

@Test
void rejectsDoctype() throws Exception {
    String xml = """
            <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
            <foo>&xxe;</foo>
            """;

    DocumentBuilderFactory factory = secureDocumentBuilderFactory();
    assertThrows(SAXException.class, () ->
            factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)))
    );
}

Deserialization filter:

@Test
void filterRejectsUnexpectedClass() throws Exception {
    ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
            "com.example.safe.ExpectedMessage;java.base/*;!*"
    );
    // Test with serialized unexpected class generated in test fixture.
}

32. Production Control Matrix

RiskPreventDetectRespond
SQL/JPQL injectionParameter binding + allowlist identifiers.SAST, query audit, DB anomaly.Patch, rotate credentials if leaked, review logs.
Command injectionAvoid shell, fixed executable, allowlisted args.Process audit, unexpected child process alerts.Disable feature, revoke workload permissions.
SSRFDestination allowlist + egress firewall.Denied egress logs, metadata access alerts.Rotate cloud credentials, isolate workload.
DeserializationRemove native deserialization.Classpath/gadget review, filter rejection logs.Disable endpoint, patch dependency, migrate format.
XXEDisable DTD/external entities.Parser exception telemetry, outbound blocks.Patch parser config, investigate file/metadata access.
Parser DoSSize/depth/time limits.CPU/memory request anomaly.Rate limit, block source, tune limits.
Template injectionNo untrusted template source.Template error anomaly.Disable dynamic templates.

33. Code Review Checklist

Interpreter boundary:

  • Is untrusted input ever concatenated into a query, command, expression, path, URL, template, or header?
  • Are values separated from grammar?
  • Are dynamic identifiers mapped through allowlists?
  • Are dangerous APIs wrapped by safe abstractions?
  • Are resource limits applied?
  • Are parser features explicitly hardened?

SSRF:

  • Does user input influence outbound URL?
  • Are scheme/host/port allowlisted?
  • Are resolved IPs checked?
  • Are redirects disabled or revalidated?
  • Is network egress restricted outside app code?
  • Is cloud metadata blocked?

Deserialization:

  • Is native Java deserialization absent from untrusted boundaries?
  • If present, is there a time-bound exception?
  • Are filters allowlist-based and context-specific?
  • Are max depth/refs/bytes configured?
  • Is migration plan defined?

XML/parser:

  • DTD disabled?
  • External entities disabled?
  • External schema/DTD access blocked?
  • Secure processing enabled?
  • Input size/depth constrained?
  • Malicious payload tests included?

34. Deliberate Practice Lab

Build a small Java service with four intentionally dangerous features, then harden each:

  1. /cases?sort= dynamic SQL/JPQL sort;
  2. /fetch?url= partner document import;
  3. /xml/import XML upload;
  4. /legacy/deserialize native Java serialized payload.

For each feature:

  • write the vulnerable version;
  • write a failing negative test;
  • implement safe abstraction;
  • add review checklist entry;
  • add audit event for rejected input;
  • document the invariant in an ADR.

Example ADR title:

ADR-SEC-020: Partner Document Import Uses Provider IDs, Not Arbitrary URLs

ADR content:

Decision:
  User cannot submit arbitrary fetch URL.
  User selects providerId and resourceId.
  Server resolves provider endpoint from configuration.
  Egress firewall allows only partner CIDRs/domains.

Reason:
  Prevent SSRF, metadata access, internal scanning, and redirect-based bypass.

Verification:
  Unit tests reject private/link-local/loopback hosts.
  Integration test confirms metadata endpoint is unreachable.
  CORS/authz tests separate browser boundary from outbound fetch boundary.

35. What Good Looks Like

A mature Java team treats injection/SSRF/deserialization/parser risks as architecture issues, not isolated bugs.

- Dangerous APIs are wrapped.
- Allowlist mappings are explicit.
- Query values are bound.
- Dynamic grammar is minimized.
- Outbound network destinations are not user-chosen.
- Native deserialization is removed from untrusted boundaries.
- XML parsers are hardened by default.
- Parser limits are enforced.
- Negative security payloads become regression tests.
- Code review detects interpreter-boundary smells early.

If Part 019 was about browser as a security participant, Part 020 is about every downstream interpreter as a potential execution surface.


36. Next Step

Part 021 moves into distributed integrity:

idempotency, replay prevention, optimistic locking, event ordering,
causality, duplicate handling, tamper detection,
and state integrity across Java services.
Lesson Recap

You just completed lesson 20 in deepen practice. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.