<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          在Netty服務(wù)被N次攻擊之后,終于抓到現(xiàn)行了!

          共 1669字,需瀏覽 4分鐘

           ·

          2021-02-04 00:50

          前言

          馬上就要過(guò)春節(jié)了,本想著完成手頭的任務(wù)就可以準(zhǔn)備過(guò)年了。沒(méi)想到Netty服務(wù)器又被攻擊了,當(dāng)收到服務(wù)器報(bào)警(CPU飆升報(bào)警)信息,就知道對(duì)方又下手了。

          之前是交給下面的兄弟來(lái)解決,這次為了過(guò)個(gè)好年,決定親自動(dòng)手把這事給了結(jié)了。

          故事前奏

          Netty服務(wù)是公司比較邊緣的服務(wù),只有一臺(tái)設(shè)備在使用,而且代碼是之前技術(shù)Leader(已離職)寫的,加上一直趕工期,所以就沒(méi)抽出時(shí)間去徹底解決這事。

          當(dāng)初被攻擊沒(méi)排查代碼,看到遭到瘋狂請(qǐng)求、CPU跑滿、日志打滿,還以為是遭遇DDoS攻擊了。

          臨時(shí)采取了幾個(gè)措施:

          • 分離服務(wù)器,確保該服務(wù)遭到攻擊時(shí)不會(huì)拖垮其他服務(wù);
          • 換了一個(gè)IP和端口;
          • 針對(duì)攻擊的IP添加黑名單;
          • 在代碼層,發(fā)現(xiàn)非法請(qǐng)求強(qiáng)制關(guān)閉連接;
          • 添加日志信息,追溯攻擊報(bào)文和源頭;
          • 對(duì)攻擊服務(wù)的IP(上海阿里云的)進(jìn)行舉報(bào);

          但沒(méi)多久,黑客又找上門來(lái)了,十天半月來(lái)一次攻擊,好像知道服務(wù)IP和后臺(tái)代碼似的,陰魂不散。

          這不,今天被逮到了,而且之前添加了日志打印,也拿到了攻擊的報(bào)文內(nèi)容,復(fù)現(xiàn)了攻擊操作。

          //?攻擊者第一次嘗試的報(bào)文
          8000002872FE1D130000000000000002000186A00001977C0000000000000000000000000000000000000000
          //?攻擊者第二次嘗試的報(bào)文
          8000002872FE1D130000000000000002000186A00001977C00000000000000000000000000000000

          上述報(bào)文,第一次的報(bào)文觸發(fā)了攻擊,第二次的報(bào)文沒(méi)有影響(與正常業(yè)務(wù)報(bào)文格式無(wú)異)。

          下面就帶大家分析分析攻擊的邏輯和代碼中存在的漏洞。

          知識(shí)儲(chǔ)備

          要了解攻擊的原理,我們需要有一定的Netty技術(shù)知識(shí)。關(guān)于Netty如何實(shí)現(xiàn)客戶端和服務(wù)器端的代碼這里就不展開(kāi)了,可以看一下實(shí)現(xiàn)實(shí)例:https://github.com/secbr/netty-all/tree/main/netty-decoder

          我們重點(diǎn)了解一下自定義解碼器和io.netty.buffer.ByteBuf。其中自定義解碼器用于對(duì)報(bào)文進(jìn)行解析,而報(bào)文內(nèi)容通過(guò)ByteBuf進(jìn)行緩存?zhèn)鬏敗?/span>

          上面的攻擊報(bào)文格式表明,黑客已經(jīng)“猜到”我們是基于16進(jìn)制Btye格式進(jìn)行內(nèi)容傳輸?shù)模ê诳途谷灰仓溃?/span>

          自定義解碼器

          要自定義解碼器,繼承MessageToMessageDecoder類并實(shí)現(xiàn)decode方法即可,下面展示一下示例代碼:

          public?class?MyDecoder?extends?MessageToMessageDecoder?{

          ????@Override
          ????protected?void?decode(ChannelHandlerContext?ctx,?ByteBuf?in,?List?out)?{
          ????}
          }

          其中解析報(bào)文的邏輯便是在decode方法內(nèi)進(jìn)行處理。其中ByteBuf in就是接收傳入報(bào)文的容器,而List out用于輸出解析之后的結(jié)果。

          下面來(lái)看一下有bug的代碼(已經(jīng)過(guò)脫敏處理):

          protected?void?decode(ChannelHandlerContext?ctx,?ByteBuf?in,?List?out)?{
          ????int?readableBytes?=?in.readableBytes();
          ????while?(readableBytes?>?3)?{
          ????????in.skipBytes(2);
          ????????int?pkgLength?=?in.readUnsignedShort();
          ????????in.readerIndex(in.readerIndex()?-?4);
          ????????if?(in.readableBytes()?????????????return;
          ????????}
          ????????out.add(in.readBytes(pkgLength));
          ????????readableBytes?=?in.readableBytes();
          ????}
          }

          上面的代碼在跑正常業(yè)務(wù)時(shí)是沒(méi)問(wèn)題的,但當(dāng)被攻擊時(shí),就進(jìn)入了死循環(huán)。因此,導(dǎo)致雖然在業(yè)務(wù)處理時(shí)添加了關(guān)閉連接的操作也是無(wú)效的。

          在分析上面代碼之前,我們還得先詳細(xì)分析一下ByteBuf的原理。

          ByteBuf的原理

          ByteBuf中會(huì)維護(hù)兩個(gè)索引:一個(gè)索引(readIndex)用于讀取,一個(gè)索引(writeIndex)用于寫入。

          當(dāng)從ByteBuf讀取時(shí),readIndex會(huì)被遞增已經(jīng)被讀取的字節(jié)數(shù),當(dāng)向ByteBuf中寫入數(shù)據(jù)時(shí),writeIndex也會(huì)被遞增。

          netty-ByteBuf

          上面圖以攻擊的報(bào)文為例進(jìn)行展示,攻擊者用了44個(gè)字節(jié)的報(bào)文進(jìn)行攻擊。由于使用的是16進(jìn)制,所以兩個(gè)字符占用1個(gè)字節(jié)。

          readIndex和writeIndex的起始位置的索引位置都為0,當(dāng)執(zhí)行ByteBuf中的readXXX或writeXXX方法時(shí),會(huì)推進(jìn)對(duì)應(yīng)的索引。當(dāng)執(zhí)行setXXX或getXXX方法的操作時(shí)則不會(huì)。

          了解了ByteBuf的基本處理原理之后,我們就來(lái)對(duì)照攻擊者的報(bào)文和源代碼來(lái)進(jìn)行攻擊過(guò)程的還原。

          攻擊還原

          下面直接通過(guò)源代碼一步步的分析,主要涉及ByteBuf類的方法。有效攻擊的報(bào)文為上面提到的第一個(gè)報(bào)文。

          //?攻擊者第一次嘗試的報(bào)文
          8000002872FE1D130000000000000002000186A00001977C0000000000000000000000000000000000000000

          下面來(lái)看代碼:

          int?readableBytes?=?in.readableBytes();

          這行代碼通過(guò)readableBytes方法獲取到當(dāng)前ByteBuf中可以讀到的字節(jié)數(shù),上述攻擊報(bào)文88個(gè)字符,所以這里得到44個(gè)字節(jié)。

          當(dāng)readableBytes大于3時(shí)便進(jìn)行具體的解析處理:

          in.skipBytes(2);

          很明顯,通過(guò)skipBytes方法跳過(guò)了兩個(gè)字節(jié)。

          netty-ByteBuf
          int?pkgLength?=?in.readUnsignedShort();

          通過(guò)readUnsignedShort方法,獲得了2個(gè)字節(jié)的內(nèi)容,這兩個(gè)字節(jié)對(duì)應(yīng)的十六進(jìn)制值為“0028”,對(duì)應(yīng)十進(jìn)制為“40”。這兩個(gè)字節(jié)在報(bào)文中的含義是(部分或整個(gè))報(bào)文的長(zhǎng)度。

          報(bào)文的長(zhǎng)度往往有兩種算法:第一,長(zhǎng)度代表整個(gè)報(bào)文的長(zhǎng)度(業(yè)務(wù)中使用的含義);第二,長(zhǎng)度代表除前4個(gè)字節(jié)之后的報(bào)文長(zhǎng)度(攻擊者使用的含義)。

          其實(shí),正是因?yàn)檫@個(gè)長(zhǎng)度含義的定義,導(dǎo)致正常業(yè)務(wù)可以執(zhí)行,而攻擊報(bào)文會(huì)進(jìn)入死循環(huán)。

          下面繼續(xù)分享代碼:

          in.readerIndex(in.readerIndex()?-?4);

          經(jīng)上面的skipBytes和readUnsignedShort的調(diào)用,ByteBuf的讀索引已經(jīng)跑到了第4個(gè)字節(jié)上了。所以這里in.readerIndex()返回的值為4,而in.readerIndex(4-4)的作用就是將讀索引重置為0,也就是從頭開(kāi)始讀。

          if?(in.readableBytes()?????return;
          }

          這個(gè)判斷是在讀索引移動(dòng)到0之后,看看報(bào)文的可讀字節(jié)數(shù)是否小于報(bào)文內(nèi)容中指定的字節(jié)數(shù)。很顯然,in.readableBytes()對(duì)應(yīng)的值為44個(gè)字節(jié),而pkgLength為40個(gè)字節(jié),不會(huì)進(jìn)行return。

          out.add(in.readBytes(pkgLength));

          讀取40個(gè)字節(jié),進(jìn)行輸出。還剩下4個(gè)字節(jié)的內(nèi)容,readIndex指向第40個(gè)字節(jié)的位置。

          readableBytes?=?in.readableBytes();

          由于readIndex已經(jīng)指向第40個(gè)字節(jié),所以此時(shí)可讀字節(jié)數(shù)為4。

          然后,進(jìn)入第二輪循環(huán)。此時(shí),神奇的情況就出現(xiàn)了。我們可以看到攻擊的后4個(gè)字節(jié)的報(bào)文值全為0。

          in.skipBytes(2);
          int?pkgLength?=?in.readUnsignedShort();

          因此跳過(guò)2個(gè)字節(jié)后,readIndex為42,pkgLength獲取第43和44字節(jié)的值:0。

          in.readerIndex(in.readerIndex()?-?4);

          上述代碼又將readIndex設(shè)置到第40個(gè)字節(jié)。

          if?(in.readableBytes()?????return;
          }

          此時(shí)會(huì)發(fā)現(xiàn)readableBytes返回值為4,但pkgLength已經(jīng)變?yōu)?了,不會(huì)return。

          接下讀取內(nèi)容時(shí)就出現(xiàn)狀況了:

          out.add(in.readBytes(pkgLength));
          //?這里還剩下4個(gè)字節(jié)
          readableBytes?=?in.readableBytes();

          上述readBytes讀取字節(jié)數(shù)為0,而readableBytes始終為4。此時(shí),整個(gè)while循環(huán)進(jìn)入了死循環(huán),大量消耗CPU資源。

          此時(shí)還沒(méi)完,最多只是把CPU跑到100%,但是當(dāng)不停的將空字符寫到接收數(shù)據(jù)的緩沖區(qū)域之后,緩沖區(qū)開(kāi)始瘋狂調(diào)用處理業(yè)務(wù)的Handler,進(jìn)一步侵入到業(yè)務(wù)處理邏輯當(dāng)中。

          雖然業(yè)務(wù)邏輯層做了判斷,也進(jìn)行了連接的關(guān)閉,但此時(shí)已經(jīng)與連接無(wú)關(guān),while循環(huán)已經(jīng)進(jìn)入死循環(huán),關(guān)掉連接也沒(méi)什么作用。同時(shí),業(yè)務(wù)層有日志輸出,大量的日志輸出到磁盤當(dāng)中,導(dǎo)致磁盤被刷滿。

          最終導(dǎo)致服務(wù)器的CPU監(jiān)控和磁盤監(jiān)控報(bào)警。乍一看,還以為是又一次DDoS攻擊……

          小結(jié)

          總結(jié)一下,其實(shí)就是攻擊者傳輸?shù)膱?bào)文長(zhǎng)度和報(bào)文內(nèi)指定的長(zhǎng)度不一致,導(dǎo)致了解析報(bào)文時(shí)進(jìn)入了死循環(huán)。

          問(wèn)題一旦發(fā)現(xiàn),解決起來(lái)就很容易了。其實(shí)通過(guò)這件事也得到一些啟發(fā)。第一,遇到問(wèn)題,迎難而上解決掉它,往往是最好的方案,逃避只能將問(wèn)題往后拖,但并不能解決掉。第二,只要靜下心來(lái)分析,一步步分析,很少有解決不掉的問(wèn)題。

          往期推薦

          線程池的7種創(chuàng)建方式,強(qiáng)烈推薦你用它...

          Spring Boot整合Shiro,兩種方式實(shí)戰(zhàn)總結(jié)(含源碼)

          面試管:如何找出字符串中無(wú)重復(fù)最長(zhǎng)子串?

          不解釋,全網(wǎng)最全Shiro認(rèn)證與授權(quán)原理分析

          如何將一個(gè)項(xiàng)目同時(shí)提交到GitHub和Gitee(碼云)上



          如果你覺(jué)得這篇文章不錯(cuò),那么,下篇通常會(huì)更好。關(guān)注一下【公眾號(hào)】或添加微信好友(微信號(hào):541075754),都是OK的。

          一篇文章就看透技術(shù)本質(zhì)的人,
          ? 和花一輩子都看不清的人,
          ? 注定是截然不同的搬磚生涯。
          ▲?長(zhǎng)按關(guān)注”程序新視界“,洞察技術(shù)內(nèi)幕
          瀏覽 45
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    大香蕉伊人综合 | 激情无码青青草 | 亚洲人操逼视频 | 午夜精品久久久99热蜜桃的推荐系统 | 亚洲精品视频区 |