
Don't Pipe to your Shell - bqe
http://blog.existentialize.com/dont-pipe-to-your-shell.html
======
endgame
The amount of projects that do this is absurd. People have been saying this
for ages and nobody seems to listen. You could have all sorts of fun based on
the user agent, as well: if it looks like a normal browser, send the harmless
script. If it's curl or wget, prepend "echo 'I hope you are not going to pipe
this into /bin/sh'; exit 1".

BTW: If you want ULTIMATE BRAVERY, you have to boot an arbitrary kernel over
the internet: [http://ipxe.org/](http://ipxe.org/) (scroll to the bottom,
where it says `iPXE> chain
[http://boot.ipxe.org/demo/boot.php`](http://boot.ipxe.org/demo/boot.php`))

~~~
chadnickbok
A quick example - RVM!

[http://rvm.io](http://rvm.io) suggests you run the command:

\curl -L [https://get.rvm.io](https://get.rvm.io) | bash -s stable

Which exhibits _exactly_ the failure scenario outlined in the article; partway
through the script RVM cleans up after itself by running "rm -rf
${rvm_src_path}"

Simple changing this to

"\curl -L [https://get.rvm.io](https://get.rvm.io) > /tmp/rvm.sh && bash
/tmp/rvm.sh stable"

Would eliminate the potential for nasty failures, with a minimum of fuss.

~~~
Dylan16807
But there's no / in that command, or any way for it to rm the wrong directory,
unless I'm misunderstanding something.

~~~
chadnickbok
You're right, I'm completely wrong - the worst thing that can happen here is
that the whole RVM directory is removed. Hardly catastrophic, and definitely
not the extreme failure case the article is talking about.

------
bryanlarsen
What's the difference between piping to a shell and running a random
executable or installing a random ppa? All 3 could do massive harm to your
system.

One difference is that piping to a shell is a lot easier to inspect. So you
could easily argue that piping to a shell is safer.

Heck, even downloading a source tarball and compiling it yourself isn't any
safer unless you actually inspect that source. And who does that?

~~~
RKearney
Did you read the post at all?

The issue isn't executing untrusted code, it's connections terminating early
and causing your shell to execute incomplete code.

The article ends with the example code

    
    
        TMP_DIR=`mktemp`
        rm -rf $TMP_DIR
    

And the stream ending pre-maturely may cause the second line to end with rm
-rf / and then execute it. While this wouldn't do anything anyway without
--no-preserve-root added, it still brings up a good point about interrupted
connections executing incomplete code which would otherwise be safe if the
command was finished.

~~~
toomuchtodo
Fine. So this:

wget -O - [https://example.com/install.sh](https://example.com/install.sh) |
sudo sh

Turns into this:

wget -O install.sh
[https://example.com/install.sh](https://example.com/install.sh) && sudo sh
./install.sh

Big deal.

EDIT: Changed protocol to https for FiloSottile.

~~~
FiloSottile
"[https://"](https://"), please. Really, please.

EDIT: Thanks <3

~~~
toomuchtodo
Credit where credit is due :)

------
FiloSottile
Also, no-one still mentioned the fact that not doing it over HTTPS with a
client that checks certificates (you would be surprised at how many tools get
this wrong, sometimes or always) is a complete code execution MitM
vulnerability.

It is like giving away all the security built everywhere else and yelling
"YOLO".

~~~
plorkyeran
It's _almost_ as insecure as running a binary installer downloaded over HTTP,
which of course is something no one would ever do.

~~~
FiloSottile
It's _exactly_ as insecure as running an unsigned binary installer downloaded
over HTTP (without checking checksums), which no one should ever do.

(Because the authors should offer it over HTTPS, sign it, provide some
checksum or ship it through some package manager that knows better, if they
can't secure the process themselves)

~~~
joeshevland
But really? It'll get pretty onerous to check every checksum and guard every
entry point. I think we should fix the real problem.

~~~
mpyne
Well yes, that's the problem. Proper security is onerous, there's no use
crying about it. Either do it right or accept that you're insecure. :P

Fix all the problems you can, of course, defense in depth is always a good
idea. But hackers managing to alter a mirrored file without getting access to
the website itself happens more often than you'd like to admit, and even
hackers getting access to _both_ a website and a tarball can be mitigated by
signing tarballs instead of just listing sha sums.

If you trust your website won't be hacked to serve malicious code then you
might do away with signing and cryptographic checksums, but then that only
makes using TLS that much more important to avoid having your tarball zoinked
by a MitM during transit.

------
codezero
This keeps coming up on Hacker News, and while I'm sure the people on Hacker
News know this is bad, they probably still do it anyways because it's never
had an adverse effect for them.

Speaking for myself, this has never caused a problem for me, and I'll probably
keep doing it because it's convenient and that convenience is more valuable
weighed against the potential bad things that could happen. Most likely is the
case that the package just doesn't execute. The probability that it ends up on
rm or something destructive is probably very low, and if someone is actively
trying to MITM you, they will find a way if you are smart enough not to run
scripts from wget, most people aren't the target of this kind of very specific
attack.

Like Apple's TouchID – it may not really be secure, but it's very convenient,
and that will often be enough to make it mainstream.

~~~
chanux
We value convenience over security. It's the new normal.

I think we all understand this is not for the best but since it's normal we'd
just go with it.

~~~
codezero
It's not a discrete one or the other choice here. I think it's very
unrealistic to believe that for the average person this is a dangerous
security risk in practical terms.

The number of times people might do this is probably well below 100 and there
are much more risky day to day security faux pas than this.

~~~
chanux
Casual pipe to shell is just a symptom of something much bigger, obviously
present only in programmers.

One reason the bad is successful being the new normal is it came in smaller
doses you don't even notice that or you think it's ok to ignore.

------
minikomi
Russian Roulette as a Service :

    
    
        - A curl endpoint, piped to shell. 
        - 5 times out of 6, the result is echo "click"
        - The other time, is something more sinister.

~~~
voltagex_
Is bang.io available?

~~~
mcpherrinm
shellroulette.ru perhaps! Looks like you need to be a russian citizen to
register, though.

~~~
minikomi
Haha.. go for it.
[https://gist.github.com/minikomi/7261610](https://gist.github.com/minikomi/7261610)

~~~
voltagex_
You're nicer that I'd be - I was going to write one that was actually nasty :P

~~~
0x0
That's the source code he wants you to believe he is running :P

------
Filligree
I would rather people not pipe to shells at all. It doesn't sound very secure.
But if you have to do it, there are ways to avoid half-executed scripts:

foo() { ... }

~~~
fdr
Thanks for the idea. A modified variant created
[https://github.com/heroku/toolbelt/pull/74](https://github.com/heroku/toolbelt/pull/74)

------
alinajaf
If I were trying to get a shell on the boxes of as many startups as possible,
here's what I'd do:

1\. Create a new library, rubygem, infrastructure thingie that was genuinely
useful but had a non-trivial setup process.

2\. To aid in that setup process, create a script that I recommend be piped
into your shell.

3\. Somewhere in the middle of that script, silently tar up everything in the
users .ssh/ directory and send it somewhere I can see it.

~~~
alexwright
What you planning to do with my public keys? Or worst case encrypted private
keys.

Now putting something _into_ ~/.ssh/...

------
StefanKarpinski
Piping commands from the network may be a bad idea, but I pipe to my shell
locally all the time. The idea is that you write a shell loop that prints a
bunch of commands using `echo` and then once the commands look right, you pipe
them to `sh -v`. It's great for the exact _opposite_ reason piping from the
network is awful: you can see what you're about to execute before you execute
anything – I don't trust myself to write complicated shell loops without
seeing what I'm about to run first.

~~~
teddyh
Piping commands to a shell will subject your (probably already completely
specified) command names and argument to all sorts of modifications: brace
expansion, tilde expansion, parameter and variable expansion, command
substitution, arithmetic expansion, word splitting, and pathname expansion.

I never do that; If I need a trial loop, I do the loop with all the relevant
commands prepended by "echo" (and every ";", "&", "&&", and "||" properly
escaped).

~~~
StefanKarpinski
Yes, that's the point. You print the commands as you would type them manually,
make sure they look right and perhaps test a single one out. Then you can pipe
that whole thing into another shell. It's even reasonable to generate shell
commands from, say, perl. Rather than having perl call system, just have perl
print each command and then pipe the output of the whole thing to `sh -v`.

------
xfs
Before piping to your shell, don't copy-paste from website to terminal.
[https://news.ycombinator.com/item?id=5508225](https://news.ycombinator.com/item?id=5508225)

~~~
claudius
But there’s C-x C-e for that.

------
cbsmith
Unfortunately he's got this wrong. As long as the server returns a content
length (which is up to the project to set up correctly), wget will retry until
it gets the full length of the script. So the partial execute can't happen.

That's really about as well as you can do, because HTTP doesn't do a good job
of reporting errors. You could try to get the content length in advance and
then check against it after the download (which is basically what wget is
doing), but that won't buy you much. Most servers won't do Content-MD5, so
that's out. One smart thing to do would be to use "Accept-Encoding" to
download a compressed version of the script and then do a decompression test
before running. Alternatively, you can make the download script into a shell
archive style script, such that it doesn't do anything until you get to the
last byte, at which point it extracts out the _real_ script and runs it (which
wouldn't change what your install command is).

The whining about disabling the certificate check is also spurious. Most of
the time these are scripts pointing to a non-https URL but which redirect to
an HTTPS URL. You are already vulnerable when you do the HTTP request. On top
of that, almost nobody is doing DNSSec, so you are already vulnerable at the
DNS level. Even ignoring _that_ , Salt offers it as a solution _if_ you can't
get the certificate check to work. The alternative would be to provide you
with instructions on how to install a CA certificate, which someone is far
more likely to screw up and unless you've established trust of the
instructions themselves, could be just as vulnerable to a man-in-the-middle
attack. Offering instructions on how to disable the check is a perfectly
reasonable solution.

~~~
penguindev
I'm pretty sure wget won't buffer an entire document before writing anything
through the pipe to sh. And since it's possible for retries to fail, you have
to assume sh could be sent a partial document, followed by EOF, no matter if
http/https were used, or chunked/content-length, etc.

It is surprising to me that the shell will honor a command line without a
newline[1], but it does:

$ (echo -n "echo rm -rf /tmp/mydir"; exit 1) | sh

OUTPUT: rm -rf /tmp/mydir

Obviously if tmpdir got truncated to /tmp, or even /, bad things would happen.

[1] Not so suprising when you consider sh -c "command" works without a
trailing newline, I suppose.

~~~
cbsmith
> I'm pretty sure wget won't buffer an entire document before writing anything
> through the pipe to sh. And since it's possible for retries to fail, you
> have to assume sh could be sent a partial document, followed by EOF, no
> matter if http/https were used, or chunked/content-length, etc.

Nope. By default wget will literally try forever (you can even shut down your
network stack, wait for 10 minutes, and then turn it back on, and wget will
proceed from there), and until it closes the stream, the shell interpreter
will act as though data is still coming. Any _normal_ way of telling wget to
stop will also stop the shell interpreter. Worst case is you end up in some
"in the middle" state of the script, but you won't be misinterpreting the
script due to early termination of the stream.

------
GhotiFish
Does anyone remember that project that was downloading a script directly from
the most recent devel in a repository, and in order to demonstrate how
insecure that practice was, someone actually included an rm -rf /home/

The maintainer didn't check the commit and included it in develop, which
consequently was downloaded and... ect.

I'm looking for it.

~~~
endgame
Wasn't it some stupid valentine's day thing? I looked for it earlier but
couldn't find it; either my search-fu is failing or engines are getting worse.

------
serf
I agree, and it's a growing trend.

------
d0m
The piping is for beginners. Rather than saying "Execute this script" or
download and compile this tarball. It just works, magic. Advanced users will
obviously wget and quickly read it. But hey, sometimes I like to live
dangerously too, and I pipe things to my shell.

~~~
njharman
The experienced know better. Beginners are exactly the people who you should
not be teaching poor habits too.

------
zokier
I think this merely hilights the bigger underlying issue: the lack of
transactionality. If the install script were wrapped in a transaction, then
premature exit or end of input (for whatever reason) would cause no harm
because the transaction would have not been commited.

------
ivanhoe
Well, it's risky, but you are anyway installing the software from those guys,
aren't you? Do you audit every single source of every single app? No, you
don't... everything is a matter of balance between a security and a
convenience..

------
kolev
It's a really disturbing craze to install server software MS-DOS style and not
use package managers! At the end of the day, building native packages with
FPM, let's say, isn't such a big deal. At least, do an installer package.

------
vhost-
wget -O - [https://example.com/install.sh](https://example.com/install.sh) |
vim

A work around I've been using for a while. I then :saveas and execute it
myself once I verify it's not doing anything fishy.

~~~
e12e
Personally I prefer to save to file, then inspect (with less or vim) -- but
some scripts are a little too clever. Lein is one great example of something
that _works_ , but at the same time is pretty scary (read time-consuming to
verify by hand).

Another favourite are those tiny installers that wrap a nice "curl
[http://yeah.not.ssl.com/boom.sh](http://yeah.not.ssl.com/boom.sh) | sudo -s"
_inside_ the "installer". Great for building confidence in a project.

Maybe the best thing to do is to just distribute the shell scripts encrypted
with the private key of the project, thereby forcing users to run it through
gpg. Or use a gpg-archive -- both PGP and GPG has support for this, see gpg-
zip(1).

That way you can at least establish a chain of trust that goes straight back
to the author, and links the installer(s) directly with a gpg-key. It's not
really possible to get anything better than that. And you avoid having a
separate .sig-file.

Apparently there's also a GNU project for distributing signed archives -- but
it doesn't appear to have wide support.

~~~
bigiain
Which reduces the problem to hip, cool projects telling newbies to just:

wget -O - [http://example.ru/public.key](http://example.ru/public.key) | gpg
--fast-import && sudo gpg --trust 0xABADIDEA

------
lcedp
Good point. Script like that should be provided:

    
    
        wget -O $URL > /tmp/f
        if echo `md5sum /tmp/f` | grep "^8a17590b8e78f8f1cf4983e0e691b7ab"; then
          sudo sh /tmp/f
        fi

------
telephonetemp
What command can you use in a pipe chain to fully read the input until EOF
before passing it on? I'm not a big fan of piping to the shell but such a
command could be useful for network applications in general.

~~~
oftenwrong
One option is vipe from moreutils

[http://joeyh.name/code/moreutils/](http://joeyh.name/code/moreutils/)

~~~
codl
Another is sponge from the same package.

------
chrsw
There is a --user-agent option supported by wget and curl. Maybe that could
help increase confidence that something will work as it should. Or at least
help investigate weirdness.

~~~
rytis
And slowly a humble `wget -O - <url>|sh` grows into a foot long command with
user agent settings, save first/execute later, etc, etc... Why not use a
package manager then? Added bonus - version control.

------
general_failure
meteor does same thing -
[https://github.com/meteor/meteor](https://github.com/meteor/meteor)

------
xr09
I only pipe in-house projects I host myself and know exactly what will be the
result, piping from the web seems rather naive.

------
MrKey
Don't wire wrap your fuses.

------
rjblackman
how about using this instead?
[https://github.com/liftoff/GateOne](https://github.com/liftoff/GateOne)

------
marcosnils
Irrelevant!.

------
secstate
While i agree with the general folly of piping to a shell, have you ever
actually tried to do a rm -rf / ? Most modern posix systems will catch you,
even if you sudo it.

Also, barring that example I can't come up with many other horrible scenario.
Unfortunate ones, sure. But not catastrophic. And as someone else said, adding
random ppas would allow much worse things, and people do that all the time.

~~~
mturmon

      dir_user_old=/home/luser/.old_version
      dir_user=/home/luser
      rm -rf $dir_user_old
                      ^

Truncate at the caret (^), and it turns into:

    
    
      dir_user_old=/home/luser/.old_version
      dir_user=/home/luser
      rm -rf $dir_user
    

which removes your home directory.

Incidentally, I think the last example in the OP ("rm -rf /") is wrong. The
"/" would never be transmitted, it is part of the variable $TMP_DIR which is
expanded on the local system, not the remote server. But the idea and the
other test with echo seem correct.

~~~
codezero
Assuming the probability that you drop connection is evenly distributed
amongst all characters, even if there is no payload and this is all that is
executed then there's barely a 1% chance of the truncation happening in the
way you describe.

Considering that there is usually a sizable payload and the probability of a
dropped connection is not evenly distributed and is probably very low, the
scenario gets even less likely.

Yes, it's possible, but it's also possible to rm -rf / because you typed a
path wrong and I bet the probability of human error is much higher than the
probability of this shell trick screwing you over. People have rm -rf /'d
their systems, but even this isn't a good reason to advocate for say, removing
rm entirely or not allowing people to type into the shell :P

