目录
看到代码的可读性很差,并且本人python代码审得也太少了(这次就老老实实当做python代码审计学习吧)。。请AI帮我们修改一下,让代码变得更可读
代码审计
#! /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')
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)
先来分析一下这段代码,大致看一下,找到一个路口,再一步步来,这里我先看到的是:
一、challenge()路由
@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下,看到了要传入的四个POST变量:action param sign ip 然后首先是遇到了waf检查,应该是要绕过waf的
1.waf()
def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
这里主要是对param进行检查,逻辑是:检查param是否以 gopher 或者 file 开头(拦截以 gopher://
或 file://
开头的输入,防御简单 SSRF 攻击)。如果是的话会被判断为hacker,那就不包含这个就好了。然后是对象的建立,将上面那四个变量传进去
二、Task()对象
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)
这里多了一个 sandbox 被赋值为md5(ip)
1.md5()
def md5(content):
return hashlib.md5(content).hexdigest()
这个md5方法就是将我们的ip进行md5加密。然后如果没创建以sandbox命名的文件目录,就进行创建。再看到后面的Exec方法
2.Exec()方法
def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
这里先是checkSign()
1.checkSign()
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
getsign()
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
有一个预定义的secert_key,当调用该函数时,会将secert_key param action 拼接起来,在进行md5加密。
回到checkSign() 这里应该是需要保证 getSign(action,param)的值 等于我们传入的sign
回到Exec()方法,再对action进行判断,如果action中含有scan的话
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
首先是可写入的txt文件result.txt。假设self.sandbox=sandbox_dir 那么路径就为:
./sandbox_dir/result.txt
2.scan()
def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"
尝试打开指定的 URL 并读取其内容,如果在1s内无法完成操作,则返回 "Connection Timeout"
提示
如果能正常读取其中的内容,就将读取的内容写入result.txt中,并附上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"
如果action中含有read的话,就是读取result.txt文件。
至此,这条通路就走完了。但还有一段代码没看
四、gengSign()路由
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
看到这个路由也用了 getSign() ,这边的action并非我们输入的,而是被赋值为 scan
漏洞发掘
重点应该是在Exec()那里,将重心放到这里
怎么利用呢?题目给了提示: flag在flag.txt中 也就是说我们要用这段读flag.txt文件,我们应该满足下面的条件
1.checkSign()返回为真才会进行读写操作
2.action中要含有 read 和 scan 才会执行读写操作
3.scan函数最为重要,传入param(flag.txt)才是真正导致我们能得到flag的因
那就想想如何满足上面几个条件:
1.绕过checkSign()
其实就是要使:md5(secert_key+param+action)==sign 但我们并不知道secert_key,那就不知道sign如何传。但这时,我发现gengSign()起作用了
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
这里也同样调用了 getSign(),在gengSign()中,应该是这样的形式
md5(secert_key+param+'scan') param我们传入flag.txt,secert_key就假设是xxx,这里就应该是
md5(xxxflag.txtscan) --> 可以得到相应的sign,再以此sign来绕过checkSign()的检查
2.使action中有read 和 scan
我们在分析第一个条件时,发现通过gengSign()只能得到action的值为scan,这该怎么办?
也就是说如果我们要满足条件二,条件一是满足不了的。但仔细看这个形式
md5(xxxflag.txtscan) 我们知道+是以拼接的方式,后面的scan是action的值,如果我构造的param值为:flag.txtread呢?我们在gengSign()中的形式就变成了:
md5(xxxflag.txtreadscan)
这样得到的sign和我们要得到flag的 param=flag.txt action=readscan 就是一样的了!
做法——拼接字符串
首先使用gengSign()得到我们要的sign值
836f4e46241851d97188840d1f6a85bb
然后回到challenge()抓包改包
就可以得到flag了!
总结
原本还打算用从别的师傅那学来的方法——哈希拓展攻击的,但无奈在kali上下不下来,先放着。
前面代码部分写得有些乱,还请见谅。