**
[De1CTF 2019]SSRF Me
由于題目對于萌新來說太難,知識點也沒有學習到位,所以借鑒了大佬們的wp后再手打了一遍!o(╥﹏╥)o
**
python-Flask知識點學習:
@app.route和其它裝飾器
要想明白“@app.route()”的作業原理,我們首先需要看一看Python中的裝飾器(就是以“@”開頭的那玩意,下面接著函式定義),
究竟什么是裝飾器?沒啥特別的,裝飾器只是一種接受函式(就是那個你用“@”符號裝飾的函式)的函式,并回傳一個新的函式,
當你裝飾一個函式,意味著你告訴Python呼叫的是那個由你的裝飾器回傳的新函式,而不僅僅是直接回傳原函式體的執行結果,
還不是很明白?這里是一個簡單的例子:
# This is our decorator
def simple_decorator(f):
# This is the new function we're going to return
# This function will be used in place of our original definition
def wrapper():
print "Entering Function"
f()
print "Exited Function"
return wrapper
@simple_decorator
def hello():
print "Hello World"
hello()
//輸出結果:
Entering Function
Hello World
Exited Function
·定義路由規則:
@app.route('/')//當路徑地址是根路徑時,就呼叫下面的函式,
·啟動web服務器:
當本檔案為程式入口(也就是用python命令直接執行本檔案)時,就會通過app.run()啟動Web服務器,如果不是程式入口,那么該檔案就是一個模塊,Web服務器會默認監聽本地的5000埠,但不支持遠程訪問,如果你想支持遠程,需要在run()方法傳入host=0.0.0.0,想改變監聽埠的話,傳入port=“埠號”,你還可以設定除錯模式debug,具體例子如下:
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8888, debug=True)
urllib.unquote():作用就是urlencode逆向解碼,相當于urldecode,
start.with():作用檢測該字串是否以引數傳遞的字串的值開頭,例子:
'testing'.startsWith('test') //true
'going on testing'.startsWith('test') //false
'testing'.startsWith('test', 2) //false
'going on testing'.startsWith('test', 9) //true
json.dumps():將python物件編碼成Json字串
gopher協議:
Gopher是Internet上一個資訊查找系統,它將Internet上的檔案組織成某種
索引,方便用戶從Internet的一處帶到另一處,在WWW出現之前,Gopher
是Internet上最主要的資訊檢索工具,使用tcp70埠,但在WWW出現
后,Gopher失去了昔日的輝煌,現在它基本過時,人們很少再使用它; gopher協議支持發出GET、POST請求
第一種解法:
一進入題目網站,已經給出原始碼,自己美化一下:
#! /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',port=80)
題目給了提示:flag在flag.txt中,
開始分析源代碼:
總的就是Python的flask框架,三個路由:
‘\’:根目錄下的就是獲取原始碼,
‘/geneSign’:這個是主要呼叫getsign,獲取MD5(“secret_key+param+action”)后的值,
‘/De1ta’:這個主要是先是獲取action,sign和param的值,再呼叫waf檢測,建立Task物件最后可以執行類Task下的Exec();
**
重點:
**觀察到第73行左右有:
def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
想到這可以幫助我們將flag.txt帶出來,
于是去尋找哪里呼叫了scan函式,可以看到Task類中的Exec函式之中就有呼叫,在繼續深入,發現是有呼叫條件的,先是要滿足checkSign,再需要滿足scan在action中,還需要滿足read在action中,最后才能回傳讀取出來的result:
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:
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
發現getSign,再轉到getSign:
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
同時發現路徑/geneSign下的geneSign函式可供我們利用獲取MD5值,于是開始構造:

然后回到路徑/De1ta呼叫Exec函式,使用Burpsuit構造cookie值傳入,獲取flag:

**
其他解法
**
其他解法原理,這里就不詳細講解了,掛上鏈接:
兩種解法:MD5長度擴展攻擊和local_file
https://www.cnblogs.com/20175211lyz/p/11440316.html
**
總結
**
這題對于大佬來說其實不是難題,一開始我拿到源代碼,其實是十分懵逼的,因為完全看不懂,只能百度搜索對應的知識點和函式講解理解,十分痛苦,
再看到大佬前輩們的MD5長度擴展攻擊和local_file協議,果然自己的知識面還是太少了,還是認真努力的刷題吧,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/289176.html
標籤:其他
