• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.security.cts;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.content.pm.ResolveInfo;
24 import android.net.Uri;
25 import android.test.AndroidTestCase;
26 import android.webkit.cts.CtsTestServer;
27 
28 import java.io.FileOutputStream;
29 import java.io.OutputStreamWriter;
30 import java.io.Writer;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.concurrent.atomic.AtomicInteger;
34 
35 import org.apache.http.HttpEntity;
36 /**
37  * Test file for browser security issues.
38  */
39 public class BrowserTest extends AndroidTestCase {
40     private CtsTestServer mWebServer;
41 
42     @Override
setUp()43     protected void setUp() throws Exception {
44         super.setUp();
45         mWebServer = new CtsTestServer(mContext);
46     }
47 
48     @Override
tearDown()49     protected void tearDown() throws Exception {
50         mWebServer.shutdown();
51         super.tearDown();
52     }
53 
54     /**
55      * Verify that no state is preserved across multiple intents sent
56      * to the browser when we reuse a browser tab. If such data is preserved,
57      * then browser is vulnerable to a data stealing attack.
58      *
59      * In this test, we send two intents to the Android browser. The first
60      * intent sets document.b2 to 1.  The second intent attempts to read
61      * document.b2.  If the read is successful, then state was preserved
62      * across the two intents.
63      *
64      * If state is preserved across browser tabs, we ask
65      * the browser to send an HTTP request to our local server.
66      *
67      * See http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-2357 for
68      * vulnerability information for this test case.
69      *
70      * See commits  096bae248453abe83cbb2e5a2c744bd62cdb620b and
71      * afa4ab1e4c1d645e34bd408ce04cadfd2e5dae1e for patches for above vulnerability.
72      */
testTabReuse()73     public void testTabReuse() throws InterruptedException {
74         List<Intent> intents = getAllJavascriptIntents();
75         for (Intent i : intents) {
76             mContext.startActivity(i);
77             mContext.startActivity(i);
78 
79             /*
80              * Wait 5 seconds for the browser to contact the server, but
81              * fail fast if we detect the bug
82              */
83             for (int j = 0; j < 5; j++) {
84                 assertEquals("javascript handler preserves state across "
85                         + "multiple intents. Vulnerable to CVE-2011-2357?",
86                         0, mWebServer.getRequestCount());
87                 Thread.sleep(1000);
88             }
89         }
90     }
91 
92     /**
93      * Verify that no state is preserved across multiple intents sent
94      * to the browser when we run out of usable browser tabs.  If such data is
95      * preserved, then browser is vulnerable to a data stealing attack.
96      *
97      * In this test, we send 20 intents to the Android browser.  Each
98      * intent sets the variable "document.b1" equal to 1.  If we are able
99      * read document.b1 in subsequent invocations of the intent, then
100      * we know state was preserved.  In that case, we send a message
101      * to the local server, recording this fact.
102      *
103      * Our test fails if the local server ever receives an HTTP request.
104      *
105      * See http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-2357 for
106      * vulnerability information this test case.
107      *
108      * See commits  096bae248453abe83cbb2e5a2c744bd62cdb620b and
109      * afa4ab1e4c1d645e34bd408ce04cadfd2e5dae1e for patches for above vulnerability.
110      */
testTabExhaustion()111     public void testTabExhaustion() throws InterruptedException {
112         List<Intent> intents = getAllJavascriptIntents();
113         for (Intent i : intents) {
114             i.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
115 
116             /*
117              * Send 20 intents.  20 is greater than the maximum number
118              * of tabs allowed by the Android browser.
119              */
120             for (int j = 0; j < 20; j++) {
121                 mContext.startActivity(i);
122             }
123 
124             /*
125              * Wait 5 seconds for the browser to contact the server, but
126              * fail fast if we detect the bug
127              */
128             for (int j = 0; j < 5; j++) {
129                 assertEquals("javascript handler preserves state across "
130                         + "multiple intents. Vulnerable to CVE-2011-2357?",
131                         0, mWebServer.getRequestCount());
132                 Thread.sleep(1000);
133             }
134         }
135     }
136 
137     /**
138      * See Bug 6212665 for detailed information about this issue.
139      */
testBrowserPrivateDataAccess()140     public void testBrowserPrivateDataAccess() throws Throwable {
141 
142         // Create a list of all intents for http display. This includes all browsers.
143         List<Intent> intents = createAllIntents(Uri.parse("http://www.google.com"));
144         String action = "\"" + mWebServer.getBaseUri() + "/\"";
145         // test each browser
146         for (Intent intent : intents) {
147             // reset state
148             mWebServer.resetRequestState();
149             // define target file, which is supposedly protected from this app
150             String targetFile = "file://" + getTargetFilePath();
151             String html =
152                 "<html><body>\n" +
153                 "  <form name=\"myform\" action=" + action + " method=\"post\">\n" +
154                 "  <input type='text' name='val'/>\n" +
155                 "  <a href=\"javascript :submitform()\">Search</a></form>\n" +
156                 "<script>\n" +
157                 "  var client = new XMLHttpRequest();\n" +
158                 "  client.open('GET', '" + targetFile + "');\n" +
159                 "  client.onreadystatechange = function() {\n" +
160                 "  if(client.readyState == 4) {\n" +
161                 "    myform.val.value = client.responseText;\n" +
162                 "    document.myform.submit(); \n" +
163                 "  }}\n" +
164                 "  client.send();\n" +
165                 "</script></body></html>\n";
166             String filename = "jsfileaccess.html";
167             // create a local HTML to access protected file
168             FileOutputStream out = mContext.openFileOutput(filename,
169                                                            mContext.MODE_WORLD_READABLE);
170             Writer writer = new OutputStreamWriter(out, "UTF-8");
171             writer.write(html);
172             writer.flush();
173             writer.close();
174 
175             String filepath = mContext.getFileStreamPath(filename).getAbsolutePath();
176             Uri uri = Uri.parse("file://" + filepath);
177             // do a file request
178             intent.setData(uri);
179             mContext.startActivity(intent);
180             /*
181              * Wait 5 seconds for the browser to contact the server, but
182              * fail fast if we detect the bug
183              */
184             for (int j = 0; j < 5; j++) {
185                 // it seems that even when cross-origin policy prevents a file
186                 // access, browser is still doing a POST sometimes, but it just
187                 // sends the query part and no private data. Make sure this does not
188                 // cause a false alarm.
189                 if (mWebServer.getRequestEntities().size() > 0) {
190                     int len = 0;
191                     for (HttpEntity entity : mWebServer.getRequestEntities()) {
192                         len += entity.getContentLength();
193                     }
194                     final int queryLen = "val=".length();
195                     assertTrue("Failed preventing access to private data", len <= queryLen);
196                 }
197                 Thread.sleep(1000);
198             }
199         }
200     }
201 
getTargetFilePath()202     private String getTargetFilePath() throws Exception {
203         FileOutputStream out = mContext.openFileOutput("target.txt",
204                                                        mContext.MODE_WORLD_READABLE);
205         Writer writer = new OutputStreamWriter(out, "UTF-8");
206         writer.write("testing");
207         writer.flush();
208         writer.close();
209         return mContext.getFileStreamPath("target.txt").getAbsolutePath();
210     }
211 
212     /**
213      * This method returns a List of explicit Intents for all programs
214      * which handle javascript URIs.
215      */
getAllJavascriptIntents()216     private List<Intent> getAllJavascriptIntents() {
217         String localServerUri = mWebServer.getBaseUri();
218         String varName = "document.b" + System.currentTimeMillis();
219 
220         /*
221          * Build a javascript URL containing the following (without spaces and newlines)
222          * <code>
223          *    if (document.b12345 == 1) {
224          *       document.location = "http://localhost:1234/";
225          *    }
226          *    document.b12345 = 1;
227          * </code>
228          */
229         String javascript = "javascript:if(" + varName + "==1){"
230                 + "document.location=\"" + localServerUri + "\""
231                 + "};"
232                 + varName + "=1";
233 
234         return createAllIntents(Uri.parse(javascript));
235     }
236 
237     /**
238      * Create intents for all activities that can display the given URI.
239      */
createAllIntents(Uri uri)240     private List<Intent> createAllIntents(Uri uri) {
241 
242         Intent implicit = new Intent(Intent.ACTION_VIEW);
243         implicit.setData(uri);
244 
245         /* convert our implicit Intent into multiple explicit Intents */
246         List<Intent> retval = new ArrayList<Intent>();
247         PackageManager pm = mContext.getPackageManager();
248         List<ResolveInfo> list = pm.queryIntentActivities(implicit, PackageManager.GET_META_DATA);
249         for (ResolveInfo i : list) {
250             Intent explicit = new Intent(Intent.ACTION_VIEW);
251             explicit.setClassName(i.activityInfo.packageName, i.activityInfo.name);
252             explicit.setData(uri);
253             explicit.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
254             retval.add(explicit);
255         }
256 
257         return retval;
258     }
259 }
260