马年解谜红包 Writeup
本文分为两部分:我自己的马年红包题解、Soha 红包签到题解。
我自己的红包
其实我是不擅长 CTF 的,更没有任何出题经验。不过在除夕夜跟父母 打电话的时候,突然灵光一现,有了一个出题的思路。于是便花了半个 小时随便出了一下。又担心太难,还改了很多次,最后发了出来。
由于没有这方面经验,在很多地方上都难以定夺:要发几个红包?活动 时长多久?多少钱?难度如何?不过这些都只得摸着石头过河了。
那么,就让我们以一个选手的角度,一步一步解这个谜题。题目原文:
为了延续 TGCN 的新年解谜抢红包传统,花了五分钟写了一个超级简易的抢红包页面,请大家随意领取!
https://ftp.yuuta.moe/hongbao2026/
- 为了照顾北美等网络不发达地区,本红包无需 IPv6 即可领取。
- 由于本红包设定为 1990 年代,暂不支持使用手机领取。
- 总共 10 个,先到先得,总价值 100 元 CNY。
- 禁止分享解谜结果。原则上不应讨论解谜内容。
祝大家新年快乐。
补充提示:最后一关仅和 Web 有关,其他关卡仅和 DNS 有关。
写错了的 CNAME
首先,打开 https://ftp.yuuta.moe/hongbao2026/,映入眼帘的是一
个简易的 Anubis 提示:
<!DOCTYPE html>
<html>
<!-- Yuuta 2026/02/16 -->
<head>
<title>Making sure you're not a bot!</title>
<meta http-equiv = "refresh" content = "5; url = https://hongbao2026.yuuta.moe" />
</head>
<body>
<h1>Making sure you're not a bot!</h1>
<p>Powered by the cute furry girl Anub1s.</p>
<p>Calculating ...</p>
<progress value="55" max="100">55%</progress>
<p>This typically takes 5 seconds.</p>
</body>
</html>
这里 “致敬” 了最近爆火的 Anubis captcha。说实话,Anubis 很烂,它 完全没有解决 AI 爬虫的问题,反而给普通用户带来极大的困扰。但这都 是后话了。这里写 Anubis 主要是黑一下它。
五秒后,网页跳转到 https://hongbao2026.yuuta.moe 。这里就是本
谜题的第一关:NXDOMAIN。用户的浏览器会提示找不到域名。这里如果是
一个 tgcn 老人,一般会想到解析 TXT 记录,因为很多群的邀请链接都是
通过这种方式发放的。
$ dig +short TXT hongbao2026.yuuta.moe
"CNAME hongbao-server.yuuta.moe."
哈哈,果然是出题人不小心把 Zone file 写错了。本来该写成
hongbao2026 IN CNAME 的,结果一不小心写成了 IN TXT CNAME。
无力抵达的 IPv6
拿到这个 CNAME,我们赶快访问一下。如果没有 IPv6,将会提示 NXDOMAIN; 如果有 IPv6,将会超时。结合题目中的 “为了照顾北美等网络不发达地区, 本红包无需 IPv6 即可领取。” 这是怎么回事呢?
$ host hongbao-server.yuuta.moe.
hongbao-server.yuuta.moe has IPv6 address 2404:f4c0:f9c3:502::100:8888
果然,这个域名只有 AAAA 记录,没有其他如 TXT 之类的记录。而如果 Ping 这个 IP,会发现是 no such route to host 的。这又是怎么回事呢?
结合题目中的 “最后一关仅和 Web 有关,其他关卡仅和 DNS 有关。” 我们深究 这个 IPv6 地址。对一个 IP 地址能做的 DNS 操作有哪些?作为一个互联网老登, 我们想到了 rDNS。那么就试试看:
$ dig +short -x 2404:f4c0:f9c3:502::100:8888
8.8.8.8.0.0.1.0.0.0.0.0.0.0.0.0.2.0.5.0.3.c.9.f.0.c.4.f.4.0.4.2.ip6.arpa.
好像啥都没有。
等等,对吗?这对吗?为什么 resolve rDNS 返回了 ip6.arpa. 自己?如果没有
rDNS,应该 NXDOMAIN 才对。ip6.arpa. 这个域名不该是 resolve rDNS 时请求的
Question 吗?为什么出现在了 Answer RR 里?
对于常规的 rDNS,在 resolve IPv6 地址时,实际上就是在请求 x.x.x.x.ip6.arpa.
这个域名的 PTR 记录的解析,而它的响应一般是一个域名(当然也可能是其他东西):
$ dig -x 2404:f4c0:f9c3:502::100:2345
;; QUESTION SECTION:
;5.4.3.2.0.0.1.0.0.0.0.0.0.0.0.0.2.0.5.0.3.c.9.f.0.c.4.f.4.0.4.2.ip6.arpa. IN PTR
;; ANSWER SECTION:
5.4.3.2.0.0.1.0.0.0.0.0.0.0.0.0.2.0.5.0.3.c.9.f.0.c.4.f.4.0.4.2.ip6.arpa. 86400 IN PTR ca.yta.moe.
而这里显然有些奇怪:
$ dig -x 2404:f4c0:f9c3:502::100:8888
;; QUESTION SECTION:
;8.8.8.8.0.0.1.0.0.0.0.0.0.0.0.0.2.0.5.0.3.c.9.f.0.c.4.f.4.0.4.2.ip6.arpa. IN PTR
;; ANSWER SECTION:
8.8.8.8.0.0.1.0.0.0.0.0.0.0.0.0.2.0.5.0.3.c.9.f.0.c.4.f.4.0.4.2.ip6.arpa. 10 IN PTR 8.8.8.8.0.0.1.0.0.0.0.0.0.0.0.0.2.0.5.0.3.c.9.f.0.c.4.f.4.0.4.2.ip6.arpa.
这个返回的 ip6.arpa. 究竟包含了什么呢?我陷入了沉思 …
沉思良久后,又想起了题目中的 “补充提示:最后一关仅和 Web 有关,其他关卡仅和 DNS 有关。” 也就是说,答案一定在这个 PTR 记录(或者它的响应)中。
这个时候,我们留意到:ip6.arpa. 也是一个域名,它和其他域名没有任何差别。而 dig -x
的时候是在请求它的 PTR 记录。那么,它会不会包含其他记录呢?
$ host 8.8.8.8.0.0.1.0.0.0.0.0.0.0.0.0.2.0.5.0.3.c.9.f.0.c.4.f.4.0.4.2.ip6.arpa.
8.8.8.8.0.0.1.0.0.0.0.0.0.0.0.0.2.0.5.0.3.c.9.f.0.c.4.f.4.0.4.2.ip6.arpa has address 172.218.166.163
果然!这个 ip6.arpa. 域名还偷偷藏了一个 A 记录。其实出题人保留了一个
返回自己的 PTR 记录就是为了方便选手看出端倪的。因为如果完全没有 PTR 记录,
选手很容易不小心就错过这个正确答案了,压根不会往它上面深究。
知识点
rDNS zone 就是普通的以
.arpa.结尾的域名。它们不光可以包含 PTR 记录,也可以包含 其他常规 DNS 记录,甚至可以申请到 TLS 证书。https://160.176.40.77.in-addr.arpa 就是一个在
in-addr.arpa.上运行的 Mastodon 实例。不过,随着最近新出的 CA/B 规范,已经禁止 Web CA 给
.arpa.颁发证书了。参见: https://cabforum.org/2025/11/10/ballot-sc-086v3-sunset-the-inclusion-of-ip-reverse-address-domain-names/
拿到了这个 IP 地址后,该怎么做呢?直接访问一下试试看:
$ curl -Lk 172.218.166.163
<html><body><h1>503 Service Unavailable</h1>
No server is available to handle this request.
</body></html>
经过跳转,它返回了 500。其实这里就进入了一个错误解,或者说更应该是题目
中的一个 bug。因为这个网页是直接跑在我的 NAS 上的,而这就是我 NAS 的
IP 地址。HAProxy 针对没有匹配的 Host header 的默认行为就是返回 503。
这个时候选手可能会针对这个 IP 进行爆破(的确有的人都开始扫我 NAS 上的
端口了,不 — 要 — 啊 —)。这个思路是不对的。
实际上,ip6.arpa. 作为一个普普通通的域名,自然可以直接作为 HTTP request Host
使用。事实也的确如此。将这个域名输入浏览器,我们会直接得到一个网页:
昇阳电脑抢红包系统
October 1996
感谢您使用本红包系统!点击下方图片 外 任意位置即可抢红包!
(打了马赛克的支付宝口令红包截图)
至此,我们来到了最后一关:来自远古的信。
来自远古的信
我快速浏览网页:它很简单,正文只有一张图。那么文字上说的图片外,又是哪里呢?
随便点击网页,会发现它完全没有任何超链接或按钮。只有图片可以点击,而点击后 会刷新。查看网页源代码,也没有任何 JavaScript。
这时就是题目中 “由于本红包设定为 1990 年代,暂不支持使用手机领取。” 的意义所在了。 因为如果使用电脑,会得到一个快速解题的 Hint。
我漫无目的地在网页上移动鼠标,试图找到 “图片外” 的链接究竟在哪,可惜一无所获。 而这个时候我发现了一件事:鼠标在图片上移动时,浏览器左下角提示的超链接中会 包含一个在不断变化的坐标:
http://8.8.8.8.0.0.1.0.0.0.0.0.0.0.0.0.2.0.5.0.3.c.9.f.0.c.4.f.4.0.4.2.ip6.arpa./hongbao?x,y
这一组数显然是图片的坐标。而这令我感到非常困惑:这个坐标谁设置的?网页上 明明没有 JavaScript 呀?打开网页源代码,仔细地搜索着:
<!DOCTYPE html>
<html>
<head>
<title>昇阳电脑抢红包系统</title>
</head>
<body>
<h1>昇阳电脑抢红包系统</h1>
<p>October 1996</p>
<p>感谢您使用本红包系统!点击下方图片 <b>外</b> 任意位置即可抢红包!</p>
<a href="/hongbao">
<img src="/Snipaste_2026-02-16_04-14-08.png" alt="https://web.archive.org/web/19961020014601/http://www.sun.com/" ismap>
</a>
<!-- Yuuta 2026/02/16 -->
</body>
</html>
没错,点击图片的超链接就是用 <a href="/hongbao"> 实现的,而点击会回到本页。
那么,坐标又是哪里来的呢?
最后,我将目光锁定到了 <img> tag 的 ismap 属性上。通过一番搜寻,发现它是 HTML 中一个
非常古老的功能,市面上所有浏览器都从最早的版本开始支持。而它的作用,就是将一张图片变成
一个可点击的地图,用户在点击图片时,浏览器会自动发送带有坐标的请求。
我顿时明白了 “图片外” 是什么意思。图片外一定就是指这个坐标超过了图片的大小。 于是,我手动构造了一个请求:
$ curl --head http://8.8.8.8.0.0.1.0.0.0.0.0.0.0.0.0.2.0.5.0.3.c.9.f.0.c.4.f.4.0.4.2.ip6.arpa./hongbao?-1,-1
HTTP/1.1 301 Moved Permanently
x-powered-by: Express
location: /photo_2026-02-16_04-12-35.jpg
date: Tue, 17 Feb 2026 10:51:26 GMT
keep-alive: timeout=5
跟着这个 301 跳转,我们得到了最终的答案:一张没有被打马的支付宝口令红包截图。 最终,我们拿到了红包口令,赢得了比赛。
其实这里保留浏览器左下角的链接提示是出题人故意留下的 Hint。 如果想去掉它肯定是有办法的,但那会使得解题难度和时间大幅增加。
在最后,我好奇地打开了 alt 中的 https://web.archive.org/web/19961020014601/http://www.sun.com/。
发现它是 Sun Microsystems 在 1996 年的主页,而其主页就是通过一张巨大的(低清)
图像加 ismap 实现的点击按钮跳转。出题人也是多年前在翻阅 Sun 史前官网时发现的这个机制。
出题人一直很敬仰 Sun 这家公司,因此这道题也是对这家公司以及这个主页的一次致敬。
知识点
ismap参数的学名叫做 “Server-side image maps”,是 1993 年 Mosaic 1.1 浏览题 新增的一种拓展。它可以让浏览器显示一张静态地图,然后将点击坐标发给后端来做处理。 这个功能已经没人用了。参见:http://1997.webhistory.org/www.lists/www-talk.1993q2/0343.html
出题人事后总结
出题时我很担心大家会因为过年懒得看电脑而放弃解谜,因此一直在控制难度。 比如:在 PTR 中保留一个解析结果和域名一样的记录、故意保留了浏览器超链接提示等。 一开始的设计难度是远比这个难的。当时还增加了一些混淆,如:在第一关的自动刷新 加入一些混淆字符(选手就需要手动挖掘那个 TXT 记录的域名是什么)。
不过也有一些 Hint 后来被去除了,如:一开始考虑将 TXT 记录的值直接写成
PTR hongbao-server.yuuta.moe. 防止选手卡在不知道去玩 rDNS 上(最后的确发现
这里卡的人最多)。以及一些,如在最后 Web 关的文字中一开始是写了图片大小的,
就是为了把选手引导到坐标上,但最后删除了。错误坐标的跳转也是一度被改为了提示语
降低难度,最后也删去了。
事后发现我还是太悲观了。只要发出题是有很多群友愿意花好几个小时去研究的。 在这里非常感谢大家在我的烂作品上面花费宝贵的新春假期时间。而难度似乎有点 高了,有很多人都卡在了 rDNS 上。同时难度有些不够线性:第一关较为容易、第二关很难、第三关很容易。 在明年的红包中我会注意这些问题。
还有一些杂七杂八的问题,比如我那个 “图片外” 一开始是没有给 “外” 字加粗的,导致 第一位解答出来的选手表示当时眼瞎了看了半天没看到 “外” 字导致乱试了半天。第二位 解答出来的选手也表示了一模一样的困惑。因此我将这个字临时加粗了。
以及,以后的口令可以设置为固定 Flag 格式,方便选手分辨。
解谜过程中还出现了一个人全部使用 AI 自动解答出来的情况。我确实没有考虑到这一点。
最后,感觉这个题目还是太单调了。应该像 Soha 那样多设置几个题目,增加趣味性。
谢谢大家的捧场。最终结果为:9 / 10 人解谜成功,其中 5 人为自行完成、3 人为询问 额外 Hint 后完成、1 人为完全使用 AI 完成。
以下为解析坐标后端源码:
#!/usr/bin/env node
// vim:set ts=2 sw=2 et:
// Yuuta Liang <yuuta@yuuta.moe> on Feb. 16 / 2026
const express = require('express');
const app = express();
app.get('/hongbao', async (req, res) => {
console.log(req.query);
const s = Object.keys(req.query)[0];
if (s == undefined) {
res.status(301).setHeader('Location', '/').send();
return;
}
const x = parseInt(s.split(',')[0]);
const y = parseInt(s.split(',')[1]);
if (isNaN(x) || isNaN(y) || ((x >= 0 && x <= 609) && (y >= 0 && y <= 875))) {
// res.status(200).setHeader('Content-Type', 'text/plain').send('猪咪听完也死了');
res.status(301).setHeader('Location', '/').send();
return;
}
res.status(301)
.setHeader('Location', '/photo_2026-02-16_04-12-35.jpg').send();
});
app.listen(3088, '127.0.0.1');
轶事:活动结束才发现上述源码就在 https://ftp.yuuta.moe/hongbao2026/main.js 里。。。。。。
Soha 的红包
待活动结束后放出!
事后
这是我第一次参加春节红包解谜,也是第一次出题。感觉非常有趣!以后还会参加!
(完)