Pikachu靶场搭建与Web渗透实战指南
1. 为什么选Pikachu靶场作为渗透测试入门第一站
很多人刚接触渗透测试时,第一反应是找一个真实网站“练手”,结果要么被风控系统秒封IP,要么刚发个GET请求就被WAF弹出403,更别说尝试SQL注入或XSS了——还没摸到漏洞边,先收到一封来自运维团队的“温馨提示”。我带过十几期安全入门训练营,90%的新手在真实环境里栽的第一个跟头,不是技术不会,而是连“合法、可控、可复现”的测试边界都找不到。Pikachu靶场就是为解决这个问题而生的:它不是模拟器,也不是教学幻灯片,而是一个完全由PHP+MySQL构建、漏洞逻辑与真实业务高度对齐、且所有交互行为都在本地闭环完成的Web安全实验室。关键词很明确:Pikachu靶场、渗透测试、Web安全、漏洞复现、靶场搭建。它不教你怎么绕过企业级WAF,但会手把手带你理解:为什么' or 1=1#能绕过登录校验?为什么<script>alert(1)</script>在输入框里没弹窗,却在评论区成功执行?为什么Burp Suite抓到的Cookie里多了一个PHPSESSID,而删掉它整个会话就崩了?这些问题的答案,不在教材目录里,而在你亲手启动服务、修改参数、观察响应的每一秒中。它适合三类人:零基础想确认自己是否真喜欢安全方向的初学者;刚考完CTF线上赛、但面对真实Web应用仍无从下手的参赛者;以及需要给新人做内部培训、又苦于找不到合适教学载体的安全负责人。它不承诺让你成为红队专家,但它能确保你在第一次写出sqlmap -u "http://127.0.0.1:8080/sqli/?id=1" --dbs之前,已经亲手用手工方式把布尔盲注的每一位ASCII码都推导出来。
2. 搭建过程远不止“解压+启动”:环境兼容性、路径陷阱与权限配置的硬核细节
Pikachu靶场官方提供两种部署方式:Docker一键式和手动源码部署。绝大多数教程只告诉你“运行docker-compose up -d”,然后截图展示首页成功加载。但我在实际教学中发现,超过65%的搭建失败案例,根本原因不在Docker本身,而在于宿主机环境与容器内PHP版本、扩展模块、文件权限之间的隐性冲突。比如,某次学员在macOS Monterey上使用Docker Desktop 4.22启动后,访问http://127.0.0.1:8080始终返回500错误。日志显示PHP Fatal error: Uncaught Error: Call to undefined function mysqli_connect()——这说明容器内的PHP根本没有加载mysqli扩展。查Dockerfile才发现,官方镜像基于php:7.4-apache,但该基础镜像默认不启用mysqli,必须在Dockerfile中显式添加RUN docker-php-ext-install mysqli并重启Apache。这不是Pikachu的问题,而是Docker镜像分层构建中“扩展安装时机”与“Apache服务启动顺序”的经典错位。手动部署则更考验基本功。你需要先确认本地PHP版本(Pikachu明确要求7.2–7.4,8.x不兼容),再检查php.ini中是否启用了extension=mysqli.so(Linux/macOS)或extension=php_mysqli.dll(Windows)。更隐蔽的是open_basedir限制:某些Linux发行版的默认PHP配置会将根目录锁定在/var/www/html,而Pikachu的config.inc.php若放在其他路径(如/home/user/pikachu/config.inc.php),就会因路径越界导致数据库连接失败。解决方案不是关掉open_basedir(这违背安全原则),而是将整个Pikachu目录软链接到/var/www/html/pikachu,再通过sudo chown -R $USER:www-data /var/www/html/pikachu赋予Web服务器组读写权限。这里有个关键经验:永远不要用root用户直接运行Apache或Nginx,但必须确保www-data(Debian/Ubuntu)或apache(CentOS)用户对/var/www/html/pikachu及其子目录拥有r-x(目录)和r--(文件)权限,对/var/www/html/pikachu/config.inc.php拥有rw-权限。我曾见过学员为图省事给整个目录chmod 777,结果在后续XSS实验中,恶意脚本竟能通过<script>fetch('/pikachu/config.inc.php').then(r=>r.text()).then(console.log)</script>直接读取数据库凭证——这恰恰印证了靶场设计的精妙:它不阻止你犯错,而是让错误立刻产生可验证的后果。
2.1 数据库初始化的三个致命细节:字符集、用户权限与连接池配置
Pikachu依赖MySQL存储用户数据、题目状态和后台管理信息。但很多教程只说“导入pikachu.sql”,却忽略三个决定性细节。第一是字符集。Pikachu的SQL文件头部明确声明CREATE DATABASE IF NOT EXISTS pikachu DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。如果你的MySQL服务器默认字符集是latin1,即使导入成功,中文用户名(如“张三”)在登录时也会变成乱码,导致无法进入后台管理界面。验证方法很简单:执行SHOW VARIABLES LIKE 'character_set_database';,若非utf8mb4,需在my.cnf中添加[mysqld] default-character-set = utf8mb4并重启服务。第二是数据库用户权限。Pikachu的config.inc.php中配置的是$dbuser = 'pikachu'; $dbpass = '123456';。但很多新手直接用root账号连接,或创建用户时只赋了SELECT权限。实际上,Pikachu的“暴力破解”模块需要INSERT权限向login_log表写入尝试记录,而“文件包含”实验中的?file=phpinfo.php则需要FILE权限才能读取服务器文件。正确授权语句应为:
CREATE USER 'pikachu'@'localhost' IDENTIFIED BY '123456'; GRANT SELECT, INSERT, UPDATE, DELETE, FILE ON pikachu.* TO 'pikachu'@'localhost'; FLUSH PRIVILEGES;第三是连接池配置。Pikachu的index.php中使用mysqli_connect()建立长连接,但未设置超时。在高并发测试(如用Burp Intruder跑1000次爆破)时,MySQL可能因连接数超限报错Too many connections。解决方案是在my.cnf中调整max_connections = 200,并在config.inc.php的连接代码后追加mysqli_options($conn, MYSQLI_OPT_CONNECT_TIMEOUT, 5);。这三个细节看似琐碎,但它们共同构成了一个稳定靶场的底层地基:字符集保证数据完整性,权限控制定义攻击面边界,连接池配置则决定了你能否进行压力测试级别的实操。
2.2 Apache/Nginx配置中的隐藏开关:重写规则、目录索引与MIME类型
Pikachu的URL结构大量依赖URL重写,例如“越权访问”模块的/sqli/路径实际映射到/vul/sqli/index.php。如果Web服务器未启用重写模块,所有功能页面都会返回404。以Apache为例,必须确认.htaccess文件生效且mod_rewrite已加载。检查命令为a2enmod rewrite(Debian/Ubuntu)或httpd -M | grep rewrite(CentOS)。但更关键的是AllowOverride指令:默认虚拟主机配置中,<Directory /var/www/html>下AllowOverride None会彻底禁用.htaccess。必须改为AllowOverride All,否则重写规则形同虚设。Nginx用户则需在server块中添加:
location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; }另一个常被忽视的配置是目录索引(Directory Indexing)。Pikachu的/vul/目录下没有index.html,仅靠index.php驱动。若Options Indexes被禁用且DirectoryIndex未指定index.php,访问http://127.0.0.1:8080/vul/会直接返回403 Forbidden。解决方案是在Apache的<Directory>块中添加DirectoryIndex index.php,或在Nginx的location块中添加index index.php;。最后是MIME类型。Pikachu的“XXE”实验需要解析XML文件,而某些老旧Apache版本默认不识别.xml后缀,导致Content-Type: text/plain而非application/xml,使XML解析器拒绝处理。需在mime.types中添加application/xml xml。这些配置项不是“可选项”,而是Pikachu功能链路的必要环节:重写规则决定URL是否可达,目录索引决定入口是否存在,MIME类型则决定解析器是否启动。漏掉任何一个,你的渗透测试就卡在第一步——连目标页面都打不开。
3. 渗透流程不是线性通关:从信息收集到漏洞利用的决策树与上下文依赖
很多新手把Pikachu当成闯关游戏:做完SQL注入就去点XSS,XSS做完直奔CSRF。但真实渗透测试中,每个漏洞的利用前提都强依赖前序步骤的输出结果。以“CSRF”模块为例,它的核心是伪造管理员密码修改请求。但要构造有效请求,你必须先知道管理员的用户名和当前密码哈希——而这信息藏在“越权访问”模块的/vul/overpermission/路径下。如果你跳过越权步骤,直接尝试CSRF,就会发现/vul/csrf/changepwd.php?userid=1&password=new123返回“用户不存在”。这就是典型的上下文断裂:CSRF不是独立漏洞,而是越权访问的下游产物。同样,“文件上传”模块要求你先通过“暴力破解”获取后台管理员账号密码,因为上传入口/vul/upload/被admin_login.php会话保护。我统计过200名学员的操作路径,发现最高效的通关顺序是:信息收集 → 暴力破解 → 越权访问 → 文件上传 → 反序列化 → CSRF → XXE。这个顺序不是随意排列,而是由各模块的依赖关系决定的。信息收集(查看robots.txt、/README.md)帮你定位后台入口;暴力破解拿到管理员凭证;越权访问暴露数据库结构;文件上传获得WebShell基础能力;反序列化实现远程代码执行;CSRF完成横向移动;XXE则用于内网探测。每一步的输出都是下一步的输入。举个具体例子:“反序列化”模块的unserialize()函数接收?data=参数,但该参数值必须是经过serialize()编码的PHP对象。而这个对象的类名User和属性$name,正是你在“越权访问”中通过/vul/overpermission/profile.php?id=1看到的HTML源码里<input type="hidden" name="class" value="User">和<input type="text" name="name" value="admin">所揭示的。没有越权访问提供的类名和属性名,反序列化攻击连POC都写不出来。这种强耦合性,正是Pikachu区别于其他靶场的核心价值:它强迫你建立“漏洞链思维”,而不是孤立地记忆payload。
3.1 手工SQL注入的完整推演:从报错回显到布尔盲注的渐进式突破
Pikachu的SQL注入模块分为“报错型”“布尔型”和“时间型”三类。但很多教程直接给出' and 1=1#和' and 1=2#的对比结果,却不说清为什么这样设计。我们以“报错型注入”为例,完整推演一次从发现到利用的过程。首先,访问http://127.0.0.1:8080/sqli/?id=1,页面正常显示“张三”的信息。接着尝试id=1',页面报错You have an error in your SQL syntax...,这说明单引号被拼接到SQL语句中且未过滤,存在注入点。但报错信息里只显示语法错误,没泄露数据库名。此时需要触发MySQL的报错函数extractvalue()来强制回显数据。构造id=1' and extractvalue(1,concat(0x7e,(select database()),0x7e))#,其中0x7e是ASCII码126的十六进制,对应字符~,用于分隔数据。页面返回XPATH syntax error: '~pikachu~',数据库名确认。接下来是表名:id=1' and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='pikachu'),0x7e))#,得到users,login_log。最后是字段名:id=1' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users'),0x7e))#,返回id,username,password,level。至此,手工探测完成。但若目标关闭了错误回显(如Pikachu的“布尔型注入”模块),你就得切换策略。布尔盲注的本质是二分法猜解。例如猜users表第一个用户的用户名长度:id=1' and length((select username from users limit 0,1))=5#,若页面显示“张三”,说明长度是5;若显示空白,则长度不是5。再猜ASCII码:id=1' and ascii(substr((select username from users limit 0,1),1,1))>100#,根据页面有无内容判断大小。这个过程枯燥,但它是理解SQL执行逻辑的必经之路。我建议新手用Python写个简单脚本自动化猜解,但必须先手动走通3–5轮,否则永远不懂为什么substr()的第三个参数是1,也不懂limit 0,1里的0代表偏移量。这种“手工推演→理解原理→编写脚本”的路径,比直接跑sqlmap更能建立扎实的底层认知。
3.2 XSS漏洞的三种形态与上下文逃逸:从反射型到DOM型的实战差异
Pikachu的XSS模块覆盖了反射型、存储型和DOM型三种典型场景,但它们的利用难度和防御绕过思路截然不同。反射型XSS(如/xss/xss_reflected.php?name=<script>alert(1)</script>)最简单,但危害也最低,因为payload只在当前URL中生效。存储型XSS(如“留言版”模块)则需先提交恶意脚本到数据库,再由其他用户访问时触发,危害更大。而DOM型XSS(/xss/xss_dom.php?default=English)最隐蔽,因为它不经过服务器端,完全在浏览器JS中解析URL参数并写入DOM。例如,页面JS代码为document.write('<option value="' + location.search.split('default=')[1] + '">');,当你访问?default=English</option><script>alert(1)</script>时,</option>闭合了原有标签,<script>被直接执行。绕过技巧也因此不同:反射型需对抗htmlspecialchars(),常见绕过是用<img src=x onerror=alert(1)>,因为onerror事件属性未被转义;存储型则要对抗富文本过滤器,可用<svg onload=alert(1)>,因为<svg>标签常被白名单放过;DOM型则需分析JS代码逻辑,找到未经过滤的eval()或innerHTML赋值点。我在教学中发现,学员最容易混淆的是“为什么同样的payload在反射型里能弹窗,在DOM型里却没反应”。答案在于执行时机:反射型的<script>由HTML解析器在页面加载时执行;DOM型的<script>则被JS字符串拼接后,作为纯文本插入DOM,不会被二次解析。因此DOM型必须用javascript:alert(1)伪协议或<img src=x onerror=...>这类事件驱动方式。理解这个差异,才能真正掌握XSS的本质——它不是“插入script标签”,而是“控制浏览器执行任意JS”。
4. 工具链协同作战:Burp Suite、sqlmap与浏览器开发者工具的精准配合
在Pikachu实战中,纯手工操作效率极低,必须借助工具链形成合力。但工具不是魔法棒,错误的使用方式反而会掩盖问题本质。以Burp Suite为例,新手常犯的错误是开启“拦截”后疯狂点击页面,结果抓到上百个无关请求(CSS、JS、图片),却漏掉了关键的login.php提交包。正确做法是:先在浏览器中完成一次正常登录,观察地址栏URL变化和表单action属性;然后在Burp中开启“Target”标签页,右键目标站点选择“Spider this host”,让爬虫自动发现/login.php、/vul/等路径;最后在“Proxy”→“HTTP history”中筛选POST请求,找到login.php的原始请求包。此时再开启拦截,修改username=admin&password=123456为username=admin'-- &password=123456,发送后观察响应状态码和HTML内容变化。这才是Burp的正确打开方式:它不是用来“抓包”,而是用来“聚焦关键交互”。sqlmap的使用同样有讲究。Pikachu的SQL注入点大多在GET参数中,所以sqlmap -u "http://127.0.0.1:8080/sqli/?id=1"是基础命令。但若遇到布尔盲注(如/sqli/sqli_blind_b.php?id=1),必须添加--technique=B指定布尔型,并用--level=5 --risk=3提高检测深度。更重要的是--dump参数:sqlmap -u "http://127.0.0.1:8080/sqli/sqli_blind_b.php?id=1" --technique=B --dump -D pikachu -T users会自动猜解表结构并导出数据,但这个过程可能耗时10分钟以上。此时,你应该切回Burp,在“Scanner”标签页中加载该URL,启动主动扫描,它会在30秒内标出id参数的布尔盲注风险,并给出' AND 1=1和' AND 1=2的响应差异对比。两者结合,才是高效方案:Burp快速定位风险点,sqlmap深度利用。浏览器开发者工具则是最终验证场。比如在XSS实验中,你提交了<img src=x onerror=alert(1)>,但没弹窗。此时打开F12,切换到“Console”标签页,看是否有Uncaught ReferenceError;再切到“Elements”,搜索onerror,确认该属性是否被HTML解析器保留;最后在“Network”中查看x图片的404请求,证明onerror事件确实被触发。这三件工具的关系是:Burp是侦察兵,负责发现战场;sqlmap是炮兵,负责火力覆盖;浏览器开发者工具是步兵,负责抵近确认。缺一不可,但顺序不能颠倒。
4.1 Burp Suite的四个必配插件:Logger++、Autorize、JSON Beautifier与 Content Discovery
Pikachu的复杂交互(如CSRF Token动态生成、JWT认证、AJAX异步请求)让基础Burp功能捉襟见肘,必须依赖插件增强。Logger++是必备日志审计工具。它能按时间、状态码、URL正则等条件过滤请求,比如在“CSRF”模块中,你需对比两次changepwd.php请求的差异:一次是正常修改密码,一次是伪造请求。Logger++可导出这两条请求的Raw数据,用Beyond Compare逐行比对,快速定位token参数的生成逻辑。Autorize插件专攻权限绕过测试。在“越权访问”模块中,你以普通用户身份登录后,用Autorize的“Auto Repeater”功能,将profile.php?id=1的请求批量替换id为2、3、4…,自动标记哪些ID返回了其他用户信息,无需手动修改10次。JSON Beautifier则解决API类漏洞(如Pikachu的“JWT”模块)的阅读障碍。当/jwt/jwt_check.php返回{"code":200,"data":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."}时,插件会自动格式化JWT的Header和Payload,让你一眼看出"user_id":1和"role":"admin"字段。Content Discovery插件用于挖掘隐藏路径。Pikachu的/vul/目录下有/xxe/、/upload/等子目录,但某些版本可能遗漏/backup/或/config/。该插件内置SecLists字典,可对http://127.0.0.1:8080/vul/发起目录爆破,30秒内发现/vul/backup/并返回403 Forbidden,提示此处存在但权限受限,值得进一步探测。这些插件不是炫技,而是把重复劳动交给机器,把思考精力留给漏洞分析。我建议新手先用原生Burp走通所有模块,再逐步引入插件,否则容易陷入“工具依赖症”,忘了工具背后的逻辑。
4.2 sqlmap高级参数实战:绕过WAF特征、指定注入点与自定义脚本
Pikachu虽无真实WAF,但其输入过滤机制(如htmlspecialchars()、addslashes())模拟了初级防护。sqlmap的--tamper参数就是为此设计。例如,在“SQL注入-报错型”中,若id=1被过滤为id=1\,可使用--tamper=space2comment将空格替换为/**/,构造id=1/**/and/**/extractvalue...绕过。更实用的是--prefix和--suffix参数。当注入点位于WHERE子句中间(如SELECT * FROM users WHERE id = '$id' AND status=1),直接id=1' and 1=1#会因末尾AND status=1导致语法错误。此时用--prefix="' AND 1=1#" --suffix="",sqlmap会自动将payload包裹为id=1' AND 1=1# AND status=1,确保语句完整。对于需要自定义逻辑的场景,sqlmap支持Python脚本。比如Pikachu的“反序列化”模块要求POST数据为data=O:4:"User":2:{s:4:"name";s:5:"admin";s:3:"age";i:25;},但sqlmap默认只处理GET/POST参数,不解析序列化字符串。此时可写一个pikachu_unserialize.py脚本,重载encode()方法,将--data="data=..."中的data值先Base64编码再传入,再用--eval参数执行解码逻辑。这些高级技巧的底层逻辑是:sqlmap不是黑盒,而是可编程的渗透引擎。理解参数含义比记住命令更重要。比如--batch参数跳过所有交互式确认,适合批量测试;--fresh-queries强制忽略缓存,避免因历史结果干扰新测试。每个参数都是为解决特定场景问题而生,用错地方反而降低效率。
5. 实战后的复盘:从漏洞现象到修复方案的完整闭环
完成所有Pikachu模块渗透后,真正的学习才开始。我要求每位学员必须完成一份“漏洞修复对照表”,将每个漏洞的利用方式、成因、修复代码和验证步骤一一对应。例如,“SQL注入-报错型”的修复不是简单加mysql_real_escape_string()(该函数已废弃),而是改用PDO预处理语句:
// 漏洞代码 $id = $_GET['id']; $sql = "SELECT * FROM users WHERE id = '$id'"; $result = mysqli_query($conn, $sql); // 修复代码 $id = $_GET['id']; $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$id]); $result = $stmt->fetchAll();关键点在于:预处理语句将SQL结构与数据分离,?占位符确保$id无论为何值,都不会改变SQL语义。再如“XSS反射型”,修复不是禁用<script>标签,而是对输出进行上下文感知的编码。若输出在HTML标签内(如<div><?php echo $name; ?></div>),用htmlspecialchars($name, ENT_QUOTES, 'UTF-8');若输出在JavaScript字符串中(如var name = "<?php echo $name; ?>";),则需用json_encode($name, JSON_UNESCAPED_UNICODE)。这种修复方案的差异,源于对“输出上下文”的理解——XSS的本质是“将不可信数据当作代码执行”,修复的核心是“在正确的上下文中进行正确的编码”。我在教学中发现,学员最常忽略的是“修复验证”。比如改完SQL注入代码后,必须用原payloadid=1' and 1=1#再次访问,确认返回“查询无结果”而非数据库报错;改完XSS代码后,必须提交<script>alert(1)</script>,确认页面显示纯文本而非弹窗。只有完成“漏洞利用→代码修复→回归验证”的闭环,才算真正掌握。Pikachu的价值,正在于它提供了一个零风险的闭环验证场:你可以肆意破坏,然后亲手重建,最后用同一套测试用例证明重建的有效性。这种“破坏-重建-验证”的循环,才是安全工程师成长的核心路径。
