原文来自:https://bugs.chromium.org/p/chromium/issues/detail?id=788936
翻译:聂心明
来自file:///的资源没有定义一个Content-Type,一个恶意页面将加载任何本地资源作为css,并且它独立于MIME类型,浏览器会将其进行解析。他允许通过css注入的方式来跨域泄露本地文件数据。这个是这个漏洞 https://bugs.chromium.org/p/chromium/issues/detail?id=419383 的小变体。
chrome浏览器似乎用文件拓展名作为MIME类型,当怀疑解析了一个错误的资源的时候,就会弹出一个警告:“资源被解析成样式表,但是被传输的MIME类型是text/html”,这个警告暗示避免使用file:/// 去探测主机文件。
攻击需要一个受害者打开一个本地恶意的HTML页面。我不在这里讨论怎么做到这点,但是有很多方法可以欺骗用户(强制下载,从本地PDF中跳转,邮件附件……),未来,我猜会在安卓或者其他的特殊电子产品中会更加有用。
在这个文章中我只展示三个POC,完成攻击只需要两个条件
<script>
document.cookie = "foo{}*{--:bar=1337"; // 几分钟之后写入sqlite数据库
</script>
<link rel="stylehseet" href="../Library/Application Support/Chrome/Default/Cookies">
<script>
var leak = getComputedStyle(document.body).getPropertyValue('--');
alert(leak);
</script>
PoC 1 - 跨域重定向的泄露
文件: redir.html
描述:'~/Library/Application Support/Google/Chrome/Default/Current Session'这个文件里面包含当前请求的信息。有两个比较有趣的事情是,一部分信息被UTF-16编码(用了这种编码可以减少解析中断的提醒),并且框架中请求被很完美的收集到父类请求中。因此,我们能用框架来跨域并且能读到重定向的结果。我们仅仅需要确定css解析器是否通过提前关闭所有的块(blocks)[1]来处于适当的状态,我们只要简单添加“}])”字符就可以判断。
PoC 2 - 本地存储SQLite泄露(localstorage.html):
文件: localstorage.html
描述:在这个案例中,目标数据库存储文件存储在‘~/Library/Application Support/Google/Chrome/Default/Local Storage/leveldb/’。因为数据是被直接存储的并且我们能完全控制二进制数据,我们很容易把我们的payload编码成UTF-16,并且能被css解析器解析。而且,文件名会采用数字自增的方式命名,我们就可以爆破文件名,直到读取到我们注入的内容。
这就会泄露其他网站和插件的敏感信息。
/*
creation_utc INTEGER NOT NULL UNIQUE PRIMARY KEY,
host_key TEXT NOT NULL,
name TEXT NOT NULL,
value TEXT NOT NULL,
path TEXT NOT NULL,
expires_utc INTEGER NOT NULL,
secure INTEGER NOT NULL,
httponly INTEGER NOT NULL,
last_access_utc INTEGER NOT NULL,
has_expires INTEGER NOT NULL DEFAULT 1,
persistent INTEGER NOT NULL DEFAULT 1,
priority INTEGER NOT NULL DEFAULT 1,
encrypted_value BLOB DEFAULT '',
firstpartyonly INTEGER NOT NULL DEFAULT 0
*/
不幸的是,多数情况下,特殊字符会截断css的解析,并且似乎不能泄露整改cookie……,我试图用UTF-16来解决这个问题,但是,令我失望的是,cookie不支持空字符
比较明显的解决方法是,设置cookie为一个有效的值(可打印的ascii字符,分号,等号,引号),当cookie被编码之后,会产生我们期望得到的payload。让我们用神奇的POC吧 !!
cookie是被128-aes加密的,加密模式采用CBC,aes的IV是固定的为,IV = 0x20202020202020202020202020202020。在Linux中,key是被硬编码的2并且,对于key的推导,它使用单次迭代的PBKDF2加密方式,并且salt也是固定的("saltysalt")。在OSX中,key只会被保存在Keychain中。所以我们就把精力放在Linux中。
事实证明,我们的payload"{}*{--:("其实只有8个字节的长度。这就意味着如果我们用UTF-16编码的话,就会产生16个字节的长度(8 + 8 NULL bytes)[3]。
下图(payload,key和iv都是已知的并且是固定值)
我们要找到一个有效的块B,它解码之后满足:
幸亏的是,B0和B2是独立的。在一两分钟之后(在Skylake的i5的电脑上经过至少十亿次aes计算),我们得到一个有效的cookie值,这个cookie被编码之后可以被存储到db中,这个cookie也包含UTF-16格式的payload。
document.cookie="whatever=i+GW*e@afGR]sYo{Wa>7[[[[[[[[[[[[xBLGWAJ|VCX<T*P;"
(你可以通过开发工具为这次实例设置cookie,然后加载本地HTML文件40秒之后)
通过这个cookie,我们已经可以可靠的泄露一部分cookie的数据库,用UTF-16解码,解析被加密的值(‘V10’做前缀),然后使用web的加密API加密它们。在文章后面附上POC文件。
注意,我们现在仅仅使用一个payload设置单个cookie。这就意味着,如果UTF-16 payload解析失败,或者css解析被截断,那么测试就会失败。改善的方法是设置一些不同cookie值(--a, --b, --c),这样可以提高成功率。在后面我依然会附上成功的截图。
为了避免读取到敏感信息,在配置文件名前加一串随机的字符串(火狐中有类似的方式),这将增加那些攻击者难度。
并且,火狐的css字符串解析应该更加严格,如果发现一个NULL字节,就停止解析,这样可以减少泄露。
似乎最明智(最简单)的方式是在每一个Linux系统中使用独一无二的key。我猜想,本地的攻击者也会有寻找key的动机,但是攻击者只有部分读取权限,这还是能保护一些人。
这可能不是一个很严重问题,但是我觉得很有趣,希望你能喜欢。
[1] https://www.w3.org/TR/css-syntax-3/#%7B%7D-block-diagram
[2] https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_posix.cc?l=44
[3] 它可以从周围块中占有更多字节,因为我们我们不需要用16字节来寻找有效的块
附件在原文的最底部