November 3, 2026research

Java XML requests target account-file uploads with Wget

Eight distinct request bodies share one command structure, with a five-week count breakdown and a practical XML hunt.

#processbuilder#xml#wget#exfiltration#java#hunting

The account-file upload request

From 2026-10-26 through 2026-11-01 UTC, BlackDome retained 4 HTTPS request bodies across 2 sensors containing a Java ProcessBuilder command that requested a Wget upload of /etc/passwd. Those bodies were 4 of 24 eligible ProcessBuilder-marked HTTPS records that week, arriving as pairs on 2026-10-26 and 2026-10-29.

The investigation pivot is the object-to-command relationship: a submitted Java XML graph pairs process creation with an account-file upload request. Follow that relationship from the inbound application request to local process and network telemetry.

The command graph that matters

Every matched body parses as an XML map containing an object with class java.lang.ProcessBuilder. Its command array names wget, followed by --post-file, /etc/passwd and a network destination. Each body also contains javax.imageio.ImageIO$ContainsFilter.

Wget --post-file supplies the local file as the POST request body. The account file provides user names, identifiers and home-directory layout useful for follow-on targeting. This makes the command relevant to an exfiltration investigation as well as an inbound exploit hunt; ATT&CK documents HTTP among the protocols used for unencrypted exfiltration. Primary-source document

Five weeks of counts and variation

Across 2026-09-28 through 2026-11-01 UTC, the matching records contained 8 distinct inputs across 3 sensors. Replacing the destination argument and removing formatting whitespace leaves 1 identical XML object and command structure. A structural hunt groups these byte-distinct bodies around the operation they request.

Weekly HTTPS body counts, with end dates exclusive:

  • 2026-09-28 to 2026-10-05: 8 eligible ProcessBuilder-marked HTTPS body records; 2 structured matches.
  • 2026-10-05 to 2026-10-12: 33 eligible records; 0 structured matches.
  • 2026-10-12 to 2026-10-19: 0 eligible records; 0 structured matches.
  • 2026-10-19 to 2026-10-26: 14 eligible records; 2 structured matches.
  • 2026-10-26 to 2026-11-02: 24 eligible records; 4 structured matches.

The preceding 28-day baseline, 2026-09-28 through 2026-10-25 UTC, contained 4 matching account-file upload request bodies among 55 eligible records; the current reporting week contained 4 among 24.

A practical XML hunt

Apply this filter to complete XML bodies in your request logs. It requires Python with defusedxml and checks the executable, file option and path within the same command array.

def xml_account_upload(body):
    from defusedxml import ElementTree
    from defusedxml.common import DefusedXmlException
    from urllib.parse import urlsplit

    if not isinstance(body, str) or len(body) > 65536:
        return False
    try:
        if len(body.encode("utf-8")) > 65536:
            return False
        root = ElementTree.fromstring(
            body, forbid_dtd=True, forbid_entities=True, forbid_external=True
        )
    except (ElementTree.ParseError, DefusedXmlException, UnicodeError):
        return False
    for node in root.iter():
        if node.get("class") != "java.lang.ProcessBuilder":
            continue
        for command in node.findall("command"):
            args = [item.text or "" for item in command.findall("string")]
            if not args or args[0].rsplit("/", 1)[-1] != "wget":
                continue
            sends_account_file = "--post-file=/etc/passwd" in args or any(
                a == "--post-file" and b == "/etc/passwd"
                for a, b in zip(args, args[1:])
            )
            if sends_account_file:
                target = args[-1]
                if target.startswith(("-", "/")):
                    continue
                try:
                    url = urlsplit(target if "://" in target else "//" + target)
                    if url.scheme in {"", "http", "https"} and url.hostname:
                        return True
                except ValueError:
                    continue
    return False

Follow the requested transfer

Start with the application route and request timestamp. Where host telemetry is available, correlate the Java parent process, a Wget child command referencing /etc/passwd, access to that file and an outbound POST to the requested destination. Capture the command line and connection metadata together so the file-transfer request remains tied to the Java workload.

Check the caller, workload owner and approved test or change record before escalating. Prioritize unexpected deserialization handlers and Java services launching file-upload commands outside their normal workload.