简介:由阿里云云效主办的2021年第3届83行代码挑战赛已经收官。超2万人围观,近4000人参赛,85个团队组团来战。大赛采用游戏闯关玩儿法,融合元宇宙科幻和剧本杀元素,让一众开发者玩得不亦乐乎。

今天请来决赛赛题设计者杜万,给大家分享一下设计与解题思路。

搭配《用代码玩剧本杀?第3届83行代码大赛剧情官方解析》使用效果更佳。

第四题整体是一个C/S架构,客户客户端是一个编译好的命令行程序,不可被修改,服务端是一个 Spring Boot 的 Web 应用;赛题要求,找出服务端程序的 BUG 并修复;客户端有两个职责,一个是说去向服务端发送正常 HTTP 请求,让参赛者发现BUG。

另一个是验证 bug 修复情况,然后发送给远端的评分程序,获得评分。整个赛题是跑在我们阿里云 DevStudio 上面,在 DevStudio 里我们启动一个Intellij IDEA 的社区版,内置了应用观测器(AppObserver) 插件。

Bug 1 :修复 Regex

我们来看第一个bug 如何修复吧。运行 ‘mvn test’,10 个测试有 9 个错误。

这里有好几个BUG,我们先看正则表达式相关的,我们先修复ExtractHtmlTest,翻阅源码,很快能定位到 Utils.stripHtmlTag 方法,方法名字面意思是去除 HTML Tag 标签,然后仔细查看日志会发现。

删除的Tag内容包括了 > 和 ,那说明正则有问题,下图是对正则的剖析。

所以该 BUG上述两种修复方法都是 OK 的。

解法:将 Utils.java 里的正则表达式`<(?.*)>`改为`<(?[^>]*)>`。

Bug 2:修复尾串缺失

再次执行 mvn test,发现还有单测没有通过,我们会发现字符串少了一截。

再次查看 Utils.stripHtmlTag 方法,发现 matcher.appendReplacement 方法,如果不熟悉该方法,查看JDK的注释后,会发现 matcher.appendReplacement 和 matcher.appendTail 是成对出现的。所以在循环外补上 matcher.appendTail(builder)。

看图是 matcher.appendReplacement 和 matcher.appendTail 的工作机制,巧用该方法,替换字符串更得心应手。

Bug 3:修复 EOFException

再次执行 mvn test,仅剩下 EOFException 错误了,很快能定位到报错的方法是 Utils.decodeMessage。

通过分析 ReactiveWebSocketHandler 的头部注释和 Utils.encodeMessage 的方法,我们了解到二进制的包结构:

/**
* 二进制包格式
* byte 字符集长度; n1
* byte[n1] 字符集数据;n1 = 字符集长度
* byte[n2] 有效数据;n2 = 包总长度 - n1 - 1
*/
@Component("ReactiveWebSocketHandler")
public class ReactiveWebSocketHandler implements WebSocketHandler {
public static byte[] encodeMessage(String message, Charset charset) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(out);
byte[] charsetNameBytes = charset.toString().getBytes(ISO_8859_1); try {
dos.write((byte) charsetNameBytes.length);
dos.write(charsetNameBytes);
dos.write(message.getBytes(charset));
dos.flush();
} catch (IOException e) {
e.printStackTrace();
} return out.toByteArray();
}

然后在对比 Utils.decodeMessage 可以发现是一个调用时序问题,改正方法如下:

return new String(dis.readAllBytes(), charsetNameDecoder.apply(dis));

=>

String charsetName = charsetNameDecoder.apply(dis);
return new String(dis.readAllBytes(), charsetName);

此单测 Bug 已经修完了,接下来我们来修运行态的BUG。

配置应用观测器

首先我们先配置一下应用观测器(AppObserver),在赛题的DevStudio中,已经预安装了 AppObserver ,这里配置一下IDEA的启动器,加上应用观测器的 Agent 就好了。

配置好应用观测器后,通过 Spring Boot 的 main 函数启动 Server 端进程。

Bug 4:修复CSRF

执行项目根目录的客户端程序 round4

$./round4

   ___          _       ___ _____
/ __\___ __| | ___ ( _ )___ /
/ / / _ \ / _` |/ _ \/ _ \ |_ \
/ /___ (_) | (_| | __/ (_) |__) |
\____/\___/ \__,_|\___|\___/____/ 「第四关」 致命真相
当你直面致命的真相,你是否能面对这残酷的现实? :: 通关要求 :: 达到 60 分以上
:: 获胜要求 :: 分数最高且用时最短 启动客户端程序.... === Step 1 ====
成功获得数据通道: [
"/ws/Codeup",
"/ws/AppObserver",
"/ws/DevStudio",
] === Step 2 ====
添加用户 reporter 失败!响应状态码: 403 Forbidden, 响应消息: "An expected CSRF token cannot be found", 请求头:"Authorization: Basic YWRtaW46YWRtaW4xMjM=" === Step 3 ====
使用 reporter 用户无法连接到:ws://localhost:8080/ws/DevStudio, 响应状态码: 401 Unauthorized, 请求头: {"authorization": "Basic cmVwb3J0ZXI6cmVwb3J0ZXI="}

Step2 有一个 CSRF 的报错,由于无法修改客户端程序,需要在 Server 端解决这个问题,关闭掉 CSRF 校验。

使用上面的报错关键字Google一下,很快能找到Spring Security的修改方法。

然后照下午修改,再验证一下,发现响应码从 403 变成了 401,所以修改生效了。

Bug 5:修复 Admin 用户密码错误

上一步再次执行 ./round4 ,Step2 返回了 401,并提示了请求头:"Authorization: Basic YWRtaW46YWRtaW4xMjM=",这里可以看出,使用了HTTP Basic的验证方式,然后401提示,可能是用户名和密码不对,所以这里可以用 base64 解开认证头,修改一下服务端的用户名密码。

Bug 6:Admin 角色不对

再次执行 ./round4 后我们发现,又变回了 403,但是返回错误变成了 Access Denied。看来密码对了,但是没有权限访问,打开 WebSecurityConfig 文件,我们会发现admin角色有两种写法“ADMIN”和“admin”,问题就出在这里,我们统一改成大写试试。

Step2,算过了,接下来出来Step3 的问题了。

Bug 7:缺失 REPORTER 角色

Step 3 报错,使用 reporter 用户无法连接到:ws://localhost:8080/ws/AppObserver, 响应状态码: 403 Forbidden, 请求头: {"authorization": "Basic cmVwb3J0ZXI6cmVwb3J0ZXI="}。

又是一个权限问题,先解开 base64 编码的 Authorization,发现用户密码都是 reporter。接下来需要借助于应用观测器,使用应用观测器在 Round4Controller.addUser 加上虚拟断点,虚拟断点和普通断点一样可以获得执行上下文的线程堆栈和变量信息,但是虚拟断点不会阻塞执行,这个特性对于生产系统非常有用。

具体操作如下图所示

通过虚拟断点,我们发现 reporter 用户的角色名为 REPORTER,而 endpoint "/ws/**", 当前只允许ADMIN角色访问,所以在Security配置里,给该路径添加 REPORTER 角色即可。

解决了角色问题,4 个 Spring Security 相关的 BUG 都已经已经修复掉了。重启服务并执行 ./round4 我们会先发有乱码,那看看乱码怎么修

Bug 8:共享 Buffer

通过对 ReactiveWebSocketHandler 里一连串mapper的分析,我们会发现 getBufferConverter 方法返回了定长的buffer,而这个buffer后面会有一连串的0值,这个很可疑。仔细看代码发现,多次调用之间共享了同一个buffer,而没有清空。解法也很简单,把共享buffer改成每次新建即可。如下图所示:

修复以后,再次执行 ./round4 乱码没有,但是返回内容有点少了,说明还有其他问题。

Bug 9:修复 NPE

修掉上面乱码问题以后,从客户端 round4 的运行输出里已经看不到明显的错误了,这是发现内容有点短,看Server这边的日志,会看到一个NPE的报错:

NPE比较好修,很快能排查到一个 return null。

改成 return ""; 即可。

Bug 10:去除 ThreadLocal

重启服务端,并再次执行 ./round4,内容多了,不过再次乱码。

最后一个Bug,不太好调试,需要靠认证的阅读代码,理解一下上下文,能看到有一个奇怪的ThreadLocal 变量用于缓存 charsetName。

在一个Thread里charset是不变的?去掉估计也不会影响效果,最多性能差一点,尝试去掉。

重启服务端,并再次执行 ./round4。

这下一切正常了。

提取线索

上面三个频道的返回包含了大赛的线索,所以我们可以使用 grep 工具赛选出来。

剧情题我们这里就不讨论了,可以看另外一篇解密文章。

小结

共计修了 10 个 Bug

  • Regex 2个
  • Spring Security 4个
  • NPE 1个
  • EOF 1个
  • 共享状态 2个

赛题涉及到的技术

  • Spring Boot
  • Spring Security
  • Spring WebFlux
  • Java IO
  • JUnit 5
  • Regex
  • Websocket
  • CSRF
  • HTTP Basic Auth

工具

  • DevStudio(Web 版 Intellij IDEA)
  • AppObserver (CloudToolkit 插件)

原文链接

本文为阿里云原创内容,未经允许不得转载。

10个Bug环环相扣,你能解开几个?的更多相关文章

  1. ArcGIS 10.1 BUG记录

    声明:笔者使用ARCGIS 10.1 XXX版,YYY版可能没有此处描写的问题 1. 关于注册数据库 发布启用FA的服务,需要为数据库进行ArcGIS Server注册,若通过ArcMap执行注册,会 ...

  2. JavaScript中常见的10个BUG及其修复方法

    如今网站几乎100%使用JavaScript.JavaScript看上去是一门十分简单的语言,然而事实并不如此.它有很多容易被弄错的细节,一不注意就导致BUG. 1. 错误的对this进行引用 在闭包 ...

  3. 嵌入式码农的10年Bug调试经验,值得一看

    下面这些都是我经历过的会导致难点bug的问题: 1.事件顺序.在处理事件时,提出下列问题会很有成效:事件可以以不同的顺序到达吗?如果我们没有接收到此事件会怎么样?如果此事件接连发生两次会怎么样?哪怕通 ...

  4. Java 开发最容易写的 10 个bug

    原文链接:10 个让人头疼的 bug 那个谁,今天又写 bug 了,没错,他说的好像就是我...... 作为 Java 开发,我们在写代码的过程中难免会产生各种奇思妙想的 bug ,有些 bug 就挺 ...

  5. cocos2d-x 3.10 PageView BUG

    cocos2d-x 3.10 PageView 拖动滚动到下一个单元,没事件,3.11有修复.

  6. getBoundingClientRect在IE9/10里的bug

    getBoundingClientRect可以获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置,最早在IE中实现,后其它浏览器均已实现. 但它在IE9,10中有个bug,当出现垂直滚动条时, ...

  7. js四舍五入的bug和方法

    简单来说js使用原生toFixed(x)截取小数的时候会有误差,出现在比如var o = 0.3303;o.toFixed(3);//0.330 toFixed(x)本来也是一个获取四舍五入的截取方法 ...

  8. ORA-01791: not a SELECTed expression 一种是不 bug 的 bug!

    [ora11@lixora ~]$ !sql sqlplus / as sysdba SQL*Plus: Release 11.2.0.1.0 Production on Wed Aug 27 09: ...

  9. 【独家】K8S漏洞报告|近期bug fix解读&1.11主要bug fix汇总

    内容提要: 1. 高危漏洞CVE-2018-1002105深度解读 2. 11/19--12/11 bug fix汇总分析 3. 1.11重要bug fix解读 4. 1.9重要bug fix解读 在 ...

  10. [中英对照]The Art Of Reporting Bugs | 报bug的艺术

    前言:因为最近要给兄弟Team分享一下如何有效地报告bug, 故多做一做功课.下面给出一篇博客的中英文对照翻译. The Art Of Reporting Bugs | 报bug的艺术 My init ...

随机推荐

  1. 风场可视化学习笔记:openlayers

    最近在弄地图控件方面的东西,这里分享一个我找到的一个添加风场的教程和demo,需要对大家有所帮助(以下为转载内容)载于https://blog.csdn.net/u010065726/article/ ...

  2. 手把手带你用香橙派AIpro开发AI推理应用

    本文分享自华为云社区<如何基于香橙派AIpro开发AI推理应用>,作者:昇腾CANN. 01 简介 香橙派AIpro开发板采用昇腾AI技术路线,接口丰富且具有强大的可扩展性,提供8/20T ...

  3. 【Pavia】遥感图像数据集下载地址和读取数据集代码

    [Pavia]遥感图像数据集下载地址和读取数据集代码 目录 [Pavia]遥感图像数据集下载地址和读取数据集代码 前言 Pavia数据集 Pavia数据集地址: Pavia数据集预览 PaviaU.m ...

  4. KingbaseES运维案例之---数据库启动“could not open shared memory segment”

    ​ 案例说明: 在kylin系统下数据库启动出现"could not open shared memory segment xxxx"的故障,故障如下所示: 适用版本: Kingb ...

  5. debian12 出现Waiting for suspend/resume device ... Begin: Running /scripts/local-block ... done.

    /etc/initramfs-tools/conf.d/resume里对应的交换分区的uuid不正确 删除/etc/initramfs-tools/conf.d/resume 再运行 sudo upd ...

  6. Scala打印输出

    1 package com.atguigu.chapter02 2 object TestCharType { 3 def main(args: Array[String]): Unit = { 4 ...

  7. #回滚莫队#AT1219 歴史の研究

    洛谷题目 AT1219 分析 不满足区间减性质的运算,如最值,就不能用普通莫队求, 考虑回滚莫队,它的核心思想就是若区间在块内直接暴力, 否则将右端点从小到大排序,右端点按普通莫队求,那么左端点由于只 ...

  8. #dp#JZOJ 1281 旅行

    分析 考虑每次都是取出一个连续段置换一下, 可以预处理出相邻差的绝对值的前缀和, 但是如果正序无法知道上一段是从哪个终止的 所以倒序就可以了 代码 #include <cstdio> #i ...

  9. [HAOI2007,P2216,BZOJ1047]理想的正方形单调队列解法

    题目描述 有一个 \(a \times b\) 的整数组成的矩阵,现请你从中找出一个 \(n \times n\) 的正方形区域,使得该区域所有数中的最大值和最小值的差最小. 输入格式 第一行为 \( ...

  10. Array and Set work process

    目录 Array work principle 分析Array操作步骤数 read find insert delete Set work principle 分析Set操作步骤数 read find ...