Screencrack
Screencrack is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator
Scenario
Screencrack attack path
Screencrack 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.
Walkthrough flow
Source and route audit
Trust boundary flaw
Exploit request chain
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.
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/Screencrack/writeup.md
- htb-challenge/Web/Screencrack/notes.md
- htb-challenge/Web/Screencrack/memory-summary.md
- htb-challenge/Web/Screencrack/hypothesis-board.md
Technical Walkthrough
Writeup
Challenge
- Name: Screencrack
- Category: Web
- Difficulty: Medium
- Mode: hybrid
Summary
Screencrack is a Laravel screenshot/source-fetching service. The solved path was:
- Abuse
/api/get-htmlas SSRF. - Bypass the literal local-IP check with a domain that resolves to loopback.
- Use gopher SSRF to push a crafted Laravel queue job into Redis.
- Let the queue worker unserialize
App\Jobs\rmFile. - Abuse
FileQueue::deleteFile()command injection through the unquotedrmcall to copy/flaginto a web-readable/src/*.txtfile.
Artifact Inventory
Reference analysis/artifact-inventory.json and summarize the relevant files or remote surface.
Relevant files:
files/extracted/challenge/routes/api.php: exposesPOST /api/getssandPOST /api/get-html.files/extracted/challenge/app/Http/Controllers/SiteShotController.php: validates only the literal URL host and misses DNS-to-loopback cases.files/extracted/challenge/app/Services/SiteShotService.php: cURLs the supplied URL and stores successful source responses under/www/public/src.files/extracted/challenge/app/Message/FileQueue.php: builds public paths and callssystem()indeleteFile().files/extracted/config/redis.conf: Redis listens on TCP port6379.files/extracted/config/job-runner.sh: startsphp artisan queue:work --queue=default.files/extracted/challenge/.env: sets<secret redacted>=redis.
Analysis
The URL validator rejects literal private IPv4 hosts, but accepts a valid domain if DNS exists. <TARGET>.nip.io passes the domain check and resolves to loopback, so the backend cURL can reach internal services.
Local reproduction established the queue format and exploitability:
analysis/local-queue-payload-example.jsonlcaptured a normal Laravel Redis queue job.analysis/local-malicious-worker-once.txtproved a crafted Redis job reachesApp\Jobs\rmFile.analysis/local-gopher-worker-once.txtproved the job can be injected through the app’s SSRF using gopher.analysis/local-rmstyle-fixed-worker-once.txtproved the final solver-generated serialized payload is valid.
Two important corrections came from testing:
/www/public/srcis absent at container start, so the solver first makes a harmless successful source request to create it.- PHP serialized class names must contain single namespace separators. Earlier solver payloads emitted doubled separators and Laravel rejected them during
unserialize().
Public research check:
https://github.com/pythagoras-19/htb-screencrack-solution
That source was advisory only; the final chain was validated locally and then against the live target.
Solve
The reproducible solver is solve/solve.py. It:
- Generates a random public output filename.
- Primes
/www/public/srcby asking/api/get-htmlto fetchhttp://<TARGET>.nip.io:80/. - Builds a Laravel queue payload for
App\Jobs\rmFile. - Puts the command injection in
fileQueue->uuid, using the unquotedrmsink. - Encodes Redis
LPUSHcommands into a gopher URL. - Sends the gopher URL to
/api/get-html. - Polls
/src/<random>.txtuntil the flag appears.
Tracked execution:
python3 scripts/challenge_exec.py Web/Screencrack -- python3 Web/Screencrack/solve/solve.py \
--base-url http://<TARGET>:31569 \
--output Web/Screencrack/analysis/flag-candidate.txt \
--transcript Web/Screencrack/analysis/remote-solve-transcript.txt \
--poll-seconds 720 \
--poll-interval 5The harness then captured the candidate into loot/flag.txt.
Flag
Raw flag is stored in loot/flag.txt and intentionally not reproduced here.
Lessons
- Source-backed Web challenges should be locally reproduced before remote iteration; the serializer bug would have been hard to see from the remote alone.
- SSRF filters that check only the literal hostname are vulnerable to DNS-to-loopback bypasses.
- Laravel Redis queue payloads are sensitive to exact PHP serialization bytes, especially namespace separators and string lengths.
- A successful exploit chain can still fail on filesystem state; here
/www/public/srchad to be created first through the normal app path.
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: Screencrack
- Category: Web
- Difficulty: Medium
- Mode: hybrid
- Remote instance: <TARGET>:31569
- Start time: 2026-06-12T14:16:47Z
- 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
| File | Size | SHA256 | Type | Notes |
|---|---|---|---|---|
files/a12c7338-7484-4edf-aac3-b37696afb957.zip | 98645 | <hash redacted> | Zip archive data, at least v2.0 to extract, compression method=deflate | zip entries: 133 shown in artifact inventory JSON |
files/extracted/Dockerfile | 1120 | <hash redacted> | ASCII text | |
files/extracted/build-docker.sh | 147 | <hash redacted> | Bourne-Again shell script text executable, ASCII text | |
files/extracted/challenge/.editorconfig | 258 | <hash redacted> | ASCII text | |
files/extracted/challenge/.env | 237 | <hash redacted> | ASCII text | |
files/extracted/challenge/.env.example | 1069 | <hash redacted> | ASCII text | |
files/extracted/challenge/.gitattributes | 186 | <hash redacted> | ASCII text | |
files/extracted/challenge/.gitignore | 238 | <hash redacted> | ASCII text | |
files/extracted/challenge/app/Console/Kernel.php | 573 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Exceptions/Handler.php | 1029 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Http/Controllers/Controller.php | 299 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Http/Controllers/SiteShotController.php | 2687 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Http/Kernel.php | 2598 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Http/Middleware/Authenticate.php | 409 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Http/Middleware/EncryptCookies.php | 307 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Http/Middleware/PreventRequestsDuringMaintenance.php | 366 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Http/Middleware/RedirectIfAuthenticated.php | 760 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Http/Middleware/TrimStrings.php | 381 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Http/Middleware/TrustHosts.php | 379 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Http/Middleware/TrustProxies.php | 649 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Http/Middleware/ValidateSignature.php | 460 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Http/Middleware/VerifyCsrfToken.php | 320 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Jobs/rmFile.php | 720 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Message/FileQueue.php | 1421 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Models/User.php | 916 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Providers/AppServiceProvider.php | 361 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Providers/AuthServiceProvider.php | 568 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Providers/BroadcastServiceProvider.php | 359 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Providers/EventServiceProvider.php | 884 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Providers/RouteServiceProvider.php | 1285 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/app/Services/SiteShotService.php | 2251 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/artisan | 1686 | <hash redacted> | a /usr/bin/env php script text executable, ASCII text | |
files/extracted/challenge/bootstrap/app.php | 1620 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/bootstrap/cache/.gitignore | 14 | <hash redacted> | ASCII text | |
files/extracted/challenge/composer.json | 1916 | <hash redacted> | JSON data | |
files/extracted/challenge/composer.lock | 288125 | <hash redacted> | JSON data | |
files/extracted/challenge/config/app.php | 7782 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/config/auth.php | 3897 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/config/broadcasting.php | 2091 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/config/cache.php | 3272 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/config/cors.php | 846 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/config/database.php | 5287 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/config/filesystems.php | 2370 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/config/hashing.php | 1572 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/config/logging.php | 4173 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/config/mail.php | 3774 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/config/queue.php | 2942 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/config/sanctum.php | 2294 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/config/services.php | 979 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/config/session.php | 7023 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/config/view.php | 1053 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/database/.gitignore | 10 | <hash redacted> | ASCII text | |
files/extracted/challenge/database/app.db | 249856 | <hash redacted> | SQLite 3.x database, last written using SQLite version 3038005, file counter 40, database pages 61, 1st free page 13, free pages 48, cookie 0x9, schema 4, UTF-8, version-valid-for 40 | |
files/extracted/challenge/database/factories/UserFactory.php | 956 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/database/migrations/2014_10_12_000000_create_users_table.php | 751 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/database/migrations/<password redacted> | 641 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/database/migrations/2019_08_19_000000_create_failed_jobs_table.php | 768 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php | 856 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/database/migrations/2023_04_08_121416_create_jobs_table.php | 814 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/database/seeders/DatabaseSeeder.php | 471 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/package.json | 226 | <hash redacted> | JSON data | |
files/extracted/challenge/phpunit.xml | 1146 | <hash redacted> | XML 1.0 document text, ASCII text | |
files/extracted/challenge/public/.htaccess | 603 | <hash redacted> | ASCII text | |
files/extracted/challenge/public/icon.png | 1100 | <hash redacted> | PNG image data, 32 x 32, 8-bit/color RGBA, non-interlaced | |
files/extracted/challenge/public/index.js | 2091 | <hash redacted> | ASCII text | |
files/extracted/challenge/public/index.php | 1710 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/public/robots.txt | 24 | <hash redacted> | ASCII text | |
files/extracted/challenge/public/style.css | 2437 | <hash redacted> | ASCII text | |
files/extracted/challenge/resources/css/app.css | 0 | <hash redacted> | empty | |
files/extracted/challenge/resources/js/app.js | 22 | <hash redacted> | Java source, ASCII text | |
files/extracted/challenge/resources/js/bootstrap.js | 1248 | <hash redacted> | Java source, ASCII text | |
files/extracted/challenge/resources/views/index.blade.php | 2561 | <hash redacted> | HTML document text, Unicode text, UTF-8 text | |
files/extracted/challenge/routes/api.php | 633 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/routes/channels.php | 558 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/routes/console.php | 592 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/routes/web.php | 493 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/storage/app/.gitignore | 23 | <hash redacted> | ASCII text | |
files/extracted/challenge/storage/app/public/.gitignore | 14 | <hash redacted> | ASCII text | |
files/extracted/challenge/storage/framework/.gitignore | 119 | <hash redacted> | ASCII text | |
files/extracted/challenge/storage/framework/cache/.gitignore | 21 | <hash redacted> | ASCII text | |
files/extracted/challenge/storage/framework/cache/data/.gitignore | 14 | <hash redacted> | ASCII text | |
files/extracted/challenge/storage/framework/sessions/.gitignore | 14 | <hash redacted> | ASCII text | |
files/extracted/challenge/storage/framework/testing/.gitignore | 14 | <hash redacted> | ASCII text | |
files/extracted/challenge/storage/framework/views/.gitignore | 14 | <hash redacted> | ASCII text | |
files/extracted/challenge/storage/logs/.gitignore | 14 | <hash redacted> | ASCII text | |
files/extracted/challenge/tests/CreatesApplication.php | 375 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/tests/Feature/ExampleTest.php | 359 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/tests/TestCase.php | 163 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/tests/Unit/ExampleTest.php | 243 | <hash redacted> | PHP script text, ASCII text | |
files/extracted/challenge/vite.config.js | 263 | <hash redacted> | Java source, ASCII text | |
files/extracted/config/httpd.conf | 2696 | <hash redacted> | ASCII text | |
files/extracted/config/job-runner.sh | 99 | <hash redacted> | POSIX shell script text executable, ASCII text | |
files/extracted/config/redis.conf | 1808 | <hash redacted> | ASCII text | |
files/extracted/config/supervisord.conf | 561 | <hash redacted> | ASCII text | |
files/extracted/flag | 26 | <hash redacted> | ASCII text, with no line terminators |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-12T14:16:47Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-12T14:17:15Z | artifact inventory | analysis/artifact-inventory.json | 95 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-12T14:18:43Z | hypothesis recorded | hypothesis-board.md | SSRF through /api/get-html to Redis TCP using a local-resolving domain, inject a crafted Laravel Redis queue job, and let the queue worker execute a command that reads /flag into a web-readable artifact. | Medium | Build/run the provided Docker image locally, inspect normal Redis queue payloads, then SSRF/gopher-inject a benign proof job locally. |
| 2026-06-12T14:18:43Z | checkpoint recorded | analysis/checkpoint-hypothesis_ready-20260612T141843288505Z-6f497413.md | Checkpoint for <secret redacted> | High | Use checkpoint to drive next decision |
| 2026-06-12T14:23:33Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-12T14:23:48Z | RAG query | analysis/rag/rag-query-20260612T142333496447Z-17f4c63d.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-12T14:23:48Z | research record | analysis/research/research-records.md | Research tagged MATCHED | Medium | Validate against current evidence |
| 2026-06-12T14:24:14Z | RAG record | analysis/rag-records.md | Retrieved memory tagged GENERIC | Medium | Validate or reject with live evidence |
| 2026-06-12T14:24:15Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-12T14:24:32Z | instrumentation plan | analysis/instrumentation-plan.md | Use /api/get-html SSRF to inject one Laravel Redis queue job that writes /flag to a random /www/public/src/*.txt artifact, then poll only that artifact. | High | Stop after one injection and the configured poll timeout without the artifact appearing; do not repeat blind Redis payloads. |
| 2026-06-12T14:24:32Z | evaluator | analysis/evaluator-20260612T142432419130Z-db6ce232.md | Proceed | High | scripts/challenge_exec.py Web/Screencrack -- python3 Web/Screencrack/solve/solve.py --base-url http://<TARGET>:31569 --output Web/Screencrack/analysis/flag-candidate.txt --transcript Web/Screencrack/analysis/remote-solve-transcript.txt |
| 2026-06-12T14:38:53Z | branch closed | hypothesis-board.md | The single remote attempt injected a job and polled one artifact for 720 seconds, but the artifact never appeared. Fresh local reproduction showed /www/public/src does not exist at startup, so cat /flag > /www/public/src/<name>.txt fails unless a normal get-html request creates the directory first. | High | Rerank hypotheses |
| 2026-06-12T14:38:53Z | checkpoint recorded | analysis/checkpoint-analysis-20260612T143853071187Z-e53d148d.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-12T14:38:53Z | evaluator | analysis/evaluator-20260612T143853129728Z-5cb5cd9f.md | Proceed | High | Run updated solver through challenge_exec. |
| 2026-06-12T14:52:12Z | branch closed | hypothesis-board.md | Remote prime succeeded and worker later deleted the prime artifact, proving /src creation and worker activity. The malicious artifact stayed 404 for the full poll window, so the next validation targets Redis key prefix mismatch or command output diagnostics. | High | Rerank hypotheses |
| 2026-06-12T14:52:12Z | checkpoint recorded | analysis/checkpoint-analysis-20260612T145212088935Z-f603f15c.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-12T14:52:12Z | evaluator | analysis/evaluator-20260612T145212148894Z-b7ca9d9a.md | Proceed | High | Run updated multi-prefix solver through challenge_exec. |
| 2026-06-12T15:07:06Z | research record | analysis/research/research-records.md | Research tagged PARTIAL | Medium | Validate against current evidence |
| 2026-06-12T15:07:06Z | branch closed | hypothesis-board.md | Local failed_jobs showed unserialize(): Error at offset 21 because solve.py emitted App\\\\Jobs instead of App\\Jobs in the PHP serialized command string. Fixed serializer and revalidated locally. | High | Rerank hypotheses |
| 2026-06-12T15:07:06Z | checkpoint recorded | analysis/checkpoint-analysis-20260612T150706681415Z-ce741baf.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-12T15:07:06Z | evaluator | analysis/evaluator-20260612T150706736557Z-7f18270d.md | Proceed | High | Run corrected solver through challenge_exec. |
| 2026-06-12T15:15:52Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-12T15:16:41Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
-
RAG / Advisory Memory
RAG output is advisory only. Record evaluated retrievals with:
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: Screencrack
- 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.
| Rank | Path | Evidence | Missing Proof | Cheapest Validation | Confidence | Status |
|---|---|---|---|---|---|---|
| 1 | SSRF through /api/get-html to Redis TCP using a local-resolving domain, inject a crafted Laravel Redis queue job, and let the queue worker execute a command that reads /flag into a web-readable artifact. | get-html cURLs attacker URL after validating only literal host string; domains resolving to local IP are not blocked; Redis binds <TARGET>:6379 protected-mode no; <secret redacted>=redis; job worker runs php artisan queue:work; FileQueue/rmFile classes include system() call during queued deletion. | Need exact Laravel Redis queue payload and command execution sink validated locally before remote mutation. | Build/run the provided Docker image locally, inspect normal Redis queue payloads, then SSRF/gopher-inject a benign proof job locally. | Medium | Active |
Closed Branches
| Branch | Evidence Tested | Failure Output | Reason Closed | Revisit Condition |
|---|---|---|---|---|
| Initial remote Redis queue write without priming /www/public/src | analysis/remote-solve-transcript.txt | The single remote attempt injected a job and polled one artifact for 720 seconds, but the artifact never appeared. Fresh local reproduction showed /www/public/src does not exist at startup, so cat /flag > /www/public/src/<name>.txt fails unless a normal get-html request creates the directory first. | ||
| Primed /src then injected only laravel_database_ queue prefix | analysis/remote-solve-transcript.txt | Remote prime succeeded and worker later deleted the prime artifact, proving /src creation and worker activity. The malicious artifact stayed 404 for the full poll window, so the next validation targets Redis key prefix mismatch or command output diagnostics. | ||
| Solver-generated payload with double namespace backslashes | analysis/local-rmstyle-failure-details.txt | Local failed_jobs showed unserialize(): Error at offset 21 because solve.py emitted App\\\\Jobs instead of App\\Jobs in the PHP serialized command string. Fixed serializer and revalidated locally. |
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 Screencrack, 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.