跳到主要内容

HGAME 2026 WriteUp

· 阅读需 6 分钟
林林
在西安读大二的小菜鸡

很多题不会做,等题解出来在这里一并记录自己当时做题的思路和照着题解做题的过程。

一周目

魔理沙的魔法目录

观察网络控制台
看控制台发现网页每过一段时间向/record发请求(通过Authorization Header 来鉴权),再通过/check检查是否达到足够的时间。tracker.js被混淆了,就是一个黑盒。用 Postman 试试看。

通过 Postman 给接口发请求
接口并没有限制 time 的大小,那我们填大一点,发完请求在浏览器等一会 flag 就出来了。

答案自己就出来了
但如果挂机等一个小时的话应该也能做出来。

Vidarshop

我自己没做出来这一题。

什么你这卖的东西这么贵 什么为什么都用uid了用户名还要抢啊, 什么凭啥admin可以管我们所有人的钱啊

题目的题干

update接口直接改的好像是User类的balance属性欸,但是User属性中balance似乎并非。。。该怎么修改balance呢

题目的提示

一开始看题目和登陆页面看感觉像是 SQLi,要直接登录到 admin 账号去管钱。注入试了两次之后发现能登录,登陆进去发现鉴权用的是 Bearer Token,token 为 JWT,用空密钥试了一下不行。然后对着/api/update接口调了半天,发现没用,睡觉。

第二天醒来接着看这道题,注册了一个用户名和 uid 都是 111 的账号,对着 JWT 解析出来的结果发呆。不知道想到什么了,试着把 111 输到密钥里,验证成功…是哪个地方有问题吗?为什么就验证成功了?我将 token 拿到工具里爆破了一下,工具给出结果:密钥还真是 111。

此时的我欣喜若狂
我猜想密钥应该和 uid 是一样的,因为 uid 也被放在 header 传给接口了。拿其他账号一试,我猜错了:是所有账号生成的 JWT 密钥都是 111 啊。这样就可以构造一个 admin 的 token 了。

然后用这个管理员的 token 在题目环境里试了试,知道了这些信息:

  • balance 貌似是所有用户的共有属性,前端的接口是假的(貌似就算用 admin 权限也无法修改,还没找到题目说的 admin 管钱是怎么管的)
  • 应用好像用 uid 来识别是否为管理员,ctf-token 来识别用户名(并且没有检验用户是否存在的逻辑)可以通过在接口请求的 body 里指定 username 来显示某个用户的 balance(虽然大家都共用同一个)
  • buy 接口能使 balance 实实在在地变少,但好像利用不了(没办法指定花的钱数,自然无法改为负数)

我又试了试,还是没有试出什么名堂来。题目归档之后,CopperKoi 同学把题解发给我,是我没见过的原型链污染。大概就是用前面伪造的 token 发请求到 /update 接口,payload 如下:

{"__init__": {"__globals__": {"balance": 2000000}}}

看样子题目确实是这么解的。

二周目

easyuu

很早以前玩虚拟空间的时候有部署过一个 PHP 文件就实现文件上传、查看、登录等功能,给我当时幼小的心灵带来一些震撼。

貌似发现了一个可以利用的接口
“uu是什么意思,很简单吗,分开来想想你就明白啦”,题干是这样说的。从同学口中得知,uu 是 upload & update 的意思。他还说,做题首先应该尝试拿到源码。我在题目环境的/app/update里发现了一个压缩包,像是源码,想着用download_file接口进行目录穿越下载下来又不行,浏览器好像会把../和链接的上一级进行“消消乐”。

要不要encode一下试试?
然后我就把源码的压缩包下载下来了。打开一看,Rust Cargo!

我将代码喂给 GitHub Copilot 和 Deepseek,Deepseek 给出一种可能的攻击方案:可以先在本地加恶意代码并编译,再通过上传接口将文件上传到/app/update(又是一个目录穿越),等代码自动更新之后拿到 flag。

import requests

url = "http://环境地址/api/upload_file"
files = {
'file': ('../../../../app/update/easyuu',
open('target/x86_64-unknown-linux-musl/release/easyuu', 'rb'),
'application/octet-stream')
}
response = requests.post(url, files=files)
print(response.status_code)
print(response.text)

但是没成功。上传文件这个接口的目录穿越可行。但自动更新这个机制能否利用不清楚,本地编译了几次,一直cargo clean cargo build --release --target x86_64-unknown-linux-musl,文件大小都是553928。估计编译出来的一直是老代码。

baby-web?

读附件的代码之后发现应用并没有限制 php 文件的上传,所以上传了一个一句话木马上去,然后用蚁剑连接。结果发现上传附件的文件夹只有我上传的文件,flag也不在文件系统当中。

HarmonyOS 6初体验

· 阅读需 3 分钟
林林
在西安读大二的小菜鸡

晚上,咖啡豆子突然问我:“你手机用的是华为吗?有升级 HarmonyOS 6 吗?”起初注意过设置里有升级鸿蒙 6 公测版的选项,但没关注。如今被问到,才在网上看看,群里问问。去年春节买的手机如今也可以用上鸿蒙操作系统了。

从安卓的鸿蒙 4 升级到鸿蒙 6,升级前我有一些顾虑:

  • 应用数据会丢吗?
  • 学校要求的软件能否兼容?
  • 没有备案的软件可以使用吗?
  • ……

咖啡豆子晚上因为忍不住好奇就先更新了,我早上找了一些回答,问咖啡豆子,明确了一些信息:

  • 在更新之前系统会进行数据备份,开机后使用微信会自动回复聊天记录,软件数据不会丢。
  • 暂不支持鸿蒙的应用会变成“卓易通”版的应用,软件数据也不会丢,功能也都能正常使用,再也不怕把 2Fa 软件里的数据丢掉了。
  • 鸿蒙操作系统的应用商店有我们学校使用的定制款学习通,至于能不能签到就要开学之后试。

早上,带着一些忐忑,我点下那个更新按钮。手机自己进行下载、备份、安装,整个流程用时不短,备份时还不能切屏,好在我没有什么要用手机处理的事情。接近中午,手机才向我展现出“鸿蒙版”的新面目。

新系统,微信、QQ、支付宝…几乎所有应用都需要重新登录。鸿蒙最开始惊艳我的一点是:输入法在将短信收到的验证码填进去之后会随即把短信标为已读并划去通知。这个体验点真的很小,虽然我手动把消息划掉也用不了多少时间,但真方便不少。

临近中午用网易云音乐听歌,虽然网易云音乐还是安卓版,但体验上优化不少。我不需要进入网易云音乐就可以实现音乐的操控。比起此前将音乐控制放在控制中心,这样我连下划调出控制中心的步骤都省了。

感觉像是苹果的灵动岛?
听说 QQ 和微信少了很多功能,如今用了一天,并没有感觉到什么不方便。微信的消息通知还是跟安卓时的一样,估计是我禁止自启动了。欸,鸿蒙 6 好像连应用启动管理的设置都没有了(都是系统来管的嘛)。

下拉呼出通知栏,解锁手机,滑动退出这些操作都很丝滑。在鸿蒙用安卓应用(比如 Chrome)感觉有些问题,我在编辑文章的时候经常触发黑屏。

目前总的来说,鸿蒙系统惊艳了我,给不时要输验证码的我方便不少,流畅的使用体验也让我舒心不少。

云上

· 阅读需 2 分钟
林林
在西安读大二的小菜鸡

建议配合听蒼鈺Danielle翻唱的《归途有风》
晚上从飞机上看
此前来西安坐的是傍晚的飞机,飞在空中只能看到地面上的灯光,灯光连成一条一条路。有些城市像是依山而建,有些城市则从中心向四周放射状伸展。

起初一片黑,什么都看不见。突然出现地上发出的灯光,我们便伸向舷窗看。有时看得清楚,还能看到路上的车灯。

白天,白白的天。从西安回来的飞机是白天的。云多,天又亮,我能从舷窗看到清晰又细腻的云层。云层望不到边。小时候说云像棉花糖一样,看上去好像真的和棉花糖一样软。云多是好,我能看看此前没见过的云层;云多不好,我还没在白天看过高空下的城市呢。回到家之后,我将我在飞机上拍的照片和视频分享给我的父母。他们坐飞机回来时是深夜,看到这些照片时颇为震撼。

回来坐飞机用不到三小时,却隔五个月。

短聚,长离。

西电信安协会招新系统 Golang 后端开发小记

· 阅读需 6 分钟
林林
在西安读大二的小菜鸡

大概是,今年 1 月份与协会的 CopperKoi 同学把协会的招新系统重新做了一版。CopperKoi 用 Vite 写前端,他和我用 Golang 写后端。后端用 gin 处理请求,用 gorm 对接数据库。文章到这里就结束了。

招新系统公测截图

接下来是废话时间。

CopperKoi 真的是一个很厉害的大佬。

吹,接着吹

迎新页面(一般每年由大一同学建设)

引自某个文档

大概就是这样,今年我们在使用的招新系统有一些功能需要改进,但我找不到代码仓库,协会服务器里的站点文件夹里赫然堆着 Flask 大代码,明显不是今年用的这个。又听学长说历来都有大一新生写招新页面的传统,就来了。

CopperKoi 此前跟我聊过前端。当我问他愿不愿意带飞我做一个招新页面时,他欣然答应了。此前写过一些关于流程和数据库的设计图,CopperKoi 也写了接口文档,开整!

Golang 写完之后可以直接部署成二进制文件,这一点比 Python 方便许多。虽然能做到这一点的语言很多,Python 也有 Pyinstaller,不过没用过。

对面试者的面试流程

对面试官的面试流程

数据库模型,很古早的流程和数据库设计

我们在开发的过程中有一些迷,这里记录一下。

前后端分离了怎么用 X-CSRF-Token 来防 csrf:好像光想着要放 csrf 了,但我们不是前后端分离了吗?😅后来 CopperKoi 想到前端把 JWT 放在 Authorization Header 不依赖 Cookies 就可以让 csrf 几乎不可能实现。

腾讯的 CodeBuddy 写代码是真的快。我刚开始边翻文档边写代码速度好慢,把文档交给 ai 没过多久路由就都写好了。目前公测没有报告出 ai 代码的问题(只有我灵机一动犯的错)。但是 ai 写代码和我一样有时会出现想当然的错误。

ai 的幻觉(幸好我略微熟悉代码)

密码怎么传输? 我原本以为在后端 bcrypt 的基础上前端用 sha256 加密之后传输会安全一点,并且将这个设计补充到文档里。但当我问 Deepseek 时,它给出了不同的意见(聊天链接)。“sha256 碰撞都来了。”CopperKoi 经过一番研究之后采用了 Deepseek 的建议,传输明文密码,传输过程中的安全由 https 来保障。

没过几天,CopperKoi 的前端也写好了。测试的时候公告的置顶接口出现了一个很奇妙的问题。

接口原本用来绑定 body 的模型

接口通过 Params 获取要置顶 / 取消置顶的公告的 uuid,通过 ShouldBindJSON 函数将 body 绑定到模型上。Pinned 布尔值为真则置顶公告,为假则取消置顶。

但是我在测试的时候发现:置顶公告的功能可以跑通,但取消置顶都会报告“参数校验失败”的错误。欸,明明 body 的结构符合要求啊。然后,就出现了惊天大瓜。

看到这个回答我脑子都宕机了

六百六十六。我和 CopperKoi 都看呆了。当我们把这个发给学长时,他给我们分享了这个。

When I send a JSON request with the value true it works, but when I send the same request with false I get the following error.

In summary you need a pointer or custom type to know if the value exists due to Go's static nature.

Bool binding required Bug · Issue #814 · gin-gonic/gin

然后学长跟我们介绍了一件事情。

对于一些可以 nil 的字段而言,静态检查十分重要。GORM 的默认主键自增是从 0 开始的,不是从 1 开始,而你初始化系统用的第一个账户大概率是 admin 账户,账户 id 大概率也是 0。如果因为某些 bug,鉴权中间件没绑上 id……所有人都是 admin 了,而且 go 不会有任何错误汇报。

寄。但我们主键用 uuid 好像把这个坑绕过去了。(不然高低也要踩一回)

给 CopperKoi 提建议的时候我可得劲啦!我给 CopperKoi 提了:将性别从 input 改为单选框、textarea 没有限制宽度可以拉出容器边框、简历显示不出来等等…我们改、测得差不多之后就把代码部署上去让协会里的人公测。

在公测之前,我灵机一动:要不把里面能提的东西都提到 env 里面吧,把参数硬写在代码里不太优雅。然后我看到了 JWT 的 secret…让程序从 env 里面获取 secret。我真是个甜菜!(操作没错,但我的环境变量是用 .env 设置的)

想当然地让程序从 env 里获取密钥

在公测的那天晚上,服务就被学长攻破了。

学长通过修改 JWT 越权访问面试官才能访问的接口

学长通过 gpt 发现的。

jwtSecret =[]byte(os.Getenv("secretKey")在包初始化阶段执行,而 env 是在 main.go 里godotenv.Load()才加载;结果 jwtSecret 可能为空字符串。攻击者可用空密钥自行签发 JWT,伪造 role=interviewer 直接接管所有受保护接口。

gpt如是说

把 secret 硬编码在代码里就把这个漏洞 fix 了。

CopperKoi/XDSEC-Recruitment-System:协会2026招新系统前端
Xiaozonglin/xdsec-join-2026: 西电信安协会2026招新系统后端

关于辞去“开往友链接力”项目维护组负责人的辞呈

· 阅读需 3 分钟
林林
在西安读大二的小菜鸡

亲爱的开往项目社区,
尊敬的项目创始人逊狼,
维护组、巡查组的各位同事们:

我在此宣布,我将在不久后辞去开往项目维护组负责人一职。

在我负责开往项目各项事务的四年多来,我受到了来自维护组的同事、博主、网友以及家人的鼓励和支持。他们的支持让我备受鼓舞,坚持在维护项目这条路上走下来。能与各位一起改进开往项目,是我的荣幸。在这里,我对与我并肩处理项目事务的维护组成员、利用零碎时间为项目做贡献的巡查组成员、向我们提出宝贵意见的社区成员、在我遇到困难时给予支持的家人,以及所有使用开往、向我们反馈问题的访客,表示由衷的敬意和衷心的感谢。

在大家的共同努力下,开往官网变得更加美观,跳转页更加多样,项目的知名度也稳步提升。截至撰写本辞呈时,项目仓库的 Star 数量已经突破 1500 个。我在浏览博客留下评论时,偶尔遇到认出我的博主;也欣慰地看到,许多博主将开往作为发现同好、串门交流的窗口。这一切都让我深感自豪。

为确保项目平稳过渡,我已制定以下计划:

  1. 项目现状:项目的代码、议题和讨论均托管在 GitHub 上,目前服务稳定,状态良好。
  2. 继任者:根据项目运维规定,并经过慎重考量,我提名 Kegongteng 接任项目维护组的负责人。他自 2024 年 4 月起加入维护组,长期负责项目的文案工作,熟悉项目各方面的情况。我对他稳步推进改革、沉着应对挑战的能力充满信心,并将全力支持他完成过渡。
  3. 过渡期:接下来的一段时间里,我将作为顾问协助 Kegongteng 熟悉各项职责,日常决策将由他牵头负责。
  4. 权限移交:开往的组织权限将在本周内完成移交。域名、服务器等关键基础设施的移交方式,将由维护组后续根据需求共同商议决定。

在项目的成长过程中,我们听到了来自社区的各种声音,其中既有鼓励也有批评。这些不同的视角,始终是推动开往前行的宝贵动力。我深信,一个健康的开源项目正是在倾听、讨论与迭代中不断完善的。此刻,虽然我选择卸下负责人的职责,但这份对项目“孩子”般的珍视之情丝毫未减。我将继续以社区成员的身份,关注并支持开往的未来发展。大家仍可通过我的社交媒体或个人博客与我联系。

我对开往项目的未来充满信心,我相信项目将迎来更辉煌的篇章。

最后,再次致以我最诚挚的谢意。

祝好,

林林