[De1CTF 2019]SSRF Me
进入即得源码
#! /usr/bin/env python # 指定脚本的解释器
#encoding=utf-8 # 指定文件的编码格式为utf-8
from flask import Flask, request # 导入Flask和request模块
import socket # 导入socket模块,用于网络操作
import hashlib # 导入hashlib模块,用于哈希操作
import urllib # 导入urllib模块,用于URL处理
import sys # 导入sys模块,用于系统操作
import os # 导入os模块,用于操作系统交互
import json # 导入json模块,用于JSON处理
reload(sys) # 重新加载sys模块
sys.setdefaultencoding('latin1') # 设置默认编码为latin1
app = Flask(__name__) # 初始化Flask应用
secert_key = os.urandom(16) # 生成一个随机的16字节密钥,用于签名操作
class Task:
def __init__(self, action, param, sign, ip):
self.action = action # 动作
self.param = param # 参数
self.sign = sign # 签名
self.sandbox = md5(ip) # 基于IP地址生成沙盒目录的MD5值
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: # 如果动作是扫描
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: # 如果代码仍然是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 # 返回假
@app.route("/geneSign", methods=['GET', 'POST']) # 定义/geneSign路由,支持GET和POST方法
def geneSign():
param = urllib.unquote(request.args.get("param", "")) # 获取参数并解码
action = "scan" # 动作为扫描
return getSign(action, param) # 返回生成的签名
@app.route('/De1ta', methods=['GET', 'POST']) # 定义/De1ta路由,支持GET和POST方法
def challenge():
action = urllib.unquote(request.cookies.get("action")) # 从Cookie中获取并解码动作
param = urllib.unquote(request.args.get("param", "")) # 获取并解码参数
sign = urllib.unquote(request.cookies.get("sign")) # 从Cookie中获取并解码签名
ip = request.remote_addr # 获取客户端IP地址
if (waf(param)): # 检查参数是否包含恶意内容
return "No Hacker!!!!" # 返回反黑客信息
task = Task(action, param, sign, ip) # 创建Task对象
return json.dumps(task.Exec()) # 执行Task并返回结果的JSON表示
@app.route('/') # 定义根路由/
def index():
return open("code.txt", "r").read() # 返回code.txt文件内容
def scan(param):
socket.setdefaulttimeout(1) # 设置默认超时时间为1秒
try:
return urllib.urlopen(param).read()[:50] # 尝试读取指定URL的前50个字符
except:
return "Connection Timeout" # 返回连接超时信息
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest() # 生成MD5签名
def md5(content):
return hashlib.md5(content).hexdigest() # 计算输入内容的MD5哈希值
def waf(param):
check = param.strip().lower() # 去除参数两端空格并转为小写
if check.startswith("gopher") or check.startswith("file"): # 检查参数是否以"gopher"或"file"开头
return True # 返回真,表示存在潜在恶意内容
else:
return False # 返回假
if __name__ == '__main__': # 如果脚本作为主程序运行
app.debug = False # 关闭调试模式
app.run(host='0.0.0.0', port=80) # 在0.0.0.0地址和80端口上运行Flask应用
代码量并不大
经过简单分析
得flag的实现点在
def Exec(self):
result = {} # 初始化结果字典
result['code'] = 500 # 默认返回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: # 如果代码仍然是500
result['data'] = "Action Error" # 设置结果数据为动作错误
else:
result['code'] = 500 # 设置错误代码
result['msg'] = "Sign Error" # 设置错误信息为签名错误
return result # 返回结果字典
首先我们要先能过
if (self.checkSign()):
跟踪方法得到
这里是进行了一个签名验证
我们继续看一下
getSign(self.action, self.param)
是一个返回签名的函数
先回到Exec
也就是我们的action既要包含scan也要包含read
也就是在这里action=readscan,param=flag.txt
我们
要得到
hashlib.md5(flag.txtreadscan).hexdigest()的值
可以利用这里
这里的action被设为定值但是我们可以决定param的值
我们设param=flag.txtread
即可返回
hashlib.md5(flag.txtreadscan).hexdigest()的值
再将其赋值给sign即可
我们再找哪里用了Exec()
@app.route('/De1ta', methods=['GET', 'POST']) # 定义/De1ta路由,支持GET和POST方法
def challenge():
action = urllib.unquote(request.cookies.get("action")) # 从Cookie中获取并解码动作
param = urllib.unquote(request.args.get("param", "")) # 获取并解码参数
sign = urllib.unquote(request.cookies.get("sign")) # 从Cookie中获取并解码签名
ip = request.remote_addr # 获取客户端IP地址
if (waf(param)): # 检查参数是否包含恶意内容
return "No Hacker!!!!" # 返回反黑客信息
task = Task(action, param, sign, ip) # 创建Task对象
return json.dumps(task.Exec()) # 执行Task并返回结果的JSON表示
在这里我们就可以了解如何传参
看一下waf
没影响
到这里就分析完了
下面是payload
url/geneSign?param=flag.txtread
//得到签名,一会赋给sign
再