在Netty服务被N次攻击之后,终于抓到现行了

原文出自:公众号 程序新视界
原文作者:https://mp.weixin.qq.com/s/GzvwZvVNHx4yjEUVibSNBA
前言
马上就要过春节了,本想着完成手头的任务就可以准备过年了 。没想到Netty服务器又被攻击了,当收到服务器报警(CPU飙升报警)信息,就知道对方又下手了 。
之前是交给下面的兄弟来解决,这次为了过个好年,决定亲自动手把这事给了结了 。
故事前奏Netty服务是公司比较边缘的服务,只有一台设备在使用,而且代码是之前技术Leader(已离职)写的,加上一直赶工期,所以就没抽出时间去彻底解决这事 。
当初被攻击没排查代码,看到遭到疯狂请求、CPU跑满、日志打满,还以为是遭遇DDoS攻击了 。
临时采取了几个措施:
  • 分离服务器,确保该服务遭到攻击时不会拖垮其他服务;
  • 换了一个IP和端口;
  • 针对攻击的IP添加黑名单;
  • 在代码层,发现非法请求强制关闭连接;
  • 添加日志信息,追溯攻击报文和源头;
  • 对攻击服务的IP(上海阿里云的)进行举报;
但没多久,黑客又找上门来了,十天半月来一次攻击,好像知道服务IP和后台代码似的,阴魂不散 。
这不,今天被逮到了,而且之前添加了日志打印,也拿到了攻击的报文内容,复现了攻击操作 。
// 攻击者第一次尝试的报文8000002872FE1D130000000000000002000186A00001977C0000000000000000000000000000000000000000// 攻击者第二次尝试的报文8000002872FE1D130000000000000002000186A00001977C00000000000000000000000000000000上述报文,第一次的报文触发了攻击,第二次的报文没有影响(与正常业务报文格式无异) 。
下面就带大家分析分析攻击的逻辑和代码中存在的漏洞 。
知识储备要了解攻击的原理,我们需要有一定的Netty技术知识 。关于Netty如何实现客户端和服务器端的代码这里就不展开了,可以看一下实现实例:https://github.com/secbr/netty-all/tree/main/netty-decoder
我们重点了解一下自定义解码器和io.netty.buffer.ByteBuf 。其中自定义解码器用于对报文进行解析,而报文内容通过ByteBuf进行缓存传输 。
上面的攻击报文格式表明,黑客已经“猜到”我们是基于16进制Btye格式进行内容传输的(黑客竟然也知道) 。
自定义解码器要自定义解码器,继承MessageToMessageDecoder类并实现decode方法即可,下面展示一下示例代码:
public class MyDecoder extends MessageToMessageDecoder<ByteBuf> {    @Override    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {    }}其中解析报文的逻辑便是在decode方法内进行处理 。其中ByteBuf in就是接收传入报文的容器,而List<Object> out用于输出解析之后的结果 。
下面来看一下有bug的代码(已经过脱敏处理):
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {    int readableBytes = in.readableBytes();    while (readableBytes > 3) {        in.skipBytes(2);        int pkgLength = in.readUnsignedShort();        in.readerIndex(in.readerIndex() - 4);        if (in.readableBytes() < pkgLength) {            return;        }        out.add(in.readBytes(pkgLength));        readableBytes = in.readableBytes();    }}上面的代码在跑正常业务时是没问题的,但当被攻击时,就进入了死循环 。因此,导致虽然在业务处理时添加了关闭连接的操作也是无效的 。
在分析上面代码之前,我们还得先详细分析一下ByteBuf的原理 。
ByteBuf的原理ByteBuf中会维护两个索引:一个索引(readIndex)用于读取,一个索引(writeIndex)用于写入 。
当从ByteBuf读取时,readIndex会被递增已经被读取的字节数,当向ByteBuf中写入数据时,writeIndex也会被递增 。
在Netty服务被N次攻击之后,终于抓到现行了

文章插图
 
上面图以攻击的报文为例进行展示,攻击者用了44个字节的报文进行攻击 。由于使用的是16进制,所以两个字符占用1个字节 。


推荐阅读