zoukankan      html  css  js  c++  java
  • 刷题[De1CTF 2019]SSRF Me

    前置知识

    本题框架是flask框架,正好python面向对象和flask框架没怎么学,借着这个好好学一下

    这里我直接听mooc上北京大学陈斌老师的内容,因为讲的比较清楚,直接把他的ppt拿过来,看看就懂了

    解题思路

    直接给源码,得知是python,flask框架。审就完事了

    代码审计

    #! /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)):          #SandBox For Remote_Addr
                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')
                    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
    
        def checkSign(self):
            if (getSign(self.action, self.param) == self.sign):
                return True
            else:
                return False
    
    #generate Sign For Action Scan.
    @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'])
    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())
    @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 getSign(action, param):
        return hashlib.md5(secert_key + param + action).hexdigest()
    
    def md5(content):
        return hashlib.md5(content).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)
    

    代码量不是很长,大概意思是创建了一个Task类,定义了若干个函数,利用时再剖析,三个路由

    1. geneSign路由:
      其调用了getSign函数,返回了经md5处理的值

    2. De1ta路由:
      通过get方法传入param的值,在cookie中传入action和sign的值,将param通过waf函数。再用传入的参数构造一个Task类,执行Exec函数。
      首先看waf函数,禁了gopher协议和file协议,这么打ssrf打不了了。
      再看exec函数,大概就是先判断成功登陆,登陆成功后,读经过scan扫描的param,那么思路就出来了,如果我们传入param的值为flag.txt,并且令action里面有scan和read这两个参数,即可读取内容

    3. /路由:
      根目录路由,指定了访问根目录时,会读整个源码,并定义了一些基础函数

    大概思路明确了。

    保证checkSign函数返回真

    保证 getSign(self.action, self.param) == self.sign

    即经过hashlib.md5(secert_key + param + action).hexdigest()处理

    需要知道这三个字符串连接后的md5值,但是纵观源码并没有哪里有secert_key

    方法一 字符串拼接

    我们又发现很有意思的一点,确认action的值时,使用的是in,并非是等于号,也就是说只要read在action里即可。

    因为使用的是连接符号加号(+),那么传入这两个值时
    md5(secert_key + 'flag.txt' + 'readscan')
    md5(secert_key + 'flag.txtread' + 'scan')

    其实是相同的字符串md5(secert_keyflag.txtreadscan),经md5处理当然也相同

    我们先令/geneSign?param=flag.txtread。在此页面下我们可以获得md5(secert_key + 'flag.txtread' + 'scan') 的值

    再访问/De1ta路由,在其中传入cookie值和get请求即可获得对应的flag

    方法二 哈希长度扩展攻击

    需要使用工具HashPump
    原理见此篇
    github源码

    git clone https://github.com/bwall/HashPump.git
    $ apt-get install g++ libssl-dev
    $ cd HashPump
    $ make
    $ make install

    首先在kali机下安装,这里我的kali机没换国内源。。。先换国内源,然后如果有问题的话aptitude降级安装

    一步一步敲命令,完成后

    使用方法:

    使用方法:
    Input Signature 为md5(salt+message)的值
    Input Data 为message内容
    Input Key Length 为salt+message长度
    Input Data to Add 为自定义添加

    (因为代码中告诉了secert_key为16位,这里的sat看成16位的secert_key+8位的flag.txt即24位)

    算出最后的值同样放在cookie中即可(x记得换成%,即url形式)

    CVE-2019-9948

    Python 2.x到2.7.16中的urllib支持local_file

    local_file允许远程攻击者绕过保护机制

    exp

    这里是使用的 urllib.urlopen(param) 去包含的文件,所以可以直接加上文件路径 flag.txt 或 ./flag.txt 去访问,也可以使用类似的 file:///app/flag.txt 去访问,但是 file 关键字在黑名单里,可以使用 local_file 代替
    如果使用 urllib2.urlopen(param) 去包含文件就必须加上 file ,否则会报 ValueError: unknown url type: /path/to/file 的错误

    总结思路

    • 踏实的代码审计功底
    • MD5扩展攻击
    • 搜索cve,拿poc打

    知识点

    • python代码审计
    • md5扩展攻击
  • 相关阅读:
    php开发环境
    Kofax Transformation Modules
    htmlagilitypack
    OpenOffice 和 LibreOffice
    php manual
    using the web deploy
    *.deploy.cmd
    Volumn Shadow Copy
    Web Deployment Tool 服务器端
    趣味编程丨如何用C语言区分旅客的国籍?教你一招,包你学会!
  • 原文地址:https://www.cnblogs.com/karsa/p/13433479.html
Copyright © 2011-2022 走看看