TIL: You can make HTTP requests without curl using Bash /dev/TCP
Posted by mrshu 16 hours ago
Comments
Comment by xenadu02 13 hours ago
Simple get: GET / HTTP/1.1 Content-Type: text/html User-Agent: l33t hax0rs lol X-Funny-Monkey: farts
For sending a mail message on port 25: HELO mail-from: whoever@whatever.com mail-to: sysadmin@yaya.com <other headers> <blank line> Body of the message yay. <two blank lines to end>
POP3 was so long ago I forgot but you could list the mailboxes then get individual messages and so on.
This revelation was the beginning of "there is no magic" for me. The realization that every part of the computer was built by human beings and was at some level understandable if one undertook the effort.
Perhaps most people in the future won't bother. They'll just let agents do it all. I'm sure that will leave some interesting holes in various systems for people willing to actually learn how they work without the filter of a model (or its safety rails).
Comment by charles_f 8 hours ago
Comment by rahimnathwani 9 hours ago
Comment by xp84 9 hours ago
> most SMTP servers would accept email from anyone anywhere to anyone anywhere (i.e. 'open relay').
to date that claim, I'd say that by the late 90s at least, true open relays ("from anyone to anyone") were still numerous but carried a huge assumption of being part of spam operations (willingly or through ineptitude), and the most basic spam filtering would reject mail that came out of one.
That said, (before things like SPF) it was easy enough to deliver email to anyone you wanted even if you didn't have your own real email account and SMTP server; you could just look up the destination's MX and connect to it with telnet like that. Since your own random IP probably wasn't blocklisted it would generally be accepted and delivered.
Back then it was still basically considered bad form to reject email simply because the server didn't know where it was from... sadly, if we were still playing by those rules today, I can only imagine how useless email would be. Now it's definitely guilty-till-proven-innocent.
Comment by awesome_dude 1 hour ago
There are rules now, but the concept is still almost intact, random people writing to the servers disk - to be later read by someone
Comment by the_arun 6 hours ago
Comment by VladVladikoff 5 hours ago
Comment by cinntaile 2 hours ago
Comment by flyingshelf 3 hours ago
Comment by eqmvii 10 hours ago
One day I realized even databases were just text files and I had to sit down.
Comment by kps 13 hours ago
Comment by vbezhenar 9 hours ago
Comment by bijowo1676 11 hours ago
Comment by __float 10 hours ago
As the calendar rolled from 1999 to 2000, we entered a new millennium, century, decade, year, day, ...
Comment by 8n4vidtmkvmk 8 hours ago
Every Jan 2 I start saying "last year" and every Dec I say "see you next year"
Comment by xeonmc 6 hours ago
Comment by bijowo1676 6 hours ago
Comment by fsckboy 5 hours ago
no, that all happened when we rolled from 2000 to 2001.
smh, even paedants today aren't what they used to be.
Comment by johncoltrane 1 hour ago
I think that's more or less when I lost faith in humanity.
Comment by chrisbrandow 10 hours ago
Comment by nico 4 hours ago
Comment by vbezhenar 9 hours ago
You also can't do that with TLS (and a lot of servers won't talk HTTP other than redirects). openssl s_client instead of telnet might allow you to tunnel text inside TLS, but that feels like a cheating.
And many other modern protocols, sadly, prefer binary encoding, which makes it impossible to tinker with it on wire level, not without specialized tools anyway.
I think people in the future will bother. I tried to make a fire with sticks once, I tried to burn a clay brick, these old things can be a lot of fun and sometimes of real use. If anything, AI actually makes tinkering a lot more easier. You don't need to dig into RFC to check your mail, you can just talk to LLM about it and it'll help you with most typical IMAP commands, for example.
Comment by linzhangrun 1 hour ago
Comment by razodactyl 10 hours ago
Comment by globular-toast 3 hours ago
Comment by alex_smart 1 hour ago
I am not sure why this is a revelation. Any college level networking course would cover this?!
Comment by reaktivo 1 hour ago
As an actual kid it's easy for it to be a revelation, no? At least it was for me, with no college level networking course experience.
Comment by alex_smart 1 hour ago
Comment by MuffinFlavored 10 hours ago
Comment by lacunary 5 hours ago
Comment by jazz9k 13 hours ago
Good times.
Comment by Denatonium 9 hours ago
Comment by sejje 12 hours ago
But the joke's on him--it led directly to me meeting a lifelong friend & mentor.
Comment by CGamesPlay 5 hours ago
But can you imagine the look on some young teen’s face when they train their own GPT on their local computer for the first time?
Comment by gatestone 2 hours ago
9front lets you play with that on Linux.
Some Plan 9 like /net things are visible in Go libraries... (Rob Pike legacy)
Comment by simonw 15 hours ago
exec 3<>/dev/tcp/example.com/80
printf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' >&3
cat <&3
Outputs: HTTP/1.1 200 OK
Date: Tue, 16 Jun 2026 17:37:45 GMT
Content-Type: text/html
...
I always end up on example.com for this kind of thing because there are so few domains these days that don't enforce https!Comment by QuantumNomad_ 14 hours ago
I open my web browser and go to http://example.com and get redirected to the captive portal page again and retry completing what they need from me to get internet access.
Comment by some_random 14 hours ago
https://gist.github.com/skull-squadron/edb8c0122f902013304c0...
Comment by QuantumNomad_ 14 hours ago
- http://connectivitycheck.gstatic.com/generate_204
- http://detectportal.brave-http-only.com/
Plus, it feels nice to depend on the reserved domain name example.com instead of relying on a domain that any one specific corporation has to maintain :D
Comment by 1f60c 13 hours ago
Comment by 0l 12 hours ago
Comment by xp84 8 hours ago
Comment by xp84 8 hours ago
My only concern would be that example.com doesn't promise to never do the 'required SSL' thing.
Comment by LeoPanthera 13 hours ago
Comment by gabrielsroka 14 hours ago
exec 3<>/dev/tcp/example.com/80
printf 'GET / HTTP/1.1\r
Host: example.com\r
Connection: close\r
\r
' >&3
cat <&3
You can even take out the \r though they should be thereComment by basilikum 15 hours ago
No, it can not. Bash lets you open TCP sockets.
What you are doing here is trying to speak HTTP yourself, which is fine for testing and debugging, and hella cool for fun to do by hand, but you will shoot yourself in the foot if you try to use this pseudo http client unattended in reality. This toy code does not parse HTTP properly and will break.
You could of course write a full http/1.1 client in bash, you can even do a full http server in pure bash: https://github.com/bahamas10/bash-web-server
For less insane, non-bash shells there is always nc which is usually probably the wiser choice.
Comment by iam-TJ 13 hours ago
bash-web-server project builds a C language socket listener [0] that is dynamically loaded at run-time as a "built-in" module that makes the functionality available.
[0] https://github.com/bahamas10/bash-web-server/tree/main/loada...
Comment by majorchord 8 hours ago
Comment by zwischenzug 5 hours ago
Comment by pastage 1 hour ago
Comment by zwischenzug 14 minutes ago
Comment by Brian_K_White 3 hours ago
Comment by mrshu 15 hours ago
Very fair pushback -- I did get carried away and will update the article to be more precise. Thanks for raising it!
> For less insane, non-bash shells there is always nc which is usually probably the wiser choice.
For completeness, `nc` or any netcat equvialent I could think of was not available in the image I was trying this with. It would certainly be a better option though.
Comment by bearjaws 15 hours ago
Comment by thih9 14 hours ago
Was grandparent comment written by an LLM?
Or is this a human who copies a style they saw in a blog post, unaware that they’re copying an AI?
Or is this a human who spent too much time talking to an AI and now they just talk like this?
Or is this an organic human response and we’re all paranoid by now?
I don’t know which would be worse.
Comment by elevation 13 hours ago
For many in the next generation of language learners, this reference will be Claude.
Comment by vbezhenar 8 hours ago
But certainly with smarter AI I do believe it'll become more fluent with choosing more diverse idioms and phrasing, rather than repeating one thing over and over, to a point of being a comically similar. So people who learn on AI-generated text, will not learn from just one recurring style.
Comment by pastage 1 hour ago
The amount of languages are decreasing on the earth, I would also say that dialects and accents are decreasing as well. I think this is a problem.
Comment by disqard 12 hours ago
Comment by 8bitsout 13 hours ago
Comment by mrshu 14 hours ago
(For what it's worth I did write the message above manually but I understand why no one would believe that now. At least I did not call netcat "load-bearing" [https://mareksuppa.com/til/load-bearing/] or something...)
Comment by sisve 11 hours ago
Before that would just made you top 5% (or maybe top 1%) of the nicest people to talk too.. know ppl think you are Claude.
We are all going crazy s a sibling comment said.
Comment by fc417fc802 8 hours ago
Comment by ffsm8 13 hours ago
I notice myself getting afflicted with llm-isms after a full workday. And I didn't always notice, sometimes I only realize the day after...
Like it slowly siphoned out my soul, which then reconnected with me over night
Comment by BearOso 8 hours ago
Comment by ed_elliott_asc 13 hours ago
Comment by nandomrumber 13 hours ago
Comment by nialv7 14 hours ago
Comment by xeyownt 14 hours ago
Comment by hnlmorg 1 hour ago
Comment by WD-42 14 hours ago
Comment by scubbo 12 hours ago
Comment by farmerbb 8 hours ago
Comment by throwrioawfo 14 hours ago
Comment by mrshu 14 hours ago
Comment by disqard 12 hours ago
I remember when the "hacker vs. cracker" distinction went away because Hollywood co-opted the former and it became de facto "hacker == bad guy"
Comment by a-dub 15 hours ago
it is insane to use it for anything serious (also the opposite, implementing webservers in bash), but for quick testing it's pretty great!
Comment by bitmasher9 14 hours ago
Comment by hnav 14 hours ago
Comment by Bender 11 hours ago
export http_proxy=http://your.proxy.server:port/
export HTTPS_PROXY=https://your.proxy.server:port/
curl -x http://proxy_server:proxy_port --proxy-user username:password
or $socks-wrapper curl # [2]
[1] - https://dev.to/gbhorwood/curl-getting-performance-data-with-...[2] - torsocks, tsocks, wireproxy, shadowsocks-rust, proxychains-ng, etc...
Comment by hnav 9 hours ago
and a server behind it like
``` mkfifo /tmp/myfifo cat /tmp/myfifo | nc -l 12345 > /tmp/myfifo ```
so if you manually type out
CONNECT host:12345 HTTP/1.1
host: host:12345
you can see exactly what's happening. To be fair you can hack curl to support that via curl -x proxy:3333 telnet://host:12345
but that's not exactly what you want and requires curl to have been compiled with telnet support.Comment by Bender 9 hours ago
I need to try this with a Squid SSL Bump MitM proxy just dont have one up at the moment.
curl -vvv -A Mozilla -H "Accept-Language: en_us" -H "Sec-Fetch-Mode: navigate" --url 'https://nochan.net/.env'Comment by a-dub 14 hours ago
telnet was always there though. it also worked for speaking all the other plaintext internet protocols. (imap, pop, smtp, etc)
Comment by HeckFeck 13 hours ago
Comment by nativeit 10 hours ago
Like if my server replied with ‘HI PLEASURE TO MEET YOU 127.0.0.1 THAT NAME SOUNDS FAMILIAR ARE YOU BY CHANCE FROM BOSTON MY MOTHER IS FROM BOSTON WELL QUINCY ACTUALLY BUT DO YOU KNOW 127.0.1.1 THEY ARE A REALLY GOOD FRIEND OF MINE YOU SHOULD MEET I HEAR THEIR DAUGHTER IS A DOCTOR DONTYAKNOW AND YOU COULD…”
etc, etc?
Comment by edoceo 9 hours ago
Comment by a-dub 12 hours ago
smtp grew up to be an antisocial curmudgeon. extended smtp starts with EHLO.
Comment by endofreach 12 hours ago
email will become so unusable, next one will have to be HELNO i guess
Comment by jolmg 11 hours ago
"EHLO" still sounds friendly. It just sounds like a different accent or something. Know someone that used to answer calls with a friendly "Jello?".
Comment by xp84 9 hours ago
Comment by a-dub 11 hours ago
Comment by dragontamer 13 hours ago
Use nc or this TCP Bash technique if you really want to ensure decent compatibility when doing hacky solutions, otherwise a random 0xFF somewhere from a terminal console color change (or other control character) might really screw you over.
EDIT or ya know, use the correct tool like Curl.
Comment by asmnzxklopqw 12 hours ago
Comment by xp84 9 hours ago
When there is no corresponding level of restraint in the libraries that we add to most applications, does it really make a difference to leave out the likes of curl, nano, ping, etc compared to how frustrating it is to operate in just busybox (etc)?
I'm not just ranting, I'd actually like someone who swears by always shipping alpine images (etc) and never installing any basic utilities in them to share their reasoning.
Comment by alex_smart 1 hour ago
Thanks to `kubectl debug`, you don't need to install debugging utilities into your production image.
Comment by crewindream 10 hours ago
Comment by sgjohnson 5 hours ago
https://sdomi.pl/weblog/15-witchcraft-minecraft-server-in-ba...
Comment by hnlmorg 2 hours ago
Though I did also notice they didn’t claim it was pure bash themselves. That’s a flare you added.
Comment by tombert 11 hours ago
Comment by TZubiri 14 hours ago
Common misconception, if you want to replace a dependency on a swiss knife you don't need to implement a swiss knife, sometimes you can just implement the last helix of the corkscrew.
Comment by cyanydeez 13 hours ago
Comment by pillmillipedes 13 hours ago
Comment by TZubiri 13 hours ago
api, GET url, content-type aplication/json, parse json
you can even invert it and make a server
Comment by andelink 13 hours ago
Comment by morpheuskafka 15 hours ago
I thought you had to use a program called netcat for that--if not then what is the point of that binary? And for that matter, can't you also use telnet to manually send HTTP?
Comment by some_random 14 hours ago
Comment by nandomrumber 13 hours ago
Comment by mrshu 16 hours ago
The main surprise was that Bash has /dev/tcp which lets you do the equivalent of an HTTP request with a bit of shell magic, for instance:
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
Where `service` is just the hostname of whatever you’re talking to and 8642 is the port you are trying to talk HTTP to.Pretty cool!
Comment by sevenzero 15 hours ago
Comment by OptionOfT 15 hours ago
So we start at compiling the codebase (Rust) against MUSL. That way we can run it with FROM scratch images.
If we need more tooling available at runtime, then we look at alpine, but still using MUSL.
If MUSL itself is proving problematic, or if some of the libraries we use need glibc then we can look at using some locked down image.
The cool part about FROM scratch images is that you'll never have to update your base image to address CVEs. Only your software and its (compiled) dependencies.
Comment by xp84 9 hours ago
> to not have any dependencies outside of the code.
> ... FROM scratch images is that you'll never have to update your base image to address CVEs...
So a FROM scratch image, basically doesn't have things like a package manager to install things, and maybe also libraries that things like curl would depend on? Sorry for my ignorance, I've heard of FROM scratch but never tried them.
Comment by gpvos 10 minutes ago
Comment by xmodem 15 hours ago
What's the benefit really, though? If you still need to be able to rapidly deploy a new image in response to a dependency CVE, what have you gained?
Comment by regularfry 14 hours ago
Comment by NewJazz 12 hours ago
Add an ephemeral container to an already running pod, for example to add debugging utilities without restarting the pod.
https://kubernetes.io/docs/reference/kubectl/generated/kubec...
Comment by xmodem 12 hours ago
And even that's only true if you assume kubernetes is the only place your container runs where you might want to also debug it.
Comment by NewJazz 12 hours ago
Comment by xp84 8 hours ago
How wasteful though? I have to admit, I envy the person whose codebase itself they have to support is so lean and space-conservative that the size of the gnu coreutils, curl, nano, etc., would show up as anything but a rounding error in the image size.
I see it like putting a thermometer in a turkey before I stick it in the oven. Sure, the thermometer adds thermal mass itself, making the turkey take a few seconds longer to cook, but the value of it being there is greater than the cost imposed.
Comment by xmodem 11 hours ago
For the rare cases where you find yourself needing to attach a debugger to your pods running in staging/prod, a debug container is absolutely the right tool to reach for.
Comment by OptionOfT 13 hours ago
But when Docker scans my image and notices that there is a CVE in one of those binaries, my image is currently out of compliance.
FROM scratch just reduces the surface.
Comment by xmodem 13 hours ago
The actual attack surface of your application? Or the attack surface of you and your team's attention from a busybody security org.
It's important not to confuse the two.
Comment by fc417fc802 8 hours ago
Comment by xmodem 1 hour ago
Comment by fc417fc802 43 minutes ago
Comment by monkpit 4 hours ago
Comment by xmodem 15 hours ago
In theory it has a couple of benefits. You don't have to re-deploy your image to patch CVE's in OS components if you don't have any OS components. And it provides some measure of defence-in-depth - one could certainly theory-craft a scenario where an attacker gains some limited control over your application and then uses some OS component to escalate.
These days if a security engineer is proposing my team adopt distro-less containers to receive these benefits, I would point out that we need to weigh them against the very real drawbacks of not having standard debugging tools available where and when we need them. And also to consider the relative impact of other defence-in-depth measures they could be pursuing instead - such as any sort of network policy to limit network traffic.
Comment by fc417fc802 8 hours ago
Keeping in mind that containers are merely a bunch of namespaces, there's nothing stopping you from entering the same PID namespace with a different mount namespace in order to debug.
Comment by xmodem 1 hour ago
To summarize, in my experience there is immense value to having basic shell tools available in the environment where you need them with zero extra friction. Stripping those out provides a security benefit only in specific nebulous and niche scenarios.
Comment by fc417fc802 38 minutes ago
I agree, however assuming you maintain a chroot for debugging this can be accomplished with a shell command that takes a single argument to target a running container by name.
Your linked comment suggests being limited to kubernates but nsenter and a chroot are entirely runtime agnostic.
Comment by NewJazz 12 hours ago
Add an ephemeral container to an already running pod, for example to add debugging utilities without restarting the pod.
https://kubernetes.io/docs/reference/kubectl/generated/kubec...
Comment by mrshu 15 hours ago
For what its worth, this container used `python:3.12.2-slim-bookworm` and I really would not expect that sort of an image to bundle `curl` -- even if it is intended for production.
Comment by TZubiri 14 hours ago
Comment by sevenzero 15 hours ago
Comment by mrshu 15 hours ago
Comment by a012 7 hours ago
Comment by figmert 15 hours ago
Comment by TZubiri 14 hours ago
Comment by monkpit 12 hours ago
Comment by giobox 15 hours ago
Comment by yread 1 hour ago
Comment by dredmorbius 6 hours ago
<https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=146464#37>
As others have mentioned, there are numerous other ways to directly access network features using shell tools, including curl (noted in TFA's title), wget, the HEAD and GET commands (from Perl), netcat (nc), socat, telnet, and I'm quite sure others.
Comment by sam_lowry_ 14 hours ago
FROM openjdk:11-jre-slim HEALTHCHECK --start-period=10s --timeout=3s --retries=5 \ CMD perl -e "use IO::Socket; $sock = IO::Socket::INET->new(Proto => 'tcp', PeerAddr => 'localhost', PeerPort => '8888') or die $@; $sock->autoflush(1); print $sock 'GET /actuator/health HTTP/1.1' . chr(0x0a) . chr(0x0d) . 'Host: localhost:8888' . chr(0x0a) . chr(0x0d) . 'Connection: close' . chr(0x0a) . chr(0x0d) . chr(0x0a) . chr(0x0d); while (my $line = $sock->getline ) { if ($line =~ /UP/) {exit;} }; close $sock; exit 1;"
Comment by hn92726819 14 hours ago
Comment by JSR_FDED 4 hours ago
Just like parsing HTML with regexes can be fine too - for instance if you know the sender.
Just like repeating code can be fine too, even though it violates DRY.
Mixing markup and code can be fine (call it Locality of Behavior).
But separating markup and code is fine too (Separation of Concerns).
goto’s can be a lifesaver for deeply nested error conditions in C.
The point is all these “you shouldn’t do this” comments are just generalities. Use your judgement, decide if the tradeoffs are right and make a deliberate choice.
Comment by mlhpdx 9 hours ago
Comment by HeadlessChild 4 hours ago
timeout 5s bash -c "echo >/dev/tcp/google.com/443" && echo "port open" || echo "port closed"
This uses the timeout command from coreutils though, so it is not a pure bash implementation.Comment by high_byte 1 hour ago
shame it's not a real device so the surface is limited to bash only
I wonder what software might be vulnerable to this attack surface
Comment by timwis 12 hours ago
docker inspect -f '{{.State.Pid}}' container-name
# let's imagine that outputs 814538
nsenter -t 814538 -n curl example.com
Comment by smoothgrammer 4 hours ago
Comment by okrad 4 hours ago
Comment by pgtan 12 hours ago
https://github.com/ksh93/ast-open-archive/blame/master/src/c...
Comment by MisterTea 10 hours ago
Comment by pickle-wizard 11 hours ago
Comment by AndrewStephens 15 hours ago
Comment by mrshu 15 hours ago
I was really just trying to see if intra-container connectivity works, and this ended up being a very quick way of doing so. (The alternative being building and deploying a new image, which would likely take significantly longer.)
Comment by KomoD 15 hours ago
You said the image was Python, though? Using that is way easier and faster. https://news.ycombinator.com/item?id=48558763
If all you need to know is that it can connect:
python3 -c 'import socket as s;s.create_connection(("8.8.8.8",53))'
or http:
python3 -c 'from urllib.request import*;print(urlopen("http://example.com").status)'
Comment by mrshu 13 hours ago
Comment by orthogonal_cube 14 hours ago
Comment by saidinesh5 13 hours ago
The routers were very basic model with very limited flash memory (~4MB?). I was brought in to build firmware for those routers. I ended up customising openwrt - removed all kinds of packages to make their packages fit on those routers. At the end, I had less than 4KB space, And I needed to implement a "heart beat" service. A lot of routers were behind firewalls that only allowed http, https and a couple of other protocols. Libcurl was too heavy. So I ended up writing a shell script that used this feature of bash to send out heart beats.
Fun times...
Comment by tzot 11 hours ago
Comment by dchest 14 hours ago
Comment by dennis16384 11 hours ago
Comment by andrewshadura 57 minutes ago
Comment by washbasin 13 hours ago
Comment by Sohcahtoa82 11 hours ago
Using /dev/tcp was also handy in getting that initial low-priv shell.
Comment by chaps 12 hours ago
Comment by quotemstr 11 hours ago
Comment by chaps 10 hours ago
Comment by tim-tday 8 hours ago
Comment by p-e-w 8 hours ago
Comment by geoctl 14 hours ago
Comment by Retr0id 13 hours ago
Comment by alienbaby 14 hours ago
Comment by nesarkvechnep 12 hours ago
Comment by ygouzerh 45 minutes ago
Comment by nedt 11 hours ago
Comment by laserbeam 4 hours ago
This is why we can’t have nice things. This feature is complex and obscure enough that you are unlikely to be able to use it manually without consulting a reference, and poorly supported that any script you write with it is unportable.
Bash is so powerful and so frustrating for this reason all the time :(
Comment by devsda 15 hours ago
Comment by sc68cal 15 hours ago
Comment by ddlsmurf 8 hours ago
Comment by charles_f 8 hours ago
Comment by m3047 14 hours ago
Comment by Steeeve 14 hours ago
Comment by johnea 9 hours ago
I discovered it for myself some years ago, when I wanted to make simple network test scripts run without depending on curl or telnet, or other executables outside of bash.
Comment by alienbaby 14 hours ago
Comment by sbseitz 2 hours ago
Comment by uberex 10 hours ago
Comment by black_knight 11 hours ago
Comment by masa-kozu 8 hours ago
Comment by phantasmat 14 hours ago
Comment by michaeltm 10 hours ago
Comment by animanoir 10 hours ago