int main (int argc, char **argv) {
...
settings_init();
...
while (-1 != (c = getopt(argc, argv,
...
"U:" /* UDP port number to listen on */
...
case 'U':
settings.udpport = atoi(optarg);
udp_specified = true;
break;
...
))) {
...
if (tcp_specified && !udp_specified) {
settings.udpport = settings.port;
} else if (udp_specified && !tcp_specified) {
settings.port = settings.udpport;
}
...
main_base = event_init();
...
thread_init(settings.num_threads, main_base);
...
/* create unix mode sockets after dropping privileges */
if (settings.socketpath != NULL) {
errno = 0;
if (server_socket_unix(settings.socketpath,settings.access)) {
vperror("failed to listen on UNIX socket: %s", settings.socketpath);
exit(EX_OSERR);
}
}
/* create the listening socket, bind it, and init */
if (settings.socketpath == NULL) {
...
// TCP
errno = 0;
if (settings.port && server_sockets(settings.port, tcp_transport,
portnumber_file)) {
vperror("failed to listen on TCP port %d", settings.port);
exit(EX_OSERR);
}
/*
* initialization order: first create the listening sockets
* (may need root on low ports), then drop root if needed,
* then daemonise if needed, then init libevent (in some cases
* descriptors created by libevent wouldn't survive forking).
*/
/* create the UDP listening socket and bind it */
errno = 0;
if (settings.udpport && server_sockets(settings.udpport, udp_transport,
portnumber_file)) {
vperror("failed to listen on UDP port %d", settings.udpport);
exit(EX_OSERR);
}
...
/* enter the event loop */
if (event_base_loop(main_base, 0) != 0) {
retval = EXIT_FAILURE;
}
...
}
在此之前初始化了一些设置,可以看到默认端口是11211,有4个worker线程。
static void settings_init(void) {
...
settings.port = 11211;
settings.udpport = 11211;
/* By default this string should be NULL for getaddrinfo() */
settings.inter = NULL;
settings.maxbytes = 64 * 1024 * 1024; /* default is 64MB */
...
settings.chunk_size = 48; /* space for a modest key and value */
settings.num_threads = 4; /* N workers */
...
}
可以检查一下:
$ echo "stats settings" | nc localhost 11211
STAT maxbytes 67108864
STAT maxconns 1024
STAT tcpport 11211
STAT udpport 11211
STAT inter NULL
...
STAT chunk_size 48
STAT num_threads 4
...
END
User Datagram Protocol
----------------------
...
protocol is transaction oriented, and delivery and duplicate protection
are not guaranteed. Applications requiring ordered reliable delivery of
streams of data should use the Transmission Control Protocol (TCP) [2].
Format
------
0 7 8 15 16 23 24 31
+--------+--------+--------+--------+
| Source | Destination |
| Port | Port |
+--------+--------+--------+--------+
| | |
| Length | Checksum |
+--------+--------+--------+--------+
|
| data octets ...
+---------------- ...
User Datagram Header Format
Fields
------
听说3月1日 GitHub 被DDoS攻击了,好像挺严重的。
来看看怎么使用mc攻击~
mc 首先通过cmd line指定UDP端口,然后初始化libevent实例,初始化线程,
在此之前初始化了一些设置,可以看到默认端口是11211,有4个worker线程。
可以检查一下:
随后线程初始化,main_base 是分发任务的主线程,创建管道用于libevent通知。主要调用了setup_thread初始化线程信息数据结构,最后创建并初始化线程,代码段都是 worker_libevent。
这里看到了 thread_libevent_process 指针,在设置线程初始化数据时,设置为me->notify_receive_fd 管道的libevent读事件。
当管道可读时回调此函数。从队列中取出一个任务,随后调conn_new。
conn_new为新的请求建立一个连接结构体。这里只填充conn结构体。主要在 libevent 中注册函数指针event_handler。
当有新的连接的时候将会回调此函数。
client connect 后,memcached server主线程被唤醒,然后调用event_handler()->drive_machine(),进入这个状态机。从别处代码看,只有tcp或UNIX域套接字才会进行conn_listening,即accept过程。conn_waiting等待新的命令请求,conn_read 为读取数据,读完请求后转换 conn 的状态,然后就是解析执行命令咯。在conn_mwrite状态下回复数据;在transmit中最终调用sendmsg写给套接字。
上文用到的读取UDP,直接调recvfrom,此处从客户端接受数据,将读取到的指令放到rbuf中。
主函数中配置的模式,允许客户端以几种方式向mc server发请求 UDP只要绑定之后,直接读取 sfd 就OK,在这里看出它 conn 初始状态应为 conn_read,而 TCP 对应的 conn 初始状态应该为 conn_listening。
针对每个interface绑定。
设置了socket的发送缓冲大小为,取默认值,然后和设置的最大值二分查找,取最后的最大值。
然后分发新的连接到线程池中的一个线程中,就是在一个线程的wq中加入一个任务,并通过管道给相应的线程发信,向一个休眠线程写字符,已注册事件会被触发,随后调thread_libevent_process(上文setup_thread 中线程pd被设置到 event 中)
这里看到mc可通过UDP模式将放大的数据返回给client,所以可以利用这个特性执行攻击,利用网络上的mc放大攻击效果。
看一下协议:RFC768
Length字段占2字节。所以UDP协议单次最大发送数据为2 ^ 16 = 65535 = 64KB。UDP协议不基于连接,可直接发送数据报到目标机器。因为UDP协议无连接,直接发数据到target,不需三次握手。target也不好验证客户源IP。
我们先批量set 多一点大value到远程开放 memcached server上,过期也设置长一点,然后利用UDP伪造源地址在memcached server get 存储的value,请求时间段尽量集中,这样就将数据通过mc server Reflect 到target,实现DRDoS过程。
2月底,dormando Release了 1.5.6,该版本默认关闭了UDP启动: https://groups.google.com/forum/#!topic/memcached/pu6LAIbL_Ks
若想预防,可以升级新版,也可以网络层做限制。也可以启动 memcached 加入 -U 0启动参数,表达式短路后就不会server_sockets,禁止监听udp协议。
Linkerist 2018年3月4日于北京街角的咖啡店