博客园 首页 私信博主 显示目录 隐藏目录 管理
Live2D

[De1CTF 2019]SSRF Me

今天做的这道题比较偏审计,当时拿道题目的确发慌,也是看着大佬的wp一步步做出来的,这题就是需要你扎实的基本功,要有耐心,一步步跟着回溯,就可以做出来

题目

拿到题目代码很乱,可放在pycharm里Ctrl+Alt+L将代码格式化一下

  1 #! /usr/bin/env python
  2 #encoding=utf-8
  3 from flask import Flask
  4 from flask import request
  5 import socket
  6 import hashlib
  7 import urllib
  8 import sys
  9 import os
 10 import json
 11 reload(sys)
 12 sys.setdefaultencoding('latin1')
 13 
 14 app = Flask(__name__)
 15 
 16 secert_key = os.urandom(16)
 17 
 18 
 19 class Task:
 20     def __init__(self, action, param, sign, ip):
 21         self.action = action
 22         self.param = param
 23         self.sign = sign
 24         self.sandbox = md5(ip)
 25         if(not os.path.exists(self.sandbox)):          #SandBox For Remote_Addr
 26             os.mkdir(self.sandbox)
 27 
 28     def Exec(self):
 29         result = {}
 30         result['code'] = 500
 31         if (self.checkSign()):
 32             if "scan" in self.action:
 33                 tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
 34                 resp = scan(self.param)
 35                 if (resp == "Connection Timeout"):
 36                     result['data'] = resp
 37                 else:
 38                     print resp
 39                     tmpfile.write(resp)
 40                     tmpfile.close()
 41                 result['code'] = 200
 42             if "read" in self.action:
 43                 f = open("./%s/result.txt" % self.sandbox, 'r')
 44                 result['code'] = 200
 45                 result['data'] = f.read()
 46             if result['code'] == 500:
 47                 result['data'] = "Action Error"
 48         else:
 49             result['code'] = 500
 50             result['msg'] = "Sign Error"
 51         return result
 52 
 53     def checkSign(self):
 54         if (getSign(self.action, self.param) == self.sign):
 55             return True
 56         else:
 57             return False
 58 
 59 
 60 #generate Sign For Action Scan.
 61 @app.route("/geneSign", methods=['GET', 'POST'])
 62 def geneSign():
 63     param = urllib.unquote(request.args.get("param", ""))
 64     action = "scan"
 65     return getSign(action, param)
 66 
 67 
 68 @app.route('/De1ta',methods=['GET','POST'])
 69 def challenge():
 70     action = urllib.unquote(request.cookies.get("action"))
 71     param = urllib.unquote(request.args.get("param", ""))
 72     sign = urllib.unquote(request.cookies.get("sign"))
 73     ip = request.remote_addr
 74     if(waf(param)):
 75         return "No Hacker!!!!"
 76     task = Task(action, param, sign, ip)
 77     return json.dumps(task.Exec())
 78 @app.route('/')
 79 def index():
 80     return open("code.txt","r").read()
 81 
 82 
 83 def scan(param):
 84     socket.setdefaulttimeout(1)
 85     try:
 86         return urllib.urlopen(param).read()[:50]
 87     except:
 88         return "Connection Timeout"
 89 
 90 
 91 
 92 def getSign(action, param):
 93     return hashlib.md5(secert_key + param + action).hexdigest()
 94 
 95 
 96 def md5(content):
 97     return hashlib.md5(content).hexdigest()
 98 
 99 
100 def waf(param):
101     check=param.strip().lower()
102     if check.startswith("gopher") or check.startswith("file"):
103         return True
104     else:
105         return False
106 
107 
108 if __name__ == '__main__':
109     app.debug = False
110     app.run(host='0.0.0.0')

 

分析

先从路由入手,本题一共有3个路由

我们先看/De1ta这个路由(因为它涵盖的面比较广)

@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"))

可以看到在/De1ta页面我们get方法传入param参数值,在cookie里面传递action和sign的值

然后将传递的param通过waf这个函数。

if(waf(param)):
        return "No Hacker!!!!"

 

于是我们先去看waf函数

waf函数找到以gopher或者file开头的,所以在这里过滤了这两个协议,使我们不能通过协议读取文件

 

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

 

接着在challenge里面,用我们传进去的参数构造一个Task类对象,并且执行它的Exec方法

我们接着去看Exec方法

task = Task(action, param, sign, ip)
    return json.dumps(task.Exec())

 

这是Exec方法

    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

 

先通过checkSign方法检测登录。

到checkSign方法里面去看看

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

当我们传入的参数action和param经过getSign这个函数之后与sign相等,就返回true

返回true之后则进入if语句里面

 

我们来追踪一下getSign这个函数,它主要是三个东西拼接然后进行md5

def getSign(action, param):
    return hashlib.md5(secert_key + param + action).hexdigest()

 

我发现/geneSign这个路由可以生成我们需要的md5(其实它也是调用了getSign这个函数)

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

但是它的action只有"scan",我们回到之前 if (self.checkSign()):中,当它为真会执行它下面的两条if语句

        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

 

代码也很好懂,就是action中必须要有"scan"和"read"两个才能读取flag

试着访问了一下 /geneSign?param=flag.txt ,给出了一个 md5 4699ef157bee078779c3b263dd895341 ,但是只有 scan 的功能,想加入 read 功能就要另想办法了

def geneSign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param)

看了一下逻辑,在 getSign 处很有意思,这个字符串拼接的就很有意思了

def getSign(action, param):
    return hashlib.md5(secert_key + param + action).hexdigest()

不妨假设 secert_key 是 xxx ,那么在开始访问 /geneSign?param=flag.txt 的时候,返回的 md5 就是 md5('xxx' + 'flag.txt' + 'scan') ,在 python 里面上述表达式就相当于 md5(xxxflag.txtscan) ,这就很有意思了。

直接构造访问 /geneSign?param=flag.txtread ,拿到的 md5 就是 md5('xxx' + 'flag.txtread' + 'scan') ,等价于 md5('xxxflag.txtreadscan') ,这就达到了目标。

 

 

直接访问 /De1ta?param=flag.txt 构造 Cookie: sign=7a2a235fcc9218dfe21e4eb400a11b5e;action=readscan 即可

 

posted @ 2020-09-17 20:17  My_Dreams  阅读(1285)  评论(0编辑  收藏  举报
(function() { $("pre").addClass("prettyprint"); prettyPrint(); })();