【大廠必備】2020超經典PHP面試題
今天做的這道題比較偏審計,當時拿道題目的確發慌,也是看著大佬的wp一步步做出來的,這題就是需要你紮實的基本功,要有耐心,一步步跟著回溯,就可以做出來
題目
拿到題目程式碼很亂,可放在pycharm裡Ctrl+Alt+L將程式碼格式化一下
1 #! /usr/bin/env python
2 #encoding=utf-8
3 from flask import Flask
4 from flask import request
5 import socket
6 import hashlib
7 import urllib
8 import sys
9 import os
10 import json
11 reload(sys)
12 sys.setdefaultencoding('latin1')
13
14 app = Flask(__name__)
15
16 secert_key = os.urandom(16)
17
18
19 class Task:
20 def __init__(self, action, param, sign, ip):
21 self.action = action
22 self.param = param
23 self.sign = sign
24 self.sandbox = md5(ip)
25 if(not os.path.exists(self.sandbox)): #SandBox For Remote_Addr
26 os.mkdir(self.sandbox)
27
28 def Exec(self):
29 result = {}
30 result['code'] = 500
31 if (self.checkSign()):
32 if "scan " in self.action:
33 tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
34 resp = scan(self.param)
35 if (resp == "Connection Timeout"):
36 result['data'] = resp
37 else:
38 print resp
39 tmpfile.write(resp)
40 tmpfile.close()
41 result['code'] = 200
42 if "read" in self.action:
43 f = open("./%s/result.txt" % self.sandbox, 'r')
44 result['code'] = 200
45 result['data'] = f.read()
46 if result['code'] == 500:
47 result['data'] = "Action Error"
48 else:
49 result['code'] = 500
50 result['msg'] = "Sign Error"
51 return result
52
53 def checkSign(self):
54 if (getSign(self.action, self.param) == self.sign):
55 return True
56 else:
57 return False
58
59
60 #generate Sign For Action Scan.
61 @app.route("/geneSign", methods=['GET', 'POST'])
62 def geneSign():
63 param = urllib.unquote(request.args.get("param", ""))
64 action = "scan"
65 return getSign(action, param)
66
67
68 @app.route('/De1ta',methods=['GET','POST'])
69 def challenge():
70 action = urllib.unquote(request.cookies.get("action"))
71 param = urllib.unquote(request.args.get("param", ""))
72 sign = urllib.unquote(request.cookies.get("sign"))
73 ip = request.remote_addr
74 if(waf(param)):
75 return "No Hacker!!!!"
76 task = Task(action, param, sign, ip)
77 return json.dumps(task.Exec())
78 @app.route('/')
79 def index():
80 return open("code.txt","r").read()
81
82
83 def scan(param):
84 socket.setdefaulttimeout(1)
85 try:
86 return urllib.urlopen(param).read()[:50]
87 except:
88 return "Connection Timeout"
89
90
91
92 def getSign(action, param):
93 return hashlib.md5(secert_key + param + action).hexdigest()
94
95
96 def md5(content):
97 return hashlib.md5(content).hexdigest()
98
99
100 def waf(param):
101 check=param.strip().lower()
102 if check.startswith("gopher") or check.startswith("file"):
103 return True
104 else:
105 return False
106
107
108 if __name__ == '__main__':
109 app.debug = False
110 app.run(host='0.0.0.0')
分析
先從路由入手,本題一共有3個路由
我們先看/De1ta這個路由(因為它涵蓋的面比較廣)
@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"))
可以看到在/De1ta頁面我們get方法傳入param引數值,在cookie裡面傳遞action和sign的值
然後將傳遞的param通過waf這個函式。
if(waf(param)):
return "No Hacker!!!!"
於是我們先去看waf函式
waf函式找到以gopher或者file開頭的,所以在這裡過濾了這兩個協議,使我們不能通過協議讀取檔案
def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
接著在challenge裡面,用我們傳進去的引數構造一個Task類物件,並且執行它的Exec方法
我們接著去看Exec方法
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
這是Exec方法
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
先通過checkSign方法檢測登入。
到checkSign方法裡面去看看
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
當我們傳入的引數action和param經過getSign這個函式之後與sign相等,就返回true
返回true之後則進入if語句裡面
我們來追蹤一下getSign這個函式,它主要是三個東西拼接然後進行md5
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
我發現/geneSign這個路由可以生成我們需要的md5(其實它也是呼叫了getSign這個函式)
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
但是它的action只有"scan",我們回到之前if (self.checkSign()):中,當它為真會執行它下面的兩條if語句
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
程式碼也很好懂,就是action中必須要有"scan"和"read"兩個才能讀取flag
試著訪問了一下 /geneSign?param=flag.txt ,給出了一個 md5 4699ef157bee078779c3b263dd895341 ,但是隻有 scan 的功能,想加入 read 功能就要另想辦法了
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
看了一下邏輯,在 getSign 處很有意思,這個字串拼接的就很有意思了
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
不妨假設 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') ,這就達到了目標。
直接訪問 /De1ta?param=flag.txt 構造 Cookie: sign=7a2a235fcc9218dfe21e4eb400a11b5e;action=readscan 即可