Challenge / SecureCoding

Agriweb

Agriweb is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator

EasyPublished 2025-08-15Sanitized local writeup

Scenario

Agriweb attack path

Agriweb is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator

Objective

Challenge walkthrough focused on SecureCoding evidence, validation, and reusable operator lessons.

Agriweb sanitized attack graph

Walkthrough flow

01

Artifact review

02

Hypothesis

03

Validated solve path

04

Proof captured

Source coverage

High source coverage

Status: complete. This article is generated from 6 sanitized Markdown sources and keeps raw flags, credentials, keys, cookies, and reusable secrets out of the rendered blog.

100% coverage
Evidence verdict

High confidence: the page is reconstructed from a primary walkthrough plus multiple supporting notes or evidence sources. Treat the chain as source-backed, while still checking the listed source files for sensitive values.

  • SecureCoding/Agriweb/writeup.md
  • htb-challenge/SecureCoding/Agriweb/notes.md
  • htb-challenge/SecureCoding/Agriweb/memory-summary.md
  • htb-challenge/SecureCoding/Agriweb/hypothesis-board.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__SecureCoding__Agriweb__memory-summary.md.6b5b543cda.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__SecureCoding__Agriweb__notes.md.b6542c363a.md

Technical Walkthrough

Writeup

Challenge

  • Name: Agriweb
  • Category: SecureCoding
  • Difficulty: Easy
  • Mode: remote

Summary

Agriweb exposes its source through an outer HTB editor service. The actual bug is in routes/profile.js: attacker-controlled profile JSON is recursively merged with an unsafe deepMerge(...) that does not block __proto__, prototype, or constructor. That allows prototype pollution, and because the admin middleware trusts req.user.isAdmin, a polluted Object.prototype.isAdmin turns ordinary authenticated users into admins. The intended fix is to harden the merge logic, restart the service, and verify the patch.

Artifact Inventory

Relevant local and remote artifacts:

  • analysis/remote/api-readonly.txt: confirms the live editor endpoints /api/directory, /api/file, /api/restart, and /api/verify
  • files/exposed-source/routes/profile.js: vulnerable recursive merge logic
  • files/exposed-source/app.js: admin guard that trusts req.user.isAdmin
  • files/exposed-source/utils/jwt.js: normal JWT handling, useful to rule out weaker bypass paths
  • files/exposed-source/exploit/solver.py: bundled proof-of-concept for the prototype-pollution payload class
  • solve/patched-profile.js: prepared remediation
  • solve/solve.py: reproducible remote patch/restart/verify helper
  • analysis/remote/verify-after-patch.json: sanitized proof that verification passed after patching

Analysis

The live service root is an HTB editor frontend, while /challenge/ serves the Agriweb application. The editor API exposed the remote project tree, and routes/profile.js contained the vulnerable merge code.

updateProfile() reads the stored profile JSON and feeds attacker-controlled profileData into a recursive deepMerge(target, source). In the vulnerable version, the merge walks nested objects without rejecting special keys, so payloads containing __proto__, prototype, or constructor can poison object prototypes.

The included bundled files/exposed-source/exploit/solver.py proves the intended bug class by sending exactly those pollution payloads. The impact is then clear in app.js: the admin guard checks req.user.isAdmin. If Object.prototype.isAdmin is polluted, the decoded JWT payload object inherits that property and the middleware grants admin access.

The remediation in solve/patched-profile.js fixes the bug at the right layer:

  • recursively blocks __proto__, prototype, and constructor
  • only iterates own enumerable keys
  • only recursively merges plain objects
  • sanitizes nested structures before merging

During execution I also had to correct the automation helper. The editor save protocol expects the MD5 of the currently loaded file as a save token, not the MD5 of the patched content. The final solve/solve.py fetches the live routes/profile.js, derives that MD5, sends the patch over Socket.IO, restarts the service, and then verifies the result.

Solve

From the workspace root:

bash
cd <local workspace>
python3 -m venv .venv
. .venv/bin/activate
python -m pip install requests 'python-socketio[client]'
python solve/solve.py --base-url http://<TARGET>:31970 --output loot/flag-candidate.txt

What the helper does:

  1. GET /api/file?path=routes/profile.js to read the current file and derive the editor’s MD5 save token.
  2. Connect to the editor’s Socket.IO endpoint and send a message event of type save for routes/profile.js.
  3. POST /api/restart.
  4. GET /api/verify.
  5. Store the returned flag candidate only in loot/flag-candidate.txt.

After that, the harness capture path is:

bash
cd <local workspace>
python3 scripts/challenge_harness.py capture-flag SecureCoding/Agriweb --from loot/flag-candidate.txt
python3 scripts/challenge_harness.py complete SecureCoding/Agriweb
python3 scripts/challenge_harness.py validate-state SecureCoding/Agriweb

Flag

Raw flag is stored in loot/flag.txt and intentionally not reproduced here.

Lessons

  • Prototype pollution is rarely just a data-integrity bug; any authorization path that trusts inherited properties turns it into privilege escalation.
  • In editor-backed SecureCoding challenges, save-token semantics matter as much as the patch itself.
  • Keep verify artifacts sanitized; the authoritative raw flag belongs in loot/ only.

Source-Backed Dossier

The sections below are merged from companion Markdown notes for the same case. They are rendered after sanitization so the article stays precise without publishing raw flags, credentials, or target-specific secrets.

Notes

Scope

  • Challenge: Agriweb
  • Category: SecureCoding
  • Difficulty: Easy
  • Mode: remote
  • Remote instance: <TARGET>:31970
  • Start time: 2026-06-07T23:12:25Z
  • Operator: harness
  • State file: challenge-state.json

Harness Status

  • Current phase: see challenge-state.json
  • Next allowed actions: see next-action.json
  • Raw flags and sensitive material stay in loot/ only. Do not paste them here.

Artifact Inventory

FileSizeSHA256TypeNotes
files/exposed-source/app.js4609<hash redacted>Java source, ASCII text
files/exposed-source/db/.gitkeep0<hash redacted>empty
files/exposed-source/directory.json1771<hash redacted>JSON data
files/exposed-source/exploit/solver.py2538<hash redacted>Python script text executable, ASCII text
files/exposed-source/package-lock.json101219<hash redacted>JSON data
files/exposed-source/package.json456<hash redacted>JSON data
files/exposed-source/routes/auth.js2067<hash redacted>Java source, ASCII text
files/exposed-source/routes/profile.js2631<hash redacted>Java source, ASCII text
files/exposed-source/static/css/styles.css4767<hash redacted>ASCII text
files/exposed-source/static/js/admin.js4500<hash redacted>ASCII text
files/exposed-source/static/js/auth.js2268<hash redacted>Java source, ASCII text
files/exposed-source/static/js/dashboard.js8988<hash redacted>Unicode text, UTF-8 text
files/exposed-source/static/js/farm.js2479<hash redacted>Unicode text, UTF-8 text
files/exposed-source/static/js/main.js257<hash redacted>Java source, ASCII text
files/exposed-source/static/js/ui.js2802<hash redacted>Java source, ASCII text
files/exposed-source/templates/admin.html22808<hash redacted>HTML document text, Unicode text, UTF-8 text
files/exposed-source/templates/index.html27714<hash redacted>HTML document text, Unicode text, UTF-8 text
files/exposed-source/templates/login.html10790<hash redacted>HTML document text, Unicode text, UTF-8 text
files/exposed-source/templates/unauthorized.html3824<hash redacted>HTML document text, Unicode text, UTF-8 text
files/exposed-source/utils/database.js1393<hash redacted>Java source, ASCII text
files/exposed-source/utils/jwt.js1022<hash redacted>Java source, ASCII text
files/exposed-source/utils/random.js383<hash redacted>Java source, ASCII text

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-07T23:12:25Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-07T23:12:37Zartifact inventoryanalysis/artifact-inventory.json0 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-07T23:12:37Zhypothesis recordedhypothesis-board.mdRemote SecureCoding service likely exposes an editable source tree and verification endpoint; mirror source first, then patch only the vulnerable code path.MediumGET /, inspect public assets, query /api/directory and /api/verify if editor pattern is present.
2026-06-07T23:12:37Zcheckpoint recordedanalysis/checkpoint-triage-20260607T231237629784Z-451fb15d.mdCheckpoint for TRIAGEHighUse checkpoint to drive next decision
2026-06-07T23:15:18Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-07T23:15:18Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-07T23:15:18Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-07T23:15:20Zhypothesis recordedhypothesis-board.mdPatch profile deep merge prototype pollution by blocking __proto__, prototype, and constructor keys recursively and merging only plain object own keys.MediumSave solve/patched-profile.js over routes/profile.js, restart service, call /api/verify, capture returned flag through harness.
2026-06-07T23:15:30Zartifact inventoryanalysis/artifact-inventory.json22 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-07T23:15:30Zevaluatoranalysis/evaluator-20260607T231530986595Z-e7b11f38.mdProceedHighApply prepared patch, restart, verify, capture flag through harness.
2026-06-07T23:15:41Zevaluatoranalysis/evaluator-20260607T231541522161Z-0a48fccd.mdProceedHighApply prepared patch, restart, verify, capture flag through harness.
2026-06-07T23:17:44Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-07T23:18:43Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • Service fingerprint: GET / serves the HTB Editor frontend; GET /challenge/ serves the AgriWeb application.
  • Real editor endpoints use /api/*, including /api/directory, /api/file, /api/restart, and /api/verify.
  • GET /api/directory exposed the source tree; mirrored copy is stored under files/exposed-source/.
  • GET /api/verify confirmed the current service is still vulnerable before patching.
  • db/agriweb.db returned HTTP 500 through the editor file API; all text source was mirrored successfully.
  • Root cause: routes/profile.js recursively deep-merges attacker-controlled profile JSON without blocking __proto__, prototype, or constructor.
  • Impact: prototype pollution can make normal JWT payload objects inherit isAdmin, passing the app.js admin middleware check.
  • The included exploit/solver.py confirms the intended exploit path and payload class.
  • Prepared remediation artifacts:

- solve/patched-profile.js

- solve/solve.py

- <secret redacted>md

  • Remote execution succeeded after patching routes/profile.js, restarting the service, and verifying through /api/verify.
  • Editor save protocol note: the Socket.IO save event must carry the MD5 of the currently loaded file as its save token; sending the MD5 of the new content prevents the save acknowledgement.

RAG / Advisory Memory

RAG output is advisory only. Record evaluated retrievals with:

bash
scripts/challenge_harness.py rag-record <workspace> --query "..." --tag MATCHED|PARTIAL|MISSING|<secret redacted>|GENERIC --validation "..."

Secrets/Flags

Raw flags and sensitive material stay in loot/ only. Use scripts/challenge_harness.py capture-flag to validate and record flag capture without printing the value.

Memory Summary

Metadata

  • Platform: HackTheBox Challenges
  • Category: SecureCoding
  • Challenge: Agriweb
  • Difficulty: Easy
  • Source workspace: <local workspace>

Validated Solve Chain

Concepts only. Do not include raw flags, reusable credentials, tokens, cookies, private keys, or live secrets.

1.

Reusable Lessons

-

Dead Ends

-

Tool Quirks

-

Evidence Paths

-

Ingestion Decision

  • Proposed for LightRAG: yes/no
  • Requires user approval before ingestion: yes

Hypothesis Board

Keep no more than 3 active hypotheses on Easy/Medium and 5 on Hard unless the user explicitly asks for breadth.

RankPathEvidenceMissing ProofCheapest ValidationConfidenceStatus
1Remote SecureCoding service likely exposes an editable source tree and verification endpoint; mirror source first, then patch only the vulnerable code path.Challenge provides remote host <TARGET>:31970 and SecureCoding category; no archive was provided.GET /, inspect public assets, query /api/directory and /api/verify if editor pattern is present.MediumActive
1Patch profile deep merge prototype pollution by blocking __proto__, prototype, and constructor keys recursively and merging only plain object own keys.files/exposed-source/routes/profile.js performs unsafe recursive deepMerge; files/exposed-source/exploit/solver.py uses prototype pollution payloads to reach admin.Save solve/patched-profile.js over routes/profile.js, restart service, call /api/verify, capture returned flag through harness.MediumActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit Condition

Memory Summary

approval_required: true

Sanitized Memory Summary

Metadata

  • Platform: HackTheBox Challenges
  • Category: SecureCoding
  • Challenge: Agriweb
  • Difficulty: Easy
  • Source workspace: <local workspace>

Validated Solve Chain

Concepts only. Do not include raw flags, reusable credentials, tokens, cookies, private keys, or live secrets.

1.

Reusable Lessons

-

Dead Ends

-

Tool Quirks

-

Evidence Paths

-

Ingestion Decision

  • Proposed for LightRAG: yes/no
  • Requires user approval before ingestion: yes

Notes

Notes

Scope

  • Challenge: Agriweb
  • Category: SecureCoding
  • Difficulty: Easy
  • Mode: remote
  • Remote instance: <TARGET>:31970
  • Start time: 2026-06-07T23:12:25Z
  • Operator: harness
  • State file: challenge-state.json

Harness Status

  • Current phase: see challenge-state.json
  • Next allowed actions: see next-action.json
  • Raw flags and sensitive material stay in loot/ only. Do not paste them here.

Artifact Inventory

FileSizeSHA256TypeNotes
files/exposed-source/app.js4609<hash redacted>Java source, ASCII text
files/exposed-source/db/.gitkeep0<hash redacted>empty
files/exposed-source/directory.json1771<hash redacted>JSON data
files/exposed-source/exploit/solver.py2538<hash redacted>Python script text executable, ASCII text
files/exposed-source/package-lock.json101219<hash redacted>JSON data
files/exposed-source/package.json456<hash redacted>JSON data
files/exposed-source/routes/auth.js2067<hash redacted>Java source, ASCII text
files/exposed-source/routes/profile.js2631<hash redacted>Java source, ASCII text
files/exposed-source/static/css/styles.css4767<hash redacted>ASCII text
files/exposed-source/static/js/admin.js4500<hash redacted>ASCII text
files/exposed-source/static/js/auth.js2268<hash redacted>Java source, ASCII text
files/exposed-source/static/js/dashboard.js8988<hash redacted>Unicode text, UTF-8 text
files/exposed-source/static/js/farm.js2479<hash redacted>Unicode text, UTF-8 text
files/exposed-source/static/js/main.js257<hash redacted>Java source, ASCII text
files/exposed-source/static/js/ui.js2802<hash redacted>Java source, ASCII text
files/exposed-source/templates/admin.html22808<hash redacted>HTML document text, Unicode text, UTF-8 text
files/exposed-source/templates/index.html27714<hash redacted>HTML document text, Unicode text, UTF-8 text
files/exposed-source/templates/login.html10790<hash redacted>HTML document text, Unicode text, UTF-8 text
files/exposed-source/templates/unauthorized.html3824<hash redacted>HTML document text, Unicode text, UTF-8 text
files/exposed-source/utils/database.js1393<hash redacted>Java source, ASCII text
files/exposed-source/utils/jwt.js1022<hash redacted>Java source, ASCII text
files/exposed-source/utils/random.js383<hash redacted>Java source, ASCII text

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-07T23:12:25Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-07T23:12:37Zartifact inventoryanalysis/artifact-inventory.json0 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-07T23:12:37Zhypothesis recordedhypothesis-board.mdRemote SecureCoding service likely exposes an editable source tree and verification endpoint; mirror source first, then patch only the vulnerable code path.MediumGET /, inspect public assets, query /api/directory and /api/verify if editor pattern is present.
2026-06-07T23:12:37Zcheckpoint recordedanalysis/checkpoint-triage-20260607T231237629784Z-451fb15d.mdCheckpoint for TRIAGEHighUse checkpoint to drive next decision
2026-06-07T23:15:18Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-07T23:15:18Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-07T23:15:18Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-07T23: <REDACTED>, prototype, and constructor keys recursively and merging only plain object own keys.MediumSave solve/patched-profile.js over routes/profile.js, restart service, call /api/verify, capture returned flag through harness.
2026-06-07T23:15:30Zartifact inventoryanalysis/artifact-inventory.json22 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-07T23: <REDACTED>, restart, verify, capture flag through harness.
2026-06-07T23: <REDACTED>, restart, verify, capture flag through harness.
2026-06-07T23: <REDACTED>
2026-06-07T23:18:43Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • Service fingerprint: GET / serves the HTB Editor frontend; GET /challenge/ serves the AgriWeb application.
  • Real editor endpoints use /api/*, including /api/directory, /api/file, /api/restart, and /api/verify.
  • GET /api/directory exposed the source tree; mirrored copy is stored under files/exposed-source/.
  • GET /api/verify confirmed the current service is still vulnerable before patching.
  • db/agriweb.db returned HTTP 500 through the editor file API; all text source was mirrored successfully.
  • Root cause: routes/profile.js recursively deep-merges attacker-controlled profile JSON without blocking __proto__, prototype, or constructor.
  • Impact: <REDACTED>, passing the app.js admin middleware check.
  • The included exploit/solver.py confirms the intended exploit path and payload class.
  • Prepared remediation artifacts:

- solve/patched-profile.js

- solve/solve.py

- <secret redacted>md

  • Remote execution succeeded after patching routes/profile.js, restarting the service, and verifying through /api/verify.
  • Editor save protocol note: <REDACTED>

RAG / Advisory Memory

RAG output is advisory only. Record evaluated retrievals with:

bash
scripts/challenge_harness.py rag-record <workspace> --query "..." --tag MATCHED|PARTIAL|MISSING|<secret redacted>|GENERIC --validation "..."

Secrets/Flags

Raw flags and sensitive material stay in loot/ only. Use scripts/challenge_harness.py capture-flag to validate and record flag capture without printing the value.

Technical analogy

How to remember this solve

Think of it like inspecting a building plan for missing guardrails. You follow each trust boundary and ask what happens if a user controls more input than the developer expected.

For Agriweb, keep the mental model simple: identify the trusted assumption, prove it with the smallest safe test, then automate or repeat only the part that directly leads to the flag.