← Back to Writeups
HTBN/AWeb

AgriWeb

XESXOR8/23/20264 min read
#web#htb#n/a

AgriWeb

Platform: HackTheBox | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-02-01 | Status: Solved Techniques: code_review, deepmerge_sanitization, prototype_pollution_fix

Summary

Task: Patch a prototype pollution vulnerability in a Node.js Express web app to get the flag. Solution: Identify unfiltered proto/constructor keys in a deepMerge function, upload a patched version that filters dangerous keys, restart the app, and verify the fix via /api/verify endpoint.

Recon

Port scan

nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: hackthebox | ID: 20260201_hackthebox_agriweb
  • Tags: nodejs, javascript, prototype_pollution, express, api, code_patching
  • Indicators: deepMerge function, recursive object merge, proto not filtered, constructor not filtered, Express.js application
  • Source: 20260201_hackthebox_agriweb.md

Foothold

Vulnerability / Misconfiguration

  1. Code_review
  2. Deepmerge_sanitization
  3. Prototype_pollution_fix
<command>

Exploitation

  • See original writeup content for detailed exploitation.

Privilege Escalation

Enumeration

sudo -l
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
cat /etc/crontab
ps aux

Exploitation

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

Key Takeaways / Lessons

  • code_review
  • deepmerge_sanitization
  • prototype_pollution_fix
  • Tags: nodejs, javascript, prototype_pollution, express, api, code_patching

Original Writeup

<details><summary>Click to expand original content</summary>

AgriWeb - HackTheBox CTF

Description

"Digital farmlands lie ruined as drones spin out of control and greenhouses overheat; the white-hats must infiltrate the corrupted AgriWeb interface and bring the fields back to life."

Target: http://94.237.120.112:57056

Analysis

Initial Reconnaissance

Accessed the target URL and found an "HTB Editor" - a code editor interface with API access to the application source code.

API Endpoints Discovered

  • /api/directory - List files
  • /api/file?path=X - Read file contents
  • /api/create-file - Create new files
  • /api/delete - Delete files
  • /api/restart - Restart application
  • /api/verify - Verify if vulnerability is patched

Application Structure

app.js              - Main Express application
routes/auth.js      - Authentication routes
routes/profile.js   - Profile update routes (VULNERABLE)
utils/jwt.js        - JWT token handling
utils/database.js   - SQLite database setup
exploit/solver.py   - Hint file showing the attack

Vulnerability: Prototype Pollution in deepMerge

Found in routes/profile.js:

function deepMerge(target, source) {
  for (let key in source) {
    if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
      if (!target[key]) target[key] = {};
      deepMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

Problem: This function doesn't filter dangerous keys (__proto__, constructor, prototype), allowing prototype pollution.

Impact: The JWT token generation in utils/jwt.js checks user.role === 'admin' to set isAdmin: true. By polluting the Object prototype with isAdmin: true, any user could bypass admin authentication.

The Twist - Code Patching Challenge

The /api/verify endpoint returned: "Application vulnerability is not patched."

This revealed the challenge wasn't about exploiting the vulnerability, but patching it!

Solution

Step 1: Delete the vulnerable file

curl -X DELETE "http://94.237.120.112:57056/api/delete" \
  -H "Content-Type: application/json" \
  -d '{"path":"routes/profile.js"}'

Step 2: Create patched version with prototype pollution protection

The fix adds a check to skip dangerous prototype pollution keys:

function deepMerge(target, source) {
  for (let key in source) {
    // PATCH: Filter dangerous prototype pollution keys
    if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
    if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
      if (!target[key]) target[key] = {};
      deepMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

Step 3: Upload the patched file

curl -X POST "http://94.237.120.112:57056/api/create-file" \
  -H "Content-Type: application/json" \
  -d '{"path":"routes/profile.js","content":"<patched code>"}'

Step 4: Restart the application

curl -X POST "http://94.237.120.112:57056/api/restart"

Step 5: Verify and get flag

curl "http://94.237.120.112:57056/api/verify"
# Response: {"flag": "HTB{REDACTED}"}

Secure Coding Fix

Always filter these keys in recursive merge functions:

  • __proto__ - Direct prototype access
  • constructor - Access to constructor.prototype
  • prototype - Direct prototype property
// Safe deepMerge implementation
function deepMerge(target, source) {
  const DANGEROUS_KEYS = ['__proto__', 'constructor', 'prototype'];
  
  for (let key in source) {
    if (DANGEROUS_KEYS.includes(key)) continue;
    
    if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
      if (!target[key]) target[key] = {};
      deepMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

Lessons Learned

  1. Prototype pollution in JavaScript occurs when user input can modify Object.prototype
  2. Always sanitize keys in recursive merge/deep copy functions
  3. The dangerous keys to filter are: __proto__, constructor, prototype
  4. Some CTF challenges require fixing vulnerabilities rather than exploiting them
  5. Code editor interfaces can expose full application source for analysis
</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR