BUUCTF | [De1CTF 2019]SSRF Me
解法一字符串拼接:
1.得到签名sign
http://8fa4531c-1164-49b7-a700-70e77e6aacb7.node3.buuoj.cn/geneSign?param=flag.txtread
2.添加Cookie发送请求得到flag
Cookie: action=readscan;sign=cf8d4365c9b27a29b29e025a7ed25fa6
原理:
1.分析源码
#! /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)
2.传入一个param,/geneSign路由会返回的一个sign
3.通过cookie得到一个sign,到时候回传到exec()中去跟getSign()产生的sign校验,所以这里直接传一个getSign()产生的sign。通过cookie传入一个action参数,看后面的Exec()可以知道action="readscan"。根据提示param=flag.txt,所以这个sign应该是md5('xxxflag.txtreadscan'),但是action在geneSign()写死了为"action"。
不妨假设
secert_key
是xxx
,那么在开始访问/geneSign?param=flag.txt
的时候,返回的 md5 就是md5('xxx' + 'flag.txt' + 'scan')
,在 python 里面上述表达式就相当于md5(xxxflag.txtscan)
,这就很有意思了。直接构造访问
/geneSign?param=flag.txtread
,拿到的 md5 就是md5('xxx' + 'flag.txtread' + 'scan')
,等价于md5('xxxflag.txtreadscan')
,这就达到了目标。
解法二文件包含:
1.得到sign
2.得到flag
原理:
Python 2.x - 2.7.16 urllib.fopen支持local_file导致LFI(CVE-2019-9948):https://bugs.python.org/issue35907
这里是使用的 urllib.urlopen(param) 去包含的文件,所以可以直接加上文件路径
flag.txt
或./flag.txt
去访问,也可以使用类似的file:///app/flag.txt
去访问,但是file
关键字在黑名单里,可以使用local_file
代替。 如果使用 urllib2.urlopen(param) 去包含文件就必须加上file
,否则会报ValueError: unknown url type: /path/to/file
的错误当不存在协议的时候,默认使用
file
协议读取可以使用
local_file:
绕过,例如local_file:flag.txt
路径就是相对脚本的路径local_file://
就必须使用绝对路径(协议一般都是这样)
PS:local-file:///proc/self/cwd/flag.txt
也可以读取,因为/proc/self/cwd/
代表的是当前路径
解法三:哈希拓展攻击
我们可以在不知道salt的具体内容的情况下,计算出任意的md5(salt+message+padding+append)值
对于本题来说就是 md5(secert_key + 'flag.txt' + 'scan')
的值延拓成 md5(secert_key + 'flag.txt' + 'readscan')
的值
由于我本机的hashpump老是安装失败就不复现了Orz Hash Length Extension Attack
参考:
https://blog.csdn.net/weixin_44255856/article/details/98946266