前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >[红日安全]代码审计Day5 - escapeshellarg与escapeshellcmd使用不当

[红日安全]代码审计Day5 - escapeshellarg与escapeshellcmd使用不当

原创
作者头像
红日安全
修改于 2020-03-18 09:58:10
修改于 2020-03-18 09:58:10
1K00
代码可运行
举报
文章被收录于专栏:红日安全红日安全
运行总次数:0
代码可运行

本文由红日安全成员: l1nk3r 编写,如有不当,还望斧正。

前言

大家好,我们是红日安全-代码审计小组。最近我们小组正在做一个PHP代码审计的项目,供大家学习交流,我们给这个项目起了一个名字叫 PHP-Audit-Labs 。现在大家所看到的系列文章,属于项目 第一阶段 的内容,本阶段的内容题目均来自 PHP SECURITY CALENDAR 2017 。对于每一道题目,我们均给出对应的分析,并结合实际CMS进行解说。在文章的最后,我们还会留一道CTF题目,供大家练习,希望大家喜欢。下面是 第5篇 代码审计文章:

Day 5 - postcard

题目叫做明信片,代码如下:

漏洞解析

这道题其实是考察由 php 内置函数 mail 所引发的命令执行漏洞。我们先看看 php 自带的 mail 函数的用法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bool mail (
    string $to ,
    string $subject ,
    string $message [,
    string $additional_headers [,
    string $additional_parameters ]]
)

其参数含义分别表示如下:

  • to,指定邮件接收者,即接收人
  • subject,邮件的标题
  • message,邮件的正文内容
  • additional_headers,指定邮件发送时其他的额外头部,如发送者From,抄送CC,隐藏抄送BCC
  • additional_parameters,指定传递给发送程序sendmail的额外参数。

Linux系统上, phpmail 函数在底层中已经写好了,默认调用 Linuxsendmail 程序发送邮件。而在额外参数( additional_parameters )中, sendmail 主要支持的选项有以下三种:

  • -O option = value QueueDirectory = queuedir 选择队列消息
  • -X logfile 这个参数可以指定一个目录来记录发送邮件时的详细日志情况。
  • -f from email 这个参数可以让我们指定我们发送邮件的邮箱地址。

举个简单例子方便理解:

上面这个样例中,我们使用 -X 参数指定日志文件,最终会在 /var/www/html/rce.php 中写入如下数据:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
17220 <<< To: Alice@example.com
 17220 <<< Subject: Hello Alice!
 17220 <<< X-PHP-Originating-Script: 0:test.php
 17220 <<< CC: somebodyelse@example.com
 17220 <<<
 17220 <<< <?php phpinfo(); ?>
 17220 <<< [EOF]

当然这题如果只是这一个问题的话,会显的太简单了,我们继续往下看,在 第3行 有这样一串代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
filter_var($email, FILTER_VALIDATE_EMAIL)

这串代码的主要作用,是确保在第5个参数中只使用有效的电子邮件地址 $email 。我们先了解一下 filter_var() 函数的定义:

filter_var :使用特定的过滤器过滤一个变量 mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] ) 功能 :这里主要是根据第二个参数filter过滤一些想要过滤的东西。

关于 filter_var()FILTER_VALIDATE_EMAIL 这个选项作用,我们可以看看这个帖子 PHP FILTER_VALIDATE_EMAIL 。这里面有个结论引起了我的注意: none of the special characters in this local part are allowed outside quotation marks ,表示所有的特殊符号必须放在双引号中。 filter_var() 问题在于,我们在双引号中嵌套转义空格仍然能够通过检测。同时由于底层正则表达式的原因,我们通过重叠单引号和双引号,欺骗 filter_val() 使其认为我们仍然在双引号中,这样我们就可以绕过检测。下面举个简单的例子,方便理解:

当然由于引入的特殊符号,虽然绕过了 filter_var() 针对邮箱的检测,但是由于PHP的 mail() 函数在底层实现中,调用了 escapeshellcmd() 函数,对用户输入的邮箱地址进行检测,导致即使存在特殊符号,也会被 escapeshellcmd() 函数处理转义,这样就没办法达到命令执行的目的了。 escapeshellcmd() 函数在底层代码如下(详细点 这里 ):

因此我们继续往下看,在第七行有这样一串代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
return escapeshellarg($email);

这句代码主要是处理 $email 传入的数据。我们先来看一下 escapeshellarg 函数的定义:

escapeshellarg — 把字符串转码为可以在 shell 命令里使用的参数 功能 :escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,shell 函数包含 exec(),system() 执行运算符(反引号) 定义string escapeshellarg ( string $arg )

具体功能作用,可以参考如下案例:

那我们前面说过了PHP的 mail() 函数在底层调用了 escapeshellcmd() 函数对用户输入的邮箱地址进行处理,即使我们使用带有特殊字符的payload,绕过 filter_var() 的检测,但还是会被 escapeshellcmd() 处理。然而 escapeshellcmd()escapeshellarg 一起使用,会造成特殊字符逃逸,下面我们给个简单例子理解一下:

详细分析一下这个过程:

  1. 传入的参数是 127.0.0.1' -v -d a=1
  2. 由于escapeshellarg先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。所以处理之后的效果如下: '127.0.0.1'\'' -v -d a=1'
  3. 接着 escapeshellcmd 函数对第二步处理后字符串中的 \ 以及 a=1' 中的单引号进行转义处理,结果如下所示: '127.0.0.1'\\'' -v -d a=1\'
  4. 由于第三步处理之后的payload中的 \\ 被解释成了 \ 而不再是转义字符,所以单引号配对连接之后将payload分割为三个部分,具体如下所示:

所以这个payload可以简化为 curl 127.0.0.1\ -v -d a=1' ,即向 127.0.0.1\ 发起请求,POST 数据为 a=1'

总结一下,这题实际上是考察绕过 filter_var() 函数的邮件名检测,通过 mail 函数底层实现中调用的 escapeshellcmd() 函数处理字符串,再结合 escapeshellarg() 函数,最终实现参数逃逸,导致 远程代码执行

实例分析

这里实例分析选择 PHPMailer 命令执行漏洞CVE-2016-10045CVE-2016-10033 )。项目代码可以通过以下方式下载:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
git clone https://github.com/PHPMailer/PHPMailer
cd PHPMailer
git checkout -b CVE-2016-10033 v5.2.17

漏洞原理

CVE-2016-10033

在github上直接diff一下,对比一下不同版本的 class.phpmailer.php 文件,差异如下:

这里在 sendmailSend 函数中加了 validateAddress 函数,来针对发送的数据进行判断,判断邮箱地址的合法性。另外针对传入的数据,调用了 escapeshellarg 函数来转义特殊符号,防止注入参数。然而这样做,就引入了我们上面讨论的问题,即同时使用 escapeshellarg 函数和 escapeshellcmd() 函数,导致单引号逃逸。由于程序没有对传命令参数的地方进行转义,所以我们可以结合 mail 函数的第五个参数 -X 写入 webshell

下面详细看一下代码,漏洞具体位置在 class.phpmailer.php 中,我们截取部分相关代码如下 :

在上图第12行处没有对 $params 变量进行严格过滤,只是简单地判断是否为 null ,所以可以直接传入命令。我们继续往下看,我们发现在上图第12行,当 safe_mode 模式处于关闭状态时, mail() 函数才会传入 $params 变量。

进一步跟跟进 $params 参数,看看它是怎么来的。这个参数的位置在 class.phpmailer.php 中,我们截取部分相关代码,具体看下图 第11行

很明显 $params 是从 $this->Sender 传进来的,我们找一下 $this->Sender ,发现这个函数在 class.phpmailer.php 中,截取部分相关代码,具体看下图 第10行

这里在 setFrom 函数中将 $address 经过某些处理之后赋值给 $this->Sender 。我们详细看看 $address 变量是如何处理的。主要处理函数均在 class.phpmailer.php 文件中,我们截取了部分相关代码,在下图 第三行 中使用了 validateAddress 来处理 $address 变量。

所以跟进一下 validateAddress 函数,这个函数位置在 class.phpmailer.php 文件中。我们看看程序流程,相关代码如下:

分析一下这段代码,大概意思就是对环境进行了判断,如果没有 prce 并且 php 版本 <5.2.0 ,则 $patternselect = 'noregex' 。接着往下看,在 class.phpmailer.php 文件中,有部分关于 $patternselectswich 操作,我只选择了我们需要的那个,跟踪到下面的 noregex

这里简单的只是根据 @ 符号来处理字符,所以这里的payload很简单。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
a( -OQueueDirectory=/tmp -X/var/www/html/x.php )@a.com

然后通过 linux 自身的 sendmail 写log的方式,把log写到web根目录下。将日志文件后缀定义为 .php ,即可成功写入webshell。

CVE-2016-10045

diff一下5.2.20和5.2.18发现针对 escapeshellcmdescapeshellarg 做了改动。

这里其实有个很奇妙的漏洞,针对用户输入使用 escapeshellarg 函数进行处理。所以,在最新版本中使用之前的 payload 进行攻击会失败,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
a( -OQueueDirectory=/tmp -X/var/www/html/x.php )@a.com

但是,却可以使用下面这个 payload 进行攻击:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
a'( -OQueueDirectory=/tmp -X/var/www/html/x.php )@a.com

实际上,可用于攻击的代码只是在之前的基础上多了一个单引号。之所以这次的攻击代码能够成功,是因为修复代码多了 escapeshellcmd 函数,结合上 mail() 函数底层调用的 escapeshellarg 函数,最终导致单引号逃逸。

我们的 payload 最终在执行时变成了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
'-fa'\\''\( -OQueueDirectory=/tmp -X/var/www/html/test.php \)@a.com\'

按照刚才上面的分析,我们将payload化简分割一下就是-fa\(-OQueueDirectory=/tmp-X/var/www/html/test.php)@a.com',这四个部分。最终的参数就是这样被注入的。

漏洞利用

漏洞有一些基本要求: 1、php version < 5.2.0 2、phpmailer < 5.2.18 3、php 没有安装 pcre(no default) 4、safe_mode = false(default)

存在正则绕过之后,以及 escapeshellargescapeshellcmd 一起使用造成的神奇现象之后。

只需要 phpmailer < 5.2.20

环境,poc,exp相关

修复建议

我们来看一下 PHPMailer 官方给出的修复代码。官方对用户传入的参数进行检测,如果当中存在被转义的字符,则不传递 -f 参数(-f 参数表示发邮件的人,如果不传递该参数,我们的payload就不会被带入 mail 函数,也就不会造成命令执行),所以不建议大家同时使用 escapeshellcmd()escapeshellarg() 函数对参数进行过滤,具体修复代码如下:

结语

看完了上述分析,不知道大家是否对 escapeshellarg()escapeshellcmd() 两个函数一起使用所产生的问题,有了更加深入的理解,文中用到的代码可以从 这里 下载,当然文中若有不当之处,还望各位斧正。如果你对我们的项目感兴趣,欢迎发送邮件到 hongrisec@gmail.com 联系我们。Day5 的分析文章就到这里,我们最后留了一道CTF题目给大家练手,题目如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//index.php
<?php
highlight_file('index.php');
function waf($a){
    foreach($a as $key => $value){
        if(preg_match('/flag/i',$key)){
            exit('are you a hacker');
        }
    }
}
foreach(array('_POST', '_GET', '_COOKIE') as $__R) {
    if($$__R) { 
        foreach($$__R as $__k => $__v) { 
            if(isset($$__k) && $$__k == $__v) unset($$__k); 
        }
    }

}
if($_POST) { waf($_POST);}
if($_GET) { waf($_GET); }
if($_COOKIE) { waf($_COOKIE);}

if($_POST) extract($_POST, EXTR_SKIP);
if($_GET) extract($_GET, EXTR_SKIP);
if(isset($_GET['flag'])){
    if($_GET['flag'] === $_GET['hongri']){
        exit('error');
    }
    if(md5($_GET['flag'] ) == md5($_GET['hongri'])){
        $url = $_GET['url'];
        $urlInfo = parse_url($url);
        if(!("http" === strtolower($urlInfo["scheme"]) || "https"===strtolower($urlInfo["scheme"]))){
            die( "scheme error!");
        }
        $url = escapeshellarg($url);
        $url = escapeshellcmd($url);
        system("curl ".$url);
    }
}
?>
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// flag.php
<?php
$flag = "HRCTF{Are_y0u_maz1ng}";
?>

题解我们会在项目第一阶段完成后放出,just having fun!

相关文章

phpmailer RCE漏洞分析

PHP escapeshellarg()+escapeshellcmd() 之殇

PHPMailer 命令执行漏洞(CVE-2016-10033)分析

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Swift 中的 Phantom(幻象)类型
模糊的数据可以说是一般应用程序中最常见的错误和问题的来源之一。虽然 Swift 通过其强大的类型系统和完善的编译器帮助我们避免了许多含糊不清的来源——但只要我们无法在编译时保证某个数据总是符合我们的要求,就总是有风险,我们最终会处于含糊不清或不可预测的状态。
韦弦zhy
2022/04/26
9090
Swift代码中的嵌套命名法
Swift支持与其他类型嵌套命名,尽管它还没有专用的命名关键词。下面我们来看看,如何使用类型嵌套来优化我们代码的结构。
莫空9081
2021/03/01
1.7K0
Swift基础语法(四)
在Swift5之前,我们一般是采用上面的方式来处理异常,在Swift5之后,苹果推出了一个Result枚举,Result枚举可以更加优雅地去处理异常。
拉维
2020/07/06
4K0
Swift基础语法(四)
开心档之Swift 访问控制
你可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、函数、初始化方法、基本类型、下标索引等设置访问级别。
爱学iOS的小麦子
2023/03/06
1K0
【面试必备】Swift 面试题及其答案
答案:optional类型被用来表示任何类型的变量都可以表示缺少值。在Objective-C中,引用类型的变量是可以缺少值得,并且使用nil作为缺少值。基本的数据类型如int 或者float没有这种功能。
编程怪才-凌雨画
2021/01/26
2.9K0
【面试必备】Swift 面试题及其答案
Swift 中的 Sendable 和 @Sendable 闭包
Sendable 和 @Sendable 是 Swift 5.5 中的并发修改的一部分,解决了结构化的并发结构体和执行者消息之间传递的类型检查的挑战性问题。
韦弦zhy
2022/11/14
1.5K0
Swift 中的 Sendable 和 @Sendable 闭包
Swift 5.6 新特性
Swift 5.6 之前只有#available表示可用,Swift 5.6 之后增加了#unavailable表示不可用,二者意思相反。
YungFan
2022/03/30
1.2K0
对Swift中some和any关键字的理解
在最新Swift版本中(Xcode14,Swift5.7),如果协议中有使用泛型,则如果要将此协议作为参数类型,必须使用any关键字进行修饰。其实在Swift5.1中也引入过一个some关键字,any和some都适用于协议,这两个关键字从语义上和写法上对泛型的使用进行了优化。
珲少
2022/11/14
1.1K1
46 道 Swift 常见面试题解
3、Set 独有的方法有哪些? 4、实现一个 min 函数,返回两个元素较小的元素 5、map、filter、reduce 的作用 6、map 与 flatmap 的区别 7、什么是 copy on write 8、如何获取当前代码的函数名和行号 9、如何声明一个只能被类 conform 的 protocol 10、guard 使用场景 11、defer 使用场景 12、String 与 NSString 的关系与区别 13、怎么获取一个 String 的长度 14、如何截取 String 的某段字符串 15、throws 和 rethrows 的用法与作用 16、try?和 try!是什么意思 17、associatedtype 的作用 18、什么时候使用 final 19、public 和 open 的区别 20、声明一个只有一个参数没有返回值闭包的别名
Swift社区
2021/11/26
5.4K0
【面试必备】Swift 面试题及其答案
答案:optional 类型被用来表示任何类型的变量都可以表示缺少值。在 Objective-C 中,引用类型的变量是可以缺少值,并且使用 nil 作为缺少值。基本的数据类型如 int 或者 float 没有这种功能。
Swift社区
2021/11/26
7K0
戴铭的 Swift 小册子
越来越多同学打算开始用 Swift 来开发了,可很多人以前都没接触过 Swift。这篇和我以前文章不同的是,本篇只是面向 Swift 零基础的同学,内容主要是一些直接可用的小例子,例子可以直接在工程中用或自己调试着看。
Swift社区
2021/12/06
2.3K0
戴铭的 Swift 小册子
了解 Swift 的 Result 类型
通常希望函数成功返回一些数据,或者如果失败则返回错误。我们通常使用throwing函数对此建模,因为如果函数调用成功,我们将获得数据,但是如果抛出错误,则将运行catch代码块,因此我们可以独立处理这两个函数。但是,如果函数调用没有立即返回怎么办?
韦弦zhy
2020/09/14
2.7K0
了解 Swift 的 Result 类型
Swift 5.5 新特性
Swift 5.5 内置于 Xcode 13,虽然版本号只增加了 0.1,看似是一个小版本升级,但却带来了非常多的新内容,其中最大的更新是引入了全新的并发编程方式。
YungFan
2021/07/16
2.1K0
Swift 5.6到5.10新特性整理
当你编写涉及共享状态的代码时,如果你不确保这个共享状态在跨线程使用时是安全的,你就会在许多地方遇到数据竞争的问题。
小刀c
2024/04/03
2.2K0
Swift 5.6到5.10新特性整理
AttributedString——不仅仅让文字更漂亮
在WWDC 2021上,苹果为开发者带来了有一个期待已久的功能——AttributedString,这意味着Swift开发人员不再需要使用基于Objective-C的NSAttributedString来创建样式化文本。本文将对其做全面的介绍并演示如何创建自定义属性。
东坡肘子
2022/07/28
4K0
AttributedString——不仅仅让文字更漂亮
WWDC 2021新Formatter API:新老比较及如何自定义
在WWDC 2021的What's in Foundation专题中,苹果隆重介绍了适用于Swift的新Formatter API。网上已经有不少文章对新API的用法进行了说明。本文将通过介绍如何创建符合新API的Formatter,让读者从另一个角度了解新Formatter API的设计机制;并对新旧两款API进行比较。
东坡肘子
2022/07/28
1.4K0
WWDC 2021新Formatter API:新老比较及如何自定义
Swift 项目中涉及到 JSONDecoder,网络请求,泛型协议式编程的一些记录和想法
最近项目开发一直在使用 swift,因为 HTN 项目最近会有另外一位同事加入,所以打算对最近涉及到的一些技术和自己的一些想法做个记录,同时也能够方便同事熟悉代码。
用户7451029
2020/06/16
6.8K0
Swift 5.5 新特性
SE-0296提案终于为开发者带来了期待已久的 async/await,语法基本上和javascript中的很像。
小刀c
2022/08/16
2.7K0
Swift 5.5 新特性
Swift 周报 第三十四期
本期是 Swift 编辑组自主整理周报的第三十四期,每个模块已初步成型。各位读者如果有好的提议,欢迎在文末留言。
Swift社区
2023/09/06
2600
Swift 周报 第三十四期
Codable 解析 JSON 配置默认值
2017年推出的 Codable 无疑是 Swift 的一大飞跃。尽管当时社区已经构建了多种用于本地 Swift 值和 JSON 之间 的编解码工具,但由于 Codable 与 Swift 编译器本身的集成,提供了前所未有的便利性,使我们能够通过使可解码类型遵守 Decodable 协议来定义可解码类型,例如:
韦弦zhy
2021/04/07
1.9K0
相关推荐
Swift 中的 Phantom(幻象)类型
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档