[De1CTF 2019]SSRF Me

进入即得源码

#! /usr/bin/env python  # 指定脚本的解释器
#encoding=utf-8  # 指定文件的编码格式为utf-8

from flask import Flask, request  # 导入Flask和request模块
import socket  # 导入socket模块,用于网络操作
import hashlib  # 导入hashlib模块,用于哈希操作
import urllib  # 导入urllib模块,用于URL处理
import sys  # 导入sys模块,用于系统操作
import os  # 导入os模块,用于操作系统交互
import json  # 导入json模块,用于JSON处理

reload(sys)  # 重新加载sys模块
sys.setdefaultencoding('latin1')  # 设置默认编码为latin1

app = Flask(__name__)  # 初始化Flask应用
secert_key = os.urandom(16)  # 生成一个随机的16字节密钥,用于签名操作


class Task:
    def __init__(self, action, param, sign, ip):
        self.action = action  # 动作
        self.param = param  # 参数
        self.sign = sign  # 签名
        self.sandbox = md5(ip)  # 基于IP地址生成沙盒目录的MD5值
        if (not os.path.exists(self.sandbox)):  # 如果沙盒目录不存在
            os.mkdir(self.sandbox)  # 创建沙盒目录

    def Exec(self):
        result = {}  # 初始化结果字典
        result['code'] = 500  # 默认返回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:  # 如果代码仍然是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'])  # 定义/geneSign路由,支持GET和POST方法
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))  # 获取参数并解码
    action = "scan"  # 动作为扫描
    return getSign(action, param)  # 返回生成的签名


@app.route('/De1ta', methods=['GET', 'POST'])  # 定义/De1ta路由,支持GET和POST方法
def challenge():
    action = urllib.unquote(request.cookies.get("action"))  # 从Cookie中获取并解码动作
    param = urllib.unquote(request.args.get("param", ""))  # 获取并解码参数
    sign = urllib.unquote(request.cookies.get("sign"))  # 从Cookie中获取并解码签名
    ip = request.remote_addr  # 获取客户端IP地址
    if (waf(param)):  # 检查参数是否包含恶意内容
        return "No Hacker!!!!"  # 返回反黑客信息
    task = Task(action, param, sign, ip)  # 创建Task对象
    return json.dumps(task.Exec())  # 执行Task并返回结果的JSON表示


@app.route('/')  # 定义根路由/
def index():
    return open("code.txt", "r").read()  # 返回code.txt文件内容


def scan(param):
    socket.setdefaulttimeout(1)  # 设置默认超时时间为1秒
    try:
        return urllib.urlopen(param).read()[:50]  # 尝试读取指定URL的前50个字符
    except:
        return "Connection Timeout"  # 返回连接超时信息


def getSign(action, param):
    return hashlib.md5(secert_key + param + action).hexdigest()  # 生成MD5签名


def md5(content):
    return hashlib.md5(content).hexdigest()  # 计算输入内容的MD5哈希值


def waf(param):
    check = param.strip().lower()  # 去除参数两端空格并转为小写
    if check.startswith("gopher") or check.startswith("file"):  # 检查参数是否以"gopher"或"file"开头
        return True  # 返回真,表示存在潜在恶意内容
    else:
        return False  # 返回假


if __name__ == '__main__':  # 如果脚本作为主程序运行
    app.debug = False  # 关闭调试模式
    app.run(host='0.0.0.0', port=80)  # 在0.0.0.0地址和80端口上运行Flask应用

代码量并不大
经过简单分析
得flag的实现点在

    def Exec(self):
        result = {}  # 初始化结果字典
        result['code'] = 500  # 默认返回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:  # 如果代码仍然是500
                result['data'] = "Action Error"  # 设置结果数据为动作错误
        else:
            result['code'] = 500  # 设置错误代码
            result['msg'] = "Sign Error"  # 设置错误信息为签名错误
        return result  # 返回结果字典

首先我们要先能过

if (self.checkSign()):

跟踪方法得到

image

这里是进行了一个签名验证
我们继续看一下

getSign(self.action, self.param)

image

是一个返回签名的函数
先回到Exec

image

也就是我们的action既要包含scan也要包含read

image

也就是在这里action=readscan,param=flag.txt
我们
要得到

hashlib.md5(flag.txtreadscan).hexdigest()的值

可以利用这里

image

这里的action被设为定值但是我们可以决定param的值
我们设param=flag.txtread
即可返回

hashlib.md5(flag.txtreadscan).hexdigest()的值

再将其赋值给sign即可


我们再找哪里用了Exec()

@app.route('/De1ta', methods=['GET', 'POST'])  # 定义/De1ta路由,支持GET和POST方法
def challenge():
    action = urllib.unquote(request.cookies.get("action"))  # 从Cookie中获取并解码动作
    param = urllib.unquote(request.args.get("param", ""))  # 获取并解码参数
    sign = urllib.unquote(request.cookies.get("sign"))  # 从Cookie中获取并解码签名
    ip = request.remote_addr  # 获取客户端IP地址
    if (waf(param)):  # 检查参数是否包含恶意内容
        return "No Hacker!!!!"  # 返回反黑客信息
    task = Task(action, param, sign, ip)  # 创建Task对象
    return json.dumps(task.Exec())  # 执行Task并返回结果的JSON表示

在这里我们就可以了解如何传参
看一下waf

image

没影响
到这里就分析完了
下面是payload

url/geneSign?param=flag.txtread
//得到签名,一会赋给sign


image

posted @ 2024-07-23 11:03  DGhh  阅读(18)  评论(0编辑  收藏  举报