佛山营销网站建设服务,积极推进政务网站建设,金山办公给我发验证码,wordpress+三主题公园简介#xff1a;由阿里云云效主办的2021年第3届83行代码挑战赛已经收官。超2万人围观#xff0c;近4000人参赛#xff0c;85个团队组团来战。大赛采用游戏闯关玩儿法#xff0c;融合元宇宙科幻和剧本杀元素#xff0c;让一众开发者玩得不亦乐乎。
今天请来决赛赛题设计者…简介由阿里云云效主办的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 6Admin 角色不对
再次执行 ./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 BootSpring SecuritySpring WebFluxJava IOJUnit 5RegexWebsocketCSRFHTTP Basic Auth
工具
DevStudioWeb 版 Intellij IDEAAppObserver CloudToolkit 插件
原文链接
本文为阿里云原创内容未经允许不得转载。