10个Bug环环相扣,你能解开几个?
简介:由阿里云云效主办的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环环相扣,你能解开几个?的更多相关文章
- ArcGIS 10.1 BUG记录
声明:笔者使用ARCGIS 10.1 XXX版,YYY版可能没有此处描写的问题 1. 关于注册数据库 发布启用FA的服务,需要为数据库进行ArcGIS Server注册,若通过ArcMap执行注册,会 ...
- JavaScript中常见的10个BUG及其修复方法
如今网站几乎100%使用JavaScript.JavaScript看上去是一门十分简单的语言,然而事实并不如此.它有很多容易被弄错的细节,一不注意就导致BUG. 1. 错误的对this进行引用 在闭包 ...
- 嵌入式码农的10年Bug调试经验,值得一看
下面这些都是我经历过的会导致难点bug的问题: 1.事件顺序.在处理事件时,提出下列问题会很有成效:事件可以以不同的顺序到达吗?如果我们没有接收到此事件会怎么样?如果此事件接连发生两次会怎么样?哪怕通 ...
- Java 开发最容易写的 10 个bug
原文链接:10 个让人头疼的 bug 那个谁,今天又写 bug 了,没错,他说的好像就是我...... 作为 Java 开发,我们在写代码的过程中难免会产生各种奇思妙想的 bug ,有些 bug 就挺 ...
- cocos2d-x 3.10 PageView BUG
cocos2d-x 3.10 PageView 拖动滚动到下一个单元,没事件,3.11有修复.
- getBoundingClientRect在IE9/10里的bug
getBoundingClientRect可以获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置,最早在IE中实现,后其它浏览器均已实现. 但它在IE9,10中有个bug,当出现垂直滚动条时, ...
- js四舍五入的bug和方法
简单来说js使用原生toFixed(x)截取小数的时候会有误差,出现在比如var o = 0.3303;o.toFixed(3);//0.330 toFixed(x)本来也是一个获取四舍五入的截取方法 ...
- 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: ...
- 【独家】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解读 在 ...
- [中英对照]The Art Of Reporting Bugs | 报bug的艺术
前言:因为最近要给兄弟Team分享一下如何有效地报告bug, 故多做一做功课.下面给出一篇博客的中英文对照翻译. The Art Of Reporting Bugs | 报bug的艺术 My init ...
随机推荐
- Spring Boot获取配置参数最简单常用的两种方式
一.自定义属性及常量 在开发过程中,我们常常用到的多环境配置文件,常用的有:dev,test,prod,在不同环境下,我们用到的一样的配置参数,例如:redis,mq,回调接口的url配置.这个情况, ...
- Android 开发Day9
/** * Automatically generated file. DO NOT MODIFY */ package com.hui.tally; public final class Build ...
- uni组件传值注意
目录介绍 01.组件传值遇到坑 02.父组件传值给子组件 03.子组件传值给父组件 01.组件传值遇到坑 子组件给父组件传值注意点 注意子组件触发事件定义的方法,首先在父组件中需要绑定子组件内部对应事 ...
- HDU 2045:不容易系列之(3)—— LELE的RPG难题(动态规划)
一.原题链接 Problem - 2045 (hdu.edu.cn) 二.题面 人称"AC女之杀手"的超级偶像LELE最近忽然玩起了深沉,这可急坏了众多"Cole&quo ...
- 记录--让URL地址都变成了"ooooooooo"
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 发现一个很有创意的小工具网站,如封面图所示功能很简单,就是将一个URL地址转换为都是 ooooooooo 的样子,通过转换后的地址访问可以 ...
- 性能测试系列:高可用测试linux常用命令
一 linux常用 df –h 看磁盘 du –h –max-depth=1 查看当前目录下,各个文件夹大小 ls –lht 查看当前目录下,各个文件大小 top –H –p pid 看进程下线程的资 ...
- SpringBoot2使用hikari报 Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl
SpringBoot2配置文件有变化,需要更改配置 #datasource spring.datasource.driver-class-name=com.mysql.jdbc.Driver spri ...
- FPGA的PCB设计
FPGA的PCB设计 一.FPGA的高速电路板设计 PCB板的设计规模增大,IO传输问题也就出现.为了兼容其他高速模块,必须对PCB的设计进行优化. 1️⃣电源滤波,降低系统噪声2️⃣匹配信号线3️⃣ ...
- Java获取客户端IP地址进行记录
1.编写工具类IpUtils public class IpUtils { /** * 访问IP:0:0:0:0:0:0:0:1 * 访问IP:192.168.1.10 */ private stat ...
- 搭建Spring Cloud父工程
1.首先创建一个maven项目 删除src目录,当做一级目录用来管理第三方jar版本控制. 2.配置pom文件. SpringCloud.SpringCloudAlibaba.SpringBoot版本 ...