vlambda博客
学习文章列表

PHP中存在sql注入的两个函数

由于本人晚上加班时比较无聊,又和几个哥们聊了聊他们面试360实验室的经历,决定自己从基础看起,打算做一下常见的语言中存在各种攻击的函数,同时说一下这些函数在哪种情景下会存在这种漏洞。

is_numberic函数

一、is_numberic函数简介国内一部分CMS程序里面有用到过is_numberic函数,我们先看看这个函数的结构bool is_numeric (mixed $var)如果 var 是数字和数字字符串则返回 TRUE,否则返回 FALSE。二、函数是否安全

接下来我们来看个例子,说明这个函数是否安全。

$s = is_numeric($_GET['s'])?$_GET['s']:0;
$sql="insert into test(type)values($s);";  //是 values($s) 不是values('$s')
mysql_query($sql);

例如:

<?php
echo is_numeric(233333);       # 1
echo is_numeric('233333'); # 1
echo is_numeric(0x233333); # 1
echo is_numeric('0x233333');   # 1
echo is_numeric('233333abc'); # 0
?>

上面这个片段程序是判断参数s是否为数字,是则返回数字,不是则返回0,然后带入数据库查询。(这样就构造不了sql语句)我们把‘1 or 1' 转换为16进制 0x31206f722031 为s参数的值程序运行后,我们查询数据库看看,如下图:

如果再重新查询这个表的字段出来,不做过滤带入另一个SQL语句,将会造成2次注入.

寻找可利用的is_numeric

我们现在已经知道了is_numeric是个危险的函数,当我们进行sql注入漏洞查找时可以进行如下搜索,判断是否存在sql注入漏洞

1.变量仅仅通过is_numeric判断是否为数字,没有经过Intval处理

2.变量进行“插入/更新”数据库的时候没有加引号(二次注入,需要先引入数据)

二次注入原理

二次注入可以理解为,攻击者构造的恶意数据存储在数据库后,恶意数据被读取并进入到SQL查询语句所导致的注入。防御者可能在用户输入恶意数据时对其中的特殊字符进行了转义处理,但在恶意数据插入到数据库时被处理的数据又被还原并存储在数据库中,当Web程序调用存储在数据库中的恶意数据并执行SQL查询时,就发生了SQL二次注入(如下图)。


sprintf函数

首先我们先了解sprintf()函数 :sprintf() 函数是把格式化的字符串写入变量中

sprintf(format,arg1,arg2,arg++)
arg1、arg2、++ 参数将被插入到主字符串中的百分号(%)符号处。该函数是逐步执行的。在第一个 % 符号处,插入 arg1,在第二个 % 符号处,插入 arg2,依此类推。
注释:如果 % 符号多于 arg 参数,则您必须使用占位符。占位符位于 % 符号之后,由数字和 "\$" 组成。

例子:

<?php
$number = 123;
$txt = sprintf("带有两位小数:%1\$.2f<br>不带小数:%1\$u",$number);
echo $txt;
?>
输出结果:
带有两位小数:123.00
不带小数:123

sprintf注入原理

底层代码实现

我们来看一下sprintf()的底层实现方法

switch (format[inpos]) {
case 's': {
zend_string *t;
zend_string *str = zval_get_tmp_string(tmp, &t);
php_sprintf_appendstring(&result, &outpos,ZSTR_VAL(str),width, precision, padding,alignment,ZSTR_LEN(str),0, expprec, 0);
zend_tmp_string_release(t);
break;
  }
  case 'd':
      php_sprintf_appendint(&result, &outpos,
                            zval_get_long(tmp),
                            width, padding, alignment,
                            always_sign);
      break;

  case 'u':
      php_sprintf_appenduint(&result, &outpos,
                            zval_get_long(tmp),
                            width, padding, alignment);
      break;

  case 'g':
  case 'G':
  case 'e':
  case 'E':
  case 'f':
  case 'F':
      php_sprintf_appenddouble(&result, &outpos,
                                zval_get_double(tmp),
                                width, padding, alignment,
                                precision, adjusting,
                                format[inpos], always_sign
                              );
      break;

  case 'c':
      php_sprintf_appendchar(&result, &outpos,
                          (char) zval_get_long(tmp));
      break;

  case 'o':
      php_sprintf_append2n(&result, &outpos,
                            zval_get_long(tmp),
                            width, padding, alignment, 3,
                            hexchars, expprec);
      break;

  case 'x':
      php_sprintf_append2n(&result, &outpos,
                            zval_get_long(tmp),
                            width, padding, alignment, 4,
                            hexchars, expprec);
      break;

  case 'X':
      php_sprintf_append2n(&result, &outpos,
                            zval_get_long(tmp),
                            width, padding, alignment, 4,
                            HEXCHARS, expprec);
      break;

  case 'b':
      php_sprintf_append2n(&result, &outpos,
                            zval_get_long(tmp),
                            width, padding, alignment, 1,
                            hexchars, expprec);
      break;

  case '%':
      php_sprintf_appendchar(&result, &outpos, '%');

      break;
  default:
      break;
}

可以看到, php源码中只对15种类型做了匹配, 其他字符类型php未做任何处理,直接跳过,所以导致了这个问题:没做字符类型检测的最大危害就是它可以吃掉一个转义符\, 如果%后面出现一个\,那么php会把\当作一个格式化字符的类型而吃掉\, 最后%\(或%1$\)被替换为空 因此sprintf注入,或者说php格式化字符串注入的原理为:要明白%后的一个字符(除了%,%上面表格已经给出了)都会被当作字符型类型而被吃掉,也就是被当作一个类型进行匹配后面的变量,比如%c匹配asciii码,%d匹配整数,如果不在定义的也会匹配,匹配空,比如%\,这样我们的目的只有一个,使得单引号逃逸,也就是能够起到闭合的作用。

这里我们举两个例子

不使用占位符号

<?php
$sql = "select * from user where username = '%\' and 1=1#';" ;
$args = "admin" ;
echo sprintf ( $sql , $args ) ;
//=> echo sprintf("select * from user where username = '%\' and 1=1#';", "admin");
//此时%\回去匹配admin字符串,但是%\只会匹配空
运行后的结果
select * from user where username = '' and 1=1#'

NO.2

使用占位符号

<?php
$input = addslashes ("%1$' and 1=1#" );
$b = sprintf ("AND b='%s'", $input );
$sql = sprintf ("SELECT * FROM t WHERE a='%s' $b ", 'admin' );
//对$input与$b进行了拼接
//$sql = sprintf ("SELECT * FROM t WHERE a='%s' AND b='%1$\' and 1=1#' ", 'admin' );
//很明显,这个句子里面的\是由addsashes为了转义单引号而加上的,使用%s与%1$\类匹配admin,那么admin由于占位原因只会出现在%s里,%1$\为空(addsashes函数是将单引号前加上反斜杠防止注入)
echo $sql ;
运行后的结果
SELECT * FROM t WHERE a='admin' AND b='' and 1=1#'

对于这个问题,我们还可以这样写

$sql = sprintf ("SELECT * FROM table WHERE a='%1$\' AND b='%d' and 1=1#' ",'admin');
//result: SELECT * FROM t WHERE a='admin' AND b='' and 1=1#'

第一个格式化处匹配时为空,会让给后面的格式化匹配 以上两个例子是吃掉''来使得单引号逃逸出来 下面这个例子我们构造单引号