利用hpp+php的特性进行巧妙的sql注入

一、

贷齐乐这个系统是安全问题比较严重的P2P金融类的CMS。由于连续出了多次安全漏洞,所以官方给贷齐乐系统中添加了严重影响正常使用的变态WAF。

1.waf1

/core/sqlin.inc.php,包含在config.inc.php中,所有请求都会经由此类过滤

class sqlin {
	function dowith_sql($str) {
		$check= eregi('select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile', $str);
		if($check)
			{
			echo "非法字符!";
			exit();
		}
		
		$newstr="";
		while($newstr!=$str){
		$newstr=$str;
        $str = str_replace("script", "", $str);
        $str = str_replace("execute", "", $str);
        $str = str_replace("update", "", $str);
        //$str = str_replace("count", "", $str);
        //注释掉对count的过滤,不然account这样的参数会被截断
        $str = str_replace("master", "", $str);
        $str = str_replace("truncate", "", $str);
        $str = str_replace("declare", "", $str);
        $str = str_replace("select", "", $str);
        $str = str_replace("create", "", $str);
        $str = str_replace("delete", "", $str);
        $str = str_replace("insert", "", $str);
        $str = str_replace("\'", "", $str);

		}
		return $str;
    }
	
//aticle()防SQL注入函数//php教程
    function sqlin() {
        foreach ($_GET as $key => $value) {
            if ($key != "content"&&strstr($key, "password") == false) {
                $_GET[$key] = $this->dowith_sql($value);
            }
        }
        foreach ($_POST as $key => $value) {
			//echo $key."|".(strpos($key, "password") == false);
			[email protected]_put_contents('wxy123123.txt', date('Ymd His') . '提交url拼接 '.$key."|".(strstr($key, "password") == false), FILE_APPEND);
            if ($key != "content"&&strstr($key, "password") == false) {
                $_POST[$key] = $this->dowith_sql($value);
            }
        }
		foreach ($_REQUEST as $key => $value) {
			//echo $key."|".(strpos($key, "password") == false);
            if ($key != "content"&&strstr($key, "password") == false) {
                $_REQUEST[$key] = $this->dowith_sql($value);
            }
        }
    }
}

GET/POST/REQUEST三个变量,都会经过这个正则:select|insert|update|delete|'|/*|*|../|./|union|into|load_file|outfile

一旦遇到select,包括单引号,包括注释符,就立即exit整个流程。

第一道防线主要是在 dowith_sql函数中进行的。

(1)

        foreach ($_GET as $key => $value) {
            if ($key != "content"&&strstr($key, "password") == false) {
                $_GET[$key] = $this->dowith_sql($value);
            }
        }

调用了 dowith_sql函数,get传参所有的参数都进行循环,当key不等于content时(官方具有content功能),没有password时,就会把所有的value通过dowith_sql函数过滤。

例如此时传入?id=1',请求到后进行循环,此时value为1',将1'放入dowith_sql进行过滤(检测到单引号),非法字符,程序结束。

(2)

 foreach ($_POST as $key => $value) {
			//echo $key."|".(strpos($key, "password") == false);
			[email protected]_put_contents('wxy123123.txt', date('Ymd His') . '提交url拼接 '.$key."|".(strstr($key, "password") == false), FILE_APPEND);
            if ($key != "content"&&strstr($key, "password") == false) {
                $_POST[$key] = $this->dowith_sql($value);
            }
        }

post也进行了循环,将post值也进行了过滤,例如使用post提交uname=1',也会被过滤掉

(3)

foreach ($_REQUEST as $key => $value) {
			//echo $key."|".(strpos($key, "password") == false);
            if ($key != "content"&&strstr($key, "password") == false) {
                $_REQUEST[$key] = $this->dowith_sql($value);
            }
        }

 request同样进行循环并且过滤,request可以接受get、post、cookie这三种,若在cookie中传值也会被过滤掉。

(4)输入测试(index.php)

打开daiqile下db.inc.php文件并且将密码修改为自己的数据库密码(数据库名也该为自己的数据库):

由于这个漏洞较早,在phpstduy中更改php版本为5.4.45 (小皮中mysql和apache都要启动)

如下图,不显示报错即为成功登入:

这里就是由于如下代码所示(index.php),request接收到get传参,然后传入 dowith_sql函数中进行过滤。

2.waf2

/core/safe.inc.php 里面的过滤

/* 检查和转义字符 */
function safe_str($str){
    if(!get_magic_quotes_gpc()) {
        if( is_array($str) ) {
            foreach($str as $key => $value) {
                $str[$key] = safe_str($value);
            }
        }else{
            $str = addslashes($str);
        }
    }
    return $str;
}

function dhtmlspecialchars($string) {
    if(is_array($string)) {
        foreach($string as $key => $val) {
            $string[$key] = dhtmlspecialchars($val);
        }
    } else {
        $string = str_replace(array('&', '"', '<', '>','(',')'), array('&amp;', '&quot;', '&lt;', '&gt;','(',')'), $string);
        if(strpos($string, '&amp;#') !== false) {
            $string = preg_replace('/&amp;((#(\d{3,5}|x[a-fA-F0-9]{4}));)/', '&\\1', $string);
        }
    }
    return $string;
}

foreach ($_GET as $key => $value) {
    $_GET[$key] = safe_str($value);
    $_GET[$key] = dhtmlspecialchars($value);
}
foreach ($_POST as $key => $value) {
    $_POST[$key] = safe_str($value);
    $_GET[$key] = dhtmlspecialchars($value);
}
foreach ($_REQUEST as $key => $value) {
    $_REQUEST[$key] = safe_str($value);
    $_REQUEST[$key] = dhtmlspecialchars($value);
}
foreach ($_COOKIE as $key => $value) {
    $_COOKIE[$key] = safe_str($value);
    $_GET[$key] = dhtmlspecialchars($value);
}

GET/POST/REQUEST/COOKIE都会经过这个替换str_replace(array('&', '"', '<', '>','(',')'), array('&', '"', '<', '>','(',')'), $string),这个替换最明显的效果,就是所有的英文括号都变成中文括号,导致user(),database()等无法执行。

(1)

检查魔术开关get_magic_quotes_gpc,如果是数组,则通过safe_str循环过滤,不是数组则使用和addslashes函数过滤。

(2)

dhtmlspecialchars函数会将英文括号转译为中文括号,函数都会失效,还进行了进行单双引号转义。

单引号、select、union无法使用也就意味着报错注入不能使用。

(3)

使用safe_str和dhtmlspecialchars函数过滤get、post、request、cookie传参。

3.变量获取的逻辑顺序

index.php -> config.inc.php -> sqlin.php -> safe.inc.php

sqlin.php是对select、union、’等关键字的拦截。一旦发现存在这些关键字,就exit出整个执行流程。

safe.inc.php是替换一些敏感字符,比如<、>、(、)等。

第一个waf拦截关键字单双引号等,第二个waf转义括号。

(1)

在safe.inc.php里找到了如下一段代码(在替换之前):

$request_uri = explode("?", $_SERVER['REQUEST_URI']);
if (isset($request_uri[1])) {
	$rewrite_url = explode("&", $request_uri[1]);
	foreach ($rewrite_url as $key => $value) {
		$_value = explode("=", $value);
		if (isset($_value[1])) {
			$_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
		}
	}
}

①先来分析第一行的代码,我们本地daileqi下创建demo.php并写入如下代码测试说明:

url地址栏传入如下数据:

添加如下一行代码展示SERVER接收的值:

下图可知接收的值为/daiqile/demo.php?id=1&username=2&password=3

由图可知是用?将request_uri得到的参数分割为两个数组

②第二三行代码中,if (isset($request_uri[1]))表示$request_uri[1]是否存在,在这里来看是肯定存在的,再使用&再做一次分割,写入如下代码,打印$rewrite_url中的内容

由上图可知,使用&分隔符将$request_uri[1]分隔开

③第五行代码,继续使用=进行分割,value就是上面出现过的1、2、3,然后将value[1]使用addslashes进行转义,然后放入waf2中dhtmlspecialchars函数中进行过滤看是否存在危险字符。

将$_SERVER['REQUEST_URI']用?分开,?后面的内容再用&切割成数组,遍历这个数组。对数组中的每个字符串,再用=分成0和1,最后填入到$_REQUEST数组中:$_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));

这个过程等于手工处理了一遍REQUEST_URI,将REQUEST_URI中的字符串分割成数组覆盖到REQUEST里。

按道理来说并没有什么大错误,但试想:这个过程是在我们的第一道WAF之后进行的,假设我们有一个方法让第一道WAF认为请求中没有恶意字符,再通过这里的覆盖,将恶意字符引入$_REQUEST中,就可以造成WAF的绕过了。

那么有什么办法让第一道WAF认为请求中没有恶意字符?这其实是个很难的问题,因为WAF会检测所有请求数组,只要有一个数组内的值存在问题,就直接退出。

4.简化代码index.php

<?php
header("Content-type: text/html; charset=utf-8");
require 'db.inc.php';
  function dhtmlspecialchars($string) {
      if (is_array($string)) {
          foreach ($string as $key => $val) {
              $string[$key] = dhtmlspecialchars($val);
          }
      }
      else {
          $string = str_replace(array('&', '"', '<', '>', '(', ')'), array('&amp;', '&quot;', '&lt;', '&gt;', '(', ')'), $string);
          if (strpos($string, '&amp;#') !== false) {
              $string = preg_replace('/&amp;((#(\d{3,5}|x[a-fA-F0-9]{4}));)/', '&\\1', $string);
          }
      }
      return $string;
  }
  function dowith_sql($str) {
      $check = preg_match('/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile/is', $str);
      if ($check) {
          echo "非法字符!";
          exit();
      }
      return $str;
  }

  // 经过第一个waf处理
  //i_d=1&i.d=aaaaa&submit=1
  foreach ($_REQUEST as $key => $value) {
      $_REQUEST[$key] = dowith_sql($value);
  }
  // 经过第二个WAF处理
  $request_uri = explode("?", $_SERVER['REQUEST_URI']);
  //i_d=1&i.d=aaaaa&submit=1
  if (isset($request_uri[1])) {
      $rewrite_url = explode("&", $request_uri[1]);
      //print_r($rewrite_url);exit;
      foreach ($rewrite_url as $key => $value) {
          $_value = explode("=", $value);
          if (isset($_value[1])) {
              //$_REQUEST[I_d]=-1 union select flag users
              $_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
          }
      }
  }

  // 业务处理
  //?i_d&i.d=aaaaaaa
  if (isset($_REQUEST['submit'])) {
      $user_id = $_REQUEST['i_d'];
      $sql = "select * from ctf.users where id=$user_id";
      $result=mysql_query($sql);
      while($row = mysql_fetch_array($result))
      {
          echo "<tr>";
          echo "<td>" . $row['name'] . "</td>";
          echo "</tr>";
      }
  }
?>

(1)业务处理板块分析

  if (isset($_REQUEST['submit'])) {
      $user_id = $_REQUEST['i_d'];
      $sql = "select * from ctf.users where id=$user_id";
      $result=mysql_query($sql);
      while($row = mysql_fetch_array($result))
      {
          echo "<tr>";
          echo "<td>" . $row['name'] . "</td>";
          echo "</tr>";
      }
  }

在业务处理板块下,它将id放在user_id,第三行这里不存在单引号也就不用闭合,但是是可以写入语句注入的。

(2)waf组合

  // 经过第一个waf处理
  //i_d=1&i.d=aaaaa&submit=1
  foreach ($_REQUEST as $key => $value) {
      $_REQUEST[$key] = dowith_sql($value);
  }
  // 经过第二个WAF处理
  $request_uri = explode("?", $_SERVER['REQUEST_URI']);
  //i_d=1&i.d=aaaaa&submit=1
  if (isset($request_uri[1])) {
      $rewrite_url = explode("&", $request_uri[1]);
      //print_r($rewrite_url);exit;
      foreach ($rewrite_url as $key => $value) {
          $_value = explode("=", $value);
          if (isset($_value[1])) {
              //$_REQUEST[I_d]=-1 union select flag users
              $_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
          }
      }
  }

分析代码,第一个waf接收参数是使用$_REQUEST接收的,第二个waf使用$_SERVER接收。

 (3)思路

分析index.php中业务处理模块也就是注入点,而这个注入点是通过$_request接收的,而不是$_service接收的,也就意味着我们需要让第一次request提交的值没有恶意字符,而第二次提交的service有恶意字符(可以绕过dhtmlspecialchars函数),

 $_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));

最终提交的恶意字符会重新赋值给$_request,然后我们在$_request中接收,就可以将赋值语句接过来。

思路:

$_REQUEST不可以有恶意字符,防止被waf1过滤掉,$_SERVER可以有恶意字符,但必须要过dhtmlspecialchars这个函数,过完之后重新赋值给request,然后request在业务处理模块中直接接受。

5.php特性

回到3中的问题,那么有什么办法让第一道WAF认为请求中没有恶意字符?

在第一次WAF检测参数的时候,检测的是第二个参数,但后面覆盖request的时候,拿到的是第一个参数,相当于第一次检测的时候是正常的,第二次检测时候将恶意代码覆盖上去,那么不就可以造成WAF的绕过了么?

php传入两个参数时,会执行第二个参数。

php另一个特性,自身在解析请求的时候,如果参数名字中包含” “、”.”、”[“这几个字符,会将他们转换成下划线,如下:

php自动将点转换为了下划线。

假设我发送的是这样一个请求: /t.php?user_id=11111&user.id=22222 ,php先将user.id转换成user_id,即为/t.php?user_id=11111&user_id=22222 ,再获取到的$_REQUEST['user_id']就是22222。

可在$_SERVER['REQUEST_URI']中,user_id和user.id却是两个完全不同的参数名,那么切割覆盖后,获取的$_REQUEST['user_id']却是11111。

完美践行了我上述的思路:WAF检测的是2,实际插入数据库的却是1.

 6.测试


  // 业务处理
  //?i_d&i.d=aaaaaaa
  if (isset($_REQUEST['submit'])) {
      $user_id = $_REQUEST['i_d'];
      $sql = "select * from ctf.users where id=$user_id";
      $result=mysql_query($sql);
      while($row = mysql_fetch_array($result))
      {
          echo "<tr>";
          echo "<td>" . $row['name'] . "</td>";
          echo "</tr>";
      }
  }
?>

再来看这串代码,注入点在第三行,我们在第二行接收到i_d,然后放在第三行中,

写入i_d=aaa&i.d=aaa,i.d里面是正常的字符,点会变成下划线(i_d),第二行REQUEST接收到的是第二个参数i.d(无恶意字符的参数),会经过第一个waf过滤,由于无恶意字符,代码执行到第二个waf,在第二个waf中,就等于传入了两个参数,此时截取的1是i_d=aaa&i.d=aaa,然后使用&拆分开,拆分成i_d=aaa和i.d=aaa,然后将value再使用=进行拆分,waf2中接受的是i_d=aaa,而此时的恶意代码正好在第一个里面,这个恶意代码还要经过dhtmlspecialchars函数的过滤,重新赋值给 $_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));中的$_REQUEST,$key是i_d,value[0]是aaa(恶意代码),此时将恶意代码赋值给了$_REQUEST,上述代码正好接到了恶意代码。

index.php测试:

此时可以看出,并未被过滤,实际上被过滤的是i.d=aaaaa,前面的恶意代码未被拦截。但是dhtmlspecialchars函数还是会过滤,所以不能用括号,使用联合查询代替。

对i_d=1&i.d=aaaaa&submit=1进行分析:

 foreach ($rewrite_url as $key => $value) {
          $_value = explode("=", $value);
          if (isset($_value[1])) {
              //$_REQUEST[I_d]=-1 union select flag users
              $_REQUEST[$_value[0]] = dhtmlspecialchars(addslashes($_value[1]));
          }

(waf2分析)接下来我们要对这个数组进行循环,第一个key为 [0],value为i_d=1;第二个key为[1] ,value为i.d=aaaaa;第三个key为[2] ,value为submit=1。

第一个为value为i_d=1,此时这个value使用等号分割为id和1,$_value[1]就是1,[0]就是i_d,这里的$_REQUEST[i_d]目前等于1。

第二行结束后,结果为数组,(由于业务处理模块需要的是i_d),第一个元素['0']='i_d',第二个元素['1']='1',然后value[1]取出1,然后经过dhtmlspecialchars函数处理,赋值给&_REQUEST,value[0]是i_d,最终结果为$_REQUEST[i_d]=1,最终传入业务处理模块,去除了1也就是name。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值