Lecturing About curl-bash Pipelines
Tags: bash, linux
Published: 2021-10-26 23:11:49

Grab some popcorn 'cuz we're here to talk about curl-bash pipelines on Linux. Personally I don't like them for maintenance reasons (how do you remove things you installed like this?), and I want to nag my fellows to not casually run a wall-of-script from the Internet.


### Interactive vs non-interactive shells

When a bash shell is started at login it's considered *interactive* and expects to be attached to a TTY. When we run `curl https://example.com/script.sh | bash` the bash process started is *non-interactive* and expects no human input. We can confirm a bash process' interactivity either by looking at the process table (run `ps`) or by checking the builtin variable `$-` (the shell's starting arguments) for an 'i' character.


### PIDs, Parents and "Siblings"

Part of the classic Unix process model is that a process has a parent that created it and may have child processes that it created. For the purpose of this exercise let's call processes that were created by the same immediate parent process "siblings".

We can find the current PID's parent with `ps -p $$ -o ppid -h`. Running a wide-ranging `ps` and grepping for a common parent PID will show us all processes started by the same parent PID as our shell.


### Seeing connections in procfs

Now that we have a list of all a PID's siblings we can cat-out procfs to see if any of those processes are connected to a known HTTP port. 

```
$ cat /proc/21456/net/tcp
  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                                                     
   0: 00000000:2328 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 264000 1 0000000000000000 100 0 0 10 0                    
   1: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 264692 1 0000000000000000 100 0 0 10 0                    
   2: 0100007F:0019 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 48214 1 0000000000000000 100 0 0 10 0                     
   3: F71CFE0A:0016 224EFBCF:FBC3 01 00000034:00000000 01:00000015 00000000     0        0 67266130 3 0000000000000000 21 4 17 10 54                 
   4: F71CFE0A:9EE0 856EC7B9:01BB 08 00000000:00000001 02:000012E3 00000000 150001        1 67376561 2 0000000000000000 20 4 0 10 -1                 
   5: F71CFE0A:88D8 0200FE0A:0035 06 00000000:00000000 03:0000028A 00000000     0        0 0 3 0000000000000000                                      
   6: F71CFE0A:89C8 0200FE0A:0035 06 00000000:00000000 03:00001073 00000000     0        0 0 3 0000000000000000                                      
   7: F71CFE0A:960E 98A27032:01BB 01 00000000:00000000 02:000008BC 00000000     0        0 67384497 2 0000000000000000 26 4 6 10 -1                  
   8: F71CFE0A:DC02 71529D36:270D 01 00000000:00000000 00:00000000 00000000     0        0 67343092 1 0000000000000000 20 4 1 10 -1       
```

This looks pretty-intense but we're only interested in the last 4 characters (TCP port) of the third column (remote host). These values are all in *sigh* hex, so it will be fastest to just grep for a few well-known ports like `0050` (80), `01BB` (443), `1F90` (8080 and `20FB` (8443)


### Putting it all together

Now that we can detect non-interactive shell's children connecting to a well-known web port and suspend their process how do we get this functionality deployed? Let's save our final shell script as /usr/local/bin/blocker.sh:

```
if [[ $(grep "$(cat /proc/$$/cmdline | tr '\000' "\n" | tail -1)$" /etc/shells) ]]
then
    echo $- | grep -qv i && ps awwwx -ocmd,pid,ppid | grep "$(ps -p $$ -o ppid -h)$" |  while read p
    do
        sib=$(echo "$p" | awk '{ print $(NF-1) }')
        egrep -q ":0050|:01BB|:1F90|:20FB"  /proc/$sib/net/tcp 2>/dev/null && kill -SIGSTOP $sib 2>/dev/null &&\
        echo "If you 𝙧𝙚𝙖𝙡𝙡𝙮 want to execute some rando script from the Internet type ctrl-Z and then 𝗳𝗴"
    done | uniq
fi
```

and set `$BASH_ENV=/usr/local/bin/blocker.sh` to execute our script at startup of every non-interactive bash shell.

As an example, here's this script pausing an install of Omnitruck

```
; curl -s https://omnitruck.chef.io/install.sh | bash
If you 𝙧𝙚𝙖𝙡𝙡𝙮 want to execute some rando script from the Internet type ctrl-Z and then 𝗳𝗴

[1]+  Stopped                 curl -s https://omnitruck.chef.io/install.sh | bash

```