Making HTTP Requests Using Bash /dev/tcp

Bash includes a built-in capability to open TCP sockets using a virtual path, /dev/tcp/host/port. This allows developers and system administrators to perform basic network connectivity tests and send raw HTTP requests without needing external utilities like curl, wget, or netcat.

How to Perform an HTTP Request in Bash

To send a basic HTTP GET request using Bash, you can use file descriptor redirection to open a socket and write the HTTP protocol strings directly to it.

Basic GET Request

exec 3<>/dev/tcp/service/8642
printf 'GET /health HTTP/1.1\r\nHost: service\r\nConnection: close\r\n\r\n' >&3
cat <&3

In this example, exec 3<> opens a bidirectional socket to the host service on port 8642 and assigns it to file descriptor 3. The printf command sends a properly formatted HTTP/1.1 request, and cat <&3 reads the response from the server.

Adding Custom Headers

To include additional headers, such as an authorization token, insert the header line before the final blank line that terminates the HTTP request:

exec 3<>/dev/tcp/service/8642
printf 'GET /v1/models HTTP/1.1\r\nHost: service\r\nAuthorization: Bearer %s\r\nConnection: close\r\n\r\n' "$API_KEY" >&3
cat <&3

Technical Constraints and Requirements

While powerful for debugging, the /dev/tcp feature is a low-level TCP client, not a full HTTP client. It has several critical limitations:

  • No TLS/SSL Support: /dev/tcp opens raw sockets. It cannot handle HTTPS. For encrypted traffic, tools like openssl s_client are required.
  • Manual HTTP Handling: Bash does not parse HTTP. It cannot handle redirects, chunked transfer encoding, compression (gzip), or retries. The user is responsible for constructing the valid HTTP request string.
  • Connection Management: In HTTP/1.1, connections remain open by default. The Connection: close header is necessary to tell the server to close the connection, allowing cat to reach the end-of-file (EOF) and exit. Alternatively, using HTTP/1.0 avoids the need for this header.
  • Shell Compatibility: This is a Bash-specific feature and is not part of the POSIX standard. It will not work in dash (the default /bin/sh on Debian/Ubuntu) or zsh (though zsh has its own separate network modules).
  • Compile-time Option: The feature must be enabled during Bash compilation with the --enable-net-redirections flag. While common in most mainstream distributions, some minimal or older versions of Debian had this disabled.

Use Cases and Alternatives

Common Applications

This technique is frequently used in constrained environments where installing new packages is impossible or forbidden:

  • Minimal Docker Containers: Checking connectivity between services in stripped-down images that lack curl or wget.
  • Embedded Systems: Implementing lightweight "heartbeat" services on devices with extremely limited flash memory.
  • Security Auditing: Performing port knocking or interacting with local web servers during penetration tests or CTF challenges when only a basic shell is available.

Alternative Methods

Depending on the environment, other tools may be more appropriate:

  • nc (Netcat): A more robust tool for raw TCP/UDP interactions.
  • nsenter: If curl is installed on the host machine, nsenter can be used to enter the network namespace of a container and run the host's curl against the container's network.
  • Perl/Python: Using IO::Socket in Perl or the socket module in Python to programmatically handle TCP connections.

Community Insights

Industry practitioners highlight that while /dev/tcp is a useful "hack," it exposes the inherent attack surface of feature-rich GNU utilities.

"This is an old post-compromise trick used when an attacker needs to download a payload or make a network connection and curl, wget and nc are all not available."

Others note that this functionality is a spiritual successor to the Plan 9 operating system's philosophy, where network resources are represented as files in a /net directory.

Sources