草庐IT

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

蚁景科技 2023-03-28 原文

起因

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

大概的意思就是,他在挖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/src/class.phpmailer.php

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

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

【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】

 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)

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

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

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

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

但是它和本文的重点不符,所以我们就不深入分析这块。

既然知道了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进行测试:

<?php
​
$to = 'La2uR1te@b.c';
​
$subject = '<?php system("whoami"); ?>'; //你想执行的任意php代码
​
$message = '<?php system("ls ./"); ?>';//同上
​
$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{<command> <args>}{<string1>}{<string2>}}

//执行命令<command> <args>,成功返回string1,失败返回string2

-be ${substr{<string1>}{<string2>}{<string3>}}

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

-be ${readfile{<file name>}{<eol string>}}

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

-be ${readsocket{<name>}{<request>}{<timeout>}{<eolstring>}{ <fail string>}}

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

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

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

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

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

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

如图,mailshell.php成功生成

其内容即为php一句话

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

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

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

后记

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

更多靶场实验练习、网安学习资料,请点击这里>>

 

有关从一次有趣的漏洞分析到一个有趣的PHP后门的更多相关文章

  1. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  2. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  3. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  4. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

  5. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  6. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  7. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  8. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  9. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

  10. ruby - Rails 关联 - 同一个类的多个 has_one 关系 - 2

    我的问题的一个例子是体育游戏。一场体育比赛有两支球队,一支主队和一支客队。我的事件记录模型如下:classTeam"Team"has_one:away_team,:class_name=>"Team"end我希望能够通过游戏访问一个团队,例如:Game.find(1).home_team但我收到一个单元化常量错误:Game::team。谁能告诉我我做错了什么?谢谢, 最佳答案 如果Gamehas_one:team那么Rails假设您的teams表有一个game_id列。不过,您想要的是games表有一个team_id列,在这种情况下

随机推荐