From 13e2f4b74530b22eb9c20bafc241b2eda943dcfd Mon Sep 17 00:00:00 2001 From: sose Date: Tue, 16 Nov 2021 18:11:29 +0000 Subject: [PATCH] Major rewrite in python, text editors no longer allowed --- .gitignore | 6 ++ image/Dockerfile | 17 +++-- image/banner | 2 +- image/bin/progress | 20 ------ image/bin/scores | 4 ++ image/bin/submit | 48 +------------ image/clues/test.txt | 1 - image/package.list | 4 -- image/rules.md | 1 + image/suicidebash.bashrc | 2 +- new_image.sh | 11 ++- readme.md | 16 ++--- reset.sh | 8 +-- scoreboard.txt | 1 - scores/.gitkeep | 0 server.py | 145 +++++++++++++++++++++++++++++++++++++++ servers/deathlistener.sh | 87 ----------------------- servers/gamelog.sh | 6 -- servers/scoreboard.sh | 8 --- solutions.txt | 5 -- start.sh | 27 ++------ 21 files changed, 191 insertions(+), 228 deletions(-) delete mode 100755 image/bin/progress create mode 100755 image/bin/scores delete mode 100644 image/clues/test.txt delete mode 100644 scoreboard.txt create mode 100644 scores/.gitkeep create mode 100755 server.py delete mode 100755 servers/deathlistener.sh delete mode 100755 servers/gamelog.sh delete mode 100755 servers/scoreboard.sh delete mode 100644 solutions.txt diff --git a/.gitignore b/.gitignore index 1f5cf21..2c763e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ .*.swp .*.swo +*.pubkey +*.pub sose.pubkey +table.route +solutions.txt +oldstuff/ +image/inhere diff --git a/image/Dockerfile b/image/Dockerfile index 68dcae0..bc5e8e3 100644 --- a/image/Dockerfile +++ b/image/Dockerfile @@ -18,7 +18,7 @@ COPY --chown=root ./slbr.env /etc COPY --chown=root ./package.list /package.list COPY --chown=root ./insults.txt /insults.txt -COPY ./bin/progress /usr/local/bin/ +COPY ./bin/scores /usr/local/bin/ COPY ./bin/submit /usr/local/bin/ COPY ./banner /etc/motd COPY ./pubkey /pubkey @@ -28,12 +28,15 @@ RUN apt remove bash-completion \ RUN apt update \ && apt install -y $(cat /package.list) \ - && rm /package.list + && apt -y remove nano \ + && rm /package.list \ + && rm /bin/grep \ + && rm /bin/tar -RUN sed -i "s/{{ HOST_IP }}/$HOST_IP/g" /etc/bash.bashrc /usr/local/bin/progress /usr/local/bin/submit \ - && sed -i "s/{{ SLBR_PORT }}/$SLBR_PORT/g" /etc/bash.bashrc /usr/local/bin/progress /usr/local/bin/submit \ - && sed -i "s/{{ INFO_PORT }}/$INFO_PORT/g" /etc/bash.bashrc /usr/local/bin/progress /usr/local/bin/submit \ - && sed -i "s/{{ LOG_PORT }}/$LOG_PORT/g" /etc/bash.bashrc /usr/local/bin/progress /usr/local/bin/submit \ +RUN sed -i "s/{{ HOST_IP }}/$HOST_IP/g" /etc/bash.bashrc /usr/local/bin/scores /usr/local/bin/submit \ + && sed -i "s/{{ SLBR_PORT }}/$SLBR_PORT/g" /etc/bash.bashrc /usr/local/bin/scores /usr/local/bin/submit \ + && sed -i "s/{{ INFO_PORT }}/$INFO_PORT/g" /etc/bash.bashrc /usr/local/bin/scores /usr/local/bin/submit \ + && sed -i "s/{{ LOG_PORT }}/$LOG_PORT/g" /etc/bash.bashrc /usr/local/bin/scores /usr/local/bin/submit \ && mkdir /home/$USERNAME \ && mkdir /run/sshd \ && mkdir /home/$USERNAME/.ssh \ @@ -42,7 +45,7 @@ RUN sed -i "s/{{ HOST_IP }}/$HOST_IP/g" /etc/bash.bashrc /usr/local/bin/progress && echo "PasswordAuthentication no" >> /etc/ssh/sshd_config \ && echo "Port $SSHD_PORT" >> /etc/ssh/sshd_config -COPY ./clues /home/$USERNAME/clues +COPY ./inhere /home/$USERNAME/inhere COPY ./rules.md /home/$USERNAME RUN useradd $USERNAME -d /home/$USERNAME \ diff --git a/image/banner b/image/banner index 209239b..6f8b229 100644 --- a/image/banner +++ b/image/banner @@ -10,4 +10,4 @@ Welcome to: For rules, `cat ~/rules.md` To submit your solution for a challenge, run `submit ""` -To see your current progress, run `progress` +To see your current scores, run `scores` diff --git a/image/bin/progress b/image/bin/progress deleted file mode 100755 index 31a2bd2..0000000 --- a/image/bin/progress +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -scoreboard="$(nc -q0 -d {{ HOST_IP }} {{ INFO_PORT }})" -my_ip="$(ip addr | grep 'scope global eth0' | cut -f6 -d ' ')" -total_challenges="$(echo "$scoreboard" | head -n1)" -completed_challenges="$( - echo "$scoreboard" \ - | grep "$my_ip" \ - | cut -f2- -d ' ' \ - | tr -d ' ' \ - | tr ',' '\n' \ - | sort \ - | tr '\n' ',' \ - | sed 's/,/, /g' \ - | sed 's/^, //' \ - | sed 's/, $//' -)" -num_completed="$(echo $completed_challenges | grep -oE '[0-9]' | wc -l)" -echo "There are $total_challenges total challenges" -echo "You have completed the following challenges: $completed_challenges" -echo "You have completed $num_completed of the $total_challenges total challenges" diff --git a/image/bin/scores b/image/bin/scores new file mode 100755 index 0000000..f29fb22 --- /dev/null +++ b/image/bin/scores @@ -0,0 +1,4 @@ +#!/bin/bash + +echo "SCORE" \ +| nc {{ HOST_IP }} {{ SLBR_PORT }} diff --git a/image/bin/submit b/image/bin/submit index 92536bf..1d0a0c4 100755 --- a/image/bin/submit +++ b/image/bin/submit @@ -5,54 +5,10 @@ submit.sh - submit an slbr solution to be verified usage: ./submit for example: ./submit 3 \"this is the solution for challenge 3\" " - challenge_no="$1" submitted_solution="$2" [ -z "$submitted_solution" ] && echo "$help_text" && exit -echo "Submitting \"$submitted_solution\" as the solution to challenge #$challenge_no..." - -my_ip="$(ip addr | grep 'scope global eth0' | cut -f6 -d ' ')" - -scoreboard="$(nc -q0 -d {{ HOST_IP }} {{ INFO_PORT }})" -prev_score="$( - echo "$scoreboard" \ - | grep "$my_ip" -)" -max_score="$(echo "$scoreboard" | head -n1)" -current_score="" - -echo "SOLUTION $challenge_no $submitted_solution" \ -| nc -q0 {{ HOST_IP }} {{ SLBR_PORT }} - -# give the server ample time to process the request -echo "Verifying..." -sleep 5 - -# we need to request the scoreboard twice here for the score to properly update -nc -q0 -d {{ HOST_IP }} {{ INFO_PORT }} > /dev/null -current_score="$( - nc -q0 -d {{ HOST_IP }} {{ INFO_PORT }} \ - | grep "$my_ip" -)" - -if [ "$prev_score" != "$current_score" ] -then - echo "w00t! You got the right answer!!!" - num_correct="$( - echo $current_score \ - | grep -oE '[0-9],' \ - | wc -l - )" - echo "$num_correct correct answers so far" - if [ "$num_correct" = "$max_score" ] - then - printf "\e[0;92]" - echo "Hey! thats all $max_score! that means YOU WIN!" - echo "!!!!!!!CONGRATULATIONS!!!!!!!" - printf "\e[0;91m THIS CONTAINER WILL NOW SELF DESTRUCT\e[0m\n" - fi -else - echo "|X_x| Your answer was rejected!!!." -fi +echo "SUBMIT $challenge_no $submitted_solution" \ +| nc {{ HOST_IP }} {{ SLBR_PORT }} diff --git a/image/clues/test.txt b/image/clues/test.txt deleted file mode 100644 index 763f5c8..0000000 --- a/image/clues/test.txt +++ /dev/null @@ -1 +0,0 @@ -ubj qb lbh fcryy ahzoref? diff --git a/image/package.list b/image/package.list index 40d43e7..5a95747 100644 --- a/image/package.list +++ b/image/package.list @@ -1,9 +1,5 @@ netcat-openbsd -emacs-nox -curl nano -vim tmux man -xxd openssh-server diff --git a/image/rules.md b/image/rules.md index 729aa0d..b26fe71 100644 --- a/image/rules.md +++ b/image/rules.md @@ -19,6 +19,7 @@ are subject to normal bash escaping rules) - You may write scripts - You may not use sh, you must use bash - You may not install more packages +- You may not use programs other than the ones provided on your container - You may not attempt to obtain root access - You may not communicate with other players - You may not interact or tamper with the containers or environments of other diff --git a/image/suicidebash.bashrc b/image/suicidebash.bashrc index 26351af..20bbfc5 100644 --- a/image/suicidebash.bashrc +++ b/image/suicidebash.bashrc @@ -15,7 +15,7 @@ function command_not_found_handle { } function log_command { - echo "$(history 1)" | nc -q0 {{ HOST_IP }} {{ LOG_PORT }} + echo "LOG $(history 1)" | nc -q0 {{ HOST_IP }} {{ SLBR_PORT }} } readonly -f command_not_found_handle diff --git a/new_image.sh b/new_image.sh index c1548a6..c9b8dc8 100755 --- a/new_image.sh +++ b/new_image.sh @@ -1,3 +1,5 @@ +export XDG_RUNTIME_DIR=/home/slbr-admin/.docker/run +export DOCKER_HOST=unix:///home/slbr-admin/.docker/run/docker.sock username="$1" pubkey_path="$2" slbr_port="1337" @@ -30,12 +32,7 @@ get_user_sshd_port() { get_user_sshd_port cp "$pubkey_path" image/pubkey -host_ip="$( - ip -br -4 addr \ - | grep eth0 \ - | awk '{print $3}' \ - | cut -f 1 -d '/' -)" +host_ip="host.docker.internal" docker build \ -t slbr:$username \ @@ -59,6 +56,8 @@ container_id="$( --cap-add SETPCAP \ --cap-add SETUID \ --cap-add SYS_CHROOT \ + --network slbr \ + --add-host host.docker.internal:host-gateway \ -p $user_sshd_port:$user_sshd_port \ -h slbr \ -d "slbr:$username" diff --git a/readme.md b/readme.md index 4a89411..d98e51d 100644 --- a/readme.md +++ b/readme.md @@ -22,21 +22,20 @@ contact ~ben or sose on tilde.chat (in the #slbr channel) to sign up. ## Configuration - Configuring a new set of challenges is as simple as editing the `solutions.txt` file with their solutions, and providing new clues in the -`./image/clues` folder. +`./image/inhere` folder. - In the `solutions.txt` file, the line number of each solution corresponds to the challenge it is the solution for. Solutions may not take up multiple lines. ## Prerequisites - docker -- pslist - jq -- BSD style netcat (`netcat-openbsd` on debian) +- python3 +- Twisted (`pip3 install twisted`) +- Python Docker SDK (`pip3 install docker`) ## Pre-Setup -- Make sure your ports 1337, 1338 and 1339 are not exposed to the internet, as - SLBR will use them internally -- Make sure to employ user namespace remapping with docker, eg. by starting - dockerd with `dockerd --userns-remap=default` +- Make sure your port 1337 is not exposed to the internet, as SLBR will use it + internally ## Setup - `mkdir /home/slbr-admin` @@ -47,10 +46,11 @@ contact ~ben or sose on tilde.chat (in the #slbr channel) to sign up. - `cd` - `git clone https://tildegit.org/sose/SLBRV2` - `cd SLBRV2` +- Run the docker daemon rootlessly, i.e. with `dockerd-rootless.sh` - You are now ready to manage an slbr game ## Running the game -- run `./start.sh` to start the listeners for various game events +- run `./start.sh` to start the sever listening for various game events - run `./new_image.sh ` to create a new user - All game events and user commands will be logged to log.txt - You can `./reset.sh` to cleanup after a game has finished diff --git a/reset.sh b/reset.sh index baa5eb3..cf6db2f 100755 --- a/reset.sh +++ b/reset.sh @@ -1,8 +1,6 @@ #!/bin/sh -num_challenges="$( - wc -l solutions.txt \ - | cut -f1 -d ' ' -)" +export XDG_RUNTIME_DIR=/home/slbr-admin/.docker/run +export DOCKER_HOST=unix:///home/slbr-admin/.docker/run/docker.sock docker ps \ -f label="description=SLBR User Container" \ --format "{{.ID}}" \ @@ -12,5 +10,5 @@ docker images -a \ | awk '{print $3}' \ | xargs docker rmi -f -printf "$num_challenges\n" > scoreboard.txt printf "" > log.txt +rm scores/* diff --git a/scoreboard.txt b/scoreboard.txt deleted file mode 100644 index 7ed6ff8..0000000 --- a/scoreboard.txt +++ /dev/null @@ -1 +0,0 @@ -5 diff --git a/scores/.gitkeep b/scores/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/server.py b/server.py new file mode 100755 index 0000000..973eb01 --- /dev/null +++ b/server.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +import os +import docker +import ipaddress +from twisted.protocols.basic import LineReceiver +from twisted.internet.protocol import Factory, Protocol +from twisted.internet.endpoints import TCP4ServerEndpoint +from twisted.internet import reactor + +class SLBRManager(Protocol): + def __init__(self, config): + self.client_addr = None + self.config = config + def suicide(self): + slbr_containers = self.config['docker_client'].containers.list( + filters = {'label': 'description=SLBR User Container'} + ) + for container in slbr_containers: + if container.attrs['NetworkSettings']['Networks']['slbr']['IPAddress'] == self.client_addr: + container.remove(force=True) + + def connectionMade(self): + self.client_addr = self.transport.getPeer().host + if ( + ipaddress.ip_address(self.client_addr) + not in + ipaddress.ip_network(self.config['docker_netmask']) + ): + print('Dropping client {} not in docker network'.format(self.client_addr)) + self.transport.loseConnection() + + def dataReceived(self, data): + message = data.decode('utf8').split(' ') + command = message[0].replace('\n', '') + if len(message) > 1: + args = message[1:] + + if command == 'LOG': + entry = '[{}]: {}'.format(self.client_addr, ' '.join(args)) + print(entry) + + elif command == 'DEAD': + print('Killing {}'.format(self.client_addr)) + self.suicide() + self.transport.loseConnection() + elif command == 'SCORE': + message = 'PLAYER | CHALLENGES COMPLETED\n' + ('-' * 30) + '\n' + slbr_containers = self.config['docker_client'].containers.list( + filters = {'label': 'description=SLBR User Container'} + ) + for player_addr in os.listdir('./scores'): + player_name = 'err' + for container in slbr_containers: + if container.attrs['NetworkSettings']['Networks']['slbr']['IPAddress'] == player_addr: + player_name = container.attrs['Config']['Image'].replace('slbr:', '') + break + message += player_name + ': ' + with open('./scores/{}'.format(player_addr)) as score_file: + completed_challenges = ', '.join(score_file.readlines()).replace('\n', '') + message += completed_challenges + '\n' + + self.transport.write(bytes(message, 'utf8')) + self.transport.loseConnection() + elif command == 'SUBMIT' and len(args) == 2: + try: + challenge_num = int(args[0]) + except ValueError: + self.transport.loseConnection() + return + + submitted_solution = args[1].replace('\n', '') + with open(self.config['solutions_file']) as solutions_file: + correct_solutions = solutions_file.readlines() + correct_solutions = list(map(lambda x: x.replace('\n', ''), correct_solutions)) + num_solutions = len(correct_solutions) + + print( + '{} submitted \"{}\" as solution for challenge {}'.format( + self.client_addr, + submitted_solution, + challenge_num + ) + ) + + game_ended = False + if challenge_num <= num_solutions: + print('(Correct answer is {})'.format(correct_solutions[challenge_num -1])) + + if correct_solutions[challenge_num -1] == submitted_solution: + message = 'w00t! You got the right answer!!!' + + with open( + '{}/{}'.format( + self.config['scores_folder'], + self.client_addr + ), 'a+' + ) as score_file: + score_file.seek(0) + completed_challenges = score_file.readlines() + completed_challenges = list( + map(lambda x: x.replace('\n', ''), completed_challenges) + ) + if str(challenge_num) in completed_challenges: + message = 'You already submitted the correct answer for that challenge!' + else: + score_file.write(str(challenge_num) + '\n') + if len(completed_challenges) + 1 == num_solutions: + message = ( + 'w00t! You got the right answer!!!\n' + + 'Hey! thats all {}! that means YOU WIN!\n'.format(num_solutions) + + '!!!!!!!CONGRATULATIONS!!!!!!!\n' + + 'THIS CONTAINER WILL NOW SELF DESTRUCT' + ) + game_ended = True + else: + message = '|X_x| answer was rejected!!!.' + else: + message = ('What? You tried to submit a solution for challenge ' + + '{} but there are only {} challenges!'.format( + challenge_num, + num_solutions + )) + + print(message.replace('You', 'User')) + self.transport.write(bytes(message + '\n', 'utf8')) + self.transport.loseConnection() + if game_ended: + self.suicide() + +class SLBRManagerFactory(Factory): + def __init__(self, config): + self.config = config + def buildProtocol(self, addr): + return SLBRManager(self.config) + +docker_client = docker.from_env() +endpoint = TCP4ServerEndpoint(reactor, 1337) +endpoint.listen(SLBRManagerFactory({ + 'docker_client': docker_client, + 'docker_netmask': '172.19.0.0/16', + 'scores_folder': './scores', + 'solutions_file': './solutions.txt', + 'log_file': './log.txt' +})) +reactor.run() diff --git a/servers/deathlistener.sh b/servers/deathlistener.sh deleted file mode 100755 index d2ff646..0000000 --- a/servers/deathlistener.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/sh -# we will need the suicide bashrc to continuously write DEAD to the nc socket -# in case two messages are sent at the same time. - -# this will not work if your slbr admin is not in the "docker" group - -PORT="$1" -LOG="log.txt" - -[ -z "$1" ] && echo "port?" && exit - -docker_subnet_mask="$( - docker network inspect bridge \ - | jq '.[0].IPAM.Config[0].Subnet' \ - | tr -d '"' \ - | cut -f2 -d '/' -)" - -handle_packet() { - packet="$1" - message="$(echo $packet | cut -f3 -d '|')" - - address="$( - echo "$packet" \ - | cut -f2 -d '|' \ - | grep -Eo \ - '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}' - )" - address="$address/$docker_subnet_mask" - if [ "$message" = "DEAD" ] - then - echo "Killing user with ip $address" >> $LOG - container_name="$( - docker network inspect bridge \ - | jq \ - '.[0].Containers - | map(select(.IPv4Address == "'"$address"'")) - | .[0].Name' \ - | tr -d '"' - )" - docker rm -f "$container_name" - echo "killed container $container_name" >> $LOG - elif [ "$(echo $message | cut -f1 -d ' ')" = "SOLUTION" ] - then - submitted_solution="$(echo $message | cut -f3- -d ' ')" - challenge_no="$(echo $message | cut -f2 -d ' ')" - correct_solution="$(sed $challenge_no'q;d' solutions.txt)" - sed_escaped_address="$(echo $address | sed 's/\./\\./g')" - - echo "$address submitted solution for challenge $challenge_no: $submitted_solution" >> $LOG - if [ "$submitted_solution" = "$correct_solution" ] - then - num_correct="" - echo "Solution was correct!" >> $LOG - - grep -qE "^$address" scoreboard.txt \ - || echo "$address" >> scoreboard.txt - - grep -qE "^$address .*$challenge_no," scoreboard.txt \ - || sed -i "s|$sed_escaped_address|$address $challenge_no,|" scoreboard.txt - - num_correct="$( - grep -m1 -E "^$address" scoreboard.txt \ - | grep -oE '[0-9],' \ - | wc -l - )" - total_solutions="$(cat solutions.txt | wc -l)" - if [ $num_correct = $total_solutions ] - then - echo "$address HAS WON THE GAME!!!" >> $LOG - sleep 5 - echo "Killing all continers..." >> $LOG - docker ps -aq --filter label="description=SLBR User Container" | xargs docker rm -f - exit - fi - - echo "$num_correct correct answers so far" >> $LOG - fi - fi -} - -echo "game server listening on $PORT" -while true -do - packet=$(nc -w 1 -W 1 -nvlp $PORT 2>&1) - handle_packet "$(echo "$packet" | head -n 3 | tr '\n' '|')" & -done diff --git a/servers/gamelog.sh b/servers/gamelog.sh deleted file mode 100755 index 517103b..0000000 --- a/servers/gamelog.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -PORT="$1" -LOG="log.txt" -[ -z "$1" ] && echo "port?" && exit -echo "log server listening on $PORT" >> $LOG -nc -w1 -W1 -nvlkp $PORT >> $LOG 2>&1 diff --git a/servers/scoreboard.sh b/servers/scoreboard.sh deleted file mode 100755 index 0e08b09..0000000 --- a/servers/scoreboard.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -PORT="$1" -[ -z "$1" ] && echo "port?" && exit -echo "scoreboard server listening on $PORT" -while (cat scoreboard.txt | nc -w 1 -W 1 -lp "$PORT") -do - echo "Got scoreboard request." -done diff --git a/solutions.txt b/solutions.txt deleted file mode 100644 index b2f931a..0000000 --- a/solutions.txt +++ /dev/null @@ -1,5 +0,0 @@ -one -two -three -four -five diff --git a/start.sh b/start.sh index 295230a..06658e4 100755 --- a/start.sh +++ b/start.sh @@ -1,22 +1,5 @@ -#!/bin/sh -LOG="log.txt" -death_pid="" -score_pid="" -log_pid="" - -cleanup() { - rkill $death_pid >/dev/null 2>&1 - rkill $score_pid >/dev/null 2>&1 - rkill $log_pid >/dev/null 2>&1 -} -trap 'cleanup' 1 2 3 9 - -servers/deathlistener.sh 1337 & death_pid="$!" -echo "death listener pid: $death_pid" >> "$LOG" -servers/scoreboard.sh 1338 & score_pid="$!" -echo "scoreboard server pid: $score_pid" >> "$LOG" -servers/gamelog.sh 1339 & log_pid="$!" -echo "log server pid: $log_pid" >> "$LOG" - -printf "\e[0;91mReading log, exit to stop game\e[0m\n" -tail -f "$LOG" +#!/usr/bin/sh +SLBR_DIR='/home/slbr-admin/SLBRV2' +docker network rm slbr +docker network create --driver=bridge --subnet=172.19.0.0/16 slbr +nsenter -U --preserve-credentials -n -m -t $(cat $XDG_RUNTIME_DIR/docker.pid) sh -c "cd $SLBR_DIR && ./server.py"