- A+
(注:ldap默认密码字段userpassword为明文存储,另openldap加密可使用sldppasswd -s password)
最近在登录公司内部的一个管理系统时,总是弹出对话框提示证书过期、是否继续云云,今天突发奇想,这个登录过程该不会没有加密传输吧?
先用 Wireshark 抓包看看,果然是未加密的 HTTP 连接,在其中一个访问 /LogicService.asmx 的 POST 请求中,可以看到如下的 XML 内容:
<?xml version="1.0" encoding="utf-8" ?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
<soap:Body>
<EncryptLogin xmlns="http://tempuri.org/">
<UserID>philip_ye</UserID>
<password>4r4g8OovaDRiiijWlEC5AQ==</password>
<ip>192.1.1.110.11.151.164</ip>
</EncryptLogin>
</soap:Body>
</soap:Envelope>
其中的 password 字段是我用「admin」作为密码输入登录界面再抓包得到的值,密码的密文 4r4g8OovaDRiiijWlEC5AQ== 最后有两个等号,应该是 base64 编码后的结果,尝试着解码看看:
$ printf 4r4g8OovaDRiiijWlEC5AQ== | base64 -d | hexdump -C
00000000 e2 be 20 f0 ea 2f 68 34 62 8a 28 d6 94 40 b9 01 |.. ../h4b.(..@..|
得到一个 128bit 的值:e2be20f0ea2f6834628a28d69440b901,这个值看着又像是 MD5 的结果,而 MD5 运算在通常的应用中是不可逆的,因此对「admin」做 MD5 运算来验证:
$ printf admin | md5sum
21232f297a57a5a743894a0e4a801fc3 -
得到的结果是 21232f297a57a5a743894a0e4a801fc3,与 e2be20f0ea2f6834628a28d69440b901 不相等,显然没有这么简单。
根据公司的这个管理系统客户端的默认图标来看,应该是 .NET Framework 写的。根据 StackOverflow 上这个问答找来 .NET Reflector 对 EXE 文件反编译,整个管理系统客户端的源代码赫然眼前。当年通过反编译学习某些功能实现原理的日子再次上演了。
找到「登录」按钮的点击事件处理函数,其中有这么一条语句:
bool flag = manage.EncryptLogin(userID, SingleEncode.Encode(text), ip);
其中的 text 就是从密码框中获取到的明文密码,也就是我之前输入的「admin」,而 SingleEncode.Encode() 就是对密码加密的接口了。
继续查看 SingleEncode.Encode() 的实现:
public static string Encode(string s) => Convert.ToBase64String(GetBytesFromStream(GetEncodeStream(Encoding.ASCII.GetBytes(s))));
可以看出,整个加密过程确实是先加密之后再使用 base64 编码的。继续看 GetEncodeStream() 的实现(Encoding.ASCII.GetBytes()、GetBytesFromStream() 都是系统接口):
private static MemoryStream GetEncodeStream(byte[] bytes) { MemoryStream stream = new MemoryStream(); CryptoStream stream2 = new CryptoStream(stream, GetEncoder(), CryptoStreamMode.Write); stream2.Write(bytes, 0, bytes.Length); stream2.FlushFinalBlock(); return stream; }
重点在 GetEncoder():
private static ICryptoTransform GetEncoder() { RijndaelManaged managed = new RijndaelManaged(); return managed.CreateEncryptor(Key, IV); }
至此可以看出,采用的是 Rijndael 加密算法,也就是大名鼎鼎的 AES 加密算法。Rijndael 与 AES 的区别可参考 The Differences Between Rijndael and AES
RijndaelManaged 是系统类,入参 Key 和 IV 可在 MSDN 上 RijndaelManaged 类的说明文档中找到:
-
IV: Gets or sets the initialization vector (IV) for the symmetric algorithm.(Inherited from SymmetricAlgorithm.)
-
Key: Gets or sets the secret key for the symmetric algorithm.(Inherited from SymmetricAlgorithm.)
在 GetEncoder() 所在的 SingleEncode 类的构造函数中,对 Key 和 IV 进行初始化赋值:
static SingleEncode() { Key = new byte[] { 0xd8, 0xd5, 0x19, 0x48, 0xea, 0xae, 0x74, 0x84, 0xe1, 0x15, 0x5e, 0xd0, 0x54, 0xf7, 0x69, 0x9b, 0x77, 0x00, 0xc4, 0x35, 0x78, 0xbb, 0x99, 0x62, 0x20, 0xb9, 0xed, 0x7f, 0xf9, 0x76, 0x6d, 0x16 }; IV = new byte[] { 0x46, 0x3e, 0xa9, 0x70, 0x1f, 0xb5, 0xaa, 0x7a, 0x0f, 0xa6, 0x59, 0xc1, 0x62, 0xef, 0x0a, 0xf6 }; }
为避免我司信息安全事故,上述 Key 和 IV 已经过修改,并非实际使用的值。
可以看到 Key 为 32 字节(256-bit),IV 为 16 字节。由 mcrypt(3) 得知,需要 IV 的只有 CBC 模式和 CFB 模式,而从明文「admin」长度 5 字节、密文长度 32 字节、Key 长度 32 字节这点来判断,使用的应该是 CBC 模式(CBC 模式的密文长度与 Key 长度相等,而 CFB 模式的密文长度与明文长度相等)。也就是说,具体加密算法应该是 AES-256-CBC。
将上述的 Key 和 IV 拼接后传入 openssl 命令验证一下(openssl 命令行使用说明详见 OpenSSL Wiki 页面):
$ printf admin | openssl enc -aes-256-cbc -K d8d51948eaae7484e1155ed054f7699b7700c43578bb996220b9ed7ff9766d16 -iv 463ea9701fb5aa7a0fa659c162ef0af6 | hexdump -C
00000000 e2 be 20 f0 ea 2f 68 34 62 8a 28 d6 94 40 b9 01 |.. ../h4b.(..@..|
Bingo!对「admin」使用 AES-256-CBC 加密得到的值正是对抓包看到的 4r4g8OovaDRiiijWlEC5AQ== 进行 base64 解码之后的值: e2be20f0ea2f6834628a28d69440b901。
把整个加密过程串起来:
$ printf admin | openssl enc -aes-256-cbc -K d8d51948eaae7484e1155ed054f7699b7700c43578bb996220b9ed7ff9766d16 -iv 463ea9701fb5aa7a0fa659c162ef0af6 | base64
4r4g8OovaDRiiijWlEC5AQ==
要知道,AES 是对称加密算法,也就是说,知道了 Key 和 IV,是可以将密文解密成明文的:
$ printf 4r4g8OovaDRiiijWlEC5AQ== | base64 -d | openssl enc -d -aes-256-cbc -K d8d51948eaae7484e1155ed054f7699b7700c43578bb996220b9ed7ff9766d16 -iv 463ea9701fb5aa7a0fa659c162ef0af6
admin
最后,把加密和解密的过程写成一个简单的脚本,命名为 ldap-password.sh:
<blockquote>
!/bin/sh
KEY=d8d51948eaae7484e1155ed054f7699b7700c43578bb996220b9ed7ff9766d16
IV=463ea9701fb5aa7a0fa659c162ef0af6
help() {
echo "Encryption: $0 enc [PASSWORD_IN_PLAIN_TEXT]"
echo "Decryption: $0 dec [PASSWORD_IN_CIPHER]"
}
if [ $# -ne "2" ]
then
help
elif [ "$1" == "enc" ]
then
printf $2 | openssl enc -aes-256-cbc -K $KEY -iv $IV | base64
elif [ "$1" == "dec" ]
then
printf $2 | base64 -d | openssl enc -d -aes-256-cbc -K $KEY -iv $IV
printf "\n"
else
help
fi
</blockquote>
这样加解密就方便多了:
$ ./ldap-password.sh enc admin
4r4g8OovaDRiiijWlEC5AQ==
$ ./ldap-password.sh dec 4r4g8OovaDRiiijWlEC5AQ==
admin
小结
-
登录时与服务器的传输是使用 HTTP 明文传输的。
-
密码字段是加密的,先对密码的明文使用 AES-256-CBC 加密,再使用 base64 编码生成密文。
-
使用 AES 加密算法时的初始化向量(IV)和密钥(Key)是硬编码的值,安全性大大折扣。
以上。
作者:yestyle
链接:https://www.jianshu.com/p/a39d07737468
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
- 我的微信
- 这是我的微信扫一扫
- 我的微信公众号
- 我的微信公众号扫一扫