Press enter or click to view image in full size
Series: curl — The Request Engine You Never Learned Properly Article: 1 of 16 Status: Draft
Most people learn curl by accident. They copy a command from a Stack Overflow answer, it works, and they move on. They treat it like wget with extra steps — a way to pull files from the internet when a browser is inconvenient.
That mental model will cap you. Hard.
curl is more than a downloader. It is a full HTTP client, a protocol engine, and in the right hands, one of the most versatile recon and testing tools available on a Linux system. The difference between someone who knows curl and someone who actually understands it is the difference between someone who uses a telescope to hang their coat on and someone who uses it to look at stars.
This series exists to close that gap.
What curl Actually Is
curl is a command-line tool built on top of libcurl, a C library that handles the actual network communication. This distinction matters because libcurl is not just some code curl happens to use. It is the same engine embedded in hundreds of other applications, devices, and language-level HTTP clients across the ecosystem. Many applications use HTTP stacks that are separate from curl, while PHP also offers a dedicated cURL extension for libcurl-based requests.
Understanding this means understanding why curl is everywhere. You are not learning a tool. You are learning an engine.
curl supports many protocols depending on how it was built and packaged — HTTP, HTTPS, FTP, FTPS, SFTP, SMTP, IMAP, POP3, SCP, LDAP, and more. For this series, we are focused primarily on HTTP and HTTPS — the protocols that run web applications and APIs. But the others are there, and some of them will come up when you least expect them.
The LOTL Mindset
LOTL stands for Living Off The Land. In offensive security, this refers to the practice of relying on tools already present on a target system rather than uploading new ones. Uploaded tools leave traces, trigger antivirus software, and require transfer time. Tools that already exist on the system are quieter, faster, and harder to detect.
curl is often already present or easy to install on Linux, macOS, and modern Windows systems.
On Linux systems — including servers, containers, and embedded devices — curl is commonly installed or easily added. On macOS, curl ships with the operating system. On Windows, it has been bundled with modern Windows 10 and later releases, though its availability and version depend on the specific build. In a post‑exploitation scenario where you already have code execution on a target, curl often becomes a first move — used for further enumeration, data exfiltration, or pulling down payloads.
Starting every curl session with this mindset changes how you use it. You stop thinking of curl as a convenience tool and start thinking of it as a persistent asset.
Lab Setup
Before going further, get your environment ready. You need three things: curl itself, a local target to test against, and a safe remote target for anything that requires a real server.
Check your curl version:
curl --versioncurl 7.68.0 (x86_64-pc-linux-gnu) libcurl/7.68.0 OpenSSL/1.1.1f zlib/1.2.11 brotli/1.0.7
libidn2/2.2.0 libpsl/0.21.0 (+libidn2/2.2.0) libssh/0.9.3/openssl/zlib nghttp2/1.40.0 librtmp/2.3
Release-Date: 2020-01-08
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: AsynchDNS brotli GSS-API HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM NTLM_WB PSL SPNEGO SSL TLS-SRP UnixSocketsPress enter or click to view image in full size
The version matters because some flags covered in this series were added in newer releases. Check the flags you need against your installed version — if something does not work as described, the version is the first thing to verify.
Spin up a local HTTP server:
One of the fastest, safest targets for curl practice is Python’s built-in HTTP server. From any directory:
python3 -m http.server 8080This gives you a live HTTP server on http://localhost:8080 serving files from your current directory. Every curl request you make against it is logged in the terminal where you ran the server — you can watch your own requests arrive in real time. This is worth more than any theory.
WSL / macOS / native Linux notes:
- On WSL, curl is available without any setup. Your localhost is
127.0.0.1. - On macOS, the system curl is fine for basics, but install a newer version via Homebrew (
brew install curl) If you want full flag support. - On native Linux, install via your package manager if it is not already present:
sudo apt install curl
Remote safe target:
For anything that requires a real remote server — redirects, authentication challenges, TLS handshakes — use TryHackMe. The “Web Fundamentals” room gives you a legal, isolated HTTP target without any setup risk. HackTheBox Starting Point machines work for the same purpose once you are on VPN.
The Full HTTP Request Lifecycle
When you run curl http://localhost:8080More happens than most people realize. Understanding each stage is the foundation for everything else in this series.
curl -v http://localhost:8080* Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.8.10
< Date: Mon, 30 Mar 2026 06:36:47 GMT
< Content-type: text/html; charset=utf-8
< Content-Length: 297
<
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
</ul>
<hr>
</body>
</html>
* Closing connection 0Press enter or click to view image in full size
* lines are connection events, > Is your request, < is the server's responseReading that output line by line:
DNS resolution — curl first resolves the hostname to an IP address. For localhost, this is trivial, but for remote targets, you will see the resolved IP in the verbose output. This is your first data point: does the hostname resolve to what you expect?
TCP handshake — curl opens a TCP connection to the target. The * Trying X.X.X.X:port... and * Connected to... lines confirm this happened. If this fails, the server is not listening, or a firewall is blocking the path.
TLS negotiation — for HTTPS targets, the TLS handshake happens here. The verbose output will show the TLS version negotiated, the cipher suite selected, and certificate details. We will read every line of this in Article 12. For now, note that it exists.
Get Roshan Rajbanshi’s stories in your inbox
Join Medium for free to get updates from this writer.
HTTP request — the > Lines in the verbose output are your outgoing request. Method, path, Host header, and any other headers you added. This is what the server actually receives.
HTTP response — the < lines are the server's response. Status line, response headers, then the body. Everything in this series teaches you to read these lines with intent.
Exit Codes: Your First Debugging Map
Curl returns an exit code every time it runs. Most people ignore these entirely. That is a mistake.
When curl fails, and the output gives you nothing useful, the exit code is often the fastest path to understanding what went wrong. Check it immediately after any failed curl command:
echo $?Common exit codes worth knowing:
Code Meaning What to check
---- -------------------------- ----------------------------------------
0 Success —
6 Could not resolve host DNS failure — check hostname, /etc/hosts, VPN
7 Failed to connect Server not listening, firewall, wrong port
28 Operation timed out Server exists but not responding
35 SSL connect error TLS handshake failed — try -k to diagnose
52 Empty reply from server Service up but broken
56 Failure receiving data Connection dropped mid-transferExit codes are clues, not conclusions — but they narrow the field fast. Exit code 6 on a VPN target often points to a DNS issue or forgetting to connect to the VPN. Exit code 7 on a known-up target often means the port is wrong. Exit code 28 during a THM/HTB test may mean the machine is still booting — but it can also indicate network delay, filtering, or service startup lag. Use them to guide your next move, not to close the case.
These are debugging facts, not guesswork. Get into the habit of checking $? before spending ten minutes wondering why curl returned nothing.
Your First Five Commands
Run these in order. Read the output of each before moving to the next. The goal is not to memorize — it is to start building the habit of reading curl’s output rather than just looking at the final result.
1. What curl can do:
curl --help all | lessDo not try to read all of it. Search for flags as you need them: /keyword in less. This is how you use the help system — as a lookup tool, not a reading list.
2. A basic GET request with verbose output:
curl -v http://localhost:8080Read every line. Identify the request section (>) and the response section (<).
3. Headers only:
curl -I http://localhost:8080HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.8.10
Date: Mon, 30 Mar 2026 06:42:01 GMT
Content-type: text/html; charset=utf-8
Content-Length: 297This sends a HEAD request. You get headers, no body. Useful when you only care about what the server announces about itself.
4. Save output to a file:
curl -o response.txt http://localhost:8080
cat response.txt% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 297 100 297 0 0 5711 0 --:--:-- --:--:-- --:--:-- 6061<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
</ul>
<hr>
</body>
</html>-o writes the body to a file. You will use this constantly in later articles to capture and process responses.
5. Silent mode with status code:
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080200This combination — -s to suppress progress, -o /dev/null to throw away the body, -w to print the status code — is one of the most useful curl patterns you will use in automation. We will build on --write-out this extensively in Article 2.
What This Series Covers
Sixteen articles. Five blocks. Every article builds on the last.
Block 1 — Foundations (Articles 1–7): How curl actually works. Flags, encoding, reading responses, building requests, and authentication.
Block 2 — Proxy & Header Control (Articles 8–9): Header manipulation as an attack surface. Burp proxy integration done properly.
Block 3 — Attack Workflows (Articles 10–13): Version hunting, file upload attacks, WAF evasion, TLS recon.
Block 4 — Advanced (Articles 14–15): Bash and jq pipelines, curl through pivot tunnels.
Block 5 — Capstone (Article 16): Full engagement chain — recon to report using nothing but curl.
The series is written for eJPT-level practitioners and curious beginners who want to go further. Every technique is demonstrated in an ethical context: local labs, TryHackMe rooms, and HackTheBox Starting Point machines.
Set up your lab. Run those five commands. Come back for Article 2 when you have read the output.
Next: Article 2 — Flag Mastery: The Flags That Run Every Engagement