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).
But at my first work (begining of 2000s) there was one person that made a fun email, using From of head of company (or was it head of that particular division) to his coworker with congratulations for pay increase and promotion. It would be all great, but that coworker didn't catch the joke and replied to it (person in the From wasn't amused). Author of the joke was fired (which is not easy thing to do in Europe), some people don't catch jokes.
> 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.
She stood behind me and watched bemused as I fired up telnet, connected to my ISP's pop server and started reading emails from friends. I think I did manage to send some emails back via SMTP but I was not as good with that protocol.
If you could bottle the creativity and enthusiasm of a bored teen, I'm pretty sure you could take over the world
One day I realized even databases were just text files and I had to sit down.
Cat homework.ps > /dev/lp0The realization that (on DOS) "copy con file.txt" as the world's worst text editor or "copy file lpt1:" was treating physical devices as files. Everything on the computer was arbitrary so you could make anything behave like anything else (for some definition of behave like)!
Not a great insight I'll grant but a key one that everyone has to go through to be any good.
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.
You're welcome. Works like netcat plus TLS. Kind of inconvenient though. Hey someone should write tlsnc.
Good times.
But the joke's on him--it led directly to me meeting a lifelong friend & mentor.
I am not sure why this is a revelation. Any college level networking course would cover this?!
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.
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?
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.
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...
cd src/bash-5.3/examples/loadables
make accept
enable -f ./accept accept
(accept -r RHOST -v SOCKETFD -b 127.0.0.1 8000;
read -u $SOCKETFD SOCKETDATA;
printf "%s: %s\n" "$RHOST" "$SOCKETDATA";
printf "goodbye, world\n" 1>&${SOCKETFD} ) &
nc 127.0.0.1 8000 <<< "hello, world"
For real use you may need to add "exec {SOCKETFD}<&-" to close the FD.Edit: https://news.ycombinator.com/item?id=39369749 (2024)
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.
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.
it is insane to use it for anything serious (also the opposite, implementing webservers in bash), but for quick testing it's pretty great!
https://sdomi.pl/weblog/15-witchcraft-minecraft-server-in-ba...
Though I did also notice they didn’t claim it was pure bash themselves. That’s a flare you added.
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?
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!
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.
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.
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.
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!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.
https://gist.github.com/skull-squadron/edb8c0122f902013304c0...
My only concern would be that example.com doesn't promise to never do the 'required SSL' thing.
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 thereI 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.)
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)'
9front lets you play with that on Linux.
Some Plan 9 like /net things are visible in Go libraries... (Rob Pike legacy)
This is awesome.
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;"
Zsh has its own zsh/net/tcp and zsh/zftp modules.
https://zsh.sourceforge.io/Doc/Release/TCP-Function-System.h...
https://zsh.sourceforge.io/Doc/Release/Zsh-Modules.html#The-...
https://zsh.sourceforge.io/Doc/Release/Zftp-Function-System....
<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.
docker inspect -f '{{.State.Pid}}' container-name
# let's imagine that outputs 814538
nsenter -t 814538 -n curl example.com
https://github.com/ksh93/ast-open-archive/blame/master/src/c...
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...
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.
Why they didn't lock those down by using different creds per client in the computer lab I still don't know. Maybe it was a VAX limitation (at the time)?
It's a TCP client
curl is an HTTP client
I prefer TCP clients to HTTP clients. Simpler, easier to modify, faster to compile
There are many to choose from. For example, I use a modified version of tcploop
For generating HTTP, I use own utilties. This is more flexible than curl. There are some things curl cannot do, even though it has too many options
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.Using /dev/tcp was also handy in getting that initial low-priv shell.
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.
bash -i >& /dev/tcp/IP/PORT 0>&1
to talk to their friends(' computers).
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
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 :(