内容纲要

前言

这周末有点不舒服 , 在宿舍躺了两天 , 啥也看不进去 , 整个人都傻了. 这两天稍微好点 , 看了道攻防世界的题目 : Web_php_wrong_nginx_config . 解题路线比较简单也很容易找 , 但是写利用脚本时却遇到了比较大的困难 . 还是我太菜了哈哈 .

下面总结一下解题过程 , 以及构造利用脚本的思路 .


Web_php_wrong_nginx_config


拿到敏感文件

打开环境会看到一个弹窗 , 提示请登录 , 然后会跳转到登录界面

没有找到注册按钮 , 随便输入用户名密码登录后会有提示弹框 : 网站建设中 !

看到弹窗就觉得这里登录并不是关键 , 应该有其他的利用点 . 于是拿 dirsearch 扫描一下后台文件

  1. /admin | /admin/ | /admin/?/login | /admin/index.php

    四个页面都提示 please continue

  2. /admin/admin.php

    弹窗显示 you need to log in , 然后跳转到 login.php 页面

  3. /images

    /images 会跳转到 /images/并返回 403 , 没有任何利用点 .

  4. robots.txt

    发现了两个敏感文件 hint.phpHack.php , 分别查看他们 .

  5. hint.php

    给了个提示 , /etc/nginx/sites-enabled/site.conf 文件的配置可能有问题 . 该文件应该是 Nginx 的站点核心配置文件之一 .

  6. Hack.php

    访问该文件得到一个空白页面 , 猜测该文件没有直接输出任何语句 .

基本的信息搜集的差不多了 , 我们现在有几个可用的点 .

  • /admin/admin.php 页面

    该页面需要登录后才能访问 , 但是我们没有帐号密码 , 也没有拿到注册页面 . 所以帐号密码登录是不太现实的 . 同样爆破帐号密码也不太可能 . 现在最理想的情况是在该站点在某个地方泄露了 Cookie , 我们可以凭借这个 Cookie 以某个用户的身份登录 .

  • /etc/nginx/sites-enabled/site.conf 文件

    这个文件是题目中给出的提示 , 肯定有用 . 但现在没有地方去读取这个文件 . 最常用的读取文件漏洞有 " 文件包含 " 与 " 任意文件下载( 读取 ) " , 但要先找到利用点 .

  • Hack.php 页面

    暂时不知道该页面是干啥用的 , 不过看这文件名字肯定有利用点 .

整理思路后 , 拿 BurpSuite 代理走一遍刚才的流程 .


本地文件包含读取 Nginx 配置文件


BurpSuite 发现一个奇怪的 Cookie

在访问的该站点的每一个页面时 , 都会带上一个名为 isLoginCookie , 这没有任何问题 , 但是 isLogin 字段的值却被定义为 " 0 " .

想到 Cookie 是用来识别用户 , 维持用户登录状态的 , 这里 " 0 " 可能代表布尔值 . 因此手动将它改为 " isLogin=1 "

然后再访问 /admin/admin.php , 发现我们成功登录到了站点后台 .

题目环境可能有点问题 , 如果这里没有成功跳转 , 就再进一次题目环境 , 有了 Cookie 后页面会自动跳转到站点后台

控制台可能会抛出一堆错误 , 但这些错误都与做题无关 , 不去管它们


本地文件包含漏洞拿到 /etc/nginx/sites-enabled/site.conf

后台看起来有很多选项卡 , 其实大部分都是假的 , 即使有几个选项存在页面跳转 , 也都是指向 index.php , 没有什么问题 .

真正的利用点在于 " 管理中心 " 选项卡 , 在访问它时会有如下这个 HTTP 请求

注意这个参数 : file=index?ext=php , file参数值是文件名 , ext参数值是文件扩展名 . 那么这里是否存在LFI( 本地文件包含漏洞 ) , 可以包含其他文件呢 ?

  1. ./ 测试

    Payload : file=./index&ext=php

    提示 please continue .

  2. ../ 测试

    Payload : file=../index&ext=php

    响应结果与 ./ 完全相同 , 看起来 ../ 并没有被解析处理 , 或者说是被过滤掉了 .

  3. ....// 测试

    既然 ../ 可能被过滤了 , 那么就重叠写为 ....// . 这样即使 ../ 被过滤删除 , 剩下的内容也还是 ../

    Payload : file=....//index&ext=php

    99页面内容出现了变化 , 不再出现 please continue , 这里可能因为过滤检测后 ../index.php 文件不存在 , 而导致站点自动跳转到 ./index.php 主页 .**

    因此重写法绕过 ../ 过滤可能是成功的 , 我们继续测试 .

  4. 测试去除 ext 参数值

    如果 HTTP 请求中 ext=php 是必须存在且无法更改的 , 那么这里的利用会非常困难 . 因为我们的目标是 /etc/nginx/sites-enabled/site.conf , 而不是 PHP 文件 .

    Payload : file=index&ext=

    没有出现 please continue , 因此这里看到的页面可能是因为当前目录下 index 文件不存在而强制跳转到的 index.php .

  5. 测试去除 ext 参数值 , 并在 file 参数中添加文件扩展名

    那么如何验证上述 没有出现 please continue 字符串是因为站点没有找到文件而做的强制跳转 这个猜想是正确的呢 ?

    我们不在 ext 参数字段中添加任何值 , 而是在 file 参数中添加文件扩展名 , 如果出现了 please continue 则表示读取到了文件 .

    Payload : file=index.php&ext=

    出现了 please continue , 说明读取到了当前目录下的 index.php . 而前面 Payload : file=index&ext= 因为站点没有读到 index 这个文件 , 直接跳转到 index.php , 因此没有出现 please continue .

    这也说明了我上面所有的猜测都是正确的 . 我们可以利用重写 ../ 来绕过站点的过滤删除机制 .

    现在我们可以直接读取到 /etc/nginx/sites-enabled/site.conf.

    Payload : file=....//....//....//....//etc/nginx/sites-enabled/site.conf&ext=

    成功读取到 /etc/nginx/sites-enabled/site.conf 配置文件


目录遍历漏洞拿到 WebShell

利用上面的本地文件包含漏洞 , 我们可以读取到所有已知文件名的文件 , 但是对于那些我们不知道的文件 , 就没有办法去读取 . 必须拿到其它的利用点 .

查看 /etc/nginx/sites-enabled/site.conf , 很容易发现其中存在的问题 .

这段代码给 /web-img 目录设置了一个别名 /images/ , 并且开启了 autoindex .

这里给不熟悉 Nginx 的同学说下 ,

 alias 用于给 localtion 指定的路径设置别名 , 在路径匹配时 , alias 会把 location 后面配置的路径丢弃掉 , 并把当前匹配到的目录指向到 alias 指定的目录 . 

注意 ! alias 会丢弃掉 location 的路径 , 因此 alias 后面的路径是从系统根目录开始的 , 然后直接跟指定的路径 . 它和 root 的用法不一样 . 可以参考这个链接 .

autoindex 是一个目录浏览功能 , 用于列出当前目录的所有文件及子目录 .

总之 , 这里在 URL 访问 /web-img , 就会访问系统根目录下的 /images/

而如果在 URL 访问 /web-img../ , 则相当于访问 /images/../ , 即访问系统根目录 . 且由于开启了 autoindex , 我们可以直接在浏览器里看到根目录下的所有内容 !

这里就是一个目录遍历漏洞 . 我们可以通过它查看系统中的所有文件~

遍历目录 , 在 /var/www 下能找到 hack.php.bak

这是 hack.php 的自动备份文件 . 点击即可下载到本地 .

打开后发现代码很乱 , 不清楚有什么用 .

但是这玩意怎么看怎么像用 weevely 生成的 WebShell 后门 .

weevely 生成的后门如下

所以猜测这也是一个 WebShell , 只不过经过了加密混淆 . 我们需要的做的是分析出该后门的构造思路 , 并且编写连接脚本 , 以连接这个 WebShell .


分析后门代码

hack.php.bak 里代码的思路是很简单的 , 先定义多个变量 , 然后通过 str_replace() 函数进行字符替换并拼接 , 接着通过 create_function() 创建了一个匿名函数 , 最后执行该函数 .

str_replace() 函数替换拼接后的代码就是匿名函数$g的内容 . 因此这里输出$g , 看看该函数到底在做什么 .

还是很乱 , 需要稍微整理下 , 这里增加了一些注释 .

  1. 先是预定义阶段 , 定义了两个字符串和一个 x() 函数 , 后面会用到

  2. 然后就需要获取攻击者发送的数据了 , 这里攻击代码是通过 Referer 字段传输的

    需要注意这个正则函数 preg_match_all() , 该函数从 Accept-Language 取值 , 然后通过正则匹配后输出到 $m 数组中 . 单独拿出来看 , $m 数组的输出内容是如下这样的 .

     $m[0] : 所有可选语言及其权重系数
     $m[1] : 所有可选语言的首字母
     $m[2] : 所有可选语言的权重值( 不清楚该怎么说 , 反正你能明白 )

    简单的说下这个 Accept-Language ( 参考链接 )

     举个例子 : Accept-Language: zh-cn,zh;q=0.5
    
     1. Accept-Language表示浏览器所支持的语言类型 . 
     2. zh-CN 表示简体中文 , zh 表示中文 , 不同语言之间用逗号分割 .
     3. q 是权重系数 , 范围为 [0,1] . q 值越大 , 请求越倾向于获得其对应语言表示的内容 . 若没有指定 q 值,则默认为1 . 若被赋值为0 , 则用于提醒服务器该语言是浏览器不接受的内容类型 .

    然后拼接了前两种可选语言的首字母 , 和预定义的字符串拼接并进行 md5 校验 , 截取等操作 . 然后赋值给 $h$f 两个变量 .

  3. 拼接 Payload

    循环中的 $p .= $q[$m[2][$z]] 会不断从 $q 中提取数据 . 结合之前的代码 , 攻击代码是放在 Referer 中的( 最后会放在 $q 中 ) , 因此这里可以看作是拼接攻击代码 , 组合成 Payload . '

    然后判断 $h 是否出现在 Payload 的开头 , 若是则设置 $_SESSION['$i'] = "" , 同时删除 Payload 的 $h 部分 .

    接着判断 $_SESSION 中那个是否存在 $i 这个键名 , 若是则将 Payload 赋值给 $_SESSION[$i] , 然后查找 $_SESSION[$i]( 也就是 Payload ) 中 $f 第一次出现的位置 .

  4. 解密并执行 Payload

    紧跟上面的代码 , 若在 Payload 中找到了 $f 第一次出现的位置( 也就是说明 $f 存在于 Payload 中 ) , 就会继续执行如下过程 .

    • 生成密钥 $k , 该值由预定义的两个字符串拼接而成 , 然后打开输出控制缓冲区 .

    • 截取 Payload 中从开头到 $f 出现位置的这部分字符串( 由此可以判断 $f 应该是出现在 Payload 的末尾 , 这里删去 $f )

    • 利用 preg_replace() 函数 , 正则替换字符串中的 " _ " 和 " - " 为 " / " 和 " + " .

    • 对替换后的字符串进行 Base64_decode 解码操作

    • 对解码后的字符串进行循环异或运算( 也就是调用 x() 函数 )

    • 对计算后的字符串调用 gzuncompress() 函数进行解压 .

    • 通过 eval() 执行解压后的字符串 .

    • 返回输出到缓冲区的内容 , 然后清空并关闭输出缓冲区 .

    • 对缓冲区输出的内容通过 gzcompress() 函数压缩 , 再通过 x() 函数循环异或计算 , 最后通过 Base64_encode 编码并输出 .

整个后门代码的思路应该就是如上这样的 , 攻击者通过 Referer 传输攻击代码 , 这段攻击代码的格式应该为 填充字符串 + 加密混淆后的Payload + 填充字符串

该后门脚本接收到攻击者传送的数据包 , 先按照一定顺序取出攻击代码 , 然后把前后两侧的填充字符串去除 , 拿到 Payload , 然后对 Payload 进行解密反混淆操作 , 接着通过 eval() 执行攻击者指定的命令 , 最后将命令执行结果加密编码呈现给攻击者 .

整体的解密流程还是非常清晰的 , 我们知道了后门是如何处理攻击者发送的恶意数据包 . 但现在我们没有连接脚本 , 无法构造出后门能处理的请求 . 因此这里需要逆向整个解密过程 , 以便构造出请求数据包 .


逆向分析后门解密过程

正如上所说的 , 我们需要根据解密流程构造出加密流程 , 其主要过程如下所示

  1. 定义一些变量与函数

    这里主要提一下 x() 函数 , 正如注释里写的 , 由于 A ^ B ^ B = A 这个特性 , 所以加密过程和解密过程的 x() 是完全相同的 .

  2. 构造 Payload

    结合加密过程的内容 , 构造 Payload 的过程不多说了 , 看注释应该很容易明白 .

  3. 对收到数据包的处理

    因为后门会在 eval() 处理完毕后返回输出信息 , 且输出信息是被加密过的 . 因此还需要一个解密过程 .

以上就是整个加密构造 Payload 并接收响应数据的流程 , 思路还是比较简单的 .

现在我们仅需要构造出最终的连接脚本就可以了 .


构造连接脚本

由于我自己的代码能力有限 , 搞了很久也没有弄出来连接脚本 . Google后发现了这篇文章 : 一个PHP混淆后门的分析 , 该文章讲的非常清楚 , 可以作为参考分析 , 且有一个已经写好的 Python 连接脚本 .

分析一下这个连接脚本是如何构造的吧 , 原理还是挺简单的 .

  1. 因为从 Referer 中提取 Payload 的顺序依赖于 Accept-Language , 而不同客户端发送的 Accept-Language 可能不一样 , 因此这里需要一个函数来辅助生成随机的可选语言及权重值 .

    先判断输入的序列串是否为空 , 不为空则建立 result 数组 , 并循环将序列串中的值添加到 result 数组中 , 这里的序列串中的值可以是可选语言 , 也可以是权重值 . 最后返回 result 数组

  2. 因为实际攻击代码的组成为 随机填充数据 + Payload + 随机填充数据 , 并且构造 " 交互式Shell " 也需要随机填充数据 , 因此这里需要创建函数来生成随机填充数据 .

    这里创建了两个生成随机填充数据的函数 , 分别对应不同的情况 .

  3. 循环异或函数

    PHP代码中的 x() 函数

  4. 开启 Debug

    这个可开可不开 , 开启 Debug 有助于代码分析 .

  5. 定义基本的变量

    这个没啥好说的 , 注释写的很清楚

  6. 生成完整的 Accept-Language 和 Payload 两侧的随机填充空白

    看注释~~~

  7. 构造 Payload

    这里的代码用于构造 Payload , 包括添加 Payload 两侧的随机填充数据 , Payload 本身的加密 , 最后把加密混淆后的 Payload 作为参数值放入到 Referer 中 .

  8. 发送请求数据包 , 接收并处理响应数据

    攻击代码构造好了 , 下面仅需要发送请求并接收数据 , 解密后即可看到攻击者命令的执行结果 .

    这里也是对应前面 PHP 代码的 , 配合注释应该非常容易理解 .

    这里的攻击代码我会放在文末


利用攻击脚本拿到 Flag

攻击脚本构造完毕 , 下面仅需要修改一些配置( 比如 url , keyh , keyf ) , 就可以利用成功拿到 Flag 了 .

搞定 , 成功拿到 Flag !


总结

本题还是非常有意思的 , 难点主要在于构造连接后门的脚本 , 分析代码的过程非常有趣 .

学到了不少东西 , 自己代码能力还是太差了 , 明明思路很清晰 , 但就是搞不出利用脚本 , 比较郁闷 .

如果您觉得本文有哪些不正确的地方 , 或者您有什么更好的想法 , 欢迎您的留言 .


poc.py

文章中使用的 poc.py 完整脚本的链接如下 :

Web_php_wrong_nginx_config_poc.py

最后修改日期:2020年3月9日

作者

留言

撰写回覆或留言

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