10个Bug环环相扣,你能解开几个?

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

简介: 由阿里云云效主办的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 插件)


大赛目前开放全部关卡点击点击https://code83.ide.aliyun.com/立即体验


对工具感兴趣的同学欢迎加入钉群与我们交流。


推荐阅读:

优秀选手分享|如何面向对象做好重构?

优秀选手分享|快速 Debug 的方法


欢迎大家使用云效,云原生时代新DevOps平台,通过云原生新技术和研发新模式,大幅提升研发效率。现云效公共云基础版不限人数0元使用。


相关文章
|
SQL JSON 前端开发
【改BUG】项目遇到的奇葩bug
【改BUG】项目遇到的奇葩bug
|
开发工具 git
Git使用不当导致代码丢失的N种场景
背景git作为目前使用最广泛的分布式版本控制软件,集团内基本上所有开发同学都使用它来做代码管理。一个最典型的使用场景,是一个git仓库存在一个master主干分支,多个需求基于master拉自己的开发分支,然后在发布日时,新建一个release分支,然后原先并行的几个开发分支merge到release分支上,最后基于该分支发布上线,上线后release再merge到master主干上,一次发布完成
2920 1
Git使用不当导致代码丢失的N种场景
|
4月前
|
存储 安全 API
GitHub代码删了也无用,任何人仍可永久访问?!微软:这不是Bug而是有意设计...
开源安全公司Truffle Security发现,GitHub上的数据删除可能只是表面现象,实际上被删的数据仍可被访问。这一发现震惊了开源社区。研究人员引入了“跨分叉对象引用”(CFOR)这一概念,描述了如何通过已删除或私有fork访问敏感数据。即便存储库被删除,提交的数据仍可通过fork存取,甚至私有存储库的数据也可能被公开访问。尽管GitHub回应称这是有意为之的设计,但对于许多用户来说,这打破了对数据隐私的基本期望。此发现不仅影响GitHub用户,还可能波及其他版本控制系统。
69 4
|
存储 Java Devops
为啥我的代码库那么大?聊聊Git使用坏习惯
阿里代码平台开发同学 在阿里内网吐槽贴 本文作者用幽默又真实的文字总结了开发者日常工作中遇到的那些事儿。
为啥我的代码库那么大?聊聊Git使用坏习惯
|
开发工具 git
sourcetree 提交代码文件丢失解决办法
sourcetree 提交代码文件丢失解决办法
544 0
SVN遗漏so文件的解决办法
SVN遗漏so文件的解决办法
95 0
SVN遗漏so文件的解决办法
|
Linux
安装pycuda的正确办法
安装pycuda的正确办法
877 0
|
缓存 Java 编译器
神奇的bug,解决方法就是试试...
神奇的bug,解决方法就是试试...
137 0
神奇的bug,解决方法就是试试...
|
网络安全 开发工具 git
Git操作与解决方法
Git操作与解决方法
117 0
|
存储 算法 安全
看完这一篇,再也不用担心 Git 的“黑魔法”
相信大部分开发者对 Git 都不陌生,Git 也已成为大部分开发者日常开发必用的工具。本文分享 Git 使用上的一些基础知识,通俗易懂,非常有用。
1401 0
看完这一篇,再也不用担心 Git 的“黑魔法”