Celestial Scribe
Celestial Scribe is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator
Scenario
Celestial Scribe attack path
Celestial Scribe 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 Mobile evidence, validation, and reusable operator lessons.
Walkthrough flow
Artifact review
Hypothesis
Validated solve path
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.
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.
- Mobile/Celestial-Scribe/writeup.md
- htb-challenge/Mobile/Celestial-Scribe/notes.md
- htb-challenge/Mobile/Celestial-Scribe/memory-summary.md
- htb-challenge/Mobile/Celestial-Scribe/hypothesis-board.md
- HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Mobile__Celestial-Scribe__memory-summary.md.754769096e.md
- HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__Mobile__Celestial-Scribe__notes.md.ee0b0ebd70.md
Technical Walkthrough
Writeup
Challenge
- Name: Celestial-Scribe
- Category: Mobile
- Difficulty: Medium
- Mode: hybrid
Summary
Celestial Scribe is a mobile/API challenge. The APK exposes a cleartext Express API for user auth and cloud notes. Ordinary IDOR, JWT tampering, path injection, account-name tricks, and delayed permission priming were all negative. The successful path was a true race condition in the note read flow: release a valid permission check for an owned note at the same time as a read of the privileged target note ID 1.
Artifact Inventory
Reference analysis/artifact-inventory.json and summarize the relevant files or remote surface.
files/a12c734c-a4c3-4e5a-8caf-a8fb8c8ae634.zipcontainscelestial_scribe.apk.- Decompiled APK package:
htb.d3vnu11.securenotes. - The app builds a cleartext base API URL of the form
http://<host>:<port>/api. - Confirmed live routes include
/api/auth/register,/api/auth/login,/api/auth/verify,/api/notes,/api/notes/<id>, and/api/notes/<id>/check-permission.
Analysis
The app source showed a two-step note read flow. The client first requests /api/notes/<guid>/check-permission, then requests /api/notes/<guid> to fetch content. That route map is recorded in analysis/source-audit.md.
Baseline API validation showed that simple cross-user note reads were blocked with 403 Access denied; creating a duplicate note ID under another account was also blocked. Those checks are recorded in analysis/api-validation.json.
Additional bounded probes closed the obvious wrong branches:
- JWT tampering and
alg:none:analysis/auth-probe.json,analysis/jwt-notes-probe.json - route/path injection:
analysis/path-probe.json - fixed-delay permission priming:
analysis/permission-race-probe.json - admin-looking account registration:
analysis/admin-email-probe.json - object/array ID parser behavior without a content leak:
analysis/parser-probe.json
The decisive clue was recorded as advisory research in analysis/research/exact-celestial-scribe-race-hint.md: this challenge requires grouped parallel requests with last-byte synchronization. That differs from the earlier fixed-delay TOCTOU test, so the branch was reopened with a new instrumentation plan.
The raw race harness validated the primitive live. In the first bounded race batch, multiple target reads of /api/notes/1 returned HTTP 200 with note ID admin-note-1 and title Secure System Configuration. The sanitized proof is stored in analysis/race-solve.json; the raw flag material is only in loot/.
Solve
The final solver is solve/race_solve.py.
High-level solve flow:
- Register and log in as a fresh user.
- List the fresh account's seeded notes and select one owned note GUID.
- Prepare raw HTTP requests for:
- GET /api/notes/<owned-guid>/check-permission
- GET /api/notes/1
- Open sockets and send every byte except the final byte of each request.
- Release the final byte of all grouped requests at the same time.
- Scan target note responses for a successful privileged note read and flag candidate.
The successful run is summarized in analysis/race-solve.json. It wrote the flag candidate to loot/flag-candidate.txt, then the harness captured it into loot/flag.txt.
Flag
Raw flag is stored in loot/flag.txt and intentionally not reproduced here.
Lessons
- Fixed-delay TOCTOU checks do not disprove a real race. When the evidence calls for last-byte synchronization, use a raw HTTP or equivalent parallel-release harness.
- Treat public writeups as leads only. The exact community hint was not accepted until the current instance returned a live privileged note response.
- Keep raw tokens and flags in
loot/; use sanitizedanalysis/reports for reproducibility and review.
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: Celestial-Scribe
- Category: Mobile
- Difficulty: Medium
- Mode: hybrid
- Remote instance: <TARGET>:30357
- Start time: 2026-06-11T08:31: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/a12c734c-a4c3-4e5a-8caf-a8fb8c8ae634.zip | 4220994 | <hash redacted> | Zip archive data, at least v2.0 to extract, compression method=deflate | zip entries: 1 shown in artifact inventory JSON |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-11T08:31:47Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-11T08:31:47Z | artifact inventory | analysis/artifact-inventory.json | 1 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-11T08:31:47Z | session bootstrap | notes.md | Challenge metadata, scenario, and prior context seeded into workspace | High | Record initial hypothesis and research |
| 2026-06-11T08:31:47Z | hypothesis recorded | hypothesis-board.md | initial triage from supplied challenge metadata | Medium | inventory files / inspect app surface / map routes depending on category |
| 2026-06-11T08:31:47Z | research task | analysis/research/task-20260611T083147401636Z-3c7ef6e0.md | Research task created for advisory investigation | Medium | Record research output |
| 2026-06-11T08:32:31Z | checkpoint recorded | analysis/checkpoint-analysis-20260611T083231575654Z-56b9ba08.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-11T08:36:57Z | local memory search | analysis/research/local-memory-search-20260611T083657651050Z-1523cd3f.md | Found 8 safe prior-note result(s) | Medium | Record useful result or skip |
| 2026-06-11T08:37:06Z | RAG query | analysis/rag/rag-query-20260611T083657652994Z-07d7509d.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-11T08:38:00Z | RAG record | analysis/rag-records.md | Retrieved memory tagged MISSING | Medium | Validate or reject with live evidence |
| 2026-06-11T08:38:00Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-11T08:38:00Z | research record | analysis/research/research-records.md | Research tagged GENERIC | Medium | Validate against current evidence |
| 2026-06-11T08:38:00Z | instrumentation plan | analysis/instrumentation-plan.md | Validate the note privacy bypass primitive from decompiled APK endpoints without an Android emulator. | High | Stop after two negative primitive families without new behavior: direct IDOR/check-permission bypass and client-supplied ID collision/JWT structure. |
| 2026-06-11T08:38:17Z | hypothesis recorded | hypothesis-board.md | Two-account note authorization bypass through /api/notes/<id> or /check-permission | High | Register two users, create a note under user A, and test list/check/read paths as user B. |
| 2026-06-11T08:38:17Z | hypothesis recorded | hypothesis-board.md | Client-supplied cloud note IDs allow collision, overwrite, or predictable reserved note access | Medium | Create same ID under two accounts and inspect ownership/read behavior; probe safe common IDs only if baseline suggests it. |
| 2026-06-11T08:38:17Z | hypothesis recorded | hypothesis-board.md | Bearer token format exposes weak JWT/identity trust | Low | Decode token structure offline and validate only if server accepts a modified or unsigned token through /auth/verify. |
| 2026-06-11T08:38:49Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-11T08:39:12Z | evaluator | analysis/evaluator-20260611T083912772764Z-1e46be29.md | Proceed | High | Build and run a token-safe API validation script, keeping raw token responses out of chat. |
| 2026-06-11T08:42:48Z | branch closed | hypothesis-board.md | Server enforces ownership for ordinary note IDs. | High | Rerank hypotheses |
| 2026-06-11T08:42:48Z | branch closed | hypothesis-board.md | Backend prevents ordinary cross-user ID collision/overwrite. | High | Rerank hypotheses |
| 2026-06-11T08:57:57Z | checkpoint recorded | analysis/checkpoint-analysis-20260611T085757810611Z-07cbf6a8.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-11T08:58:10Z | RAG query | analysis/rag/rag-query-20260611T085757869096Z-6ec31048.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-11T08:58:20Z | RAG record | analysis/rag-records.md | Retrieved memory tagged GENERIC | Medium | Validate or reject with live evidence |
| 2026-06-11T09:05:36Z | branch closed | hypothesis-board.md | No route/path payload produced content, stack traces, or status variance useful for exploitation. | High | Rerank hypotheses |
| 2026-06-11T09:05:36Z | branch closed | hypothesis-board.md | Notes authorization verifies token signatures consistently for tested variants. | High | Rerank hypotheses |
| 2026-06-11T09:05:36Z | branch closed | hypothesis-board.md | Permission-check state appears note-specific or read endpoint still enforces ownership. | High | Rerank hypotheses |
| 2026-06-11T09:05:36Z | branch closed | hypothesis-board.md | No role/seeded-flag behavior was triggered by account email semantics. | High | Rerank hypotheses |
| 2026-06-11T09:05:49Z | checkpoint recorded | analysis/checkpoint-analysis-20260611T090549738376Z-38d8b262.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-11T09:05:49Z | evaluator | analysis/evaluator-20260611T090549791289Z-cb686457.md | Validate first | High | Do not repeat IDOR/JWT/path/route/special-email probes; seek a source-backed clue or targeted hint. |
| 2026-06-11T10:25:17Z | instrumentation plan | analysis/instrumentation-plan.md | Validate exact source-backed note-read race primitive without broad fuzzing. | High | Stop after bounded attempts against id 1 if all target reads remain access denied/timeouts and no new response class appears. |
| 2026-06-11T10:25:17Z | hypothesis recorded | hypothesis-board.md | Last-byte race between owned check-permission and target note read | Medium | Register fresh user, take an owned default note GUID, then repeatedly release raw GET /check-permission and GET /notes/1 at the same final byte and scan sanitized responses for a flag or non-403 target read. |
| 2026-06-11T10:25:17Z | research record | analysis/research/research-records.md | Research tagged PARTIAL | Medium | Validate against current evidence |
| 2026-06-11T10:25:28Z | evaluator | analysis/evaluator-20260611T102528053220Z-91b9d4a1.md | Proceed | High | Implement and run solve/race_solve.py against current remote instance. |
| 2026-06-11T10:27:03Z | flag capture | loot/flag.txt | HTB-format flag captured; raw value kept in loot only | High | Write solution and run completion gate |
| 2026-06-11T10:27:46Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
- APK package:
htb.d3vnu11.securenotes; the app is a cleartext HTTP client forhttp://<host>:<port>/api. - Live API is Express and implements
/auth/register,/auth/login,/auth/verify,/notes,/notes/<id>, and/notes/<id>/check-permission. - Regular two-account IDOR is negative: cross-account check/read returns
403 Access denied. - JWT is HS256 and is verified consistently by both
/auth/verifyand notes routes for tested tampered/alg:nonevariants. - Route/path traversal and SQL-style note-ID payloads returned uniform access denied.
- Permission-check priming did not allow reading a different note ID at 0/50/100/200 ms delays.
- Special admin-looking email registrations produced ordinary accounts with only generic seeded notes.
- Notable unresolved quirk:
POST /notesaccepts object/arrayidvalues, and object IDs can produce update/access-denied behavior. No content leak or ownership takeover was proven. - Current harness decision is
Validate first; do not repeat the closed branches without backend source, a recovered JWT secret, or a narrow hint for the object-ID/parser primitive.
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.
Scenario
A new secure note-taking app promises complete privacy, or is it?
Operator Question
HTB Mobile Medium Celestial Scribe secure note-taking app privacy APK source remote flag solve path
Memory Summary
Metadata
- Platform: HackTheBox Challenges
- Category: Mobile
- Challenge: Celestial-Scribe
- 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 | initial triage from supplied challenge metadata | challenge name, category, difficulty, scenario, and remote target were provided by operator | inventory files / inspect app surface / map routes depending on category | Medium | Active | |
| 1 | Two-account note authorization bypass through /api/notes/<id> or /check-permission | APK source exposes direct note read and a separate permission-check route; challenge scenario targets note privacy. | Register two users, create a note under user A, and test list/check/read paths as user B. | High | Active | |
| 2 | Client-supplied cloud note IDs allow collision, overwrite, or predictable reserved note access | APK POST /api/notes accepts caller-provided id instead of forcing server-generated ID. | Create same ID under two accounts and inspect ownership/read behavior; probe safe common IDs only if baseline suggests it. | Medium | Active | |
| 3 | Bearer token format exposes weak JWT/identity trust | APK stores and reuses server token; no client-side crypto beyond bearer flow. | Decode token structure offline and validate only if server accepts a modified or unsigned token through /auth/verify. | Low | Active | |
| 1 | Last-byte race between owned check-permission and target note read | Exact community hint plus APK-confirmed two-step note-read flow; previous test only used delayed priming, not concurrent release. | Register fresh user, take an owned default note GUID, then repeatedly release raw GET /check-permission and GET /notes/1 at the same final byte and scan sanitized responses for a flag or non-403 target read. | Medium | Active |
Closed Branches
| Branch | Evidence Tested | Failure Output | Reason Closed | Revisit Condition |
|---|---|---|---|---|
| Simple direct note IDOR and check-permission bypass | analysis/api-validation.json: cross-account check and direct read of user A note as user B both returned 403. | read_b_a_note_direct/check_b_a_note returned access denied | Server enforces ownership for ordinary note IDs. | Only revisit if token/ID confusion or route injection changes identity or note ownership context. |
| Simple client-supplied note ID collision | analysis/api-validation.json: user B attempted to create a note with user A's existing ID. | create_b_same_id returned 403 access denied | Backend prevents ordinary cross-user ID collision/overwrite. | Only revisit with parser/path traversal payload or if token forging changes ownership. |
| Route/path injection in note ID | analysis/path-probe.json and analysis/api-validation.json | Encoded traversal and SQL-style route payloads returned uniform access denied/no flag. | No route/path payload produced content, stack traces, or status variance useful for exploitation. | Only revisit if backend source or a hint identifies a different ID parser. |
| JWT tampering / alg none / decoded-token notes authorization | analysis/auth-probe.json and analysis/jwt-notes-probe.json | Auth verify and notes endpoints rejected tampered, none-alg, and object-shaped ID payload tokens with 403. | Notes authorization verifies token signatures consistently for tested variants. | Only revisit with recovered JWT secret or backend source. |
| Permission-check priming / TOCTOU | analysis/permission-race-probe.json | Successful own-note permission checks did not allow reading other note IDs at 0/50/100/200 ms. | Permission-check state appears note-specific or read endpoint still enforces ownership. | Only revisit with evidence of server-side cache keyed incorrectly. |
| Special admin-looking registrations | analysis/admin-email-probe.json | Admin-looking emails registered as ordinary users and received only generic seeded notes. | No role/seeded-flag behavior was triggered by account email semantics. | Only revisit with a known existing admin email or password reset route. |
Memory Summary
approval_required: true
Sanitized Memory Summary
Metadata
- Platform: HackTheBox Challenges
- Category: Mobile
- Challenge: Celestial-Scribe
- 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
Notes
Notes
Scope
- Challenge: Celestial-Scribe
- Category: Mobile
- Difficulty: Medium
- Mode: hybrid
- Remote instance: <TARGET>:30357
- Start time: 2026-06-11T08:31: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/a12c734c-a4c3-4e5a-8caf-a8fb8c8ae634.zip | 4220994 | <hash redacted> | Zip archive data, at least v2.0 to extract, compression method=deflate | zip entries: 1 shown in artifact inventory JSON |
Evidence Ledger
| Time | Action | Output/File | Finding | Confidence | Next |
|---|---|---|---|---|---|
| 2026-06-11T08:31:47Z | harness init | challenge-state.json | Workspace initialized with deterministic state file | High | Inventory artifacts |
| 2026-06-11T08:31:47Z | artifact inventory | analysis/artifact-inventory.json | 1 artifact(s) inventoried | High | Build or update hypotheses |
| 2026-06-11T08:31:47Z | session bootstrap | notes.md | Challenge metadata, scenario, and prior context seeded into workspace | High | Record initial hypothesis and research |
| 2026-06-11T08:31:47Z | hypothesis recorded | hypothesis-board.md | initial triage from supplied challenge metadata | Medium | inventory files / inspect app surface / map routes depending on category |
| 2026-06-11T08:31:47Z | research task | analysis/research/task-20260611T083147401636Z-3c7ef6e0.md | Research task created for advisory investigation | Medium | Record research output |
| 2026-06-11T08:32:31Z | checkpoint recorded | analysis/checkpoint-analysis-20260611T083231575654Z-56b9ba08.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-11T08:36:57Z | local memory search | analysis/research/local-memory-search-20260611T083657651050Z-1523cd3f.md | Found 8 safe prior-note result(s) | Medium | Record useful result or skip |
| 2026-06-11T08:37:06Z | RAG query | analysis/rag/rag-query-20260611T083657652994Z-07d7509d.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-11T08:38:00Z | RAG record | analysis/rag-records.md | Retrieved memory tagged MISSING | Medium | Validate or reject with live evidence |
| 2026-06-11T08:38:00Z | source audit | analysis/source-audit.md | Source audit recorded | High | Gate before exploit |
| 2026-06-11T08:38:00Z | research record | analysis/research/research-records.md | Research tagged GENERIC | Medium | Validate against current evidence |
| 2026-06-11T08: <REDACTED> | |||||
| 2026-06-11T08:38:17Z | hypothesis recorded | hypothesis-board.md | Two-account note authorization bypass through /api/notes/<id> or /check-permission | High | Register two users, create a note under user A, and test list/check/read paths as user B. |
| 2026-06-11T08:38:17Z | hypothesis recorded | hypothesis-board.md | Client-supplied cloud note IDs allow collision, overwrite, or predictable reserved note access | Medium | Create same ID under two accounts and inspect ownership/read behavior; probe safe common IDs only if baseline suggests it. |
| 2026-06-11T08: <REDACTED> | |||||
| 2026-06-11T08:38:49Z | local memory record | analysis/local-memory-records.md | Prior local notes reviewed as fallback/advisory context | Medium | Validate against current evidence |
| 2026-06-11T08: <REDACTED>, keeping raw token responses out of chat. | |||||
| 2026-06-11T08:42:48Z | branch closed | hypothesis-board.md | Server enforces ownership for ordinary note IDs. | High | Rerank hypotheses |
| 2026-06-11T08:42:48Z | branch closed | hypothesis-board.md | Backend prevents ordinary cross-user ID collision/overwrite. | High | Rerank hypotheses |
| 2026-06-11T08:57:57Z | checkpoint recorded | analysis/checkpoint-analysis-20260611T085757810611Z-07cbf6a8.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-11T08:58:10Z | RAG query | analysis/rag/rag-query-20260611T085757869096Z-6ec31048.txt | RAG helper exited 0; output saved | Medium | Record retrieval tag and validation |
| 2026-06-11T08:58:20Z | RAG record | analysis/rag-records.md | Retrieved memory tagged GENERIC | Medium | Validate or reject with live evidence |
| 2026-06-11T09:05:36Z | branch closed | hypothesis-board.md | No route/path payload produced content, stack traces, or status variance useful for exploitation. | High | Rerank hypotheses |
| 2026-06-11T09: <REDACTED> | |||||
| 2026-06-11T09:05:36Z | branch closed | hypothesis-board.md | Permission-check state appears note-specific or read endpoint still enforces ownership. | High | Rerank hypotheses |
| 2026-06-11T09: <REDACTED> | |||||
| 2026-06-11T09:05:49Z | checkpoint recorded | analysis/checkpoint-analysis-20260611T090549738376Z-38d8b262.md | Checkpoint for ANALYSIS | High | Use checkpoint to drive next decision |
| 2026-06-11T09: <REDACTED> | |||||
| 2026-06-11T10:25:17Z | instrumentation plan | analysis/instrumentation-plan.md | Validate exact source-backed note-read race primitive without broad fuzzing. | High | Stop after bounded attempts against id 1 if all target reads remain access denied/timeouts and no new response class appears. |
| 2026-06-11T10: <REDACTED>, take an owned default note GUID, then repeatedly release raw GET /check-permission and GET /notes/1 at the same final byte and scan sanitized responses for a flag or non-403 target read. | |||||
| 2026-06-11T10:25:17Z | research record | analysis/research/research-records.md | Research tagged PARTIAL | Medium | Validate against current evidence |
| 2026-06-11T10:25:28Z | evaluator | analysis/evaluator-20260611T102528053220Z-91b9d4a1.md | Proceed | High | Implement and run solve/race_solve.py against current remote instance. |
| 2026-06-11T10: <REDACTED> | |||||
| 2026-06-11T10:27:46Z | completion gate | challenge-state.json | Completion gate passed; state marked COMPLETE | High | Optional sanitized memory summary approval |
Key Findings
- APK package:
htb.d3vnu11.securenotes; the app is a cleartext HTTP client forhttp://<host>:<port>/api. - Live API is Express and implements
/auth/register,/auth/login,/auth/verify,/notes,/notes/<id>, and/notes/<id>/check-permission. - Regular two-account IDOR is negative: cross-account check/read returns
403 Access denied. - JWT is HS256 and is verified consistently by both
/auth/verifyand notes routes for tested tampered/`alg: <REDACTED> - Route/path traversal and SQL-style note-ID payloads returned uniform access denied.
- Permission-check priming did not allow reading a different note ID at 0/50/100/200 ms delays.
- Special admin-looking email registrations produced ordinary accounts with only generic seeded notes.
- Notable unresolved quirk:
POST /notesaccepts object/arrayidvalues, and object IDs can produce update/access-denied behavior. No content leak or ownership takeover was proven. - Current harness decision is
Validate first; do not repeat the closed branches without backend source, a recovered JWT secret, or a narrow hint for the object-ID/parser primitive.
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.
Scenario
A new secure note-taking app promises complete privacy, or is it?
Operator Question
HTB Mobile Medium Celestial Scribe secure note-taking app privacy APK source remote flag solve path
Technical analogy
How to remember this solve
Think of the app like a packed suitcase. You unpack it, inspect the labels and hidden pockets, then trace which local file or network call contains the useful clue.
For Celestial Scribe, 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.