PHP代码审计——Day5-Postcard

漏洞解析

class Mailer {
  private function sanitize($email) {
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
      return '';
    }

    return escapeshellarg($email);
  }

  public function send($data) {
    if (!isset($data['to'])) {
      $data['to'] = 'none@ripstech.com';
    } else {
      $data['to'] = $this->sanitize($data['to']);
    }

    if (!isset($data['from'])) {
      $data['from'] = 'none@ripstech.com';
    } else {
      $data['from'] = $this->sanitize($data['from']);
    }

    if (!isset($data['subject'])) {
      $data['subject'] = 'No Subject';
    }

    if (!isset($data['message'])) {
      $data['message'] = '';
    }

    mail($data['to'], $data['subject'], $data['message'],
      '', "-f" . $data['from']);
  }
}

$mailer = new Mailer();
$mailer->send($_POST);

考察点:php内置函数mail引发的命令执行漏洞

bool mail (
	string $to , // 接收人
	string $subject , // 邮件标题
	string $message [, // 邮件正文内容
	string $additional_headers [, // 指定邮件发送时其他的额外头部,如发送者From,抄送CC,隐藏抄送BCC
	string $additional_parameters ]]// 指定传递给发送程序sendmail的额外参数
)

程序中使用filter_var函数来确保只使用有效的邮件地址。关于FILTER_VALIDATE_EMAIL在这篇帖子中有个结论:所有的特殊符号必须放在双引号中

filter_var() 问题在于,在双引号中嵌套转义空格仍然能够通过检测。同时由于底层正则表达式的原因,我们通过重叠单引号和双引号,欺骗 filter_val() 使其认为我们仍然在双引号中,这样我们就可以绕过检测。

php中,mail函数的底层实现调用了escapeshellcmd() :PHP 中用于转义 shell 命令中的特殊字符的函数。它将会转义任何 shell 命令中具有特殊含义的字符,使得这些字符在 shell 中被当作字面量对待,从而避免了命令注入漏洞的发生。

程序中,当对$email进行有效性检查之后,会作下一步处理,return escapeshellarg($email);

escapeshellarg把字符串转码为可以在 shell 命令里使用的参数

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

由于PHP的 mail() 函数在底层调用了 escapeshellcmd() 函数对用户输入的邮箱地址进行处理,即使使用带有特殊字符的payload,绕过 filter_var() 的检测,但还是会被 escapeshellcmd() 处理。然而 escapeshellcmd() 和 escapeshellarg 一起使用,会造成特殊字符逃逸

demo:

<?php
$param = "127.0.0.1' -v -d a=1";
$a = escapeshellarg($param);
// $a: '127.0.0.1'\' '-v -d a=1'

$b = escapeshellcmd($a);
// $b: '127.0.0.1'\\' '-v -d a=1\'
//  这里 \\ 被解释成了 \ 而不再是转义字符

$cmd = "curl ".$b;
// $cmd : curl 127.0.0.1\ -v -d a=1'
// 即向 127.0.0.1\ 发起请求,POST 数据为 a=1'

var_dump($a)."\n";
var_dump($b)."\n";
var_dump($cmd)."\n";
system($cmd);
?>

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

posted @ 2024-04-04 11:12  smile_2233  阅读(6)  评论(0编辑  收藏  举报