CVE-2017-5223-PHPMailer任意文件读取漏洞复现
CVE-2017-5223-PHPMailer任意文件读取漏洞复现
影响版本: PHPMailer <= 5.2.21
漏洞原理
文件读取的函数主要是 encodeFile函数。(贴关键代码)
protected function encodeFile($path, $encoding = 'base64')
{
......
$file_buffer = file_get_contents($path);
$file_buffer = $this->encodeString($file_buffer, $encoding);
.......
return $file_buffer;
} catch (Exception $exc) {
$this->setError($exc->getMessage());
return '';
}
}
该函数中接收了一个$path变量,最后该$path变量的值带入到了file_get_contents函数中执行。如果该$path变量可控即可任意文件读取.
$path
参数回溯
在attachAll函数中:
// Add all attachments
foreach ($this->attachment as $attachment) {
// Check if it is a valid disposition_filter
if ($attachment[6] == $disposition_type) {
// Check for string attachment
$string = '';
$path = '';
$bString = $attachment[5];
if ($bString) {
$string = $attachment[0];
} else {
$path = $attachment[0];
}
可以看到$path的赋值过程,只要是$this->attachment数组中每一个attachment的第六个元素为False,就将$attachment[0] 赋值给 $path.
跟进attachment数组
public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
{
...
$this->attachment[] = array(
0 => $path,
1 => $filename,
2 => $name,
3 => $encoding,
4 => $type,
5 => false, // isStringAttachment
6 => $disposition,
7 => 0
);
public function addStringAttachment(
$string,
$filename,
$encoding = 'base64',
$type = '',
$disposition = 'attachment'
) {
// If a MIME type is not specified, try to work it out from the file name
if ($type == '') {
$type = self::filenameToType($filename);
}
// Append to $attachment array
$this->attachment[] = array(
0 => $string,
1 => $filename,
2 => basename($filename),
3 => $encoding,
4 => $type,
5 => true, // isStringAttachment
6 => $disposition,
7 => 0
);
}
public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
{
if (!@is_file($path)) {
$this->setError($this->lang('file_access') . $path);
return false;
}
// If a MIME type is not specified, try to work it out from the file name
if ($type == '') {
$type = self::filenameToType($path);
}
$filename = basename($path);
if ($name == '') {
$name = $filename;
}
// Append to $attachment array
$this->attachment[] = array(
0 => $path,
1 => $filename,
2 => $name,
3 => $encoding,
4 => $type,
5 => false, // isStringAttachment
6 => $disposition,
7 => $cid
);
return true;
}
public function addStringEmbeddedImage(...){
$this->attachment[] = array(
0 => $string,
1 => $name,
2 => $name,
3 => $encoding,
4 => $type,
5 => true, // isStringAttachment
6 => $disposition,
7 => $cid
);
}
只有addAttachment()函数和addEmbeddedImage() 函数可以,其他两个函数$attachment[5] 为true.
主要看AddEmbeddedImage函数,该函数是处理邮件内容中的图片的,回溯该函数发现msgHTML函数调用了该函数,msgHTML 函数是用来发送html格式的邮件。
public function msgHTML($message, $basedir = '', $advanced = false)
{
preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
if (array_key_exists(2, $images)) {
foreach ($images[2] as $imgindex => $url) {
// Convert data URIs into embedded images
if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) {
$data = substr($url, strpos($url, ','));
if ($match[2]) {
$data = base64_decode($data);
} else {
$data = rawurldecode($data);
}
$cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) {
$message = str_replace(
$images[0][$imgindex],
$images[1][$imgindex] . '="cid:' . $cid . '"',
$message
);
}
} elseif (substr($url, 0, 4) !== 'cid:' && !preg_match('#^[a-z][a-z0-9+.-]*://#i', $url)) {
// Do not change urls for absolute images (thanks to corvuscorax)
// Do not change urls that are already inline images
$filename = basename($url);
$directory = dirname($url);
if ($directory == '.') {
$directory = '';
}
$cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
if (strlen($basedir) > 1 && substr($basedir, -1) != '/') {
$basedir .= '/';
}
if (strlen($directory) > 1 && substr($directory, -1) != '/') {
$directory .= '/';
}
if ($this->addEmbeddedImage(
$basedir . $directory . $filename,
$cid,
$filename,
'base64',
self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION))
)
$url是通过解析$message里src=”xxxxx”而来的,$url最终被解析出来就是xxxxx,而$message就是我们发送邮件的自定义的内容。这样可控点就找到了,即可成功利用该漏洞了
查找所有触发encodeFile 函数的触发链。
我们直接查看一下encodeFile的所有引用,只有class.phpmailer.php中的attachAll函数有调用。
发现也只有同文件的createBody()函数调用了,而且是多次调用。
继续往上回溯,我们发现调用链为;
send()->preSend()->createBody->attachAll()->encodeFile()
我们找到一处触发链,找到两个函数可以添加我们的可控变量$path参数。
复现
使用vulhub环境即可,具体操作详见
https://vulhub.org/#/environments/phpmailer/CVE-2017-5223/
“意见反馈”页面,正常用户填写昵称、邮箱、意见提交,这些信息将被后端储存,同时后端会发送一封邮件提示用户意见填写完成:
该场景在实战中很常见,比如用户注册网站成功后,通常会收到一封包含自己昵称的通知邮件,那么,我们在昵称中插入恶意代码`<img src="/etc/passwd">`,目标服务器上的文件将以附件的形式被读取出来。
总结
代码审计步骤仍然是找到危险函数,然后变量回溯,寻找可控变量,利用漏洞。
参考
https://github.com/jiangsir404/PHP-code-audit/blob/master/PHPmailer/PHPmailer 任意文件读取漏洞.md
本文来自博客园,作者:kzd的前沿思考,转载请注明原文链接:https://www.cnblogs.com/Fram3/p/15865949.html