PHP程式碼審計——Day 5-postcard

smile_2233發表於2024-04-04

漏洞解析

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() 函式,最終實現引數逃逸,導致遠端程式碼執行。

相關文章