Using nc (netcat) to make an HTTP request

I must have had some reason for wanting to do this, although I can’t think of why right now. curl is an excellent tool for ad hoc HTTP requests.

On a server running Apache 2.4.6, first I tried:

# nc 127.0.0.1 80
GET / HTTP/1.1

Which returned a HTTP/1.1 400 Bad Request error.

Next I tried:

# printf "GET /index.html HTTP/1.1\r\n\r\n" | nc 127.0.0.1 80

Which also returned a HTTP/1.1 400 Bad Request error.

I decided to take a look at what curl was sending, since that was working:

# curl -v http://127.0.0.1
* About to connect() to 127.0.0.1 port 80 (#0)
* Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 127.0.0.1
> Accept: */*
...

I put the same headers (with a modified User-Agent) into my printf statement:

# printf "GET /index.html HTTP/1.1\r\nUser-Agent: nc/0.0.1\r\nHost: 127.0.0.1\r\nAccept: */*\r\n\r\n" | nc 127.0.0.1 80
HTTP/1.1 200 OK
Date: Sun, 28 Jan 2018 23:11:04 GMT
Server: Apache/2.4.6 (CentOS) PHP/5.4.16
Last-Modified: Sun, 28 Jan 2018 20:10:37 GMT
ETag: "78-563dbb912bfe0"
Accept-Ranges: bytes
Content-Length: 120
Content-Type: text/html; charset=UTF-8

<!DOCTYPE html>
<html>
<head>
<title>well that worked</title>
</head>
<body>
<h1>apache is running</h1>
</body>
</html>

That worked!

I eliminated the User-Agent the Accept headers and it still worked, so the missing Host header was the cause of my problems. I swear I’ve done this before without a Host header though.

I looked up the HTTP specification, and as described in section 5.2 of the RFC:

1. If Request-URI is an absoluteURI, the host is part of the Request-URI. Any Host header field value in the request MUST be ignored.

2. If the Request-URI is not an absoluteURI, and the request includes a Host header field, the host is determined by the Host header field value.

3. If the host as determined by rule 1 or 2 is not a valid host on the server, the response MUST be a 400 (Bad Request) error message.

Recipients of an HTTP/1.0 request that lacks a Host header field MAY attempt to use heuristics (e.g., examination of the URI path for something unique to a particular host) in order to determine what exact resource is being requested.

I could not get it to work with an absoluteURI, even using the example in the RFC. However I did find that I could ignore the Host header if I specified HTTP/1.0:

# printf "GET / HTTP/1.0\r\n\r\n" | nc 127.0.0.1 80

I also found that Apache didn’t care what the Host header was when using HTTP/1.1, just so long as something was there:

# printf "GET / HTTP/1.1\r\nHost: z\r\n\r\n" | nc 127.0.0.1 80

That’s a little odd. I did not specify a ServerName in my Apache config, but even after I specified ServerName 127.0.0.1:80 in /etc/httpd/conf/httpd.conf and restarted Apache, it still required the Host header and it still didn’t care what the content of the Host header was (so long as it was not empty).

9 thoughts on “Using nc (netcat) to make an HTTP request”

  1. thanks for the share!!! really useful! However, I am wondering why netcat is not working straight:

    netcat 127.0.0.1 80
    GET / HTTP/1.1

    And then before having time to set the host i receive 400 Bad Request.. However, the print method piping in nc is working.. is it because of the instant inputting? Is there any way to make it work? thanks:D

  2. I also tried the same using Burp repeater forging the request exactly like

    GET / HTTP/1.0
    Host: 127.0.0.1

    and totally worked… for me it doesn’t make sense why nc is not working and not even having time to put the host. Thanks for any help offered 😀

  3. @nikos, as soon as you hit Enter in the interactive netcat session, you are sending the request to the server, in this case without the Host: header and without the proper end-of-line marker.

    By the way, using piping the output of echo to nc will also work, so long as you use the -e flag to enable backslash escape sequences:

    echo -e "GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n" | nc 127.0.0.1 80

    RFC 1945 (HTTP 1.0) and RFC 2616 (HTTP 1.1) specify that the end-of-line marker is a carriage return character (\r) followed by a newline character (\n). Pressing Enter on a Linux host will include just a newline, so this will be a bad HTTP request.

    See:

    Hope that helps!

  4. @Chris Thanks a million! Totally helped! Until now, i have gathered the following ways:

    -input of file with the request statements piping into nc
    -echoing in it like you said
    -printing in it

    This helped a lot! So there is no way to achieve the result straight with nc right?

  5. @nikos, I just discovered a way to do this with nc! It may depend on the version of netcat you’re running. Here’s what I’m using:

    # nc --version
    Ncat: Version 7.50 ( https://nmap.org/ncat )

    If you include the –crlf flag (or -C), nc will use CRLF for the EOL sequence. So the following should work:

    # nc --crlf 127.0.0.1 80
    GET / HTTP/1.0

    Or:

    # nc --crlf 127.0.0.1 80
    GET / HTTP/1.1
    Host: 127.0.0.1

    Note that after completing the HTTP request, you’ll need to press Enter twice.

    Thanks for your questions, it’s actually improved my understanding of the problem quite a bit!

  6. Using the -crlf flag (note the single dash in this case) works for openssl as well, in case you are testing an HTTPS connection.

    Example:

    $ openssl s_client -crlf -connect 127.0.0.1:443
    [TLS handshake info]
    ---
    GET / HTTP/1.1
    Host: 127.0.0.1

Leave a Reply

Your email address will not be published. Required fields are marked *