Y3llowDuck

Mapping an AI API's
Attack Surface with Feroxbuster

AI APIs · FastAPI · Feroxbuster · OSAI Reconnaissance

You land on an internal AI service. Some FastAPI app running on a random port, backing a chatbot, a RAG pipeline, an agent framework, who knows yet. No documentation link, no README. First question is always the same: what's actually here?

Modern APIs built on FastAPI love to expose a self documenting openapi.json file. If you find it, you get the full endpoint list for free, every path, every method, every request body, straight from the app itself. Problem is, that only works if you already know the file is there, and if whoever built the thing didn't lock it down. So before I go looking for the spec, I go looking for the app itself. That's where forced browsing comes in.

01 — wordlist

Why a generic wordlist falls flat

Feroxbuster brute forces a wordlist of likely paths against a target and records how the server responds to each. It's the same idea as gobuster or dirb, just faster and with better filtering built in. The catch is that a standard web wordlist, things like admin, login, backup.zip, wp-content, mostly comes back empty against an AI backed API. That's not where the interesting paths live.

AI apps have their own conventions. OpenAI compatible services expose things like /v1/chat/completions and /v1/embeddings. LangServe apps expose /invoke, /batch, /stream, /playground. LiteLLM proxies expose their own admin surface, things like /key/generate, /spend/logs, /model/info. And RAG based apps almost always have some kind of ingestion endpoint, usually something like /kb/add or /upload, where documents get fed into the vector store.

So the first move was building a wordlist tuned to that structure instead of reusing a generic one. I used Claude to pull real route conventions from the OpenAI API reference, the LangServe repo, and the LiteLLM docs, and put together an ai-api-endpoints.txt I could point feroxbuster at.

prompt — used to build ai-api-endpoints.txt
Research and compile a wordlist of real, documented API endpoint paths for three systems: OpenAI-compatible APIs, LangServe, and LiteLLM. For each system: 1. Pull the endpoint paths only from official documentation or the source code (OpenAI API reference, LangServe's GitHub repo/docs, LiteLLM's GitHub repo/docs). 2. Cite the source for each path or group of paths. 3. Do not include a path unless you can confirm it against a current source, omit anything you're inferring or guessing at. 4. Note the current version/date of the docs you pulled from, since these APIs change. Output the final result as a flat plain-text wordlist, one path per line, no leading slash duplication issues, formatted for direct use with feroxbuster's -w flag. Group with comment lines (#) separating the three systems so it's still usable as a single combined file with ffuf/feroxbuster (most of these tools ignore lines starting with # only if that's their comment syntax, confirm this too before assuming it).
● note
The wordlist doesn't have to be huge to be useful. Smaller and specific beats larger and generic, especially here.
02 — the scan

Running the command

kali — feroxbuster AI API scan
└─$ feroxbuster -u http://10.1.1.10:9876 -w ai-api-endpoints.txt -x json,yaml --filter-status 404

Flag breakdown: -u is the target base URL. -w is the wordlist to fuzz with. -x json,yaml appends those extensions to every word in the list, so it also tries things like openapi.json and config.yaml on top of the plain paths. --filter-status 404 hides plain misses from the output so the terminal stays readable.

● default filtering
Feroxbuster ships with its own default allow list of interesting status codes: 200, 204, 301, 302, 307, 308, 401, 403, 405. A lot of the noise is already filtered before you add flags. It also auto detects soft 404s, pages that return 200 but are really a generic error page, and filters those out on the fly once it spots the pattern.

Link extraction is on by default too. Once feroxbuster lands on something like /docs or openapi.json, it parses the response body for more paths and queues them up automatically. That turned out to matter a lot here, more on that below.

03 — results

Reading the output

kali — scan results
404 GET 1l 2w 22c Auto filtering found 404 like response and created new filter 405 GET 1l 3w 31c http://10.1.1.10:9876/chat 200 GET 66l 385w 3256c http://10.1.1.10:9876/ 200 GET 1l 2w 61c http://10.1.1.10:9876/health 405 GET 1l 3w 31c http://10.1.1.10:9876/session/new 200 GET 1l 45w 4906c http://10.1.1.10:9876/openapi.json 200 GET 31l 66w 891c http://10.1.1.10:9876/redoc 405 GET 1l 3w 31c http://10.1.1.10:9876/reset 200 GET 1l 1w 12c http://10.1.1.10:9876/logs/latest 405 GET 1l 3w 31c http://10.1.1.10:9876/browse 405 GET 1l 3w 31c http://10.1.1.10:9876/review 405 GET 1l 3w 31c http://10.1.1.10:9876/summarize 405 GET 1l 3w 31c http://10.1.1.10:9876/kb/add 405 GET 1l 3w 31c http://10.1.1.10:9876/upload 200 GET 1l 1w 18c http://10.1.1.10:9876/logs/last-tool-call 307 GET 0l 0w 0c http://10.1.1.10:9876/docs/ => http://10.1.1.10:9876/docs 200 GET 81l 240w 3012c http://10.1.1.10:9876/docs/oauth2-redirect 200 GET 31l 62w 931c http://10.1.1.10:9876/docs [####################] - 2s 786/786 0s found:17 errors:0

786 requests, 17 hits, 2 seconds. Fast, and worth actually reading properly instead of just skimming for 200s.

200 OK

  • /
  • /health
  • /openapi.json
  • /docs, /redoc
  • /logs/latest
  • /logs/last-tool-call

405 not a miss

  • /chat
  • /session/new
  • /reset
  • /browse, /review
  • /summarize
  • /kb/add, /upload

200 OK means the endpoint exists and answered GET successfully. Here that's /, /health, /openapi.json, /docs, /redoc, the FastAPI boilerplate, confirming the spec is live and both auto generated doc UIs are reachable.

Two entries stand out from the boilerplate though: /logs/latest and /logs/last-tool-call. Those aren't default FastAPI routes, they're custom to this app, and "last-tool-call" is a strong hint this thing does function or tool calling internally. An unauthenticated logs endpoint on an agentic app is worth digging into, it can leak prompts, tool arguments, or reasoning traces you were never supposed to see.

405 Method Not Allowed is the one people misread as a miss when it's actually the opposite. A 405 means the route exists and is registered on the server, it just doesn't accept GET. Most real application logic in a REST API lives behind POST, so scanning with GET only will turn up a pile of 405s on exactly the endpoints that matter. In this scan that's /chat, /session/new, /reset, /browse, /review, /summarize, /kb/add, /upload, eight confirmed, live, functional endpoints, mapped out without sending a single POST. /kb/add in particular is almost certainly the RAG ingestion route, the one you'd target for document injection or poisoning testing later.

307 Temporary Redirect shows up on /docs/ redirecting to /docs. Just FastAPI normalizing the trailing slash. Not a finding by itself, but always worth a glance in case a redirect points somewhere you didn't expect.

404 Not Found is filtered out of the output entirely, since it just means nothing's there.

04 — the real find

The part the wordlist didn't do

Here's the thing worth calling out: none of /chat, /session/new, /reset, /browse, /review, /summarize, /kb/add, /upload, /logs/latest, or /logs/last-tool-call were actually in my wordlist. They came from link extraction. Once feroxbuster reached /openapi.json, the file that lists every real route the app has, it parsed it and queued up everything it found. The wordlist got the door open, link extraction did the rest.

⚠ takeaway
The wordlist doesn't need to be exhaustive. It needs to be good enough to find the one file that then hands you everything else.

Forced browsing plus link extraction did the whole job here. Feroxbuster didn't just find that openapi.json existed, it read it and handed back the app's real, functional endpoints without me ever touching the spec by hand. That's the payoff: one scan, one command, and the attack surface is mapped.

This workflow maps directly onto the reconnaissance phase of OffSec's OSAI (AI-300) course. Before you can test prompt injection, RAG poisoning, or jailbreaks, you need to know what's actually exposed. Same principle as AD recon before you have credentials: don't rush it, the phase before you can attack anything is the one that decides how effective everything after it is.