題目提示我們flag在./flag.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
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')
對python寫的后端程序我們首先看路由。
可以看到在/De1ta頁面我們get方法傳入param參數值,在cookie里面傳遞action和sign的值,將傳遞的param通過waf這個函數。
於是我們先去看waf函數
waf函數找到以gopher或者file開頭的,所以在這里過濾了這兩個協議,使我們不能通過協議讀取文件
接着在challenge里面,用我們傳進去的參數構造一個Task類對象,並且執行它的Exec方法
我們接着去看Exec方法
先通過checkSign方法檢測登錄。
到checkSign方法里面去看看
當我們傳入的參數action和param經過getSign這個函數之后與sign相等,就返回true
返回true之后則進入if語句里面
這里可以看到,如果scan在action里面,則我們可以讓param進入scan這個函數。值得注意的是,這里使用的是in而不是==,於是在這里必定是破題的地方。
跳轉到scan函數
這里我們很容易注意到,此處傳入到scan里面的param沒有被過濾,對於param參數的過濾,僅僅在於最開始的waf函數,意於阻止我們使用gopher協議和file協議讀取服務器本地的文件。於是如果我們能夠令param為flag.txt,我們就能讀取它的內容。
大概捋了一下思路之后,我們反向進行構造。
首先我們需要是self.checkSign()函數返回為真,所以我們需要讓經過了getSign函數的action和param返回值等於sign
需要secert_key+param+action三個字符串連接起來之后進行md5加密作為返回值,但是這里我們不知道secert_key的值。
但是這里的字符串是連接起來的,舉個例子:
param=abc; action=def; param+action=abcdef; param=a; action=bcdef; param+action=abcdef;
又因為我們需要讓param能夠讀取flag.txt
而又因為
所以我們需要令action里面有scan和read這兩個參數
所以我們現在就需要
md5(key+flag.txt+scanread/readscan)
雖然key我們不知道,但是這里
訪問這個頁面,我們可以得到一個getSign返回的md5值,不過action已經被寫死為scan了。
但是最后的時候我們還是需要讀取flag.txt,所以我們令param為flag.txtread
這樣就能得到對應的用來登錄的md5值了。
可以看到我們獲得了
0f10b9dfb586adf81d993acd6af48f84
這就是我們登錄需要的md5
接着我們訪問De1ta頁面,傳遞參數
按照之前的思路,獲取到了flag
這道題目的收獲:(直接抄的別的師傅的,我覺得說的很對hhh)代碼審計類的題目總體上講沒有什么特別的套路,尤其是這種體量較小的代碼,也不需要很大的腦洞,只要有較為扎實的基本功,認真的審計代碼,找出關鍵點,耐心回溯與跟進,搞清楚流程,構建一個大體的思路,去實踐去驗證它的可行性,即使沒有接觸題目,也一定有所提升。