DEV Community

Piotr Zarycki
Piotr Zarycki

Posted on

How to make http request without curl or wget in bash

Bash has a hidden capability to make HTTP requests without needing tools like curl or wget. Let’s explore how this works.

First, let’s check the bash man page:

man bash

In the REDIRECTION section, you’ll find an interesting detail:

Bash handles several filenames specially when they are used in redirections. If the operating system on which Bash is running provides these special files, Bash will use them; otherwise, it will emulate them internally with the behavior described below.

  Bash handles several filenames specially when they are used in redirections, as described in the following table.  If the operating system on which bash is  running  provides  these
       special files, bash will use them; otherwise it will emulate them internally with the behavior described below.

              /dev/fd/fd
                     If fd is a valid integer, file descriptor fd is duplicated.
              /dev/stdin
                     File descriptor 0 is duplicated.
              /dev/stdout
                     File descriptor 1 is duplicated.
              /dev/stderr
                     File descriptor 2 is duplicated.
              /dev/tcp/host/port
                     If host is a valid hostname or Internet address, and port is an integer port number or service name, bash attempts to open the corresponding TCP socket.
              /dev/udp/host/port
                     If host is a valid hostname or Internet address, and port is an integer port number or service name, bash attempts to open the corresponding UDP socket.
Enter fullscreen mode Exit fullscreen mode

If you try to list the /dev/tcp directory, you’ll get the following error:

ls -lat /dev/tcp
/dev/tcp: No such file or directory (os error 2).
Enter fullscreen mode Exit fullscreen mode

This is because /dev/tcp is not an actual path on the filesystem; it’s a feature of Bash itself, not the underlying Linux system.

To use this feature, you need to call it as part of the Bash execution environment using the exec command:

exec 3<>/dev/tcp/example.org/80 
Enter fullscreen mode Exit fullscreen mode

We’ll discuss the 3<> syntax later. After running this command, it may seem like nothing happens, but there’s more going on behind the scenes.

Let’s use strace to investigate further. Among the output, the most interesting part is:

.......
close(3)                                = 0
socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("93.184.215.14")}, 16) = 0
Enter fullscreen mode Exit fullscreen mode

This confirms that the exec command created a TCP/IP socket bound to example.org.

In Linux, everything is treated as a file, including network connections, which are represented as “files” called file descriptors. You can list all file descriptors for the current process by checking the /proc/self/fd/ directory:

ls -lat /proc/self/fd/

lrwx------ 1 piotr piotr 64 Aug 25 15:01 0 -> /dev/pts/0
lrwx------ 1 piotr piotr 64 Aug 25 15:01 1 -> /dev/pts/1
lrwx------ 1 piotr piotr 64 Aug 25 15:01 2 -> /dev/pts/2
lrwx------ 1 piotr piotr 64 Aug 25 15:01 3 -> 'socket:[2910856]'
lr-x------ 1 piotr piotr 64 Aug 25 15:01 4 -> /proc/1579316/fd
Enter fullscreen mode Exit fullscreen mode

Here, 3 is the file descriptor for the socket connected to example.org. The exec 3<> /dev/tcp/... syntax essentially means:

"create a socket for input and output operations with file descriptor with process identifier equals 3."

Next, let’s send a GET request:

echo -ne "GET / HTTP/1.1\r\nHost: example.org\r\n\r\n" >&3
Enter fullscreen mode Exit fullscreen mode

Although it may appear that nothing happens, you’ve just sent data through the socket.

To read the response, use:

cat <&3  

HTTP/1.1 200 OK
Age: 243157
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Sun, 25 Aug 2024 14:15:54 GMT
Etag: "3147526947+ident"
Expires: Sun, 01 Sep 2024 14:15:54 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECAcc (dcd/7D77)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 1256

<!doctype html>
<html>
<head>
    <title>Example Domain</title>

    <meta charset="utf-8" />
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style type="text/css">
......................REST............
Enter fullscreen mode Exit fullscreen mode

You’ve successfully downloaded a webpage! Afterward, remember to close the socket:

exec 3<&- 
Enter fullscreen mode Exit fullscreen mode

You might wonder why this feature exists.

Originally, it was copied from the KornShell (ksh) to open network connections and assign them to file descriptors

It was intended to send data across networks, such as for generating reports.

https://github.com/ksh93/ksh/blob/fa0ee84029e0cd203d698151a0459413678ab095/src/cmd/ksh93/README-AUDIT.md?plain=1#L158

This feature can also be exploited to create reverse shells by hackers or penetration testers.

Top comments (0)