[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)):  # 如果没有该文件夹,则创立一个文件夹
            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')  # 注意w,可以对result.txt文件进行修改
                resp = scan(self.param)
                if (resp == "Connection Timeout"):
                    result['data'] = resp
                else:
                    print
                    resp
                    tmpfile.write(resp)  # 这个将resp中的数据写入result.txt中,可以利用为将flag.txt中的数据放进result.txt中
                    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()  # 读取result.txt中的数据
            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




@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'])  # 注意这个绑定,接下来的几个函数都很重要,这个相当于c语言里面的主函数,接下来是调用其他函数的过程
def challenge():
    action = urllib.unquote(request.cookies.get("action"))  # cookie传递action参数,对应不同的处理方式
    param = urllib.unquote(request.args.get("param", ""))  # 传递get方式的参数param
    sign = urllib.unquote(request.cookies.get("sign"))  # cookie传递sign参数sign
    ip = request.remote_addr  # 获取请求端的ip地址
    if (waf(param)):  # 调用waf函数进行过滤
        return "No Hacker!!!!"
    task = Task(action, param, sign, ip)  # 创建Task类对象
    return json.dumps(task.Exec())  # 以json的形式返回到客户端




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


def scan(param):
    socket.setdefaulttimeout(1)
    try:
        return urllib.urlopen(param).read()[:50]  # 这个可以利用为访问flag.txt。读取然后为下一步将flag.txt文件中的东西放到result.txt中做铺垫
    except:
        return "Connection Timeout"


def getSign(action, param):  # getSign的作用是拼接secret_key,param,action,然后返回拼接后的字符串的md5加密值
    return hashlib.md5(secert_key + param + action).hexdigest()


def md5(content):  # 将传入的字符串进行md5加密
    return hashlib.md5(content).hexdigest()


def waf(param):  # 防火墙的作用是判断开头的几个字母是否是gopher 或者是file  如果是的话,返回true
    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=9999)
代码非常多

@app.route('/De1ta', methods=['GET', 'POST'])
从这个路由开始审计,因为这个路由东西最多

里面包含一个challenge()函数

点击查看代码
def challenge():
    action = urllib.unquote(request.cookies.get("action"))  # cookie传递action参数,对应不同的处理方式
    param = urllib.unquote(request.args.get("param", ""))  # 传递get方式的参数param
    sign = urllib.unquote(request.cookies.get("sign"))  # cookie传递sign参数sign
    ip = request.remote_addr  # 获取请求端的ip地址
    if (waf(param)):  # 调用waf函数进行过滤
        return "No Hacker!!!!"
    task = Task(action, param, sign, ip)  # 创建Task类对象
    return json.dumps(task.Exec())  # 以json的形式返回到客户端
总结一下,action和sign两个参数用cookie传入,param用get方式来传递 ip直接获取客户端的也就是我们写题人的ip

然后param这个参数经过waf函数过滤

点击查看代码
def waf(param):  # 防火墙的作用是判断开头的几个字母是否是gopher 或者是file  如果是的话,返回true
    check = param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False
看看waf()这个函数,其实也就是是看看开头的字母是什么,gopher或者file,如果是这两个,就会报waf。但是我感觉这这个waf没有什么软用。 回到上面的代码。 把我们获取的四个参数,action,param,sign,ip传入Task类创造了一个新类task。 最后返回一个json格式的经过exce()函数的task 我们看看exce()函数 首先看看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)
这里就是创建,把ip进行MD5. 存进去文件
点击查看代码
    def Exec(self):
        result = {}
        result['code'] = 500
        if (self.checkSign()):
            if "scan" in self.action:
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w')  # 注意w,可以对result.txt文件进行修改
                resp = scan(self.param)
                if (resp == "Connection Timeout"):
                    result['data'] = resp
                else:
                    print
                    resp
                    tmpfile.write(resp)  # 这个将resp中的数据写入result.txt中,可以利用为将flag.txt中的数据放进result.txt中
                    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()  # 读取result.txt中的数据
            if result['code'] == 500:
                result['data'] = "Action Error"
        else:
            result['code'] = 500
            result['msg'] = "Sign Error"
        return result
这里是exce()函数

result = {} result['code'] = 500
这两句就是创建了一个叫result的字典。给了一个键值对,code对500
然后直接执行了checkSign()函数
看看

点击查看代码
    def checkSign(self):
        if (getSign(self.action, self.param) == self.sign):
            return True
        else:
            return False
怎么判断,就是将action和param参数传入getsign函数 看看getsign函数
点击查看代码
def getSign(action, param):  # getSign的作用是拼接secret_key,param,action,然后返回拼接后的字符串的md5加密值
    return hashlib.md5(secert_key + param + action).hexdigest()
这里对传入的action和param参数再加上一个不知道哪里来的secert_key相拼接然后再进行md5 得到这个md5就去和我们传入的sign去比较,如果相等就通过了第一个if条件 然后开始执行
点击查看代码
  if "scan" in self.action:
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w')  # 注意w,可以对result.txt文件进行修改
                resp = scan(self.param)
我觉得这里很奇怪,'scan'这个字符串出来的莫名其妙
点击查看代码
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param)
ctrl f 查找后发现这里有个路由/geneSign下面有一个函数geneSign。仔细读读,get方式接收param,action赋值为字符串'scan'然后返回getsign函数
点击查看代码
def getSign(action, param):  # getSign的作用是拼接secret_key,param,action,然后返回拼接后的字符串的md5加密值
    return hashlib.md5(secert_key + param + action).hexdigest()
6,这好像给了我们一个登录的密码感觉。回到原来的代码
点击查看代码
            if "scan" in self.action:
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w')  # 注意w,可以对result.txt文件进行修改
                resp = scan(self.param)
                print resp
                tmpfile.write(resp)  # 这个将resp中的数据写入result.txt中,可以利用为将flag.txt中的数据放进result.txt中
                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()  # 读取result.txt中的数据
完全懂了,显示scan,读入flag,在是read,读出flag 就是要有read和scan 然后我们还需要传入sign这个参数,用geneSign()函数去生成就好了 我们获取登录的sign的方法是secert.key+flag.txtread+scan 我们登录时是secert.key+flag.txt+readscan

image

记住这个
image
感觉和ssrf没什么关系呀,可能是我太菜了

posted @ 2023-04-18 19:54  Dr0se  阅读(2)  评论(0)    收藏  举报