Y3llowDuck
crAPI API Security Burp Suite Docker

API Pentesting with crAPI

OWASP crAPI · Kali · Burp · ~5 min read

Windows Active Directory pentesting is cool and sexy. But the reality is that bad actors use "the front door" to get access to a company's resources. And that door is usually an externally facing app. This is the reason why API and web pentesting is so important, and why it is gaining more relevance in today's cybersecurity landscape. Traditional web apps with three tiers are things of the past. Now they use APIs. And those may be well documented or not.

Getting our hands dirty and learning the basics of API pentesting is actually easy. There's already a free docker app called crAPI that we can use to practice. It is very close to a real API application and easy to deploy.

This is the 1st in a series of two posts where I will show how to test API applications. On this 1st post I will explain in detail how to configure crAPI and the basic docker commands we need to start and stop the app, once we are done with our testing.

crAPI stands for "completely ridiculous API." It's OWASP's intentionally vulnerable API, built by Traceable AI and donated to OWASP. WebGoat is more for general web vulnerabilities. crAPI is built around the OWASP API Top 10, so you get broken object-level authorization, excessive data exposure, SSRF, the API-specific stuff you won't find on WebGoat.

One thing to know before you start. crAPI isn't a single container. It's a Docker Compose stack running Go, Java, Python, MongoDB and PostgreSQL together, which is why every command below goes through docker compose instead of plain docker.

I'm running Kali on a MacBook Pro with Apple Silicon, so my guest is arm64. But every image in this stack now publishes a native arm64 build, so nothing gets emulated. And the same exact commands should also work on Windows. But leave me a comment below if that's not the case.

settingvalue
guest_osKali Linux 2026.1, codename kali-rolling
host_hardwareMacBook Pro 2024, Apple Silicon (ARM64)
guest_archarm64 (aarch64)
01 — prerequisites

Docker runs the containers. Docker Compose starts the several related ones crAPI needs together as one stack.

kali — install docker and compose
kali@kali:~$ sudo apt update kali@kali:~$ sudo apt install -y docker.io docker-compose git kali@kali:~$ sudo systemctl enable --now docker kali@kali:~$ sudo usermod -aG docker $USER kali@kali:~$ newgrp docker

On Kali, Docker Engine is the docker.io package, not docker. A separate, unrelated package already uses that name. And the docker-compose package on Kali is Compose v2, so it gives you the docker compose subcommand, not the old hyphenated v1 tool.

kali — verify
kali@kali:~$ docker --version kali@kali:~$ docker compose version
02 — clone and start

-f points to the compose file to use. --compatibility applies the resource limits it defines, which Compose otherwise ignores. -d runs everything in the background.

kali — bring the stack up
kali@kali:~$ mkdir -p ~/src kali@kali:~$ cd ~/src kali@kali:~$ git clone https://github.com/OWASP/crAPI kali@kali:~$ cd crAPI/deploy/docker kali@kali:~$ docker compose -f docker-compose.yml --compatibility up -d # then confirm every container is healthy, not Restarting kali@kali:~$ docker ps -a

Two URLs come out of this. The app itself is on http://127.0.0.1:8888. MailHog, which catches every email crAPI tries to send, is on http://127.0.0.1:8025. You'll need the second one sooner than you'd think.

03 — watch what the app actually calls

No scanners, no wordlists. Just use the app with Burp in the middle, and the frontend will tell you where its API lives. You don't have credentials and you don't need them yet. The failures are informative on their own.

Point the browser's proxy at Burp, turn intercept off so traffic just accumulates in the history, and load http://127.0.0.1:8888.

Start by entering the wrong credentials. Type anything into the login form. The point isn't to get in, it's to see what the form does when you submit it. To inspect the responses, good or bad.

REQUEST
POST /identity/api/auth/login HTTP/1.1 Host: 127.0.0.1:8888 Content-Type: application/json {"email":"admin@crapi.local","password":"password123"}
RESPONSE
HTTP/1.1 401 Content-Type: application/json { "token": null, "type": "Bearer", "message": "Invalid Credentials", "mfaRequired": false }

Using wrong credentials, for example, shows that there's a REST endpoint at /identity/api/auth/login. And it takes a JSON, not a form post. It gives us back a token field with a Bearer type, which means the whole app runs on JWTs. None of that was on the page. And this is an important piece of information.

Now let's sign up through the UI and watch the request.

REQUEST
POST /identity/api/auth/signup HTTP/1.1 Host: 127.0.0.1:8888 Content-Type: application/json {"name":"duck","email":"duck@example.com", "number":"5551234567","password":"Quack!123"}
RESPONSE
HTTP/1.1 200 Content-Type: application/json { "message": "User registered successfully! Please Login.", "status": 200 }

Use an @example.com address. crAPI routes mail for that domain to MailHog, so the message is guaranteed to land somewhere you can read it. Our registration attempt creates a vehicle for the new account and mails you its details. That email will contain a VIN and pincode.

Now log in with your new credentials.

RESPONSE
HTTP/1.1 200 Content-Type: application/json { "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIi...", "type": "Bearer", "message": "", "mfaRequired": false }

Compare that against the 401 from a minute ago. The response structure does not change, only the contents. That's a well-behaved API telling you exactly how it works.

Then click through the app. Every screen fires XHR calls, and this is where the API prefix comes from. Here's where Burp comes handy and the initial OSINT starts taking place.

burp — proxy history, filtered to the target
POST /identity/api/auth/signup POST /identity/api/auth/login GET /identity/api/v2/user/dashboard GET /identity/api/v2/vehicle/vehicles GET /community/api/v2/community/posts/recent GET /workshop/api/shop/products

Here's what we now know: there are three services, each with its own versioned prefix under /api/. Note that auth sits outside the version while everything else is under v2. A version number in a path implies a v1 that may still be listening, and nothing in the UI will ever call it.

Here's one of those calls in full.

REQUEST
GET /identity/api/v2/user/dashboard HTTP/1.1 Host: 127.0.0.1:8888 Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
RESPONSE
HTTP/1.1 200 Content-Type: application/json { "id": 6, "name": "duck", "email": "duck@example.com", "number": "5551234567", "picture_url": null, "video_url": null, "video_name": null, "video_id": 0, "available_credit": 100.0, "role": "ROLE_USER" }

Read that response again. We asked for a dashboard and the API gives us an internal id, a phone number, an account balance and a role string. Nothing here seems wrong. But an object carrying a sequential id is exactly the shape broken object-level authorization takes.

● this is your lab, keep it that way
crAPI is deliberately vulnerable, and poking at it is only appropriate because the target is a container on your own machine. Leave LISTEN_IP at its default 127.0.0.1 so none of this is reachable from the rest of your network.
04 — stop it when you're done

docker compose down stops and removes every container and network Compose created, the reverse of up. Unlike a plain docker stop, it also tears down the network the services use to reach each other by name, which avoids stale state before the next start. It does not delete the images, so the next run doesn't re-download anything.

kali — tear it down and confirm
kali@kali:~$ cd ~/src/crAPI/deploy/docker kali@kali:~$ docker compose down kali@kali:~$ docker ps # empty list means everything is stopped

So there it is. Setting up a local API app for pentesting is not that hard with dockers. And we can use Burp, feroxbuster, even Claude Code or Codex to start poking around and look for any API misconfiguration. But let's leave those details for a follow up article.