RC RANDOM CHAOS

Removing curl and wget stops nothing

Bash /dev/tcp opens TCP sockets and sends HTTP with no curl or wget, evading process-name detection while leaving cleartext on the wire.

· 7 min read
Removing curl and wget stops nothing

Bash can open a TCP socket without help. No curl. No wget. No external binary. The mechanism is /dev/tcp, a pseudo-device the shell exposes when compiled with —enable-net-redirections. That flag is the default on Debian, Ubuntu, RHEL, Alpine’s bash package, and nearly every mainstream distribution.

There is no CVE here. No CVSS vector. No patch boundary. /dev/tcp is documented behaviour in the Bash reference manual and has shipped since Bash 2.04 in 2000. That is the relevant fact. The exposure is not a memory-corruption primitive. It is a detection assumption that fails the moment the network client is the shell process itself.

Start with the mechanism. /dev/tcp/host/port and /dev/udp/host/port are not real entries under /dev. No device node exists. Bash intercepts the path inside its redirection handling. When the shell parses a redirection targeting that path, it calls getaddrinfo, then socket with AF_INET or AF_INET6 and SOCK_STREAM, then connect, directly inside the bash process. The descriptor returned is a live TCP socket. A construct binding file descriptor 3 to the path opens the connection read-write and hands the operator a handle to it. The same path form resolves IPv6 destinations, so an egress test against a v6-only endpoint works without any change in technique.

From that point an HTTP request is plain text written to the descriptor. Request line, Host header, a blank line to terminate the headers. The kernel ships the bytes over the established connection. The server response returns on the same descriptor and gets read back line by line. This is HTTP/1.1 spoken over a raw socket by the shell. The shell does no protocol parsing of its own. It does not follow 3xx redirects, decode chunked transfer-encoding, or honour Content-Length. The operator’s script handles all of that, or ignores it and reads until the peer closes. No TLS is involved. Bash does not negotiate TLS, present a certificate, or run a handshake. /dev/tcp speaks cleartext TCP only. That constraint matters for the defensive analysis later.

Connection timeouts are controlled in-band. A non-routable destination hangs on connect until the kernel SYN timeout expires, which is why operators set a read timeout on the descriptor and treat a stalled connect as a filtered port. That behaviour turns /dev/tcp into a passable port scanner and blind-SSRF egress prober as well as a transport, all from the shell, with no nmap or nc on disk.

The syscall trace is short and specific. socket, connect, then write and read against the resulting descriptor. The executing image is /usr/bin/bash. The parent process is whatever spawned the shell, a web application worker, an SSH session, a cron job, a CI runner. No curl, python, perl, or nc appears anywhere in the process tree.

One precision point decides whether the technique even applies. /dev/tcp is a bash feature, mirrored in ksh and zsh. It is not POSIX. The busybox ash shell that ships as /bin/sh in many Alpine images does not implement it. A container with only busybox is not exposed to this specific primitive. A container that includes the bash package, common in CI base images and anything derived from ubuntu or debian slim, is.

The technique earns its place where network tooling is absent. Hardened and minimal container images strip curl and wget to reduce footprint. Bash frequently survives the cut because entrypoint scripts depend on it. An operator who has gained shell execution, through T1190 exploitation of a public-facing application, an unsafe deserialization path, or a CI/CD command injection, finds no download utility on the host. /dev/tcp restores ingress tool transfer, MITRE T1105. The second stage is pulled over cleartext HTTP into a tmpfs path or an in-memory file descriptor. No curl process is ever created.

The same descriptor mechanic produces an interactive reverse shell. Redirecting an interactive shell’s standard input, output, and error onto the socket descriptor yields a remote session over TCP. That maps to T1059.004, Unix shell. The exact payload is in every offensive cheat sheet and stays out of this writeup. The point is the primitive, descriptor redirection over a shell-opened socket, not the one-liner.

The attacker controls the request fully. Method, path, every header, the User-Agent string, the body. That control supports command-and-control over web protocols, T1071.001, and defense evasion by avoiding known-bad binaries, a T1562-class outcome. The /dev/udp variant carries the same control to UDP, which is the basis for low-profile DNS-style exfiltration straight from the shell.

The framing that /dev/tcp bypasses WAF payload inspection needs correction, and precision here changes the control that follows. A network WAF or IDS inspecting cleartext HTTP still observes every byte. /dev/tcp encrypts nothing. Snort, Suricata, or a Cloudflare-style inspection layer sees the same request a curl client would emit, minus any signature keyed on the default curl User-Agent. The evasion is host-side. Detection keyed on the process image name, curl, wget, python, never fires, because the image name is bash. Egress filtering that allow-lists by User-Agent or known tool fingerprint passes the traffic, because the operator writes the User-Agent. To actually defeat on-wire payload inspection, attackers wrap the raw socket in TLS using openssl s_client or stunnel as a separate channel. At that point TLS performs the hiding, not /dev/tcp. Overstating the primitive sends defenders toward the wrong fix.

Real-world use is well established on Linux. Bash /dev/tcp reverse shells and downloaders are standard in cloud and container post-exploitation. TeamTNT and Kinsing, crews focused on exposed Docker and Kubernetes infrastructure, ran bash-based droppers against misconfigured daemons and stolen credentials. Log4Shell, CVE-2021-44228, frequently chained into bash retrieval one-liners for second-stage delivery on Linux targets after the initial JNDI callback. The technique persists in Linux malware loaders because it assumes nothing beyond a bash binary already present on the host.

Telemetry separates the detections that fire from the rules that stay silent. Sysmon for Linux Event ID 3, network connection, attributes the outbound flow to /usr/bin/bash rather than a network utility. auditd records the connect syscall with bash as the executing process and the destination in the saddr field, given a rule armed on the connect syscall. eBPF-based sensors, Falco and Cilium Tetragon, instrument the socket and connect syscalls and observe the connection regardless of which binary issued it. Falco ships rules that fire on outbound connections originating from shell processes, matching on proc.name against a shell set and on fd.type of ipv4 or ipv6.

The silent side is process-creation telemetry. Sysmon for Linux Event ID 1 and EDR process-spawn alerts have nothing new to report, because no child process is created. A correlation rule written as alert when curl or wget executes with a remote URL is blind by construction. Command-line argument matching, a heavy dependency in many EDR rule sets, sees only bash and its script text, never a URL handed to a recognised tool. When the second stage streams to an in-memory descriptor, file-write telemetry stays quiet as well.

The gap is the dependency on process identity. A detection that asks which binary opened this connection fails when the binary is the shell. A detection that asks did a shell process open an outbound socket to a destination outside the allow-list catches it cleanly. The reliable signal is the connection from bash plus the destination, joined in the SIEM against the process image, not the tool name in a command line.

Baselining makes the host-side signal usable. In a steady-state Linux workload, the set of processes that legitimately open outbound sockets is small and stable, web servers, package managers during deploy, the monitoring agent. A bash process opening a connection to an external address outside a deploy window is anomalous on its own, before any payload is examined. The detection content is the tuple of process image equal to a shell, socket family inet, and destination outside the egress allow-list. That fires on /dev/tcp, on a bash-spawned nc, and on most living-off-the-land egress from a shell, because it keys on behaviour rather than tool identity.

For operators under the SOCI Act, shell-originated egress from a production system in a critical-infrastructure asset is a material anomaly and maps to the incident-reporting obligations that follow a suspected intrusion. Cleartext second-stage retrieval into a regulated environment also carries Privacy Act exposure once the pulled payload touches personal information. A confirmed bash outbound connection to an unrecognised destination is an escalation to the incident-response function, not a tuning exercise for the detection team.

There is no patch, because there is no defect. /dev/tcp is a Bash feature and exists wherever Bash is built with net redirections, which is the default. The residual exposure is structural and survives any update. Three conditions keep it live. Minimal images retain Bash after stripping curl and wget. Egress policy keys on binaries or User-Agent strings rather than destination and process. Detection logic anchors to process image names. The defensive realities follow directly. Remove Bash from production runtime images where the workload does not require an interactive shell, which collapses the primitive to nothing. Restrict egress by destination at the network layer, where the shell’s socket faces the same allow-list as any other process. Instrument the connect syscall through eBPF, not the process name. The cleartext HTTP from /dev/tcp remains fully inspectable on the wire. The blindness is host-side, and it lives entirely in the assumption that network egress comes from a network tool.

Share

Keep Reading

Stay in the loop

New writing delivered when it's ready. No schedule, no spam.