共计 4571 个字符,预计需要花费 12 分钟才能阅读完成。
简介:由阿里云云效主办的 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 元应用。