内容纲要

今天研究了一道Web题 --- Cat

png

本题来自 WHCTF 2017 , 大致流程是 : 输入一个域名 , 然后返回该域名是否存在


解题思路

  1. 首先看了一下有没有可读的敏感文件 . 拿 dirsearch 扫了一下发现一共就这么一个网页 , 没有什么可挖的

    png


  1. 然后我就一直以为这是一道命令执行题目 , 然后就想方设法绕过限制 , 所以思路都跑偏了2333 , 试到最后都没有能找到exp .

  1. 好了言归正传 , 遇到这道题首先要FUZZ一下 , 看一下URL到底能接收或过滤哪些字符 . 然后发现一共存在如下三种情况

    • 正常的域名 , 没有返回结果

    • 特殊符号( " ; " , " / " , " & ", ... ) , 返回错误信息 : Invalid URL

    • 宽字节字符("%A0" , "%B0" , "%C0" , ...) : 返回 Django 报错

      png


  1. 发现Django报错 , 那么说明在处理请求时用的是 Python Django 框架 , 而由于DEBUG模式未被关闭 , 所以出错时指向了特定的错误页面 , 从而暴露了敏感文件内容

    在通过django-admin startproject ProjectName创建Django项目时 , 会自动生成一个settings.py文件

    png

    settings.py中可以设置是否开启DEBUG模式

    png

    如果DEBUG参数被设置为True , 那么 :

    • 所有的数据库查询将会以django.db.connection.queries的形式被保存在内存中 , 这会消耗大量的内存

    • 任何404错误都会呈现django特殊的404页面 , 这个页面包含了潜在的敏感信息 , 但是该页面不会在互联网上暴露出来

      png

    • 任何没有被捕获的异常( 从基本的python语法错误到数据库错误以及模板语法错误 )都会返回漂亮的django页面( 在后面你能看到这个页面 ) , 这个页面包含了大量的敏感信息 , 甚至包含部分源代码 !

      总的来说 , 当DEBUG被设置为True时 , 该网站只能被可信任的开发人员使用 . 当你想要在互联网上部署该应用时 , 需要设置DEBUGFalse.


  1. 回到Django报错本身上 , 为了便于研究 , 这里把报错进行HTML解码后转存查看

    png

    下面发现了三段代码

    1. 这段代码用于拼接 requests.FILES 和 requests.POST

    png

     这段代码很奇怪 , 因为在页面中是通过 GET 传递参数的 , 为什么会有 request.POST的值 ? 另外页面并没有任何文件上传点 , 为什么会有 request.FILES?
    
    1. 这里从POST中获取url参数的值 , 对该值转义后用一个正则表进行过滤 , 如果出现非法字符则返回 Invalid URL , 若通过过滤则将其放入ping -c 1命令中执行

    png

     这个正则表非常的严谨 , 命令执行漏洞中常用的 "& | ;" 都不在其中且好像无法绕过 , 所以这里应该不存在命令执行的可能
    
    1. 这段代码是escape()函数的实现 .

    png

     因为用户可能会输入一些特殊字符 , 而且这些特殊字符可能会被解释为正则运算符 . 所以通过对它们进行转义 . 
    
     转义后将数据进行GBK编码后返回
    

    接下来是请求信息

    png

    如果你对HTTP请求很熟悉 , 你应该会发现有问题 , 一般来说正常网页提交表单时 , Content-Type应该为 : "application/x-www-form-urlencoded" , 但是这里用的却 "multipart/form-data" , 这是用于上传文件的表单 . 看来表单中拼接的那个requests.FILES肯定有问题 !


  1. 现在这个网站就比较奇怪了

    • 唯一的页面是一个php页面( index.php ) , 而页面的报错是Python Django的报错 , 为什么PHP页面会出现Python的报错?

    • 页面是通过GET请求获取数据 , 为什么后台会拼接POST请求数据?这个request.FILES请求是怎么来的?

    • 这个Django报错并没有通过HTML渲染 , 而是是通过类似于PHP的show_source显示输出的.

    其实这里已经不难猜出 , 本题后台可能不止一个应用

    1. 通过PHP将界面呈现给用户 , 通过GET请求获取参数 .

    2. 将POST参数与request.FILES拼接处理后通过POST请求传递给Django搭建的API上 .

    3. Django API 解析收到的数据 , 对其进行特殊字符转义并编码为GBK , 然后将数据通过正则判断 , 如果未通过则返回错误信息Invalid URL给PHP , 如果通过则将其放入ping -c 1执行系统命令


  1. 接下来怎么想?

    现在上面很多疑问已经能解释清楚了!

  • 为什么会出现POST请求?

    这个POST请求是PHP构造好后发送给Python Django的 , 与用户操作无关

  • 为什么会输出Django报错信息?

    因为当Django正则不匹配时 , 会将报错信息返回给PHP , PHP本身只作为传递数据的媒介而不解析数据 , 所以仅输出Django的报错信息

  • 为什么用户输入 " %A0 " 等特殊字符会出现Django Debug , 而不是写好的Invalid URL 报错?

    因为在正则匹配之前 , 先将数据转义并进行GBK编码 . 因为像 "%A0" , "%B0" 等并不在GBK编码表中 , 所以这些Unicode字符不能被解码为GBK , 引发报错并终止程序 , 没有执行后面的正则匹配 . 所以在DEBUG界面会输出对应的报错信息

    png

    那么漏洞到底在哪里呢? 其实整个流程大致可以分成两个阶段.

    1. PHP处理阶段
      这个过程感觉是可以利用的 , 因为到现在我都还不清楚那个request.FILES到底有什么用 , 因为正常情况下没必要把一个无关的文件拼接进用户的请求啊~

    2. Django处理阶段
      Django处理阶段需要通过那个正则表的过滤 , 而且这个正则表非常的严谨 , 暂时没有什么很好的方式绕过它 . 所以这个阶段看上去难以利用


  1. PHP处理阶段解析
  • 通过PHP获取数据 , 再传递数据给Django , 那么究竟是如何传递数据的呢?

    可以是配置Django主动请求PHP数据 , 也可能是PHP再构造表单发送给Django . 很明显这里是 PHP重新构造表单给Django

    如何通过PHP构造POST请求? 你肯定会想到libcurl , 这个库在SSRF中常常被使用

    这里就要用到几个小知识点~

    • CURLOPT_POSTFILEDS

      png

    • CURLOPT_SAFE_UPLOAD

      png

      总结来说 , 就是在 PHP 5.5 之前且 CURLOPT_SAFE_UPLOADFalse 时 , 可以通过 " @ + 文件绝对路径 " 来发送文件

  • 还有个疑问就是这个 requests.FILES到底有什么用 .

    HttpRequests.FILES 是Django处理文件上传时的类字典对象 , 表单上传的文件对象就存储在其中 , 在使用时表单格式需要为multipart/form-data! , 现在你应该明白为什么要用这个 Content-Type 了!

    而下面又有这么一段代码

    if isinstance(v, InMemoryUploadedFile):
             v = v.read()
    

    这里将文件v进行类型比较 , 如果v的类型为InMemoryUploadedFile , 那么就读取它

     这个InMemoryUploadedFile 是一个特殊的类型 , 它表示通过表单上传的文件对象的类型 . 
    
     该对象有一个read()方法 , 用于从文件对象中访问文件的内容
    

    那么哪些文件可以通过这个条件呢?

    InMemoryUploadedFile译为内存中的上传文件 , 你是否还记得前文我提到开启DEBUG模式结果中的第一条内容?

    png

    看来我们似乎有目标了 , 找一找前面的DEBUG页面有没有透露什么数据库文件吧~

    png

    绝对路径都给你了~ , 搭配上面这两个利用点 , 是否可以读取该文件中的内容呢?


  1. 最终利用

    现在有了明确的方向 , 来看下是否可以利用 . 为了便于查看 , 通过CURL + XXD + GREP命令来发起请求~

    png

    成功拿到Flag!!!


总结

其实这道题利用的条件真的非常苛刻 , 通过这道题我有了不小的收获

利用条件 :

  • Django开启了DEBUG模式

  • PHP需要开启 CURLOPT_POSTFILEDS , 且设置 CURLOPT_SAFE_UPLOAD = False

  • 读取的文件中存在GBK的无法编码的特殊字符 , 且服务没有捕获到这个异常

利用原理 :

请求中通过 " @ + 文件绝对路径 " 发送文件 , GBK无法对文件中的特殊字符编码导致GBK抛出未捕获的错误 , 把POST请求中的内容爆出 , 而这个POST请求恰好拼接了敏感文件的内容 , 所以可以拿到敏感文件中的内容

最后修改日期:2019年9月17日

作者

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。