BUUCTF-[De1CTF 2019]SSRF Me1——python代码审计

目录

代码审计

一、challenge()路由

1.waf()

二、Task()对象

1.md5()

2.Exec()方法

1.checkSign()

getsign()

2.scan()

四、gengSign()路由

漏洞发掘

1.绕过checkSign()

2.使action中有read 和 scan

做法——拼接字符串

总结


看到代码的可读性很差,并且本人python代码审得也太少了(这次就老老实实当做python代码审计学习吧)。。请AI帮我们修改一下,让代码变得更可读

代码审计

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

先来分析一下这段代码,大致看一下,找到一个路口,再一步步来,这里我先看到的是:

一、challenge()路由

@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())

在/De1ta下,看到了要传入的四个POST变量:action param sign ip  然后首先是遇到了waf检查,应该是要绕过waf的

1.waf()

def waf(param):
    check = param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False

 这里主要是对param进行检查,逻辑是:检查param是否以 gopher 或者 file 开头(拦截以 gopher://file:// 开头的输入,防御简单 SSRF 攻击)。如果是的话会被判断为hacker,那就不包含这个就好了。然后是对象的建立,将上面那四个变量传进去

二、Task()对象

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)):
            os.mkdir(self.sandbox)

这里多了一个 sandbox 被赋值为md5(ip) 

1.md5()

def md5(content):
    return hashlib.md5(content).hexdigest()

这个md5方法就是将我们的ip进行md5加密。然后如果没创建以sandbox命名的文件目录,就进行创建。再看到后面的Exec方法

2.Exec()方法

    def Exec(self):
        result = {}
        result['code'] = 500
        if (self.checkSign()):

这里先是checkSign()

1.checkSign()
    def checkSign(self):
        if (getSign(self.action, self.param) == self.sign):
            return True
        else:
            return False
getsign()
def getSign(action, param):
    return hashlib.md5(secert_key + param + action).hexdigest()

 有一个预定义的secert_key,当调用该函数时,会将secert_key param action 拼接起来,在进行md5加密。

回到checkSign() 这里应该是需要保证 getSign(action,param)的值 等于我们传入的sign

回到Exec()方法,再对action进行判断,如果action中含有scan的话

            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

 首先是可写入的txt文件result.txt。假设self.sandbox=sandbox_dir 那么路径就为:
./sandbox_dir/result.txt

2.scan()
def scan(param):
    socket.setdefaulttimeout(1)
    try:
        return urllib.urlopen(param).read()[:50]
    except:
        return "Connection Timeout"

尝试打开指定的 URL 并读取其内容,如果在1s内无法完成操作,则返回 "Connection Timeout" 提示

如果能正常读取其中的内容,就将读取的内容写入result.txt中,并附上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"

如果action中含有read的话,就是读取result.txt文件。

至此,这条通路就走完了。但还有一段代码没看

四、gengSign()路由

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

 看到这个路由也用了 getSign() ,这边的action并非我们输入的,而是被赋值为 scan

漏洞发掘

重点应该是在Exec()那里,将重心放到这里

怎么利用呢?题目给了提示: flag在flag.txt中  也就是说我们要用这段读flag.txt文件,我们应该满足下面的条件

1.checkSign()返回为真才会进行读写操作

2.action中要含有 read 和 scan 才会执行读写操作

3.scan函数最为重要,传入param(flag.txt)才是真正导致我们能得到flag的因

那就想想如何满足上面几个条件:

1.绕过checkSign()

其实就是要使:md5(secert_key+param+action)==sign   但我们并不知道secert_key,那就不知道sign如何传。但这时,我发现gengSign()起作用了

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

这里也同样调用了 getSign(),在gengSign()中,应该是这样的形式

md5(secert_key+param+'scan')  param我们传入flag.txt,secert_key就假设是xxx,这里就应该是

md5(xxxflag.txtscan) --> 可以得到相应的sign,再以此sign来绕过checkSign()的检查

2.使action中有read 和 scan

我们在分析第一个条件时,发现通过gengSign()只能得到action的值为scan,这该怎么办?
也就是说如果我们要满足条件二,条件一是满足不了的。但仔细看这个形式

md5(xxxflag.txtscan) 我们知道+是以拼接的方式,后面的scan是action的值,如果我构造的param值为:flag.txtread呢?我们在gengSign()中的形式就变成了:

md5(xxxflag.txtreadscan)

这样得到的sign和我们要得到flag的 param=flag.txt action=readscan 就是一样的了!

做法——拼接字符串

首先使用gengSign()得到我们要的sign值

836f4e46241851d97188840d1f6a85bb 

然后回到challenge()抓包改包

就可以得到flag了!

总结

原本还打算用从别的师傅那学来的方法——哈希拓展攻击的,但无奈在kali上下不下来,先放着。

前面代码部分写得有些乱,还请见谅。
 

 

 

 

 

 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值