1. 程式人生 > 實用技巧 >【大廠必備】2020超經典PHP面試題

【大廠必備】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 即可