考点:代码审计,关于flask框架的应用,python读写文件
目录
2. 将flag.txt中的数据读入result.txt,然后读取result.txt
payload2:如下,注意修改cookie中参数action和参数sign的值
解题过程
打开题目,就是进行代码审计
#! /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)): #如果没有该文件夹,则创立一个文件夹
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') #注意w,可以对result.txt文件进行修改
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp) #这个将resp中的数据写入result.txt中,可以利用为将flag.txt中的数据放进result.txt中
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() #读取result.txt中的数据
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
@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']) #注意这个绑定,接下来的几个函数都很重要,这个相当于c语言里面的主函数,接下来是调用其他函数的过程
def challenge():
action = urllib.unquote(request.cookies.get("action")) #cookie传递action参数,对应不同的处理方式
param = urllib.unquote(request.args.get("param", "")) #传递get方式的参数param
sign = urllib.unquote(request.cookies.get("sign")) #cookie传递sign参数sign
ip = request.remote_addr #获取请求端的ip地址
if(waf(param)): #调用waf函数进行过滤
return "No Hacker!!!!"
task = Task(action, param, sign, ip) #创建Task类对象
return json.dumps(task.Exec()) #以json的形式返回到客户端
@app.route('/')
def index():
return open("code.txt","r").read()
def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50] #这个可以利用为访问flag.txt。读取然后为下一步将flag.txt文件中的东西放到result.txt中做铺垫
except:
return "Connection Timeout"
def getSign(action, param): #getSign的作用是拼接secret_key,param,action,然后返回拼接后的字符串的md5加密值
return hashlib.md5(secert_key + param + action).hexdigest()
def md5(content): #将传入的字符串进行md5加密
return hashlib.md5(content).hexdigest()
def waf(param): #防火墙的作用是判断开头的几个字母是否是gopher 或者是file 如果是的话,返回true
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=9999)
代码审计:
一个flask框架,先说一些需要注意的地方
1. /De1ta和/geneSign
分别绑定不同的函数,具有不同的功能,接下俩会具体分析。
2. Task类
这个类中有不同的参数action,对应不同的函数执行,但是需要注意到
if "scan" in self.action:
if "read" in self.action:
判断action中的值的时候,用的是in,而不是==,所以如果action中是scanread或者是reanscan的话,if语句同时满足,相应的代码都执行。
3. python文件操控
有许多关于python的文件操控的代码,所以在本地进行了复现
#因为是在windows系统下复现,所以文件路径和源码中有些不同,但是原理一样,都是将flag.txt中的文件放在result.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
f = open("D:/phpstudy_pro/WWW/test/result.txt",'w') #注意切换为写的功能
resp = open("D:/phpstudy_pro/WWW/test/flag.txt").read()
f.write(resp)
4. 得到flag的大致思路有了
首先绕过self.checkSign(),并且传入的action需要同时包含scan和read,然后if “scan” in self.action:执行将flag.txt中的数据写入result.txt中,继续if “read” in self.action:执行读取result.txt中的数据,并且放在 result[‘data’] 中 , return json.dumps(task.Exec()) 接着返回以json的形式返回到客户端。
构造payload的步骤:
1. 首先需要绕过 self.checkSign()
分析一下相关源码,源码分别截取,方便分析
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
def getSign(action, param): #getSign的作用是拼接secret_key,param,action,然后返回拼接后的字符串的md5加密值
return hashlib.md5(secert_key + param + action).hexdigest()
需要满足self.checkSign(),
就需要getSign(self.action, self.param) == self.sign,(而sign值通过cookie传值)
就需要hashlib.md5(secert_key + param + action).hexdigest() == self.sign,
说白了也就是hashlib.md5(secert_key + ‘flag.txt’ + ‘readscan’).hexdigest() == self.sign,即我们需要得到
secert_key + ‘flag.txtreadscan’的哈希值
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
但是我们不知道secret_key的值是多少,它只存在于服务端,但是我们可以通过上面截取的源码中/geneSign,来返回我们所需要的编码之后的哈希值
注意到/geneSign中已经将action定为 scan,所以我们传入的param可以为flag.txtread,这样的话还是会拼接为 secert_key + ‘flag.txtreadscan’
payload1:
/geneSign?param=flag.txtread
返回哈希值
2. 将flag.txt中的数据读入result.txt,然后读取result.txt
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w') #注意w,可以对result.txt文件进行修改
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp) #这个将resp中的数据写入result.txt中,可以利用为将flag.txt中的数据放进result.txt中
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() #读取result.txt中的数据
if result['code'] == 500:
result['data'] = "Action Error"
然后得到flag.
总结
本题我感觉还是主要考察代码审计能力,只要肯下功夫,仔细研读,会有帮助。
主要参考的博客:
Original: https://blog.csdn.net/RABCDXB/article/details/115412359
Author: Sk1y
Title: [De1CTF 2019]SSRF Me
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/749021/
转载文章受原作者版权保护。转载请注明原作者出处!