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环环相扣,你能解开几个?相关推荐
- 2022年国内外主流的10款Bug跟踪管理软件
这篇文章将会分享国内外主流的10款Bug跟踪管理软件,实现对对Bug报告的记录.分析和状态更新等管理,他们分别是:PingCode .ClickUp. Jira.Redmine Tool.Redmin ...
- 00003 不思议迷宫.0009.10:Bug之二:免称号锻造、升级装备,合成卷轴
00003 不思议迷宫.0009.10:Bug之二:免称号锻造.升级装备,合成卷轴 今天玩德古拉城堡,想击杀100层Boss完成"首领礼包".平时我都是用双大地,但一直听说啥主流套 ...
- 快检查一下你的sudo:无需密码就能获取root权限,还是个10年老bug
贾浩楠 鱼羊 发自 凹非寺 量子位 报道 | 公众号 QbitAI "这可能是近期内最需要重视的sudo漏洞." 程序员都知道,一句sudo可以"为所欲为". ...
- 嵌入式码农的10年Bug调试经验,值得一看
下面这些都是我经历过的会导致难点bug的问题: 1.事件顺序.在处理事件时,提出下列问题会很有成效:事件可以以不同的顺序到达吗?如果我们没有接收到此事件会怎么样?如果此事件接连发生两次会怎么样?哪怕通 ...
- 软件史上的10大bug
在软件开发 过程中,不可避免的会出现软件Bug.在商业软件中,一个小小的Bug可能造成数以百万计的损失,那么在其他高科领域呢? 按照时间顺序,著名网络媒体Wired News列出了到目前为止10个最严 ...
- Java 开发最容易写的 10 个bug
原文链接:10 个让人头疼的 bug 那个谁,今天又写 bug 了,没错,他说的好像就是我...... 作为 Java 开发,我们在写代码的过程中难免会产生各种奇思妙想的 bug ,有些 bug 就挺 ...
- 连接已失效_Win 10 蓝牙bug,无法删除蓝牙设备导致键盘无法连接,如何解决?...
今天遇到的,头疼得要死 Win10 与蓝牙设备Filco键盘出现了无法连接的情况,本来打算删除已配对的设备,再重新配对连接.但 Win10 很多 Bug 呢,删除设备后重启蓝牙,但是一直提示无法删除设 ...
- cocos2d-x 3.10 PageView BUG
cocos2d-x 3.10 PageView 拖动滚动到下一个单元,没事件,3.11有修复. 转载于:https://www.cnblogs.com/qianwang/p/6106324.html
- 2018-2021,60+篇阿里研发效能提升合集,都在这里了
今年,研发效能特别火,不少企业的CTO都把研发效能提升作为部门的年度重点.但是,大家都希望提升研发效能,很多却不知道从何开始. 事实上,从2018年开始,云效已经在系统地向业界输出阿里的研发效能提升方 ...
最新文章
- C/C++ 取整函数 ceil()、floor()、trunc()
- 在新的数学证明中,人工智能取胜
- mysql获取两个表中日期字段的最小差值
- 网编编程必看书籍:unix网络编程
- 计算机及网络技术发展趋势,网络技术发展对计算机技术的影响
- python入门——条件语句、for、while循环4
- 【c基础】之 文件及其操作
- 动态规划 —— 背包问题 P05 —— 二维背包
- java 中subtract的用法_java中BigDecimal加减乘除基本用法
- pro android学习笔记,【转】Pro Android学习笔记(一):Android 平台 2013.6.4
- 中断占用CPU的时间分析
- 此电脑怎么放在桌面上_电脑内部与麦克风的声音怎么同时录制?详细教程在此...
- unity相关的javascript脚本:unity圣典学习笔记————MonoBehaviour
- dll缺失怎么修复?有什么好的修复方法推荐?
- 904L 是一种耐酸不锈钢含低碳
- 整理springmvc+mybatis+velocity的整合一
- Paypal REST API Java 版 PC端商城支付接口对接。
- 告白或写给对象的网页。
- IPFS 之包管理器GX
- 一劳永逸的解决jquery的本地引入的方法
热门文章
- 青岛旅游学校计算机证书,【我和我的旅校】青岛旅游学校优秀毕业生郭千瑜
- centos6安装mysql并远程连接_MySQL5.7数据库安装与远程连接
- java文件传输连接方式_Java 学习笔记 网络编程 使用Socket传输文件 CS模式
- 异步fifo_跨时钟域同步(异步FIFO)
- ajax实现翻书效果,jQuery实现手机版页面翻页效果的简单实例
- babylonjs 设置面板位置_一篇关于开关面板的详细集合,值得收藏转发
- 关于python_关于python的基础知识
- ef.extensions mysql_EFcore 使用 EFCore.BulkExtensions(不支持mysql)或 EntityFramework-Plus 批量增加、删除、修改...
- keras 分布式_TensorFlow 2.0正式版官宣!深度集成Keras
- mysql odbc.ini_关于unixodbc中odbc.ini和odbcinst.ini的介绍