HackThisSite - Programming Mission 8

Challenge

Level 8 — Code an IRC bot

写一个 IRC bot 完成指定动作:bot 必须用一个已注册、开启 autoop 并已绑定 HTS 账号的 nick 连到 irc.hackthissite.org;notice 机器人 moo 发送 !perm8moo 会回一条 !md5 <随机串>,bot 必须回 !perm8-result <hash>;哈希正确后在限时内 moo 会向 bot 发 VERSION 请求(bot 必须应答,且不能是 mIRC);随后 moo 会发 !perm8-attack,bot 必须尽快加入 #takeoverz 并尝试 kick moo

绑定方式:在 #perm8 里输入 !link <hts 账号>,然后在站点的 IRC Nickname Link Page 上确认。

状态:blocked。关卡依赖的握手机器人 moo 当前不在 IRC 网络上(证据见下),因此无法完成 md5/version/attack 流程。

Solution

题目要求的第一件事是用一个已注册并绑定 HTS 账号的 nick。这一步是真实操作过的:

  1. 用 NickServ 注册 nick:/msg NickServ REGISTER <password> <email>。服务端要求该 nick 已在线至少 30 秒才允许注册(第一次直接注册会被拒绝:You must have been using this nick for at least 30 seconds to register)。
  2. 注册后会收到确认邮件,必须 /msg NickServ CONFIRM <passcode>;未确认的账号会在 1 天后过期(Your account will expire, if not confirmed, in 1 day)。
  3. 登录后 !link <hts 账号>/msg NickServ IDENTIFY <password>/msg NickServ SET AUTOOP ON(返回 Services will from now on set status modes on <nick> in channels)。
  4. #perm8!link <hts 账号>,然后到 https://www.hackthissite.org/pages/irc/link.php 确认绑定。
1
2
3
4
5
6
7
>> NS REGISTER <password> <mailbox>
<< :NickServ!services@hackthissite.org NOTICE <botnick> :Nickname <botnick> registered.
<< :NickServ!services@hackthissite.org NOTICE <botnick> :Your email address is not confirmed. ...
>> NS CONFIRM <passcode>
<< :NickServ!services@hackthissite.org NOTICE <botnick> :Your email address of ... has been confirmed.
>> NS SET AUTOOP ON
<< :NickServ!services@hackthissite.org NOTICE <botnick> :Services will from now on set status modes on <botnick> in channels.

!link 没有生效(Link Page 上始终没有出现待确认记录),继续排查发现根本原因:moo 这个 nick 在网络上不存在

1
2
3
4
5
6
>> ISON moo            # 空回复:不在线
>> WHOIS moo
<< :wolf.irc.hackthissite.org 401 <botnick> moo :No such nick/channel
<< :wolf.irc.hackthissite.org 318 <botnick> moo :End of /WHOIS list.
>> PRIVMSG #perm8 :!link <hts 账号>
(无任何回应)

同时:

  • #perm8 里只有一个闲置的 op(353 <botnick> = #perm8 :<botnick> @weekend),没有任何机器人;
  • #takeoverz 存在(322 <botnick> #takeoverz 1 :[+ntr]),加入时确实会像题面说的自动拿到 op;
  • Link Page 表格一直是空的(没有待确认的 nick 行),说明 !link 没有任何进程在处理。

服务端 ISONmooMoomoo_HTSBot 等候选一律返回空,只有 ChanServ 存在。结论:这关依赖的服务端机器人已下线!link!md5 → VERSION → !perm8-attack 的整条链路都无法触发;这是关卡自身的失效,不是本地实现问题。

为完成本关的注册步骤,按题面协议写了一个不依赖任何现成客户端脚本的最小实现(纯 socket + TLS,VERSION 应答完全可控,因此不会被判成 mIRC):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import socket
import ssl
import time

HOST, PORT = "irc.hackthissite.org", 6697
VERSION_REPLY = "htsbot 1.0 (python socket IRC client)"

class IRC:
"""Minimal IRC client: TLS connect, NickServ identify, PING/PONG."""

def __init__(self, nick, timeout=2.0):
self.nick = nick
self.buf = ""
raw = socket.create_connection((HOST, PORT), timeout=20)
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
self.s = ctx.wrap_socket(raw, server_hostname=HOST)
self.s.settimeout(timeout)

def send(self, line):
self.s.sendall((line + "\r\n").encode())

def pump(self, seconds):
"""Read for `seconds`, auto-PONG the server, return the new lines."""
end, got = time.time() + seconds, []
while time.time() < end:
try:
data = self.s.recv(8192)
except socket.timeout:
continue
except OSError:
break
if not data:
break
self.buf += data.decode("utf-8", "replace")
while "\r\n" in self.buf:
line, self.buf = self.buf.split("\r\n", 1)
got.append(line)
if line.startswith("PING"):
self.send("PONG " + line.split(":", 1)[-1])
return got

def login(self, password):
self.send("NICK " + self.nick)
self.send(f"USER {self.nick} 0 * :{VERSION_REPLY}")
self.pump(5)
self.send(f"NS IDENTIFY {password}")
self.pump(4)

def notice(self, target, text):
self.send(f"NOTICE {target} :{text}")

def version_reply(self, target):
# CTCP 应答里不能出现 mIRC,否则会被判定用了 mIRC 脚本
self.send(f"NOTICE {target} :\x01VERSION {VERSION_REPLY}\x01")

事件循环部分(等 moo 上线后可直接跑):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def serve(bot, password):
import hashlib
bot.login(password)
bot.notice("moo", "!perm8")
while True:
for line in bot.pump(2):
if "!md5 " in line:
challenge = line.split("!md5 ", 1)[1].strip()
bot.notice("moo", "!perm8-result " + hashlib.md5(challenge.encode()).hexdigest())
if "\x01VERSION\x01" in line:
bot.version_reply("moo")
if "!perm8-attack" in line:
bot.send("JOIN #takeoverz")
bot.pump(2)
bot.send("KICK #takeoverz moo :bye")

(完整可运行版本在工作区 challenges/hts-prog/8/ircbot.pyregister_nick.py 负责注册/确认/identify,link_nick.py 负责发 !link。所有脚本都从命令行/环境变量取账号与凭据,不把凭据写进文件。)

1
2
3
$ cd <hts-workspace> && export HTS_COOKIE='<mission-cookie>'
$ uv run python challenges/hts-prog/8/link_nick.py confirm <passcode> # 邮箱确认
$ uv run python challenges/hts-prog/8/link_nick.py link <hts账号> # #perm8 里 !link
  • moo 不在线是可复现的:WHOIS moo 稳定返回 401 No such nick/channelISON 无回显。
  • 关卡页面本身没有任何服务中断提示,Link Page 也不报错,只是永远不会有记录,容易误判成自己的绑定姿势有问题。
  • 判定口径:服务端行为(机器人是否存在)优先于题面描述;这类依赖站方 bot 的关卡一旦 bot 下线,应当记录为 blocked 而不是继续堆代码。

Vulnerabilities

关卡设计上依赖一个长期在线的站方机器人来做状态机推进,这种耦合本身就是可用性风险:机器人一停,关卡对所有玩家同时失效,而且从关卡页面看不出任何异常(题面照常给出、表单照常存在)。更稳妥的做法是把握手逻辑放到站点自身的服务里(例如由站点后端持有状态、机器人只是展示层),或者至少在 bot 掉线时在关卡页显示维护状态。

未通关:服务端机器人 moo 已下线(WHOIS 返回 401,见正文证据);前置注册/autoop/link 与 bot 实现均已完成,moo 恢复后可继续