Management

linuxopenamdeserializationrdiff-backuppassword-reuse

A managed-services provider: nginx fronting a small marketing site and an SSO portal, backed by a Java identity server (with its directory) and a service-desk database.

Spoiler-light notes — no flags, credentials or copy-paste exploits. Solve it yourself first.

Recon

Plain scanning gives you the shape fast: SSH, a web front end, and a handful of Java/LDAP ports that don’t belong to “a marketing site”.

Two virtual hosts sit behind the same TLS certificate. The “public” one is a static SPA whose bundle is encrypted (key in the page) — it’s flavour. The real application is the SSO hostname, and it tells you its exact version if you ask politely (an unauthenticated info endpoint, plus a version bean reachable through a well-known path-normalisation quirk).

The interesting part

The SSO is an old, well-documented Java product, and the “obvious” deserialization bug everyone knows about is patched here. But the fix covered one request parameter and missed a second, sibling parameter that goes through the same deserialization path.

That’s the theme of the box: the patch was real, but incomplete.

Two details make it harder than the CVE write-ups suggest:

  1. The vulnerable parameter only deserializes while a specific JSP template renders, and it silently does nothing if a certain other parameter is present in the same request.
  2. The standard public gadget chain does not work out of the box, because the vendor shades (repackages) the library the chain depends on. When “the exploit should work but doesn’t”, pull the vendor JAR and javap it — check the real class names, the serial version UIDs, and which classes weren’t relocated.

Also worth internalising: Runtime.exec returns immediately, so the classic sleep-and-time-it trick is useless for confirming execution. Prove RCE with a side effect you can observe (write a file into the web root and fetch it) — ideally with no outbound traffic, so egress rules can’t confuse the result.

Getting from one user to the next

Once you’re on the box as the service account:

  • Everything is readable in the app’s config directory. Bootstrap files, keystore password files, and the directory’s own config — including admin password material.
  • A second application (the service desk) stores its database credentials in a plain config file, and stores its directory bind password encrypted — with the encryption key sitting in the same folder. Decrypt it and you own a directory service account.
  • That service account’s password is reused as a local OS account password. Service-account ↔ OS-account reuse is still one of the most reliable pivots in the real world.

Root

A short sudo -l reveals a backup tool running as root through a rule with a trailing wildcard. The tool takes “restrict these paths” options — and those options are repeatable, so the same flag that sandboxes you also lets you point it at /. A second option lets you feed the (fake) hostname in as a harmless argument so the remote-execution syntax is satisfied.

The result: you ask the root-owned backup server to mirror a directory you were never meant to read.

Takeaways

  • Patch one sink, find the second.
  • Verify class names in the shipped artefact before assuming “patched”.
  • Confirm execution with effects, not delays.
  • Read every config file once you land.
  • Assume password reuse.
  • Never give a sudo rule a wildcard onto a tool with relaxation flags.