启动BUUCTF靶场,先查看一下提示:
显示出flag的文件路径是/flag.txt
发现是一段python代码,整理一下:
#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')
app = Flask(__name__)
secert_key = os.urandom(16)
class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)): #SandBox For Remote_Addr
os.mkdir(self.sandbox)
def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
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
if "read" in self.action:
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
#generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
@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)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
@app.route('/')
def index():
return open("code.txt","r").read()
def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
def md5(content):
return hashlib.md5(content).hexdigest()
def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0')
这段代码实现了一个简单的 Flask web 应用程序,主要功能是处理特定的网络请求并执行一些操作。下面逐步分析代码的主要部分:
导入库:
Flask 用于创建 web 应用,request 用于处理 HTTP 请求。
socket, hashlib, urllib, sys, os, json 是用于网络、加密、URL 处理、系统操作和 JSON 数据操作的库。
Flask 应用程序:
创建一个 Flask 应用实例 app。
密钥生成:
secert_key = os.urandom(16) 生成一个随机的 16 字节的密钥,用于后续的签名生成。
任务类 Task:
__init__ 方法:初始化一个任务,接收 action、param、sign 和 ip。并使用 MD5 对 IP 地址进行哈希,作为沙盒文件夹的名称。检查沙盒文件夹是否存在,如果不存在则创建。
Exec 方法:根据 action 执行相应的操作。
如果 checkSign() 返回真,继续执行;
如果 action 包含 "scan",则尝试通过 scan(param) 函数获取参数的响应,并将响应写入 result.txt 文件;
如果 action 包含 "read",则读取 result.txt 内容。
如果未匹配到有效操作或签名不正确,返回相应错误信息。
签名生成接口:
geneSign 路由:接收参数并返回该参数的签名,签名使用 getSign 方法生成。
挑战接口:
challenge 路由:获取请求中的 action、param 和 sign,并获取请求的 IP 地址。
调用 waf(param) 检查参数是否合法,如果不合法则返回 "No Hacker!!!!"。
创建 Task 实例并执行。
首页路由:
index 路由:返回名为 "code.txt" 的文件内容。
网络请求处理:
scan(param) 函数尝试访问给定的 URL,并返回前 50 个字符,超时则返回 "Connection Timeout"。
签名生成函数 getSign:
使用 hashlib.md5 对密钥、参数和动作进行哈希处理,生成签名。
MD5 哈希函数:
md5(content) 函数对给定内容进行 MD5 哈希处理。
WAF(Web 应用防火墙)检查:
waf(param) 检查参数是否以 "gopher" 或 "file" 开头,若是则返回真,表示参数不合法。
运行应用:
在主程序中运行 Flask 应用,监听所有 IP 地址。
总体来看,这段代码是一个简单的 web 服务,允许执行网络请求并对输入参数进行一些安全性检查,同时实现了基本的请求签名机制。
发现代码中中有两处地方可以传参(/geneSign和 /De1ta)
先看一下调用函数Task的函数(/De1ta的函数):
可以传入可控的action,param,sign三个函数
先经过waf判断:
param中不能有'gopher'和'file',然后最后return json.dumps(task.Exec())
而task.Exec()最后return result,所以我们判断,最后应该要将return的值等于flag,即return flag
看一下Exec()的构成
可以看到跟checkSign()函数有关
溯源checkSign():
可以看到跟getSign()函数有关
溯源getSign():
即让(secert_key + param + action)的md5值等于sign
但secert_key的值不知道,所以要用到第一个路由(/geneSign)
再来看(/geneSign)的函数:
action="scan"的值已经给定,我们只能控制param的值。
最后return getSign(action,param)
得到secert_key + param + action的md5值。(即secert_key + param + 'scan')
了解到关键词'scan'立马想到Exec()函数有关:
第一个if要求传入的action中含有scan,然后调用scan函数从文件中查找param这个文件,将文件名给resp,然后通过tmpfile函数写入result.txt中,且令它连通。即(code=200);
第二个if要求传入的action中含有read,然后从result.txt中查找连通的文件作为f,并让result['data']=f.read得到它的内容。最后return result输出内容。
所以此处的action必须为含read和scan。
而/geneSign路由中规定的action必须为scan。但可以控制param
而/De1ta路由中两个值都可以控制。
且两者最后的secert_key + param + action相等。我们已知flag在flag.txt中,而且最终要通过/De1ta中的param来得到,所以/De1ta中的param=flag.txt
所以,在(/geneSign路由)中param=flag.txt,读取action,此时action='scan'
构造payload:
/geneSign?param=flag.txtread
得到一个值,是md5值,再来看看先前的回顾:
再来看(/De1ta路由)
令param=flag.txt,action='readscan'
这样,最后secert_key + param + action==sign的值
用burp抓包,修改cookie内容:(/De1ta?param=flag.txt)
Cookie:action=readscan;sign=adf22554dca69e25f96ce25d67b84444
(注意:做到现在,脑子糊涂了,不知道怎么解释了,就硬撑着做下去了)
得到flag:
flag{0bb9d18a-aed4-456d-9fa6-de37c90a7a54}
成功了,但身心疲惫
标签:return,SSRF,scan,De1CTF,self,param,CTF,result,action From: https://blog.csdn.net/weixin_73049307/article/details/142577194