September 1, 2026
๐๐ curl Me Maybe? โก๏ธ โฌ ๏ธHacking APIs & ๐Web Apps From the Terminal ๐
curl is my first move, not my last resort

By Sudarshan Patel
8 min read
A pentester's guide to one of the most boring-looking tools that absolutely refuses to become obsolete.
If you spend enough time in cybersecurity, you eventually realize something:
The coolest-looking tool is rarely the tool you use the most.
And curl is probably the best example. ๐
No fancy dashboard. No attack graph. No neon-green hacker UI.
Just:
curl https://target.example.comcurl https://target.example.comYet somehow, this tiny command-line tool can become one of the most useful things in your pentesting and bug-bounty toolbox.
Headers? โ APIs? โ Authentication testing? โ GraphQL? โ IDOR/BOLA testing? โ Host-header manipulation? โ Automation? โ Weird malformed requests? Also โ
The best part?
curl mostly stays out of your way.
You tell it what request to send.
It sends it.
The server responds.
You stare at the response and start asking uncomfortable questions. ๐ต๏ธ
๐ง Why curl Is So Useful for Pentesters
Browsers try very hard to be helpful.
They may:
- follow redirects
- normalize URLs
- encode characters
- handle cookies automatically
- modify requests
- hide some protocol details
Normally, that's great.
During security testing?
Sometimes you want the opposite.
You want to know exactly what was sent and exactly what came back.
That's where curl shines.
Think of it as a remote control for HTTP.
And once you learn roughly a dozen useful flags, you can do a surprising amount of testing without touching anything heavier.
โก The curl Flags Worth Memorizing
You don't need to memorize the entire man page.
Start here.
-v โ Verbose mode
curl -v https://target.example.com/curl -v https://target.example.com/This shows the request and response conversation.
You'll see lines like:
> GET / HTTP/1.1
> Host: target.example.com
< HTTP/1.1 200 OK
< Content-Type: text/html> GET / HTTP/1.1
> Host: target.example.com
< HTTP/1.1 200 OK
< Content-Type: text/htmlIf something behaves strangely, -v is often the first thing I reach for.
-i โ Include response headers
curl -i https://target.example.com/curl -i https://target.example.com/Useful when you want both:
- headers
- response body
If you only want headers:
curl -I https://target.example.com/curl -I https://target.example.com/โ ๏ธ Small warning:
-I sends a HEAD request.
Some servers treat HEAD differently from GET, so don't immediately assume the behavior is identical.
-sS โ Shut Up, but Tell Me When Something Breaks
curl -sS https://target.example.com/curl -sS https://target.example.com/-s removes the progress meter.
-S keeps useful error messages.
For scripts, this combination is beautiful. ๐ค
-L โ Follow redirects
curl -L https://target.example.com/curl -L https://target.example.com/Useful normally.
But when testing redirects?
Leave it off.
Otherwise this:
302 โ attacker.example302 โ attacker.examplemight disappear because curl immediately follows it.
-k โ Ignore TLS certificate validation
curl -k https://target.example.com/curl -k https://target.example.com/Useful for:
- staging environments
- self-signed certificates
- internal testing systems
- lab environments
Don't confuse this with fixing TLS problems though.
You're simply telling curl:
"Yes, I know the certificate looks suspicious. Continue anyway." ๐
-H โ Control Headers
curl https://target.example.com/ \
-H 'X-Test: hello'curl https://target.example.com/ \
-H 'X-Test: hello'Stack as many as you want:
curl https://target.example.com/api \
-H 'Authorization: Bearer TOKEN' \
-H 'Content-Type: application/json' \
-H 'X-Forwarded-For: 127.0.0.1'curl https://target.example.com/api \
-H 'Authorization: Bearer TOKEN' \
-H 'Content-Type: application/json' \
-H 'X-Forwarded-For: 127.0.0.1'This flag alone opens the door to a ridiculous number of security tests.
-d โ Send Request Data
curl https://target.example.com/login \
-d 'username=test&password=test'curl https://target.example.com/login \
-d 'username=test&password=test'For JSON:
curl https://target.example.com/api/login \
-H 'Content-Type: application/json' \
-d '{"username":"test","password":"test"}'curl https://target.example.com/api/login \
-H 'Content-Type: application/json' \
-d '{"username":"test","password":"test"}'-X โ Choose the HTTP Method
curl -X PUT https://target.example.com/api/profilecurl -X PUT https://target.example.com/api/profileYou can test:
GET
POST
PUT
PATCH
DELETE
OPTIONSGET
POST
PUT
PATCH
DELETE
OPTIONSโฆand whatever else the application exposes.
--data-urlencode โ Let curl Handle Encoding
Instead of manually fighting with characters like:
&
=
?
%
+&
=
?
%
+you can use:
curl -G https://target.example.com/search \
--data-urlencode 'q=test value'curl -G https://target.example.com/search \
--data-urlencode 'q=test value'Very handy when you're experimenting with parameters.
๐ต๏ธ Web App Testing With curl
Now we get to the fun part.
1๏ธโฃ Check Security Headers
One quick request can already tell you quite a bit.
curl -sI https://target.example.com | \
grep -iE 'strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy'curl -sI https://target.example.com | \
grep -iE 'strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy'You're looking for things like:
Strict-Transport-Security
Content-Security-Policy
X-Frame-Options
X-Content-Type-Options
Referrer-Policy
Permissions-PolicyStrict-Transport-Security
Content-Security-Policy
X-Frame-Options
X-Content-Type-Options
Referrer-Policy
Permissions-PolicyMissing headers don't automatically mean:
๐จ CRITICAL SECURITY INCIDENT ๐จ
Context matters.
But they're useful signals when assessing the application's overall security posture.
2๏ธโฃ Ask the Server Which Methods It Really Accepts
Applications occasionally expose HTTP methods nobody expected to be reachable.
Try:
for m in GET POST PUT DELETE PATCH OPTIONS TRACE; do
printf '%s: ' "$m"
curl -s -o /dev/null \
-w '%{http_code}\n' \
-X "$m" \
https://target.example.com/admin/config
donefor m in GET POST PUT DELETE PATCH OPTIONS TRACE; do
printf '%s: ' "$m"
curl -s -o /dev/null \
-w '%{http_code}\n' \
-X "$m" \
https://target.example.com/admin/config
doneExample output:
GET: 403
POST: 403
PUT: 200
DELETE: 405
PATCH: 405
OPTIONS: 204
TRACE: 405GET: 403
POST: 403
PUT: 200
DELETE: 405
PATCH: 405
OPTIONS: 204
TRACE: 405That PUT: 200 deserves attention. ๐
A 200 alone doesn't prove a vulnerability.
But it tells you:
"Heyโฆ investigate me."
3๏ธโฃ Host Header Testing
Because curl lets us control headers directly, testing Host-header behavior is easy.
curl -s https://target.example.com/password-reset \
-H 'Host: test.example'curl -s https://target.example.com/password-reset \
-H 'Host: test.example'Also try common proxy headers:
curl -s https://target.example.com/password-reset \
-H 'X-Forwarded-Host: test.example'curl -s https://target.example.com/password-reset \
-H 'X-Forwarded-Host: test.example'Other headers worth checking in an authorized assessment include:
Forwarded
X-Forwarded-Host
X-Original-Host
X-HostForwarded
X-Forwarded-Host
X-Original-Host
X-HostYou're interested in places where the application generates absolute URLs from attacker-controlled headers.
For example:
https://test.example/reset?token=...https://test.example/reset?token=...instead of:
https://target.example.com/reset?token=...https://target.example.com/reset?token=...If sensitive links are constructed this way, you may have a Host-header injection or password-reset-poisoning issue.
4๏ธโฃ Meet One of My Favorite Flags: --path-as-is
Normally, curl may normalize parts of the URL.
During path traversal testing, that's sometimes exactly what you don't want.
Use:
curl --path-as-is \
'https://target.example.com/assets/../../test.txt'curl --path-as-is \
'https://target.example.com/assets/../../test.txt'--path-as-is basically tells curl:
"Don't clean my URL. Send the weird thing exactly how I wrote it."
Very useful when testing how reverse proxies, web servers, and application routers interpret paths differently.
Those differences are where interesting bugs sometimes hide. ๐
5๏ธโฃ --resolve: DNS Manipulation Without Touching DNS
This flag deserves more love.
Syntax:
curl --resolve HOST:PORT:IP https://HOST/curl --resolve HOST:PORT:IP https://HOST/Example:
curl -sk \
--resolve target.example.com:443:203.0.113.10 \
https://target.example.com/curl -sk \
--resolve target.example.com:443:203.0.113.10 \
https://target.example.com/curl connects to:
203.0.113.10203.0.113.10while still requesting:
target.example.comtarget.example.comThis is useful when validating:
- specific backend servers
- load-balancer behavior
- DNS changes
- staging infrastructure
- virtual hosts
- CDN/origin architecture
Use this carefully and only against infrastructure explicitly included in scope.
๐ฅ API Testing: Where curl Becomes Dangerousโฆly Useful
APIs are basically structured HTTP requests.
curl speaks HTTP fluently.
Which makes the combination almost unfair. ๐
6๏ธโฃ Testing Authenticated APIs
The classic authenticated request:
curl -s https://target.example.com/api/v1/me \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' | jqcurl -s https://target.example.com/api/v1/me \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' | jqAnd yes:
Install jq.
Please.
Your eyes deserve better than one-line minified JSON. ๐
Example:
curl -s https://target.example.com/api/v1/me | jqcurl -s https://target.example.com/api/v1/me | jqTurns this:
{"id":42,"name":"John","email":"john@example.com","role":"user"}{"id":42,"name":"John","email":"john@example.com","role":"user"}into something humans were actually meant to read.
7๏ธโฃ BOLA / IDOR Testing
Broken Object Level Authorization remains one of the most important API weaknesses to look for.
Imagine your account can access:
/api/v1/users/42/api/v1/users/42What happens with:
/api/v1/users/43/api/v1/users/43โฆ???!!!
Start manually:
curl -s \
-H 'Authorization: Bearer YOUR_LOW_PRIV_TOKEN' \
https://target.example.com/api/v1/users/43 | jqcurl -s \
-H 'Authorization: Bearer YOUR_LOW_PRIV_TOKEN' \
https://target.example.com/api/v1/users/43 | jqIf you have authorization to test multiple known test objects, comparing response metadata can also help:
for id in 40 41 42 43 44; do
printf '%s: ' "$id"
curl -s \
-o /dev/null \
-w '%{http_code} %{size_download}\n' \
-H 'Authorization: Bearer YOUR_TOKEN' \
"https://target.example.com/api/v1/users/$id"
donefor id in 40 41 42 43 44; do
printf '%s: ' "$id"
curl -s \
-o /dev/null \
-w '%{http_code} %{size_download}\n' \
-H 'Authorization: Bearer YOUR_TOKEN' \
"https://target.example.com/api/v1/users/$id"
doneExample:
40: 403 81
41: 403 81
42: 200 387
43: 200 421
44: 403 8140: 403 81
41: 403 81
42: 200 387
43: 200 421
44: 403 81Why is 43 returning a different result?
That's where the investigation begins.
Important:
A different response size is not proof of IDOR.
Actually verify whether unauthorized data or functionality becomes accessible.
False positives are boring.
Reproducible authorization failures are not. ๐ฏ
8๏ธโฃ Mass Assignment
Imagine the application expects:
{
"email": "me@example.com",
"name": "Tester"
}{
"email": "me@example.com",
"name": "Tester"
}What happens if you send additional properties?
For a controlled test account:
curl -s -X PATCH \
https://target.example.com/api/v1/profile \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"name":"Tester",
"role":"admin",
"isAdmin":true
}'curl -s -X PATCH \
https://target.example.com/api/v1/profile \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"name":"Tester",
"role":"admin",
"isAdmin":true
}'The important question is not:
Did the server return 200?
The real question is:
Did the server actually persist or honor a property the current user should never be able to control?
That's the difference between noise and a finding.
9๏ธโฃ Content-Type Confusion
Servers sometimes have multiple request parsers.
And occasionally those parsers disagree.
For example:
curl -s -X POST \
https://target.example.com/api/v1/example \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d '{"test":true}'curl -s -X POST \
https://target.example.com/api/v1/example \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d '{"test":true}'You can compare behavior between:
application/json
application/x-www-form-urlencoded
multipart/form-data
text/plainapplication/json
application/x-www-form-urlencoded
multipart/form-data
text/plainWhy?
Because validation middleware may inspect the request one way while application code parses it another way.
And whenever two components disagree about what they're looking atโฆ
security researchers start smiling. ๐
๐ฎ GraphQL Testing
Found something like:
/graphql
/api/graphql
/v1/graphql/graphql
/api/graphql
/v1/graphqlโฆ???!!!
One of the first things worth checking is whether schema introspection is enabled.
curl -s -X POST \
https://target.example.com/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{__schema{types{name fields{name}}}}"}' | jqcurl -s -X POST \
https://target.example.com/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{__schema{types{name fields{name}}}}"}' | jqIf introspection is available, you may learn about:
- queries
- mutations
- object types
- arguments
- relationships
- potentially forgotten API functionality
Remember:
GraphQL introspection being enabled is not automatically a vulnerability.
But during an assessment?
It's one hell of a map. ๐บ๏ธ
๐ Authentication Testing
For an authenticated endpoint, compare behavior under different legitimate test conditions.
Try:
No token
curl -i https://target.example.com/api/v1/accountcurl -i https://target.example.com/api/v1/accountInvalid token
curl -i https://target.example.com/api/v1/account \
-H 'Authorization: Bearer invalid'curl -i https://target.example.com/api/v1/account \
-H 'Authorization: Bearer invalid'Expired test token
curl -i https://target.example.com/api/v1/account \
-H 'Authorization: Bearer EXPIRED_TEST_TOKEN'curl -i https://target.example.com/api/v1/account \
-H 'Authorization: Bearer EXPIRED_TEST_TOKEN'Low-privilege account
curl -i https://target.example.com/api/v1/admin \
-H 'Authorization: Bearer LOW_PRIV_TOKEN'curl -i https://target.example.com/api/v1/admin \
-H 'Authorization: Bearer LOW_PRIV_TOKEN'This simple matrix catches a surprising number of:
- missing authentication checks
- broken authorization
- stale-session issues
- privilege-boundary mistakes
The server should not merely ask:
"Do you have a token?"
It should ask:
"Is this token valid, and is this identity actually allowed to perform this action?"
Huge difference.
๐ ๏ธ Making curl Useful at Scale
One clever request is nice.
But real security assessments involve hundreds or thousands of requests.
That's where shell scripting makes curl much more powerful.
๐ Stop Copy-Pasting Tokens
Instead of manually copying an authentication token every five minutes:
TOKEN=$(curl -s -X POST \
https://target.example.com/api/v1/login \
-H 'Content-Type: application/json' \
-d '{"username":"testuser","password":"TEST_PASSWORD"}' \
| jq -r '.token')TOKEN=$(curl -s -X POST \
https://target.example.com/api/v1/login \
-H 'Content-Type: application/json' \
-d '{"username":"testuser","password":"TEST_PASSWORD"}' \
| jq -r '.token')Then:
curl -s https://target.example.com/api/v1/me \
-H "Authorization: Bearer $TOKEN" | jqcurl -s https://target.example.com/api/v1/me \
-H "Authorization: Bearer $TOKEN" | jqMuch cleaner.
Just remember:
Don't accidentally save sensitive production tokens into shell history, scripts, screenshots, or reports.
Future-you will thank present-you.
๐ -w: Turn curl Into a Tiny HTTP Measurement Tool
One of the most underrated curl features:
-w-wExample:
curl -s \
-o /dev/null \
-w 'status=%{http_code} time=%{time_total}s size=%{size_download}b\n' \
https://target.example.com/api/v1/healthcurl -s \
-o /dev/null \
-w 'status=%{http_code} time=%{time_total}s size=%{size_download}b\n' \
https://target.example.com/api/v1/healthOutput:
status=200 time=0.184s size=742bstatus=200 time=0.184s size=742bNow we're measuring:
- HTTP status
- response time
- body size
Why does that matter?
Because sometimes the vulnerability isn't visible in the response body.
The clue might be:
200 โ 403200 โ 403or:
421 bytes โ 6,841 bytes421 bytes โ 6,841 bytesor:
0.2 sec โ 5.1 sec0.2 sec โ 5.1 secTiny differences can tell you where to look deeper.
๐ DIY Content Discovery With curl
Yes, tools like ffuf, feroxbuster, and dirsearch are much better for serious content discovery.
But knowing how the underlying logic works is still useful.
while read -r path; do
code=$(curl -s \
-o /dev/null \
-w '%{http_code}' \
"https://target.example.com$path")
echo "$code $path"
done < paths.txtwhile read -r path; do
code=$(curl -s \
-o /dev/null \
-w '%{http_code}' \
"https://target.example.com$path")
echo "$code $path"
done < paths.txtSample output:
200 /login
200 /robots.txt
403 /admin
404 /backup
302 /dashboard200 /login
200 /robots.txt
403 /admin
404 /backup
302 /dashboardCongratulations.
You just built the world's saddest directory scanner. ๐
But now you understand exactly what tools such as ffuf are doing at a much higher speed.
And understanding the primitive is always useful.
๐งช One Habit That Makes Reports Much Better
Whenever possible, provide a minimal reproducible curl command in your vulnerability report.
For example:
curl -i \
-H 'Authorization: Bearer LOW_PRIVILEGE_TEST_TOKEN' \
https://target.example.com/api/v1/orders/TEST_OBJECT_IDcurl -i \
-H 'Authorization: Bearer LOW_PRIVILEGE_TEST_TOKEN' \
https://target.example.com/api/v1/orders/TEST_OBJECT_IDThen clearly explain:
Expected:
403 Forbidden
Actual:
200 OK containing another test user's order informationExpected:
403 Forbidden
Actual:
200 OK containing another test user's order informationThis is much better than writing three paragraphs saying:
"An attacker may potentially under certain circumstances possibly access unauthorized resourcesโฆ"
Nobody has time for that. ๐
Give developers:
- the exact request
- the expected behavior
- the actual behavior
- the security impact
If they can reproduce the issue in 30 seconds, your report becomes much harder to misunderstand.
๐งฐ My Practical curl Cheat Sheet
If I had to survive an assessment with only a few commands, these are the ones I'd remember:
curl -v URLcurl -v URLSee everything.
curl -i URLcurl -i URLHeaders + body.
curl -sS URLcurl -sS URLClean output.
curl -L URLcurl -L URLFollow redirects.
curl -k URLcurl -k URLIgnore certificate validation.
curl -H 'Header: value' URLcurl -H 'Header: value' URLManipulate headers.
curl -X METHOD URLcurl -X METHOD URLChange HTTP method.
curl -d 'data' URLcurl -d 'data' URLSend request bodies.
curl --path-as-is URLcurl --path-as-is URLPreserve unusual URL paths.
curl --resolve HOST:443:IP https://HOST/curl --resolve HOST:443:IP https://HOST/Control DNS resolution.
curl -w '%{http_code} %{time_total} %{size_download}' URLcurl -w '%{http_code} %{time_total} %{size_download}' URLMeasure responses.
Master those first.
Everything else comes naturally.
๐ Final Thoughts
curl survives because it solves a very simple problem extremely well.
It puts almost nothing between you and HTTP.
And that's incredibly valuable when your job is figuring out why an application behaves differently from how its developers expected it to behave.
You don't need 200 flags memorized.
Start with:
-v
-i
-sS
-L
-k
-H
-d
-X
-G
--data-urlencode
--path-as-is
--resolve
-w-v
-i
-sS
-L
-k
-H
-d
-X
-G
--data-urlencode
--path-as-is
--resolve
-wLearn what each one actually changes.
Then combine them.
Then put them into loops.
Then start comparing responses.
That's when curl stops feeling like a command-line downloader and starts feeling like a pentesting instrument. ๐งชโ๏ธ
And once it becomes muscle memory?
You'll probably catch yourself opening the terminal before opening half the security tools installed on your machine.
Sometimes the old tools are still around because they're simply that damn good. ๐
Thanks a lot for reading! โค๏ธ
I hope this made curl feel a little less boring and a lot more useful.
If you found this helpful, feel free to share it with others who are learning web security. Stay curious, stay ethical, and keep hacking responsibly ๐๐.
See you in the next Writeups or Walkthroughs๐ก ๐ฐ๏ธ
Happy hacking! ๐
Stay curious, keep learning๐โจ๐ง ๐
Crafted by: Sudarshan Patel ๐จโ๐ป
Connect with me on LinkedIn: www.linkedin.com/in/sudarshan-patel
Follow my Tweets on X: @loneliestwolf3