is_numeric()
is_numeric()函数语法:
Copy is_numeric — 检测变量是否为数字或数字字符串
bool is_numeric ( mixed $var )
如果 var 是数字和数字字符串则返回 TRUE ,否则返回 FALSE 。
示例代码
Copy <? php
$a = '23333333abc' ;
if ( is_numeric ( $a ) ){
echo "6" ;
} else {
echo "flase" ;
}
if ($a > 1000 ){
echo "666" ;
}
?>
因为在php中如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换成数值并且比较按照数值来进行,所以就绕过了is_numeric()函数。
我们下面来看一下is_numeric()函数造成的sql注入问题。
示例代码如下:
Copy <? php
$conn = mysql_connect ( '127.0.0.1' , 'root' , 'root' ) ;
mysql_select_db ( 'fendo' , $conn ) ;
$user = addslashes ( $_GET[ 'user' ] ) ;
$flag = is_numeric ( $user ) ? true : false ;
if ($flag){
$sql = " SELECT id,username, password FROM user WHERE username = $user" ;
$res = mysql_query ( $sql ) ;
if ($res){
while ($row = mysql_fetch_array ( $res ) ){
var_dump ( $row ) ;
}
} else {
echo '----------数据库没有符合此条件的用户记录----------' ;
echo '<br />' ;
}
} else
{
var_dump ( $flag ) ;
}
?>
这个代码的逻辑其实很简单,接收user的值,判断是否为数字,若为数字,则带入到数据库进行查询,否则不查询。
那么我们再来看下数据库的结构:
可是我们的username是一个字符型,但是参数又只能为数字型,理论上来说是没有办法进行注入的 。
但是在is_numeric()函数中,不仅仅是十进制的数字,如:
Copy <? 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
?>
我们可以看到,它是支持是hex编码的,同样,mysql也支持hex编码,所以这里就就导致我们可以使用hex进行注入,如:
admin=0x61646d696e
则:
我们来看一个二次注入的例子:
示例代码:
Copy <? php
$conn = mysql_connect ( '127.0.0.1' , 'root' , 'root' ) ;
mysql_select_db ( 'dvwa' , $conn ) ;
$user = addslashes ( $_GET[ 'user' ] ) ;
$flag = is_numeric ( $user ) ? true : false ;
if ($flag)
{
//保存最大user_id
$max_id;
//保存导出的playload
$playload;
//查出数据库中最大的user_id
$query_max_id = "select max(user_id) from users" ;
$res = mysql_query ( $query_max_id ) ;
if ($res)
{
$rows = mysql_fetch_array ( $res ) ;
$max_id = $rows[ 'user_id' ];
//echo $max_id;
echo '----------max_id查询成功----------' ;
}
else
{
echo '----------max_id查询失败----------' ;
}
//更新最大user_id行的用户名
$gx = "update users set user=$user where user_id=$max_id" ;
$res = mysql_query ( $gx ) ;
if ($res)
{
echo '----------用户数据更新成功----------' ;
echo '----------插入十六进制成功----------' ;
}
else
{
echo '----------用户数据更新失败----------' ;
echo '----------插入十六进制未成功----------' ;
}
//导出插入的playload
$playload = " SELECT user FROM users WHERE user_id = $max_id" ;
$res = mysql_query ( $playload ) ;
if ($res)
{
$rows = mysql_fetch_array ( $res ) ;
$playload = $rows[ 'user' ];
echo '----------playload成功导出----------' ;
}
else
{
echo '----------playload导出失败----------' ;
}
//将导出的playload拼接到select语句中执行
$play = " SELECT user_id,user, password FROM users WHERE user = $playload" ;
$res = mysql_query ( $play ) ;
if ($res)
{
$row = mysql_fetch_array ( $res ) ;
var_dump ( $row ) ;
echo '----------注入已成功----------' ;
}
else
{
echo '----------输入数据不是数字或者数字字符串----------' ;
}
?>
这段代码实现对max(user_id)行的用户名实现十六进制playload的写入,然后,通过select语句又将写入的十六进制playload查询出来,从而实现了二次注入。
PHPYun二次注入.
问题代码如下:
Copy # /app/public/action.class.php
function FormatValues ($Values){
$ValuesStr = '' ;
foreach ($Values as $k => $v){
if ( is_numeric ( $k ) ){
$ValuesStr .= ',' . $v;
} else {
if ( is_numeric ( $v ) ){
$ValuesStr .= ',`' . $k . '`=' . $v;
} else {
$ValuesStr .= ',`' . $k . '`=\'' . $v . '\'' ;
}
}
}
return substr ( $ValuesStr , 1 ) ;
}
多个数据表模型都用到了这个函数,基本都是Update操作。现在的思路是利用接口将恶意代码Update进数据库,再通过其他接口,从数据库读恶意数据,在再次存到数据库的时候,进行注入
从DB层(friend.model.php)可以找到GetFriendInfo():
Copy function GetFriendInfo ($Where = array () , $Options = array ()){
$WhereStr = $this -> FormatWhere ( $Where ) ;
$FormatOptions = $this -> FormatOptions ( $Options ) ;
$row = $this -> DB_select_once ( 'friend_info' , $WhereStr , $FormatOptions[ 'field' ] ) ;
if ($row[ 'pic' ] == '' ){
$row[ 'pic' ] = $row[ 'pic_big' ] = $this -> config[ 'sy_weburl' ] . '/' . $this -> config[ 'sy_friend_icon' ];
} else {
$row[ 'pic' ] = str_replace ( "../" , $this -> config[ 'sy_weburl' ] . "/" , $row[ 'pic' ] ) ;
$row[ 'pic_big' ] = str_replace ( "../" , $this -> config[ 'sy_weburl' ] . "/" , $row[ 'pic_big' ] ) ;
}
return $row;
}
跟进member_log函数:
Copy function member_log ($content , $opera = '' , $type = '' ){
if ($_COOKIE[ 'uid' ]){
$value = "`uid`='" . ( int )$_COOKIE[ 'uid' ] . "'," ;
$value .= "`usertype`='" . ( int )$_COOKIE[ 'usertype' ] . "'," ;
$value .= "`content`='" . $content . "'," ;
$value .= "`opera`='" . $opera . "'," ;
$value .= "`type`='" . $type . "'," ;
$value .= "`ip`='" . fun_ip_get () . "'," ;
$value .= "`ctime`='" . time () . "'" ;
$this -> DB_insert_once ( "member_log" , $value ) ;
}
}
跟进DB_insert_once函数:
Copy function DB_insert_once ($tablename , $value){
$SQL = " INSERT INTO `" . $this -> def . $tablename . "` SET " . $value;
$this -> db -> query ( "set sql_mode=''" ) ;
$this -> db -> query ( $SQL ) ;
$nid = $this -> db -> insert_id () ;
return $nid;
}
先尝试通过hex编码写入测试信息,如图所示 。刷新后,成功写入! http://127.0.0.1/phpyun0625/friend/index.php?c=info
测试二次注入的接口,在DB_insert_once()打印SQL语句 http://127.0.0.1/phpyun0625/ask/index.php?c=friend&a=atnuser POST: id=2 返回结果如下:
Copy INSERT INTO `phpyun_atn` SET `uid` = '2' , `sc_uid` = '1' , `usertype` = '1' , `sc_usertype` = '1' , `time` = '1444726415'
INSERT INTO `phpyun_friend_state` SET `uid`='2',`content`='关注了<a href=\"http://127.0.0.1/phpyun0625/ask/index.php?c=friend&uid=1\">\'\"</a>',`type`='2',`ctime`='1444726415'
INSERT INTO `phpyun_sysmsg` SET `fa_uid` = '1' , `content` = '用户 bb2 关注了你!' , `username` = 'bb1' , `ctime` = '1444726415'
INSERT INTO `phpyun_member_log` SET `uid`='2',`usertype`='1',`content`='关注了'"',`opera`='',`type`='',`ip`='127.0.0.1',`ctime`='1444726415'
1
php比较
php在转码时会把16进制转化为十进制
Copy <? php
error_reporting ( 0 ) ;
function noother_says_correct ($temp)
{
$flag = 'flag{test}' ;
$one = ord ( '1' ) ; //ord — 返回字符的 ASCII 码值
$nine = ord ( '9' ) ; //ord — 返回字符的 ASCII 码值
$number = '3735929054' ;
// Check all the input characters!
for ($i = 0 ; $i < strlen ( $number ) ; $i ++ )
{
// Disallow all the digits!
$digit = ord ( $temp{$i} ) ;
if ( ($digit >= $one) && ($digit <= $nine) )
{
// Aha, digit not allowed!
return "flase" ;
}
}
if ($number == $temp)
return $flag;
}
$temp = $_GET[ 'password' ];
echo noother_says_correct ( $temp ) ;
?>
首先分析代码,函数要求变量$temp不能存在1~9之间的数字, 最后,又要求$temp=3735929054;利用php在转码时会把16进制转化为十进制.于是把 3735929054转换成16进制为0xdeadc0de,记得带上0x; 构造payload
?password=0xdeadc0de