[De1CTF 2019]SSRF Me

本文详细解析了一个使用Flask构建的Web应用,涉及代码审计,尤其是如何通过代码审查发现并利用SSRF漏洞,绕过签名验证,实现flag文件的操作。关键步骤包括构造payload绕过检查、文件读写的利用。
摘要由CSDN通过智能技术生成

考点:代码审计,关于flask框架的应用,python读写文件

目录

解题过程

代码审计:

1. /De1ta和/geneSign

2. Task类

3. python文件操控

4. 得到flag的大致思路有了

构造payload的步骤:

1. 首先需要绕过self.checkSign()

2. 将flag.txt中的数据读入result.txt,然后读取result.txt

payload2:如下,注意修改cookie中参数action和参数sign的值

总结


解题过程

打开题目,就是进行代码审计

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

代码审计:

一个flask框架,先说一些需要注意的地方

1. /De1ta和/geneSign

分别绑定不同的函数,具有不同的功能,接下俩会具体分析。

2. Task类

这个类中有不同的参数action,对应不同的函数执行,但是需要注意到

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

判断action中的值的时候,用的是in,而不是==,所以如果action中是scanread或者是reanscan的话,if语句同时满足,相应的代码都执行。

3. python文件操控

有许多关于python的文件操控的代码,所以在本地进行了复现

#因为是在windows系统下复现,所以文件路径和源码中有些不同,但是原理一样,都是将flag.txt中的文件放在result.txt中
#! /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

f = open("D:/phpstudy_pro/WWW/test/result.txt",'w')     #注意切换为写的功能
resp = open("D:/phpstudy_pro/WWW/test/flag.txt").read()
f.write(resp)

4. 得到flag的大致思路有了

首先绕过self.checkSign(),并且传入的action需要同时包含scan和read,然后if "scan" in self.action:执行将flag.txt中的数据写入result.txt中,继续if "read" in self.action:执行读取result.txt中的数据,并且放在 result['data'] 中 , return json.dumps(task.Exec())   接着返回以json的形式返回到客户端。


构造payload的步骤:

1. 首先需要绕过self.checkSign()

分析一下相关源码,源码分别截取,方便分析

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



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



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

需要满足self.checkSign(),

就需要getSign(self.action, self.param) == self.sign,(而sign值通过cookie传值)

就需要hashlib.md5(secert_key + param + action).hexdigest() == self.sign,

说白了也就是hashlib.md5(secert_key + 'flag.txt' + 'readscan').hexdigest() == self.sign,即我们需要得到

secert_key + 'flag.txtreadscan'的哈希值

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

但是我们不知道secret_key的值是多少,它只存在于服务端,但是我们可以通过上面截取的源码中/geneSign,来返回我们所需要的编码之后的哈希值

注意到/geneSign中已经将action定为scan,所以我们传入的param可以为flag.txtread,这样的话还是会拼接为secert_key + 'flag.txtreadscan'

payload1:

/geneSign?param=flag.txtread

返回哈希值

2. 将flag.txt中的数据读入result.txt,然后读取result.txt

  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"

payload2:如下,注意修改cookie中参数action和参数sign的值

然后得到flag.


总结

本题我感觉还是主要考察代码审计能力,只要肯下功夫,仔细研读,会有帮助。

主要参考的博客:

[De1CTF 2019]SSRF Me之愚见

  • 10
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值