diff --git a/.env.template b/.env.template index 25323a33..76808e88 100644 --- a/.env.template +++ b/.env.template @@ -3,4 +3,12 @@ DISCOUNTS_PORT=2814 AUTH_PORT=7578 POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres -DD_API_KEY= \ No newline at end of file +DD_API_KEY= +ATTACK_SSH=0 +ATTACK_GOBUSTER=0 +ATTACK_HYDRA=0 +ATTACK_HOST=nginx +ATTACK_PORT=80 +ATTACK_SSH_INTERVAL=0 +ATTACK_GOBUSTER_INTERVAL=0 +ATTACK_HYDRA_INTERVAL=0 \ No newline at end of file diff --git a/.github/workflows/attackbox.yml b/.github/workflows/attackbox.yml new file mode 100644 index 00000000..ae391e87 --- /dev/null +++ b/.github/workflows/attackbox.yml @@ -0,0 +1,50 @@ +name: Attackbox + +on: + push: + branches: [ main ] + paths: + - services/attackbox/** + workflow_dispatch: + branches: [ main ] + +defaults: + run: + working-directory: attackbox + +jobs: + + build: + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Login to ECR + id: login-ecr + uses: docker/login-action@v1 + with: + registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: ./services/attackbox + platforms: linux/amd64 + push: true + tags: ${{ secrets.PUBLIC_ECR_REGISTRY }}/storedog/attackbox:latest + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 098dba34..2303e61d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,6 +39,7 @@ jobs: ${{ secrets.PUBLIC_ECR_REGISTRY }}/storedog/backend ${{ secrets.PUBLIC_ECR_REGISTRY }}/storedog/discounts ${{ secrets.PUBLIC_ECR_REGISTRY }}/storedog/ads + ${{ secrets.PUBLIC_ECR_REGISTRY }}/storedog/attackbox ${{ secrets.PUBLIC_ECR_REGISTRY }}/storedog/auth ) diff --git a/docker-compose.yml b/docker-compose.yml index 3d199edd..b8ccbd1a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -103,12 +103,11 @@ services: - DD_APPSEC_ENABLED=true build: context: ./services/discounts - command: flask run --port=${DISCOUNTS_PORT} --host=0.0.0.0 # If using any other port besides the default 8282, overriding the CMD is required volumes: - "./services/discounts:/app" ports: - "${DISCOUNTS_PORT}:${DISCOUNTS_PORT}" - - "22" + - "22:22" networks: - storedog-net auth: @@ -154,6 +153,25 @@ services: - /sys/fs/cgroup/:/host/sys/fs/cgroup:ro networks: - storedog-net + attackbox: + build: + context: ./services/attackbox + profiles: + - attackbox + environment: + - ATTACK_GOBUSTER + - ATTACK_HYDRA + - ATTACK_GOBUSTER_INTERVAL + - ATTACK_HYDRA_INTERVAL + - ATTACK_SSH + - ATTACK_SSH_INTERVAL + - ATTACK_HOST + - ATTACK_PORT + networks: + - storedog-net + depends_on: + - web + - discounts volumes: redis: diff --git a/services/attackbox/.dockerignore b/services/attackbox/.dockerignore new file mode 100644 index 00000000..c244720b --- /dev/null +++ b/services/attackbox/.dockerignore @@ -0,0 +1,2 @@ +Dockerfile* +.dockerignore \ No newline at end of file diff --git a/services/attackbox/Dockerfile b/services/attackbox/Dockerfile new file mode 100644 index 00000000..6b15716c --- /dev/null +++ b/services/attackbox/Dockerfile @@ -0,0 +1,43 @@ +FROM kalilinux/kali-rolling:latest +RUN mkdir /app + +ADD https://github.com/OJ/gobuster/releases/download/v3.1.0/gobuster-linux-amd64.7z /app +ADD https://github.com/vanhauser-thc/thc-hydra/archive/refs/tags/v9.3.zip /app + +# Install packages +RUN export DEBIAN_FRONTEND=noninteractive && \ + apt-get update && \ + apt-get upgrade --yes && \ + apt-get install --yes libssl-dev libssh-dev libidn11-dev libpcre3-dev \ + libgtk2.0-dev libmariadb-dev libpq-dev libsvn-dev \ + firebird-dev libmemcached-dev libgpg-error-dev \ + libgcrypt20-dev openssh-client iputils-ping wordlists \ + build-essential libpq-dev p7zip unzip jq && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Create user and ssh dir +RUN useradd -m user +RUN mkdir -p /home/user/.ssh +COPY keys/storedog-leaked-key /home/user/.ssh/id_rsa + +# Install gobuster and hydra +WORKDIR /app +RUN gzip -d /usr/share/wordlists/rockyou.txt.gz +RUN 7zr e ./gobuster-linux-amd64.7z && chmod +x gobuster +RUN unzip v9.3.zip && cd thc-hydra-9.3/ && ./configure && make && make install + +# Copy attack script and keys +COPY . . + +# Update permissions so ssh keys can be accessed outside sudo user +RUN chown -R user:user /home/user/.ssh +RUN chmod +x attack.sh +RUN chown -R user ./keys + +# Switch back to new user so we can SSH properly +USER user +RUN chmod 400 /home/user/.ssh/id_rsa +RUN chmod 400 ./keys/attacker-key + +CMD [ "./attack.sh"] \ No newline at end of file diff --git a/services/attackbox/README.md b/services/attackbox/README.md new file mode 100644 index 00000000..652833fd --- /dev/null +++ b/services/attackbox/README.md @@ -0,0 +1,32 @@ +# Attack Box + +This is a container that simulates an adversary attempting to hack the online store. + +The script has 3 stages: +1) Malicious SSH configuration +2) Gobuster +3) Hydra + +## Deployment + +The attack box is configurable via environment variables found in the `.env` file. +- **ATTACK_SSH**: Set to `1` to run the SSH attack script against the discounts container +- **ATTACK_GOBUSTER**: Set to `1` to run the Gobuster tool for crawling directories on the container specified in `ATTACK_HOST` +- **ATTACK_HYDRA**: Set to `1` to run the Hydra tool for brute force login +- **ATTACK_SSH_INTERVAL**: Number of seconds between SSH attack invocations (if ommited, SSH attack will run once) +- **ATTACK_GOBUSTER_INTERVAL**: Number of seconds between GOBUSTER invocations (if ommited, GOBUSTER will run once) +- **ATTACK_HYDRA_INTERVAL**: Number of seconds between HYDRA invocations (if ommited, HYDRA will run once) +- **ATTACK_PORT**: The web port you want to run the attacks against for hydra and dirbuster. +- **ATTACK_HOST**: The web host that hydra and dirbuster will attack. ( Probably frontend or nginx ) + +For example, if you wanted to run Gobuster every 60 seconds and Hydra ever 90 seconds, your `.env` file would look like this: +``` +ATTACK_GOBUSTER=1 +ATTACK_GOBUSTER_INTERVAL=60 +ATTACK_HYDRA=1 +ATTACK_HYDRA_INTERVAL=90 +``` +## How to start the attackbox service +1. `cp .env.template .env` +2. Set the attack config variables as explained above +3. Run `docker compose --profile attackbox up ` \ No newline at end of file diff --git a/services/attackbox/attack.sh b/services/attackbox/attack.sh new file mode 100644 index 00000000..7e0f6ecb --- /dev/null +++ b/services/attackbox/attack.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +function ssh_attack() +{ +# attempt to copy attacker key to discounts +scp -o StrictHostKeyChecking=no ./keys/attacker-key.pub test@discounts:/home/test/.ssh + +# attempt to update authorized_keys to have attacker key +ssh -o StrictHostKeyChecking=no test@discounts /bin/bash <> /home/test/.ssh/authorized_keys +exit +EOT + +# attempt to clear log file and zero out unallocated disk space +ssh -o StrictHostKeyChecking=no -i ./keys/attacker-key test@discounts /bin/bash <> /home/test/.ssh/authorized_keys +USER root # Let Flask know what to boot ENV FLASK_APP=discounts.py -# Start the app using ddtrace so we have profiling and tracing -ENTRYPOINT ["ddtrace-run"] -CMD flask run --port=8282 --host=0.0.0.0 +# Install dependencies via pip and avoid caching build artifacts +RUN pip install --no-cache-dir -r requirements.txt +RUN chmod +x ./my-wrapper-script.sh + +# Pass in Port mapping (default to 8282) +ARG DISCOUNTS_PORT=8282 +# Because CMD is a runtime instruction, we have to create an additional ENV var that reads the ARG val +# Only ENV vars are accessible via CMD +ENV DISCOUNTS_PORT ${DISCOUNTS_PORT} + +# required to get logs out of the container +ENTRYPOINT ["/usr/bin/dumb-init", "--"] + +# runs sshd and flask server +CMD ./my-wrapper-script.sh ${DISCOUNTS_PORT} diff --git a/services/discounts/keys/README.md b/services/discounts/keys/README.md new file mode 100644 index 00000000..6153ae3b --- /dev/null +++ b/services/discounts/keys/README.md @@ -0,0 +1,3 @@ +### Attention + +These keys are used for training purposes and were **intentionally** added to this repository. These keys are self-contained within the docker images used by `deploy/docker-compose-fixed-instrumented-attack.yml`. They do not reside in any other external server. Please do not flag for security purposes and/or a bug bounty program. \ No newline at end of file diff --git a/services/discounts/keys/storedog-leaked-key.pub b/services/discounts/keys/storedog-leaked-key.pub new file mode 100644 index 00000000..b8784d31 --- /dev/null +++ b/services/discounts/keys/storedog-leaked-key.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDUXq470vMlwLcCrG7BvwpE7eJQSY6dFpRMS7wHhIBECkD2w/ej2oXLA7lj5Go+A4oWEjXytNwBE7NwdMKGM5yHfaWp8zg7sqeDmx8ODrlW4shDPFBzqxP3XLZ7svYjcFqKDNpsKH74a+uk0wRGuVMWzjmednSAokGsPz84lmnHhuwKRaeVL99MteBeBD23youq9nrI7XmCZZ1wGsuN8yEUBOp3vl8dlD3PBXYCYguFIdnVufvQ0WuzKeayK/dbbi9moxVAHG7pRF7V8MMKyP2ySVV9/FcFNLTETCJLl7RKQCFhyMcUZ03Ehx3j4is7VCceMzJz49Mey2vgIR7YsQSLEiTSdCvGvnYyWZqYkTSSA4ObRV0ifSzyVcfouzP8QvD4YWHDSRjac4lI++Jo+/HdGnHQ6YA0cMlu7Px6Mje65TMcFq8EiwIk552fEem4sLG65h6OKAqQIt+qnBnN2tmKMxe/bqHOECzasyVREkiWCxqzUNt14dUmhqve/K4dXLk= colin.cole@COMP-C02CD0UULVDN diff --git a/services/discounts/my-wrapper-script.sh b/services/discounts/my-wrapper-script.sh new file mode 100755 index 00000000..e3f7a300 --- /dev/null +++ b/services/discounts/my-wrapper-script.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Start the first process +service ssh start && service rsyslog start && ln -sf /proc/1/fd/1 /var/log/auth.log +status=$? +if [ $status -ne 0 ]; then + echo "Failed to start my_first_process: $status" + exit $status +fi + +# Start the second process +ddtrace-run flask run --port=$1 --host=0.0.0.0 +status=$? +if [ $status -ne 0 ]; then + echo "Failed to start my_second_process: $status" + exit $status +fi + +# Naive check runs checks once a minute to see if either of the processes exited. +# This illustrates part of the heavy lifting you need to do if you want to run +# more than one service in a container. The container exits with an error +# if it detects that either of the processes has exited. +# Otherwise it loops forever, waking up every 60 seconds +while sleep 60; do + ps aux |grep my_first_process |grep -q -v grep + PROCESS_1_STATUS=$? + ps aux |grep my_second_process |grep -q -v grep + PROCESS_2_STATUS=$? + # If the greps above find anything, they exit with 0 status + # If they are not both 0, then something is wrong + if [ $PROCESS_1_STATUS -ne 0 -o $PROCESS_2_STATUS -ne 0 ]; then + echo "One of the processes has already exited." + exit 1 + fi +done \ No newline at end of file