先看漏洞函数: add_action
function add_action(){
[....]
if($_POST['submit']){
[....]
$_POST=$this->post_trim($_POST);
$_POST['mans'] = (int)$_POST['mans'];
$_POST = yun_iconv('utf-8','gbk',$_POST);
$_POST['status']=$this->config['com_fast_status'];
$_POST['ctime']=time();
$_POST['edate']=strtotime("+".(int)$_POST['edate']." days");
$password=md5(trim($_POST['password']));
if(is_uploaded_file($_FILES['pic']['tmp_name'])){
$upload=$this->upload_pic("../data/upload/once/",false);
$pictures=$upload->picture($_FILES['pic']);
$pic=str_replace("../data/upload/once/","data/upload/once/",$pictures);
$_POST['pic']=$pic;
}
unset($_POST['submit']);
$id=intval($_POST['id']);
if($id<1){
[....]
}else{
$arr=$TinyM->GetOncejobOne(array('id'=>$id,'password'=>$password),array('field'=>'pic,id'));
if($arr['id']){
$data['mans']=$_POST['mans'];
$data['title']=$_POST['title'];
$data['require']=$_POST['require'];
$data['companyname']=$_POST['companyname'];
$data['phone']=$_POST['phone'];
$data['linkman']=$_POST['linkman'];
$data['provinceid']=$_POST['provinceid'];
$data['cityid']=$_POST['cityid'];
$data['three_cityid']=$_POST['three_cityid'];
$data['address']=$_POST['address'];
$data['status']=$this->config['com_fast_status'];
$data['password']=$password;
$data['edate']=$_POST['edate'];
if ($_POST['pic']!=''){
$data['pic']=$_POST['pic'];
}else{
$data['pic']=$arr['pic'];
}
$nid=$TinyM->UpdateOncejob($data,array("id"=>$id));
if($this->config['com_fast_status']=="0"){
$msg="操作成功,等待审核!";
}else{
$msg="操作成功!";
}
[....]
首先在这个 cms 中,单引号传进去先转义然后被替换成了中文的,所以直接通过单引号逃逸是不现实的
在这个函数流程中,当满足有post发包并且 id >= 1的时候,他做了两件事,首先先从job_one 表中根据 id 获取数据,然后将 POST 包里的数据取出来插入数据库中进行更新,在此之前有这么个操作:
$_POST = yun_iconv('utf-8','gbk',$_POST);
这是为了出现乱码的情况,去转码一次
在整理 POST 包数据的时候,注意这一点
if ($_POST['pic']!=''){
$data['pic']=$_POST['pic'];
}else{
$data['pic']=$arr['pic'];
}
如果 pic 为空,那么会用数据库中的 pic 进行填充
我们先跟进 yun_iconv 里看看:
function yun_iconv($in_charset,$out_charset,$str){
if(is_array($str)){
foreach($str as $k=>$v){
$str[$k]=iconv($in_charset,$out_charset,$v);
}
return $str;
}else{
return iconv($in_charset,$out_charset,$str);
}
}
就只是处理了一下数组,没什么特别的
那现在跟进 UpdateOncejob 函数看看,他是怎么更新数据的
function UpdateOncejob($Values=array(),$Where=array()){
$WhereStr=$this->FormatWhere($Where);
$ValuesStr=$this->FormatValues($Values);
return $this->DB_update_all('once_job',$ValuesStr,$WhereStr);
}
POST 里的数据被传入了 FormatValues 函数,继续跟
function FormatValues($Values){
$ValuesStr='';
foreach($Values as $k=>$v){
if(preg_match("/^[a-zA-Z0-9_]+$/",$k)){
if(preg_match('/^[0-9]+$/', $k)){
[....]
}else{
$ValuesStr.=',`'.$k.'`=\''.$v.'\'';
}
}
}
return substr($ValuesStr,1);
}
这个函数简单来说就是 key 值不为纯数字字符串的话,就返回类似 key
= " ' value' " 的字符串形式
而后又带入了 DB_update_all 函数里,跟进去
function DB_update_all($tablename, $value, $where = 1,$pecial=''){
if($pecial!=$tablename){
$where =$this->site_fetchsql($where,$tablename);
}
$SQL = "UPDATE `" . $this->def . $tablename . "` SET $value WHERE ".$where;
$this->db->query("set sql_mode=''");
$return=$this->db->query($SQL);
return $return;
}
直接将传入了 $value 字符串拼接进了 update 语句中
在从控制器到执行sql语句之前的流程中,他只是对 post 的值,包裹了一次单引号
因为起初有 iconv 转码所有的 POST,所以我们可不可以通过构造payload,去吃掉它加上的单引号
现在我们去看看他的过滤形式,首先,经过了 db.safety.php 文件里的过滤流程,因为流程太复杂,就不贴了,看看几处字符串替换,和正则匹配的地方
$str = preg_replace('/([\x00-\x08\x0b-\x0c\x0e-\x19])/', '', $str);
这里替换的都是 0开头的,也就替换到 0x19 ,高位字符一律不管
if(preg_match("/select|insert|update|delete|load_file|outfile/is", $str)){
exit(safe_pape());
}
if(preg_match("/select|insert|update|delete|load_file|outfile/is", $str2)){
exit(safe_pape());
}
这里替换了关键字,而后也有对关键字进行中文替换、拦截,特别是 360waf 里
那么首先这个漏洞触发点他是一个 UPDATE ,入库的时候可以是 16 进制字符串,那么完美绕过所有关键字拦截,但是有个问题
"0x"=>"Ox"
0x 被处理成了 Ox
但是随后就经过了一次 strip_tags 处理,所以这里也是可以绕过的, 0<x>x 就行了</x>
那么目前就是,能够控制插入数据库的数据了,还没有引发注入,还记得开头说的对 pic 字段的处理吗,二次注入点就在这里
现在就可以构造payload了,选择在 'three_cityid' 段利用 iconv 去吃掉执行sql前为其值加上的单引号,造成单引号逃逸
我们选择 \xe98ca6
,它转成 gbk 就变成了 \xe55c
(从 gbk 表中随便选一个 5c 结尾的就行),如果我们在其后加上一个 \ 字符串就是 \xe98ca65c
,首先经过转义变成 \xe98ca65c5c
,然后经过 iconv 处理变成了 \xe55c5c5c
,而他数据库又是这样设置的:
character_set_client=binary
所以,目前更新数据库的时候变成了 \xe\\\
多了一个 \ ,造成了单引号逃逸,从而可以在后面的字段值中载入 payload,插入 16 进制字符串
开始构造payload:
three_cityid 处插入 utf8 字符,然后自己添加 address 字段
二次注入的sql payload为如下字符串的16进制:
', qq=version(), email=user() where 1#
0x272c2071713d76657273696f6e28292c20656d61696c3d757365722829207768657265203123
然后这里有个问题就是,它上传用的是 multipart/form-data; 表单形式,那么想要把 \xe98ca6 传过去就不好弄,我选择修改它的 Content-Type 为application/x-www-form-urlencoded,这样就可以通过 url 编码传值过去,然后自己将 表格形式的键值改成 key=values 形式
额....因为相关规定,我们就只看看其触发过程中的 sql 记录
首先是 update 时:
接着就是漏洞触发点时的记录:
最后造成的影响: