前言


SQL注入漏洞可以说是在企业运营中会遇到的最具破坏性的漏洞之一,它也是目前被利用得最多的漏洞。要学会如何防御SQL注入,首先我们要学习它的原理。

针对SQL注入的攻击行为可描述为通过在用户可控参数中注入SQL语法,破坏原有SQL结构,达到编写程序时意料之外结果的攻击行为。其成因可以归结为以下两个原因叠加造成的:

程序编写者在处理应用程序和数据库交互时,使用字符串拼接的方式构造SQL语句。未对用户可控参数进行足够的过滤便将参数内容拼接进入到SQL语句中。
注入攻击的本质,是把用户输入的数据当做代码执行。这里有两个关键条件:用户能够控制输入。
原本程序要执行的代码,拼接了用户输入的数据。

Sql注入漏洞危害

1 、 攻击者可以做到

业务运营的所有数据被攻击

对当前数据库用户拥有的所有表数据进行增、删、改、查等操作

若当前数据库用户拥有file_priv权限,攻击者可通过植入木马的方式进一步控制DB所在服务器

若当前数据库用户为高权限用户,攻击者甚至可以直接执行服务器命令从而通过该漏洞直接威胁整个内网系统

2、可能对业务造成的影响

① 用户信息被篡改

② 攻击者偷取代码和用户数据恶意获取

线上代码被非法篡改,并造成为恶意攻击者输送流量或其他利益的影响

Sql注入示例

一、union查询注入

union查询注入是MySQL注入中的一种方式,在SQL注入中说了注入漏洞存在的相关条件,而联合查询注入这种方法需要满足查询的信息在前端有回显,回显数据的位置就叫回显位。

源代码

<?php

        require 'db.php';

        header('Content-type:text/html;charset=utf8');

        $username=$_POST["username"]; //获取用户输入

        $password=$_POST["password"];

        $dl="SELECT * FROM xs WHERE username='$username' and password='$password'"; //登录界面后台处理

        $ck=mysqli_query($db,$dl);

        $row = mysqli_fetch_array($ck);

        if($row) {

      echo "你的账号为:".$row['username'];

      echo "你的密码为:".$row['password'];

        } else {

	  echo "你的输入" .$username;

}

这段代码一上来加载了一个文件db.php根据名字可以判断是连接数据库文件,接着设置编码为UTF8,然后获取用户输入的账号和密码设置为变量,赋入sql查询语句判断账号密码是否输入正确,利用query执行sql语句,再用mysqli_fetch_array把结果作为数组输出到row中。最后用if语句判断sql语句是否执行,执行成功输出用户的账号和密码,执行失败会输出用户输入的账号。代码没有任何防御,有两个回显位所以直接union注入。

注入语句为-1’ union select 1,2 #

二、sql盲注

所谓的盲注就是在服务器没有错误回显的时候完成的注入攻击。服务器没有错误回显,对于攻击者来说缺少了非常重要的“调试信息

源代码

<?php

        require 'db.php';

        header('Content-type:text/html;charset=utf8');

        $username=$_POST["username"]; //获取用户输入

        $password=$_POST["password"];

        $dl="SELECT * FROM xs WHERE username='$username' and password='$password'"; //登录界面后台处理

        $ck=mysqli_query($db,$dl);

        $row = mysqli_fetch_array($ck);

        if($row) {

            echo "你的输入" .$username;

        } else {

        echo "输入错误" ;

}

代码和上面联合查询基本一样只是执行成功以后没有了回显,只返回成功和失败,我们可以使用用布尔盲注通过返回构造的注入语句正确与否来获取数据库里的信息。

注入语句

1’ and length(database())=3 #

三、sql延时注入

时间差注入也叫延迟注入,是一种盲注的手法提交对执行时间铭感的函数sql语句,通过执行时间的长短来判断是否执行成功,比如:正确的话会导致时间很长,错误的话会导致执行时间很短,这就是所谓的高级盲注。

源代码

<?php

        require 'db.php';

        header('Content-type:text/html;charset=utf8');

        $username=$_POST["username"]; //获取用户输入

        $password=$_POST["password"];

        $dl="SELECT * FROM xs WHERE username='$username' and password='$password'"; //登录界面后台处理

        $ck=mysqli_query($db,$dl);

        $row = mysqli_fetch_array($ck);

        if($row) {

            echo "你的输入" .$username;

        } else {

            echo "你的输入" .$username;

}

这次执行成功和失败返回的结果一模一样布尔盲注的方法也不行了,我们可以使用延时注入语句让浏览器语句执行成功的同时延时五秒,延时注入一般用if()加sleep语句配合burpsuite。

注入语句

1’ and if((length(database())=3),sleep(5),1) #

四、报错注入

报错注入就是利用了数据库的某些机制,人为地制造错误条件,使得查询结果能够出现在错误信息中。

源代码

<?php

    require 'db.php';

    header('Content-type:text/html;charset=utf8');

    $username = $_POST["username"]; //获取用户输入

    $password = $_POST["password"];

    $dl = "SELECT * FROM xs WHERE username='$username' and password='$password'"; //登录界面后台处理

    $ck = mysqli_query($db, $dl);

    $row = mysqli_fetch_array($ck);

	if($row)
	
	{
	
	  echo "登录成功";
	
	  }
	
	else
	
	{
	
	print_r(mysql_error());
	
	}

}

报错注入就是利用返回错误信息的函数,把注入的结果通过错误信息返回来,这里可以看到代码执行失败以后会调用报错函数,可以直接在账号框里构造报错注入代码,下面示范采用的updatexml语句。报错注入常用的有floor,updatexml,extractvalue这三种。

注入语句

admin") and updatexml(1,concat(0x7e,(select database()) ,0x7e),1) #

五、cookie注入

Cookie注入简单来说就是利用Cookie而发起的注入攻击。从本质上来讲,Cookie注入与传统的SQL注入并无不同,两者都是针对数据库的注入,只是表现形式上略有不同罢了。

先来看看源码

require 'db.php';
	
	header('Content-type:text/html;charset=utf8');
	
	$username=addslashes($_POST['username']);
	
	$password=MD5($_POST['password']);
	
	$dl="SELECT * FROM xs WHERE username='$username' and password='$password'"; //登录界面后台处理
	
	$ck=mysqli_query($db,$dl);
	
	$row = mysqli_fetch_array($ck);
	
	if($_POST['login']){
	
	if($row) {
	
	setcookie('uname',$row['username']);
	
	$cooke=$_COOKIE['uname'];
	
	$ql="select * from xs where username='$cooke'";
	
	$qk=mysqli_query($db,$ql);
	
	$row1 = mysqli_fetch_array($qk);
	
	if($row1){
	
	echo "nidecooke 创建成功";
	
	}
	
	}

}?>

可以看到cookie注入和上面的代码有很大的区别。前面一大段是正常的登录判断语句,账号用了addslashes()语句进行转义,密码通过MD5加密。而cookie语句在if(sqlalchemy 防止注入 防止sql注入的函数_sqlrow)需要返回正确的值下面的语句才会执行。

sqlalchemy 防止注入 防止sql注入的函数_php_02

可以看到uname=1 uname是上面代码中创建的cookie我们只需在在这个位置进行布尔盲注就可以拿到想要的数据。

sqlalchemy 防止注入 防止sql注入的函数_sql_03

构造这个获取当前数据库长度语句返回成功说明当前数据长度为3.

如何有效防止SQL注入

  1. 输入合法性验证:在应用程序中对用户输入的内容进行检查和验证,仅允许输入合法的数据,如数字、字母等。需要注意的是,输入检查和验证功能应该在服务端进行,而不是在客户端,以防止攻击者通过修改客户端代码绕过验证。
在项目入口处引入该文件
<?php
define('tsz_IS_AJAX', ((isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest')) ? true : false);
if(!function_exists('getFullUrl')){
	function getFullUrl(){
	    $requestUri = '';
	    if (isset($_SERVER['REQUEST_URI'])) {
	        $requestUri = $_SERVER['REQUEST_URI'];
	    } else {
	        if (isset($_SERVER['argv'])) {
	            $requestUri = $_SERVER['PHP_SELF'] .'?'. $_SERVER['argv'][0];
	        } else if(isset($_SERVER['QUERY_STRING'])) {
	            $requestUri = $_SERVER['PHP_SELF'] .'?'. $_SERVER['QUERY_STRING'];
	        }
	    }
	//    echo $requestUri.'<br />';
	    $scheme = empty($_SERVER["HTTPS"]) ? '' : ($_SERVER["HTTPS"] == "on") ? "s" : "";
	    $protocol = strstr(strtolower($_SERVER["SERVER_PROTOCOL"]), "/",true) . $scheme;
	       //端口还是蛮重要的,毕竟需要兼容特殊的场景
	    $port = ($_SERVER["SERVER_PORT"] == "80") ? "" : (":".$_SERVER["SERVER_PORT"]);
	    # 获取的完整url
	    $_fullUrl = $protocol . "://" . $_SERVER['SERVER_NAME'] . $port . $requestUri;
	    return $_fullUrl;
	}
}
if(!function_exists('filterWords')){
	function filterWords($str)
	{
	    $farr = array(
	            "/<(\\/?)(script|i?frame|style|html|body|title|link|meta|object|\\?|\\%)([^>]*?)>/isU",
	            "/(<[^>]*)on[a-zA-Z]+\s*=([^>]*>)/isU",
	            "/select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dump/is"
	    );
	    $str = preg_replace($farr,'',$str);
	    return $str;
	}
}
if(!function_exists('filterArr')){
	function filterArr($arr)
	{
	    if(is_array($arr)){
	        foreach($arr as $k => $v){
	        	if(is_array($v)){
	        		foreach ($v as $key => $value) {
	            		$arr[$k][$key] = filterWords($value);
	        		}
        		}else{
            		$arr[$k] = filterWords($v);
        		}
	        }
	    }else{
	        $arr = filterWords($v);
	    }
	    return $arr;
	}
}
if(!function_exists('str_clear_json')){
	function str_clear_json($value='',$code=5000032,$msg='输入内容不符合规范,请检查重新提交'){
		if(tsz_IS_AJAX){
			exit(json_encode(['code'=>$code,'res'=>$msg,'value'=>$value]));
		}else{
            echo "<script>alert('".$msg."');history.go(-1);</script>";exit();
		}
	}
}

if(strstr(getFullUrl(),'user_homePage_update')===false)
{

$ra1 = Array('javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base','style','alert','script','<script>','</script>');   
$ra2 = Array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload','script','<script>','</script>');   
$ra3 = array('benchmark','sleep','create','exec','execute','insert','delete','drop','truncate','update','select','database','script','union','sleep');
$ra = array_merge($ra1, $ra2, $ra3);   
	$fal=array('~','!','@','#','$','%','^','&','*','(',')','-','+','=','|',';',"'",'"','?','<','>','/');
	foreach($fal as $key2=>$value2){ 
		if($key2!='file')
		{
			if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])&&strstr($_SERVER['HTTP_X_FORWARDED_FOR'],$value2)!==false)
			{
				header("location:/");
				exit("error param140000!");
			}
		}
	}
	foreach($_GET as $key=>$value){ 
		if($key == 'jatkt'){
			continue;
		}
		$fal=array('~','!','@','#','$','%','^','&','*','(',')','-','+','=','|',':',';',"'",'"','?','.','<','>','/');
		foreach($fal as $key2=>$value2){ 
			if($key2!='file')
			{
				if(strstr($value,$value2)!==false)
				{
					header("location:/");
					exit("error param140000!");
				}
			}
		}
		foreach($ra as $key2=>$value2){ 
			if($key2!='file')
			{
				if(strstr($value,$value2)!==false)
				{
					header("location:/");
					exit("error param140000!");
				}
			}
		}
		
	}
	foreach($_COOKIE as $key=>$value){ 
		
		$fal=array('~','!','@','#','$','%','^','&','*','(',')','+','=','|',':',';','?','<','>','/');
		foreach($fal as $key2=>$value2){ 
			if(strstr($value,$value2)!==false)
			{
				header("location:/");
				exit("error param140000!");
			}
		}
		foreach($ra as $key2=>$value2){ 
			if(strstr($value,$value2)!==false)
			{
				header("location:/");
				exit("error param140000!");
			}
		}
		
	}
	$_POST = filterArr($_POST);
	foreach($_POST as $key=>$value){ 
		$fal=array('(',')','if','+','=','|','?','<','>','$','#');
		$fal=array('(',')','if','+','=','|','?','<','>');
		$pre_arr = ['file','photos'];
		foreach($fal as $key2=>$value2){ 
			if(!in_array($key, $pre_arr))
			{
				if(is_array($value)){
					foreach ($value as $key3 => $value3) {
						if(is_array($value3)){
							foreach ($value3 as $ke4 => $value4) {
								if(strstr($value4,$value2)!==false)
								{
									str_clear_json($value4);
									// exit(json_encode(['code'=>5000032,'res'=>'输入内容不符合规范,请检查重新提交-5','value'=>$value4]));
								}
							}
						}else{

							if(strstr($value3,$value2)!==false)
							{
								str_clear_json($value3);
								// exit(json_encode(['code'=>5000032,'res'=>'输入内容不符合规范,请检查重新提交-6','value'=>$value3]));
							}
						}
					}
				}else{
					if(strstr($value,$value2)!==false)
					{
								str_clear_json($value);
						// exit(json_encode(['code'=>5000032,'res'=>'输入内容不符合规范,请检查重新提交-7','value'=>$value]));
					}
				}
			}
		}

		foreach($ra as $key2=>$value2){ 
			if(!in_array($key, $pre_arr))
			{
				if(is_array($value)){
					foreach ($value as $key3 => $value3) {
						if(is_array($value3)){
							foreach ($value3 as $key4 => $value4) {
								if(strstr($value4,$value2)!==false)
								{
								str_clear_json($value4);
									// exit(json_encode(['code'=>5000032,'res'=>'输入内容不符合规范,请检查重新提交-1','value'=>$value4]));
								}
							}
						}else{

							if(strstr($value3,$value2)!==false)
							{
								str_clear_json($value2);
								// exit(json_encode(['code'=>5000032,'res'=>'输入内容不符合规范,请检查重新提交-2','value'=>$value2]));
							}
						}
					}
				}else{
					if(strstr($value,$value2)!==false)
					{
								str_clear_json($value);

						// exit(json_encode(['code'=>5000032,'res'=>'输入内容不符合规范,请检查重新提交-3','value'=>$value]));
						// header("location:/");
						// exit("error param140000!");
					}
				}
			}
		}	
	}
}
?>
  1. 参数化查询:使用参数化查询,将SQL语句与查询参数分离,避免在SQL语句中直接拼接用户输入的数据。采用参数化查询可以有效防止SQL注入攻击。
下面是mysqli_query()函数的使用示例:

// 连接数据库
$mysqli = @new mysqli("127.0.0.1","root","root","dbname");

if(mysqli_connect_errno())
{
    exit ("连接数据库失败:".mysqli_connect_error());
}

$id=  ' 10 or error !=""';

// 查询当前数据库中所有的表
$result = $mysqli->query('SELECT * FROM tables_name where lid = '.$id);

echo "-- <pre>\n\r";


if(mysqli_num_rows($result) > 0){
  while($row = mysqli_fetch_assoc($result)){
    echo "id: " . $row["lid"] . " - Name: " . $row["error"] . "
";
  }
}

echo "-- </pre>";
//查询结果为所有的记录

参数化查询
$mysqli = @new mysqli("127.0.0.1","root","root","dbname");

if(mysqli_connect_errno())
{
    exit ("连接数据库失败:".mysqli_connect_error());
}

$id=  ' 10 or error !=""';

$stmt = $mysqli->prepare('SELECT * FROM table_name where lid = ?');

mysqli_stmt_bind_param($stmt, "d", $id);
$id=  ' 10 or error !=""';
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
if(mysqli_num_rows($result) > 0){
  while($row = mysqli_fetch_assoc($result)){
    echo "id: " . $row["lid"] . " - Name: " . $row["error"] . "
";
  }
}
echo "-- </pre>";
//查询结果为符合条件的数据
  1. 最小化数据访问权限:为不同的数据库用户分配不同的访问权限,避免给攻击者提供不必要的机会。例如,Web应用程序通常只需要对数据库进行查询和插入操作,应该限制Web应用程序对数据库的访问权限,不允许Web应用程序执行删除、修改等操作。
  2. 防火墙和安全审计:在网络层和应用程序层都可以使用防火墙和安全审计工具,保护服务器和Web应用程序免受攻击。例如,使用WAF(Web应用防火墙)可以对网络请求进行监控和过滤,避免恶意请求进入系统。

总的来说,防止SQL注入攻击需要综合应用多种技术和策略,确保系统的安全性。