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).

10 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. thanks man! amazing work! thanks a lot 😀 that totally solved everything!

  7. 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 *