Challenge / Web

Jerrytok

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

MediumPublished 2025-09-16Sanitized local writeup

Scenario

Jerrytok attack path

Jerrytok 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 Web evidence, validation, and reusable operator lessons.

Jerrytok sanitized attack graph

Walkthrough flow

01

Source and route audit

02

Trust boundary flaw

03

Exploit request chain

04

Admin or proof proof

Source coverage

High source coverage

Status: complete. This article is generated from 4 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.

  • Web/Jerrytok/writeup.md
  • htb-challenge/Web/Jerrytok/notes.md
  • htb-challenge/Web/Jerrytok/memory-summary.md
  • htb-challenge/Web/Jerrytok/hypothesis-board.md

Technical Walkthrough

Writeup

Challenge

  • Name: Jerrytok
  • Category: Web
  • Difficulty: Medium
  • Mode: hybrid

Summary

JerryTok is a Symfony/Twig application with server-side template injection in

the location query parameter. Direct command execution is blocked by

disable_functions, but the template can still call file_put_contents and

chmod through Twig's sort filter. The solve writes a .htaccess file that

enables CGI scripts in the public web root, writes an executable shell CGI that

calls the SUID /readflag helper, then requests that CGI endpoint to retrieve

the flag.

Artifact Inventory

  • files/a12c738f-4a20-4868-a833-da74ea8fd3ac.zip: original HTB archive.
  • files/extracted/web_jerrytok/challenge/src/Controller/DefaultController.php:

route/controller containing the template injection.

  • files/extracted/web_jerrytok/entrypoint.sh: runtime hardening with

disabled command functions and open_basedir=/www.

  • files/extracted/web_jerrytok/config/httpd.conf: Apache configuration with

AllowOverride All for /www/public.

  • files/extracted/web_jerrytok/readflag.c: SUID helper that reads /root/flag.
  • Remote instance: http://<TARGET>:32250.

Analysis

The vulnerable controller builds a Twig template directly from user-controlled

input:

php
$message = $this->container->get('twig')->createTemplate(
        "Located at: {$location} from your ship's computer"
    )
    ->render();

A harmless local request with location={{7*7}} rendered 49, recorded in

analysis/local-ssti-7x7.txt.

The obvious Twig callback approach, such as calling system, fails because

entrypoint.sh disables command execution functions. Direct file reads outside

/www also fail under the configured runtime controls. The working primitive is

to use Twig's sort filter with enabled PHP callables:

  • file_put_contents writes controlled files under /www/public.
  • chmod makes the written script executable.
  • .htaccess enables CGI execution for .sh files in /www/public.

Local Docker validation confirmed the exact chain without storing raw flag data

in analysis:

  • analysis/local-ssti-htaccess-cgi-validation.txt
  • analysis/local-solver-transcript.txt
  • analysis/source-audit.md

Public research was treated as advisory only and is recorded in

analysis/research-jerrytok-community-sources.md. The RAG result was generic,

not challenge-specific, and was recorded as checklist-only in

analysis/rag-records.md.

Solve

The solver is solve/solve.py. It performs four steps:

  1. Send an SSTI payload that writes /www/public/.htaccess:

Options +ExecCGI and AddHandler cgi-script .sh.

  1. Send an SSTI payload that writes /www/public/jerrytok.sh with a CGI header

and /readflag.

  1. Send an SSTI payload that calls chmod on the shell script.
  2. Request /jerrytok.sh, extract the HTB-format flag value, and write it to

the requested output file.

Remote execution was run through the harness wrapper:

bash
python3 scripts/challenge_exec.py Web/Jerrytok -- python3 Web/Jerrytok/solve/solve.py \
  --base-url http://<TARGET>:32250 \
  --output Web/Jerrytok/analysis/flag-candidate.txt \
  --transcript Web/Jerrytok/analysis/remote-solve-transcript.txt

The candidate was captured with challenge_harness.py capture-flag, then the

temporary candidate file was removed.

Flag

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

Lessons

  • Blocking only obvious command execution functions is not enough when a

template injection can still call file-writing and permission-changing

functions.

  • Apache .htaccess behavior can turn a file-write primitive into execution if

overrides and CGI handlers are available.

  • For Medium Web challenges, local reproduction prevents wasted remote probing:

the final remote run was a single validated write/chmod/read sequence.

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: Jerrytok
  • Category: Web
  • Difficulty: Medium
  • Mode: hybrid
  • Remote instance: none
  • Start time: 2026-06-12T15:28:58Z
  • 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/a12c738f-4a20-4868-a833-da74ea8fd3ac.zip718681<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 86 shown in artifact inventory JSON
files/extracted/web_jerrytok/Dockerfile1441<hash redacted>ASCII text
files/extracted/web_jerrytok/build-docker.sh133<hash redacted>Bourne-Again shell script text executable, ASCII text
files/extracted/web_jerrytok/challenge/.env1971<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/.env.test215<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/.gitignore468<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/assets/app.js321<hash redacted>Java source, Unicode text, UTF-8 text
files/extracted/web_jerrytok/challenge/assets/bootstrap.js210<hash redacted>Java source, ASCII text
files/extracted/web_jerrytok/challenge/assets/controllers/hello_controller.js500<hash redacted>Java source, ASCII text
files/extracted/web_jerrytok/challenge/assets/controllers.json323<hash redacted>JSON data
files/extracted/web_jerrytok/challenge/assets/styles/app.css40<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/bin/console492<hash redacted>a /usr/bin/env php script text executable, ASCII text
files/extracted/web_jerrytok/challenge/bin/phpunit776<hash redacted>a /usr/bin/env php script text executable, ASCII text
files/extracted/web_jerrytok/challenge/composer.json3222<hash redacted>JSON data
files/extracted/web_jerrytok/challenge/config/bundles.php925<hash redacted>PHP script text, ASCII text
files/extracted/web_jerrytok/challenge/config/packages/asset_mapper.yaml125<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/packages/cache.yaml687<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/packages/debug.yaml261<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/packages/doctrine.yaml1516<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/packages/doctrine_migrations.yaml268<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/packages/framework.yaml411<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/packages/mailer.yaml56<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/packages/messenger.yaml861<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/packages/monolog.yaml2009<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/packages/notifier.yaml357<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/packages/routing.yaml315<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/packages/security.yaml1732<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/packages/translation.yaml163<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/packages/twig.yaml91<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/packages/validator.yaml316<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/packages/web_profiler.yaml337<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/preload.php184<hash redacted>PHP script text, ASCII text
files/extracted/web_jerrytok/challenge/config/routes/framework.yaml120<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/routes/security.yaml79<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/routes/web_profiler.yaml258<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/routes.yaml72<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/config/services.yaml1328<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/importmap.php849<hash redacted>PHP script text, ASCII text
files/extracted/web_jerrytok/challenge/migrations/.gitignore0<hash redacted>empty
files/extracted/web_jerrytok/challenge/phpunit.xml.dist1195<hash redacted>XML 1.0 document text, ASCII text
files/extracted/web_jerrytok/challenge/public/.htacess698<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/public/index.php199<hash redacted>PHP script text, ASCII text
files/extracted/web_jerrytok/challenge/public/static/css/main.css6332<hash redacted>ASCII text
files/extracted/web_jerrytok/challenge/public/static/images/ehV1Ut.jpg82291<hash redacted>JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, progressive, precision 8, 1396x1113, components 3
files/extracted/web_jerrytok/challenge/public/static/images/favicon.png16467<hash redacted>PNG image data, 862 x 760, 8-bit colormap, non-interlaced
files/extracted/web_jerrytok/challenge/public/static/images/sticker_220x200-bg_ffffff-pad_220x200_ffffff.u1_gs3scf.jpg9535<hash redacted>JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, baseline, precision 8, 220x200, components 3
files/extracted/web_jerrytok/challenge/public/static/images/sticker_220x200-bg_ffffff-pad_220x200_ffffff.u3_xijmuh.jpg8164<hash redacted>JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, baseline, precision 8, 220x200, components 3
files/extracted/web_jerrytok/challenge/public/static/images/sticker_220x200-pad_220x200_ffffff.u1_emzmha.jpg8378<hash redacted>JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, baseline, precision 8, 220x200, components 3
files/extracted/web_jerrytok/challenge/public/static/images/sticker_375x360.u1_a1pvp9.png42267<hash redacted>PNG image data, 375 x 360, 8-bit/color RGBA, non-interlaced
files/extracted/web_jerrytok/challenge/public/static/images/sticker_375x360.u1_hpcnno.png57149<hash redacted>PNG image data, 375 x 360, 8-bit/color RGBA, non-interlaced
files/extracted/web_jerrytok/challenge/public/static/images/sticker_375x360.u2_gpiuxw.png80113<hash redacted>PNG image data, 375 x 360, 8-bit/color RGBA, non-interlaced
files/extracted/web_jerrytok/challenge/public/static/images/transdimensional_council_of_ricks_pin_by_stewartisme-d7dtrr7_jxkjm9.png98312<hash redacted>PNG image data, 830 x 962, 8-bit/color RGBA, non-interlaced
files/extracted/web_jerrytok/challenge/public/static/images/tumblr_nwv20kmNTl1ujcz1oo1_1280_cvjpyj.jpg286820<hash redacted>JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, Exif Standard: [TIFF image data, little-endian, direntries=10, manufacturer=Apple, model=iPhone 6, orientation=upper-left, xresolution=150, yresolution=158, resolutionunit=2, software=8.4.1, datetime=2015:10:26 23:03:21, GPS-Data], baseline, precision 8, 1280x960, components 3
files/extracted/web_jerrytok/challenge/src/Controller/.gitignore0<hash redacted>empty
files/extracted/web_jerrytok/challenge/src/Controller/DefaultController.php894<hash redacted>PHP script text, ASCII text
files/extracted/web_jerrytok/challenge/src/Entity/.gitignore0<hash redacted>empty
files/extracted/web_jerrytok/challenge/src/Kernel.php201<hash redacted>PHP script text, ASCII text
files/extracted/web_jerrytok/challenge/symfony.lock8024<hash redacted>JSON data
files/extracted/web_jerrytok/challenge/templates/base.html.twig5024<hash redacted>HTML document text, ASCII text
files/extracted/web_jerrytok/challenge/tests/bootstrap.php320<hash redacted>PHP script text, ASCII text
files/extracted/web_jerrytok/challenge/translations/.gitignore0<hash redacted>empty
files/extracted/web_jerrytok/config/httpd.conf2833<hash redacted>ASCII text
files/extracted/web_jerrytok/config/supervisord.conf270<hash redacted>ASCII text
files/extracted/web_jerrytok/entrypoint.sh436<hash redacted>Neil Brown's ash script text executable, ASCII text
files/extracted/web_jerrytok/flag27<hash redacted>ASCII text
files/extracted/web_jerrytok/readflag.c106<hash redacted>c program text, ASCII text

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-12T15:28:58Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-12T15:29:08Zartifact inventoryanalysis/artifact-inventory.json66 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-12T15:37:04Zhypothesis recordedhypothesis-board.mdExploit Twig SSTI in location to write .htaccess and a CGI shell under /www/public, then execute /readflag through the CGI shell.HighRun solve.py through challenge_exec against the remote once.
2026-06-12T15:37:04Zsource auditanalysis/source-audit.mdSource audit recordedHighGate before exploit
2026-06-12T15:37:04Zcheckpoint recordedanalysis/checkpoint-analysis-20260612T153704538887Z-f0bcefb2.mdCheckpoint for ANALYSISHighUse checkpoint to drive next decision
2026-06-12T15:37:04Zresearch recordanalysis/research/research-records.mdResearch tagged MATCHEDMediumValidate against current evidence
2026-06-12T15:37:31ZRAG queryanalysis/rag/rag-query-20260612T153713598450Z-657fe640.txtRAG helper exited 0; output savedMediumRecord retrieval tag and validation
2026-06-12T15:37:58ZRAG recordanalysis/rag-records.mdRetrieved memory tagged GENERICMediumValidate or reject with live evidence
2026-06-12T15:37:58Zlocal memory recordanalysis/local-memory-records.mdPrior local notes reviewed as fallback/advisory contextMediumValidate against current evidence
2026-06-12T15:37:58Zinstrumentation plananalysis/instrumentation-plan.mdProve and execute the source-backed Twig SSTI to CGI-readflag chain with bounded remote mutation.HighStop after one failed remote flag response or any 500 during the write/chmod sequence; do not fuzz or branch blindly.
2026-06-12T15:38:07Zevaluatoranalysis/evaluator-20260612T153807844284Z-b8dbe751.mdProceedHighRun solve.py against <TARGET>:32250 through challenge_exec.
2026-06-12T15:38:29Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-12T15:39:40Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

-

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: Web
  • Challenge: Jerrytok
  • Difficulty: Medium
  • 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
1Exploit Twig SSTI in location to write .htaccess and a CGI shell under /www/public, then execute /readflag through the CGI shell.DefaultController creates a Twig template from user-controlled location; entrypoint disables direct command functions but leaves file_put_contents and chmod enabled; Apache allows .htaccess overrides in /www/public; local validation executed /cmd.sh with redacted flag-like output.Remote target must match the local source and accept the same write/chmod sequence.Run solve.py through challenge_exec against the remote once.HighActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit Condition

Technical analogy

How to remember this solve

Think of the web app like a building with signs on every door. The solve usually comes from reading the map carefully, finding the door the app forgot to hide, then sending the exact request that proves you understand the route.

For Jerrytok, 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.