[De1CTF 2019]SSRF Me

#! /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代码审计
[De1CTF 2019]SSRF Me_第1张图片
要我们传的参数有三个

action,param,sign

传入后,要过一个waf(),禁止我们用"gopher"和"file"协议读取文件
然后再调用Task类中的Exec()方法
看看Task类中的Exec()方法
[De1CTF 2019]SSRF Me_第2张图片
可以发现当action等于scan的时候
他会调用scan函数
[De1CTF 2019]SSRF Me_第3张图片
scan函数是一个读取文件内容的函数
[De1CTF 2019]SSRF Me_第4张图片
根据hint提示flag在flag.txt里
所以我们来读取flag.txt的内容

回到Exec()函数
可以发现,当action等于read和scan的时候刚好将我们读取的flag.txt文件写入result.txt并且读取result.txt

现在问题的关键是通过下面这个函数
[De1CTF 2019]SSRF Me_第5张图片

方法一:

MD5扩展长度攻击
根据那篇文章,直接利用工具
hashpump

安装方法

使用工具之前,需要知道几个东西

1.md5加密后的值
进到/geneSign页面传入?param=flag.txt

2.最后一个字符的值
[De1CTF 2019]SSRF Me_第6张图片
上图函数定死action等于scan

3.整个字符窜的长度
在这里插入图片描述
源码告诉我们key的值是啥,打印他的长度,发现长度保持在16
[De1CTF 2019]SSRF Me_第7张图片
而后面flag.txtscan字符的长度是8,所以整个字符窜长度是24

要加上字符的值
我们需要给action加上read才能读取flag.txt
所以加上字符的值为flag.txt

[De1CTF 2019]SSRF Me_第8张图片
进到/De1ta页面,将跑出来的MD5值传入sign,和下面的东东传到action
记得将\x换成%
才能正常传入
[De1CTF 2019]SSRF Me_第9张图片

方法二:

这个方法挺简单的

在这里插入图片描述
因为这个MD5加密是将字符拼接的后的字符窜加密,要让md5值相等在flag.txt后面加个read,不就让他们MD5
值相等吗

由此去/geneSign页面传入?param=flag.txtread
得到sign的值
再将sign和action值传入

sign=7bf4a62aa1b1ff757986331fc289576d;action=readscan

同样得到flag

[De1CTF 2019]SSRF Me_第10张图片

你可能感兴趣的:([De1CTF 2019]SSRF Me)