If you’re new to penetration testing, curl might seem like just another command line tool in an already overwhelming toolkit. But here’s the thing: curl is likely the most universally available HTTP client you’ll encounter. Whether you’ve just compromised a Linux server, you’re working from a minimal environment, or you simply need a quick way to test an endpoint, you can usually count on curl to be there.
This is the first post in a series where I’ll share the curl techniques I use during penetration tests, along with some new techniques I picked up while writing these posts. We’ll start with the absolute basics and gradually work our way up to more advanced techniques in future posts.
Why cURL is Useful for Pentesters
Before diving into commands, let’s talk about when curl is the right tool. Yes, specialized tools like FFUF, Gobuster, or Burp Suite are powerful for specific tasks. But curl really shines when:
- You’re living off the land: You’ve gained access to a server and need to probe internal services. Curl is already installed on virtually every Linux system as well as Windows.
- Quick testing: You need to fire off a single request to test authentication or check if an endpoint exists.
- Scripting: You’re automating tasks. Curl’s predictable output makes it perfect for bash scripts.
Think of curl as your lightweight, (almost) always-available Swiss Army knife. I’ll be honest: curl won’t replace FFUF for heavy directory brute forcing, but when you need to quickly test something or don’t have your full toolkit available, curl has your back.
Basic Usage
Before we start playing with flags, let’s go over the simplest form of curl usage. From a Linux shell, you can type the following command:
$ curl "https://www.attackd.com"
Or if you’re using Windows:
PS C:\Users\TJ> curl.exe "https://www.attackd.com"
The curl command with a URL returns the page’s HTML to standard out:
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="pingback" href="https://www.attackd.com/xmlrpc.php" />
<script type="text/javascript">
document.documentElement.className = 'js';
</script>
<title>ATTACKD | We Secure Progress</title>
Understanding Headers: The Foundation
HTTP headers are critical in security testing. They control authentication, caching, content types, and much more. Let’s start with the flags that help you work with headers, because understanding these will make everything else make more sense.
Reading Headers: -i and -I
Now that we have a grasp of curl’s basic functionality, we can start throwing some options into the mix. Let’s look at HTTP response headers. You’ve got two options: the first one is to use the -i flag:
$ curl -i "https://www.attackd.com/"
The output shows:
HTTP/1.1 200
Date: Fri, 28 Nov 2025 10:30:00 GMT
Server: Apache
Vary: Accept-Encoding
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
...
<!DOCTYPE html>
Use-i to see headers followed by the response body. This is helpful when you want both the server’s headers and the actual content..
The second option is useful for when you only care about the headers. Maybe you’re checking if a resource exists or what content-type it returns. In this situation, you should go with -I, which sends a HEAD request (headers only and no body):
$ curl -I "https://www.attackd.com/"
The output shows headers only:
HTTP/1.1 200
Date: Fri, 28 Nov 2025 10:36:32 GMT
Server: Apache
Content-Type: text/html; charset=UTF-8
...
The -I flag is faster because it doesn’t download the entire response body. Use this when you’re doing things like finding the server version, determine where a page is directing me to, or checking status codes in bulk.
Let’s say you’re testing an API and want to quickly check if user IDs 1-10 exist. We can do that easily in this contrived example:
$ for id in {1..10}; do
echo -n "User $id: "
curl -I -s "https://www.example.com/api/users/$id" | grep HTTP
done
Notice I put the URL in quotes? That’s a good habit to have as it prevents the shell from misinterpreting special characters in URLs. Let’s see what the response from the above request might look like:
User 1: HTTP/1.1 404 Not Found
User 2: HTTP/1.1 200 OK
User 3: HTTP/1.1 404 Not Found
User 4: HTTP/1.1 404 Not Found
User 5: HTTP/1.1 200 OK
At this point we don’t necessarily care about the contents of the response just yet. We only want to know which users exist.
Sending Custom Headers: -H
Now that you can read headers, let’s talk about sending them. The -H flag lets you add or modify request headers, which is helpful for tasks such as including authorization and changing the default user agent. Are you…
Adding an Authorization header? No problem:
$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." "https://www.example.com/api/user/profile"
Sending multiple headers? OK here’s where it gets complicated… just kidding! That’s simple:
$ curl -H "Authorization: Bearer TOKEN" -H "X-API-Key: supersecretkey" -H "Content-Type: application/json" "https://www.example.com/api/data"
This is important to know because many APIs use bearer tokens or API keys in headers. Sometimes internal sites such as admin panels check for specific custom headers like X-Forwarded-For or X-Original-URL.
Following Redirects: -L
After you understand headers, redirects will make much more sense. By default, curl doesn’t follow redirects, it just shows you the HTML pointing to the new location.
$ curl -i "http://www.attackd.com"
HTTP/1.1 302
Location: https://www.attackd.com/
...
<p>The document has moved <a href="https://www.attackd.com/">here</a>.</p>
That’s nice, but manually following those redirects can be tedious. Add -L and curl automatically follows the redirect chain to the final destination:
$ curl -L "http://www.attackd.com/"
HTTP/1.1 302 Found
Date: Thu, 04 Dec 2025 16:29:53 GMT
Server: Apache
Location: https://www.attackd.com/
Content-Type: text/html; charset=iso-8859-1
HTTP/1.1 200 OK
Date: Thu, 04 Dec 2025 16:29:53 GMT
Server: Apache
Link: <https://www.attackd.com/wp-json/>; rel="https://api.w.org/", <https://www.attackd.com/wp-json/wp/v2/pages/362>; rel="alternate"; title="JSON"; type="application/json", <https://www.attackd.com/>; rel=shortlink
Content-Type: text/html; charset=UTF-8
And there you go – you’re seeing the redirect journey from http://www.attackd.com to https://www.attackd.com/. This can be useful for understanding authentication flows or spotting open redirect vulnerabilities.
Changing the User Agent: -A
Some web applications might behave differently based on the user agent. By default, curl sends the User-Agent header as something like User-Agent: curl/8.13.0. We can change that with -A
$ curl -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" "https://www.attackd.com/"
Basic Request Methods
Of course HTTP uses more than just GET requests. The -X flag lets you specify different methods:
GET (this is the default, so there’s no -X needed here):
$ curl "https://www.example.com/api/users"
POST (you’ll need to send data with the -d flag. More on that in the next section):
$ curl -X POST "https://www.example.com/api/users"
DELETE:
$ curl -X DELETE "https://www.example.com/api/users/1"
OPTIONS (see what methods are allowed):
$ curl -X OPTIONS -I "https://www.espn.com"
This is important because sometimes an API only checks authorization on certain methods. Make sure to always test different HTTP methods. The response to the OPTIONS request shows the allowable methods in an Allow header:
HTTP/1.1 200 OK
Content-Length: 0
Connection: keep-alive
Date: Thu, 04 Dec 2025 15:41:36 GMT
...
allow: GET, HEAD, POST, TRACE, OPTIONS
Sending Data: The Basics
When you need to send data (login forms, API requests), you’ve got options with the -d flag. Setting this flag will automatically make it a POST request, so there’s no need to specify it with the -X flag:
$ curl -d "username=admin&password=test123" "https://www.example.com/user-login"
Send JSON (remember that you’ll need to to set the Content-Type header):
$ curl -H "Content-Type: application/json" -d '{"username":"admin","password":"test123"}' "https://www.example.com/api/user-login"
Important note about quotes: double quotes allow variable expansion and single quotes do not.
Let’s pause for a moment to explore this a bit more. We’ll set a variable, USER, to TJ and echo it with double and single quotes to see how it’s handled:
$ USER=TJ
$ echo "Big news! $USER thinks curl is great!"
Big news! TJ thinks curl is great!
$ echo 'Big news! $USER thinks curl is great!'
Big news! $USER thinks curl is great!
As you can see, using double quotes fills in the value of $USER with TJ, but the single quotes use the literal $USER. Curl itself doesn’t care which you use; this is purely a shell thing. For simple strings with no variables, I’ll stick with double quotes for consistency.
Silent Mode and Output Control
When scripting or testing multiple endpoints, you don’t want curl’s progress meter cluttering your output. Fortunately it’s possible to hide these:
-sfor silent (no progress bar on uploads or downloads)-Sto still show errors (if applicable)-Oto save a file with the remote filename
Combining these flags will save the file to your current working directory as nvidia-in-wsl2.png without displaying the progress bar:
$ curl -sSO "https://www.attackd.com/wp-content/uploads/2025/08/nvidia-in-wsl2.png"
You can use -o to save output to a file with a specified name:
$ curl -sS "https://www.attackd.com/wp-content/uploads/2025/08/nvidia-in-wsl2.png" -o image.png
Or you can use redirection to save a response:
$ curl -sS "https://www.example.com/api/data" > response.json
An Example: Testing Authentication
Let’s put it all together. Imagine you’re testing a login endpoint Let’s see what headers the login page sends:
$ curl -I "https://www.example.com/user-login"
Attempt login, follow redirects, and see the full response:
$ curl -L -i -d "username=admin&password=admin" "https://www.example.com/user-login"
Test with a custom User-Agent (some apps block curl’s default or behave differently with other browsers):
$ curl -L -i -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" -d "username=admin&password=admin" "https://www.example.com/user-login"
Each request teaches you something. Are there different response codes? Different headers? Does the User-Agent matter?
Quick Reference
Here are the flags we covered:
-i: Include response headers in output-I: HEAD request (headers only, no body)-H: Add/modify request header-A: Change the User-Agent header-L: Follow redirects-X: Specify HTTP method (GET, POST, PUT, DELETE, etc.)-d: Send data in request body-s: Silent mode (no progress bar)-S: Show errors even in silent mode-o: Save output to file-O: Save output with the remote filename
What’s Next?
In Part 2, we’ll cover cookies and sessions, SSL/TLS certificates, and using curl’s write-out format to extract specific information.
Remember: if you’ve compromised a system and need to test internal services, these basic curl commands can be a big help. Master the fundamentals, and you’ll be surprised how far they take you.