我的编程空间,编程开发者的网络收藏夹
学习永远不晚

从一次有趣的漏洞分析到一个有趣的PHP后门

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

从一次有趣的漏洞分析到一个有趣的PHP后门

起因

事情的起因很有趣,前几天我正对着电脑发呆的时候,突然有个安全交流群的群友来找我交流一个问题

大概的意思就是,他在挖class="lazy" data-src的时候,发现一处资产存在目录遍历漏洞,它通过这个漏洞,找到目标资产使用了一个名为phpmailer的中间件(应该类似于中间件吧),问我有没有办法利用,我查了一下这个组件的漏洞信息。最新的洞似乎截止到6.5.0版本以前

很不幸,群友这个版本是6.5.1,刚好就不能利用了

找不到符合版本的洞没关系,抱着学习的心态,我还是看了一下它的历史漏洞成因,不看不知道,看了之后就学到一些好玩的新知识了,这也就是为什么会有这篇文章的原因。

CVE-2016-10033的简单分析

CVE-2016-10033是Phpmailer出现过的最经典的的漏洞,在本文正式开始之前,我们先来简单分析一下这个漏洞。读者可以到:

https://github.com/opsxcq/exploit-CVE-2016-10033/blob/master/class="lazy" data-src/class.phpmailer.php

看到phpmailer的代码。这里先开门见山地说,漏洞的成因其实就在mail()函数的第五个参数,只要控制了第五个参数,我们就能进行RCE、文件读取等操作。因此我们先追溯mail()函数:

因此我们可以先定位到mailPassthru()这一方法,可以看到,这个方法内部就使用了mail(),maill的第五个参数也就是mailPassthru()的第五个参数。

【----帮助网安学习,以下所有学习资料免费领!加@v~:yj009991,备注“ csdn ”获取!】
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+class="lazy" data-src漏洞分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

因此,我们再查看有没有别的地方使用了mailPassthru(),可以找到这个maillSend()方法中使用了mailPassthru()方法,并且第五个参数$params是来自于当前类中的Sender属性

那我们回溯Sender属性,看看有什么地方可以控制Sender属性。

这里可以看到,setFrom方法当中,就可以对Sender属性进行赋值

当然,这个漏洞还有一个重点就是对validateAddress()这一方法的绕过,这也是CVE-2016-10033的精彩部分。

但是它和本文的重点不符,所以我们就不深入分析这块,感兴趣的读者可以拓展阅读:

https://paper.seebug.org/161/

既然知道了Sender这个关键属性是怎么赋值的,接下来我们继续分析mailSend()方法的调用链,可以找到postSend()方法

继续看postSend(),最终可以找到send()方法

自此,整个漏洞的传参流程我们就已经分析完了。大体上来说,只要我们用setFrom()方法对Sender属性赋值,再调用send()方法。那么Sender属性的值就会进入到mail()函数的第五个参数中,从而实现RCE。看到这想必很多读者已经对开篇提到的这个mail()函数的第五个参数提起兴趣了,为什么控制了它就能实现RCE呢?这就要提到php
中 mail()函数的实现原理了。

有趣的mail()

mail()函数是php定义的用来发送邮件的函数,其支持的参数如下:


为什么一个发送邮件的函数能造成RCE?前人的安研经验已经告诉了我们答案。Php的mail()函数,其底层其实是调用了linux下的sendmail命令。由于sendmail支持一些有趣的参数,这就会造成更大的危害。

①日志写入导致的RCE

接着上面提到的内容来说,我们首先要介绍的是sendmaill的X和O参数,其效果分别为:

-X logfile :指定一个文件来记录邮件发送的详细日志

-O option=value :临时设置一个邮件储存的临时位置。

看到这大部分读者应该马上能反应过来,我们能指定文件来储存邮件发送的日志,那不就可以写日志getshell了吗?事实也的确如此。了解这个信息之后,我们再回过头看mail()函数支持的第五个参数:

没错,我们可以用这个第五个参数来控制sendmail的额外参数,那我们控制X的参数值不就拿下了?我们可以使用如下demo进行测试:

$to = 'La2uR1te@b.c';$subject = ''; //你想执行的任意php代码$message = '';//同上$headers = '';$addtionparam = '-f La2uR1te@1 -OQueueDirectory=/tmp/-X/var/www/html/1.php';//假设我们已知目标站点绝对路径mail($to, $subject, $message, $headers, $param);?>

比如我在自己的服务器上运行如下代码,我们假设网站根目录是/root/,我们运行一下上述代码看看会发生什么。(在复现的时候确保你已经安装过sendmail,不然没用)。

运行完之后,我们在root目录下确实发现了一个名为testmail.php的文件。

我们看看它的内容是什么:

其实他的内容很多,就是日志文件。但是看箭头指的地方,毫无疑问,我们的代码已经被成功写入了。这时候如果我们再用php来执行这个testmail.php,注意看前后的区别

当前用户就是root,当前目录下只有testmail.php和test.php,毫无疑问,我们的恶意代码已经被成功执行了。

综上,如果我们知道目标网站的绝对路径、目标网站是linux环境并且php底层使用sendmail进行发送邮件(默认),那么就可以使用mail()函数来执行写入日志文件的GETSHELL。

②读取配置文件导致的任意文件读取

这个函数好玩的地方不止于此,它还可以用于任意文件读取。我们修改一下上面的demo

注意看这里,我们使用了-C参数,后面跟着我们想读取的文件。这样就能直接实现任意文件读取了!

如下图,直接读文件一把梭

③进阶技巧之利用配置文件执行代码

设想这么一个变态的情况,整个网站,假设我们只有一个可供文件写入的点,并且还有很严格的过滤。这个时候有没有能够用mail()来操作的可能呢?再说回sendmail命令的特性,默认会使用sendmail-mta来解析待发送的邮件内容,我们其实有办法覆盖sendmail的解析配置,让它用php来解析我们要发送的邮件内容,从而直接完成命令执行。

我们首先到/etc/mail/sendmail.cg,复制其内容。然后在其内容结尾加上如下配置:

Mlocal, P=/usr/bin/php, F=lsDFMAw5:/|@qPn9S, S=EnvFromL/HdrFromL,

R=EnvToL/HdrToL,

T=DNS/RFC822/X-Unix,

A=php – $u $h ${client_addr}

把这个新文件命名为sendmail_cf

接着我们执行如下命令,因为这玩意不能回显,所以我们还是让它新创建几个文件:

(各位师傅恭喜发财新年快乐哈)

执行上面的代码之后。可以看到tmp目录下多出一个xnklgxfc(提前祝师傅们新年快乐恭喜发财哈)

④进阶技巧之Exim4情况下mail()函数操作

这里因为环境没配置好,所以没有演示成功,我就借助别的师傅的结果了

mail()函数的底层虽然是sendmail命令,但有时它也有可能是exim4命令。比方说在ubuntu/debain中,sendmail实际上软连接到了exim4。也就是说,我们有新姿势了(exim4的各种参数和能打出的操作都是不同的)

这里我们介绍一个,-be参数,这个参数事exim4中的运行扩展模式参数,这个参数支持我们打出大量操作,例如:

-be ${run{ }{}{}}

//执行命令 ,成功返回string1,失败返回string2

-be ${substr{}{}{}}

//字符串的截取,在string3中从string1开始截取string2个字符

-be ${readfile{}{}}

//读文件file name,以eol string分割

-be ${readsocket{}{}{}{}{}}

//发送socket消息,消息内容为request

没错,你可以发现,这玩意除了可以进行直接的命令执行(不需要写日志文件getshell了),虽然不能回显,但是弹个shell就是简简单单。并且还是可以任意文件读取。并且最骚的是可以用substr来进行字符串截取,这样的话就支持我们进行很多绕过WAF的操作。

利用mail()来造一个简单的后门

上文提到了各种mail()的骚操作,我在学习复现的过程中除了感到NB无话可说。它的种种特性不禁让我思考,它是否有成为后门的潜质,它在正常的linux环境就可以实现日志getshell以及任意文件读取。在其它特定情况下它也可以无回显进行命令执行。并且它是一个再正常不过的函数,一般的开发和安全人员可能都不会觉得这样一个人畜无害的邮件发送函数能造成什么危害。抱着这样的心态,我先简单的实现了这么一个功能:

其中$e的明文内容为:-f La2uR1te@1 -OQueueDirectory=/tmp/
-X/root/mailshell.php

a / a/ a/b/ c / c/ c/d的内容均为phpshell。我们拿它到实际环境去试试看能不能成功实现我们刚刚演示的效果。如果运行成功,那么应该会在root目录下生成mailshell.php

如图,mailshell.php成功生成

其内容即为php一句话

所以我们继续改造一下这个后门,让其变得更加可控。根据上面的总结我们可以知道只要控制第五个参数就行,所以我们的改造也十分简单:

这个后门最后能实现的效果包括但不限于:①linux环境下的任意文件写入(可getshell)②linux环境下的任意文件读取
③特定环境(比如mail()底层使用exim4或软连接到exim4)下的无回显命令执行、代码执行

截至本文发表,这个结构极其简单的后门依然具有不错的免杀能力:

1674118324840

后记

之所以说本文是炒冷饭,是因为安全圈至少在2016年之前就已经知道mail()函数造成的危害。而更多深入利用姿势在17年和18年都有师傅总结(绿盟的师傅以及安恒的师傅)。对于我这个萌新来说,确实是大开眼界叹为观止,果然漏洞的复现一定要及时搞,不然容易错过很多有趣的知识。

来源地址:https://blog.csdn.net/qq_38154820/article/details/129028869

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

从一次有趣的漏洞分析到一个有趣的PHP后门

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

一个有趣的命令:php -S(小技巧分享)

我们知道 PHP从5.4.0起,内置了一个http服务器,开发人员就可以借助这个内置服务器来做一些本地测试,那是如何启动的呢?启动方式如下:php -S ip:port这种启动,如果关闭了当前终端后,服务即停止了...那咋办?这里给大家介绍一个小技巧,我们可以这么执行,变为在后台一直运行的进程:nohup php -S ip:port &日志默认是当前目录下,默认名称为 nohup.out,可以重定
2022-08-10

PHP如何查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符

本篇文章介绍了PHP中查找字符串最后一次出现的位置的方法,并获取从该位置到字符串结尾的所有字符。使用strrpos()函数确定最后出现的位置,然后用substr()函数获取后续字符。示例代码展示了如何使用这些函数查找并获取子字符串。其他注意事项包括offset参数、空子字符串和子字符串长度超过主字符串的情况。
PHP如何查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符
2024-04-02

Python如何查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符

Python中,可以使用rfind()方法查找字符串在另一个字符串中最后一次出现的位置。要提取从最后出现位置到字符串结尾的字符,使用substringing操作即可。此外,还有in操作符、正则表达式和第三方库等其他方法可供选择。选择哪种方法取决于具体情况的效率和功能需求。
Python如何查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符
2024-04-02

Java如何查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符

本文介绍了在Java中查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符的方法。通过使用lastIndexOf()方法和substrin()方法,可以轻松实现此功能。此外,还提供了替代方法,使用正则表达式来查找字符串的最后一次出现。
Java如何查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符
2024-04-02

C语言如何查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符

详解如何使用C语言strstr函数查找字符串中最后一次出现的子串,并返回子串后续内容。方法是遍历主串,找到子串后计算其长度,然后返回子串在主串中的位置加上长度后的内容。
C语言如何查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符
2024-04-02

Go语言如何查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符

在Go中查找字符串最后一次出现的索引并返回剩余内容。使用strings.LastIndex查找索引,然后通过strings.substr提取剩余内容。其他方法包括正则表达式和循环。代码示例说明了strings.LastIndex和strings.substr的用法。
Go语言如何查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符
2024-04-02

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录