readIndex和writeIndex的起始位置的索引位置都为0,当执行ByteBuf中的readXXX或writeXXX方法时,会推进对应的索引 。当执行setXXX或getXXX方法的操作时则不会 。
了解了ByteBuf的基本处理原理之后,我们就来对照攻击者的报文和源代码来进行攻击过程的还原 。
攻击还原下面直接通过源代码一步步的分析,主要涉及ByteBuf类的方法 。有效攻击的报文为上面提到的第一个报文 。
// 攻击者第一次尝试的报文8000002872FE1D130000000000000002000186A00001977C0000000000000000000000000000000000000000
下面来看代码:
int readableBytes = in.readableBytes();
这行代码通过readableBytes方法获取到当前ByteBuf中可以读到的字节数,上述攻击报文88个字符,所以这里得到44个字节 。
当readableBytes大于3时便进行具体的解析处理:
in.skipBytes(2);
很明显,通过skipBytes方法跳过了两个字节 。
文章插图
int pkgLength = in.readUnsignedShort();
通过readUnsignedShort方法,获得了2个字节的内容,这两个字节对应的十六进制值为“0028”,对应十进制为“40” 。这两个字节在报文中的含义是(部分或整个)报文的长度 。报文的长度往往有两种算法:第一,长度代表整个报文的长度(业务中使用的含义);第二,长度代表除前4个字节之后的报文长度(攻击者使用的含义) 。
其实,正是因为这个长度含义的定义,导致正常业务可以执行,而攻击报文会进入死循环 。
下面继续分享代码:
in.readerIndex(in.readerIndex() - 4);
【在Netty服务被N次攻击之后,终于抓到现行了】经上面的skipBytes和readUnsignedShort的调用,ByteBuf的读索引已经跑到了第4个字节上了 。所以这里in.readerIndex()返回的值为4,而in.readerIndex(4-4)的作用就是将读索引重置为0,也就是从头开始读 。if (in.readableBytes() < pkgLength) { return;}
这个判断是在读索引移动到0之后,看看报文的可读字节数是否小于报文内容中指定的字节数 。很显然,in.readableBytes()对应的值为44个字节,而pkgLength为40个字节,不会进行return 。out.add(in.readBytes(pkgLength));
读取40个字节,进行输出 。还剩下4个字节的内容,readIndex指向第40个字节的位置 。readableBytes = in.readableBytes();
由于readIndex已经指向第40个字节,所以此时可读字节数为4 。然后,进入第二轮循环 。此时,神奇的情况就出现了 。我们可以看到攻击的后4个字节的报文值全为0 。
in.skipBytes(2);int pkgLength = in.readUnsignedShort();
因此跳过2个字节后,readIndex为42,pkgLength获取第43和44字节的值:0 。in.readerIndex(in.readerIndex() - 4);
上述代码又将readIndex设置到第40个字节 。if (in.readableBytes() < pkgLength) { return;}
此时会发现readableBytes返回值为4,但pkgLength已经变为0了,不会return 。接下读取内容时就出现状况了:
out.add(in.readBytes(pkgLength));// 这里还剩下4个字节readableBytes = in.readableBytes();
上述readBytes读取字节数为0,而readableBytes始终为4 。此时,整个while循环进入了死循环,大量消耗CPU资源 。此时还没完,最多只是把CPU跑到100%,但是当不停的将空字符写到接收数据的缓冲区域之后,缓冲区开始疯狂调用处理业务的Handler,进一步侵入到业务处理逻辑当中 。
虽然业务逻辑层做了判断,也进行了连接的关闭,但此时已经与连接无关,while循环已经进入死循环,关掉连接也没什么作用 。同时,业务层有日志输出,大量的日志输出到磁盘当中,导致磁盘被刷满 。
最终导致服务器的CPU监控和磁盘监控报警 。乍一看,还以为是又一次DDoS攻击……
小结总结一下,其实就是攻击者传输的报文长度和报文内指定的长度不一致,导致了解析报文时进入了死循环 。
问题一旦发现,解决起来就很容易了 。其实通过这件事也得到一些启发 。第一,遇到问题,迎难而上解决掉它,往往是最好的方案,逃避只能将问题往后拖,但并不能解决掉 。第二,只要静下心来分析,一步步分析,很少有解决不掉的问题 。
推荐阅读
- 网线接续的4种方法
- 中国传奇黑客,一位让国旗飘扬在美国网站,一位使半个日本瘫痪
- 曹操时代至今过了多少年 关于在曹操那里几年
- 衬衫|英国官员盯上了在家办公的公务员:不回办公室上班就把你开除
- h5转盘抽奖jquery?抽奖大转盘如何制作
- 严歌苓写作领域?在严歌苓众多的长篇中
- 现在怎么才能告御状 古代告御状要经历什么
- 中国礼品网服务特色简介
- 明朝皇帝朱祁钰为什么没葬入13陵 朱祁钰葬在皇陵了吗
- 3种倾向最易成为别人备胎