summaryrefslogtreecommitdiffstats
path: root/chrome/test
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/test')
-rw-r--r--chrome/test/data/dom_checker/README.chromium7
-rw-r--r--chrome/test/data/dom_checker/dom_blank_page.html2
-rw-r--r--chrome/test/data/dom_checker/dom_checker.html1237
-rw-r--r--chrome/test/data/dom_checker/dom_config.js71
-rw-r--r--chrome/test/data/dom_checker/dom_target_page.html154
5 files changed, 1471 insertions, 0 deletions
diff --git a/chrome/test/data/dom_checker/README.chromium b/chrome/test/data/dom_checker/README.chromium
new file mode 100644
index 0000000..453dff91
--- /dev/null
+++ b/chrome/test/data/dom_checker/README.chromium
@@ -0,0 +1,7 @@
+This directory contains DOM checker, a tool to help automatically validate
+domain security policy enforcement. This tool will check cross-domain DOM
+accesses, JavaScript cookies, XMLHttpRequest calls, and more.
+
+DOM checker was written by Michal Zalewski and Filipe Almeida of Google.
+
+Version: 1.01
diff --git a/chrome/test/data/dom_checker/dom_blank_page.html b/chrome/test/data/dom_checker/dom_blank_page.html
new file mode 100644
index 0000000..df8be06
--- /dev/null
+++ b/chrome/test/data/dom_checker/dom_blank_page.html
@@ -0,0 +1,2 @@
+<html>Not so blank after all!
+<script>var private_var = 1;</script> \ No newline at end of file
diff --git a/chrome/test/data/dom_checker/dom_checker.html b/chrome/test/data/dom_checker/dom_checker.html
new file mode 100644
index 0000000..661d60e
--- /dev/null
+++ b/chrome/test/data/dom_checker/dom_checker.html
@@ -0,0 +1,1237 @@
+<html>
+<!--
+
+ DOM checker - browser domain context separation validator
+ ----------------------------------------------------------
+
+ Authors: Michal Zalewski <lcamtuf@google.com>
+ Filipe Almeida <filipe@google.com>
+
+ Copyright 2008 by Google Inc. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+
+<head>
+<title>DOM checker - browser domain context separation validator</title>
+
+<script src="dom_config.js"></script>
+
+<script>
+
+var option_long = false; // Run more timing tests?
+var option_badonly = false; // Report failed tests only?
+var running_local = false; // Running from file:///?
+
+var bad = 0; // Number of failed tests
+var now_running = 0; // Run-level nesting counter
+var target_page; // Target page location
+var same_blank; // Same-domain blank page location
+var blank_page; // Blank page location
+
+var blank_win; // about:blank window handle
+
+var init_timer; // Initialization timer
+var check_timer; // Check execution timer
+var flip_timer; // Page transition check timer
+var write_timer; // IPC write test timer
+
+var flip_count = 0; // Page transition counter
+
+var disable_ipc = false; // IPC abort flag
+var write_state; // IPC write state
+var cur_write; // IPC write pointer
+
+var ef_loaded = false; // Local file load test flag
+
+var test_list = []; // Test schedule
+var test_size = 0;
+
+var test_count = 1; // Test counter
+
+var read_list = []; // Test data sets
+var read_hash = {};
+var write_list = [];
+var write_hash = [];
+
+var last_ipc; // Last IPC command
+var ipc_wait_count; // IPC wait cycle count
+var ipc_count = 0; // IPC total command count
+var ipc_state = 2; // Current IPC state
+var fail_cycles = 0; // IPC failure count
+
+var output; // Output container.
+
+/* Send a reset command to IPC peer. */
+function ipc_reset() {
+
+ last_ipc = 'RESET';
+ if (disable_ipc) return;
+
+ ipc_wait_count = 0;
+ ipc_count++;
+ document.getElementById('ipc_read').src = blank_page + '#RESET';
+
+}
+
+
+/* Send evaluation request to IPC peer. */
+function ipc_eval(expr) {
+
+ last_ipc = 'EVAL(' + expr + ')';
+ if (disable_ipc) return;
+
+ ipc_wait_count = 0;
+ ipc_count++;
+ document.getElementById('ipc_read').src = blank_page + '#' + escape(expr);
+
+}
+
+
+/* Test for IPC state change, handle errors and timeouts. */
+function ipc_changed() {
+
+ if (disable_ipc) {
+ if (last_ipc == 'RESET') ipc_state = '2'; else ipc_state = '0';
+ return true;
+ }
+
+ try {
+
+ ipc_value = frames[0].frames['ipc_write'].location.hash.substr(1);
+
+ } catch (e) {
+
+ fail_cycles++;
+ if (fail_cycles == 500) {
+
+ /* So, Opera is a bit naughty and won't let us do that. Oh well. */
+
+ alert('Unable to get ipc_write frame hash in response to ' + last_ipc +
+ ' (' + ipc_count + ') in state ' + ipc_state +
+ '\nWARNING: Disabling side channel IPC, results may be less accurate!');
+
+ disable_ipc = true;
+
+ }
+ return false;
+
+ }
+
+ fail_cycles = 0;
+
+ if (ipc_value == ipc_state) {
+ ipc_wait_count++;
+
+ if (ipc_wait_count == 500)
+ alert('Waited more than 500 cycles on IPC command ' + last_ipc + ' (' +
+ ipc_count + ') in state ' + ipc_state);
+
+ return false;
+ }
+
+ ipc_state = ipc_value;
+ return true;
+
+}
+
+
+/* Point frames to preconfigured locations, initialize other stuff. */
+function init_frames() {
+
+ try {
+ if (main_host == undefined) throw 1;
+ } catch (e) {
+ alert('Config file dom_checker.js could not be loaded. Please check your configuration.');
+ return;
+ }
+
+ if (location.protocol == 'http:' && main_host != location.hostname) {
+ alert('Config file dom_checker.js specifies main_host that does not match current hostname.\n' +
+ 'Please check your configuration.');
+ return;
+ }
+
+ if (location.href.indexOf('/' + main_host + '/') == -1)
+ running_local = true;
+
+ target_page = 'http://' + alt_host + '/' + alt_dir + '/dom_target_page.html';
+ document.getElementById('f').src = target_page;
+
+ same_blank = 'http://' + main_host + '/' + main_dir + '/dom_blank_page.html';
+ blank_page = 'http://' + alt_host + '/' + alt_dir + '/dom_blank_page.html';
+ document.getElementById('ipc_read').src = blank_page;
+
+ init_timer = setInterval('check_frame()',1000);
+
+}
+
+
+/* Wait for 'f' location to be updated; this is actually kind of dodgy,
+ but so is onload= behavior when frame location is modified by scripts. */
+function check_frame() {
+
+ try {
+
+ var x = frames['f'].location.hostname;
+ if (x != main_host) throw 1;
+
+ } catch (e) {
+
+ clearInterval(init_timer);
+ init_button();
+
+ }
+
+}
+
+
+/* Make the page runnable. */
+function init_button() {
+ output = document.getElementById('results');
+ document.getElementById('start').disabled = false;
+ document.getElementById('start').value="Click here to begin tests";
+}
+
+
+/* Shorthand notation... */
+function E(x) { return eval(x); }
+
+
+/* Open a new javascript: window, see if it gets to read window properties to
+ other sites' about:blank windows, and phone back by pointing the window back
+ to our domain. */
+function open_blankwin() {
+
+ /* XXX: Is opener.* the best route to follow? Maybe open('',name) instead? */
+
+ try {
+ blank_win = open('javascript:void(location = "' + same_blank +
+ '?" + opener.frames[0].frames[0].location)','blank_win',
+ 'height=100,width=100,location=no,menubar=no,resizable=no,scrollbars=no,status=no,toolbar=no');
+ } catch (e) {
+ blank_win = open('javascript:void(location = "' + same_blank +
+ '?" + opener.frames[0].frames[0].location)','blank_win',
+ 'height=100,width=100,location=no,menubar=no,resizable=no,scrollbars=no,status=no,toolbar=no');
+ }
+
+}
+
+
+/* See if we get to access that window's properties, and whether it succeeded at
+ retrieving the data. */
+function blankwin_verify() {
+
+ try {
+ var x = blank_win.location.search;
+ if (x.indexOf('about:blank') == -1) throw 1;
+ BAD("about:blank crosstalk");
+ } catch (e) {
+ GOOD("about:blank crosstalk");
+ }
+
+ blank_win.close();
+ now_running--;
+
+}
+
+
+/* Prepare blank window checks... */
+function blankwin_checks() {
+ now_running++;
+ setTimeout('blankwin_verify()',2000);
+}
+
+
+/* Main test entry point. Set up all tests, get the ball rolling. */
+function do_tests() {
+
+ try {
+ if (frames['control_frame'].location.pathname.indexOf('/dom_blank_page.html') == -1) throw 1;
+ } catch (e) {
+ alert('Unable to load control frame - perhaps dom_blank_page.html not present?');
+ return;
+ }
+
+ document.getElementById('start').disabled = true;
+ document.getElementById('start').value = "Tests in progress...";
+
+ if (document.getElementById('option_badonly').checked)
+ option_badonly = true;
+
+ if (document.getElementById('option_long').checked)
+ option_long = true;
+
+ /* We need to call this first, as the ability to open new windows expires pretty
+ quickly after UI events in some browsers */
+
+ open_blankwin();
+ schedule_test('blankwin_checks()');
+
+ // First, try some non-destructive ways of stacking odds in our favor...
+
+ schedule_test('opening_tricks()');
+
+ // Try to look up frames by name...
+
+ schedule_test('name_lookup_test()');
+
+ schedule_test('variable_injection_check()');
+
+ // Then, try non-intrusive checks on the third-party domain frame
+
+ schedule_test('basic_checks("frames[0]")');
+
+ // Proceed with checks against nested about:blank frame
+
+ schedule_test('basic_checks("frames[0].frames[0]")');
+
+ // Try to bypass IFRAME access checking by going through document.*
+
+ schedule_test('dom_bypass_checks()');
+
+ // Try to perform no-op location updates for that frame (this should be non-disruptive,
+ // and is required because we otherwise blacklist location.* writes).
+
+ schedule_test('blank_location_checks_access()');
+ schedule_test('blank_location_checks_call()');
+
+ // Now that we're done with r/w checks, try destructive overwrites of the nested
+ // about:blank frame we put in the target window:
+
+ schedule_test('docwrite_checks("frames[0].frames[0]")');
+
+ // Let's try the same against the primary target frame itself:
+
+ schedule_test('docwrite_checks("frames[0]")');
+
+ // Try javascript: context inheritance tricks to see if we get to run javascript:
+ // in third party domains.
+
+ schedule_test('context_checks()');
+
+ // Clown around with our own settings for a while to spot more general issues.
+
+ schedule_test('closing_tricks()');
+
+ // Try transition attacks (this is slow and most certainly disruptive).
+
+ schedule_test('context_switch_checks()');
+
+ // Can setTimeout be smuggled across page transitions?
+
+ schedule_test('timeout_checks()');
+
+ // Can IFRAMEs access file:// URLs?
+
+ if (!running_local)
+ schedule_test('file_frame_checks()');
+
+ // That's all, folks...
+ run_tests();
+
+}
+
+
+// Queue a test for running.
+function schedule_test(cmd) {
+ test_list.push(cmd);
+}
+
+
+// Begin test execution.
+function run_tests() {
+ test_size = test_list.length;
+ next_test();
+}
+
+
+// Try to see if we get to look up frames by name across sites.
+function name_lookup_test() {
+ var x;
+
+ try {
+ x = open('','nf');
+ if (x == null || x == undefined) throw 1;
+ BAD("open() frame name lookup");
+ } catch (e) {
+ GOOD("open() frame name lookup");
+ }
+
+}
+
+
+// Fetch test from list, run, schedule next.
+function next_test() {
+
+ /* If previous timer-based test is still running, yield. */
+
+ if (now_running > 0) {
+ setTimeout(next_test, 100);
+ return;
+ }
+
+ var cmd = test_list.shift();
+ var e = document.getElementById('status');
+ test_count++;
+
+ if (cmd) {
+ e.innerHTML = "Running test " + (test_count-1) + "/" + test_size +
+ " <font color=gray>(" + cmd + ")</font>";
+ eval(cmd);
+ setTimeout(next_test, 100); // yield
+ } else {
+ e.innerHTML = "";
+ update_finalize();
+ }
+}
+
+
+function update_finalize() {
+
+ document.getElementById('start').value = "Testing complete!";
+
+ if (bad)
+ document.getElementById('status').innerHTML =
+ "<font color=red>Failed checks: " + bad + "</font>";
+ else
+ document.getElementById('status').innerHTML =
+ "<font color=green>All checks passed (whoa!)</font>";
+
+}
+
+
+/* Append a message to test log */
+function log(message) {
+ var e = document.createElement('li');
+ e.innerHTML = message;
+ output.appendChild(e);
+}
+
+
+/* Log passed test event. */
+function GOOD(x) {
+ if (option_badonly) return;
+ x = x.replace('frames[0].frames[0]','(blank)').replace('frames[0]','(third-party)');
+ log('<font color=teal>Check passed : ' + x + ' access denied.</font>');
+}
+
+
+/* Log test failure. */
+function BAD(x) {
+ bad++;
+ x = x.replace('frames[0].frames[0]','(blank)').replace('frames[0]','(third-party)');
+ log('<font color=red>CHECK FAILED : ' + x + ' is possible!</font>');
+}
+
+
+/* For debugging and notification purposes... */
+function DEBUG(x) {
+ log('<font color=black>Debug output : ' + x + '</font>');
+}
+
+
+/* Let's see if the target page succeeded at planting a variable in our
+ context (this test is actually carried out on load by target page). */
+function variable_injection_check() {
+ var message = "(third-party).__defineGetter__('injected_var', ...)";
+
+ try {
+
+ if (injected_var == 1) { }
+ BAD(message);
+
+ } catch(e) {
+
+ GOOD(message);
+
+ }
+
+}
+
+
+/* See if we may just set document.domain to whatever we want. */
+function opening_tricks() {
+ try {
+ document.domain = alt_host;
+ if (document.domain != alt_host) throw 1;
+ BAD("arbitrary document.domain");
+ } catch (e) { GOOD("arbitrary document.domain"); }
+}
+
+
+/* Try some tests that do not involve cross-frame access, but are nevertheless
+ pretty common. */
+function closing_tricks() {
+ var x;
+
+ if (!running_local) {
+
+ /* NOTE: This requires Apache or IIS server and a generic 'Bad request'
+ response page. If you host the script at a place where these conditions
+ are not met, tough. */
+
+ x = new XMLHttpRequest();
+ try {
+ x.open('GET /? HTTP/1.0\r\nBadHeader\r\nBar: ','http://' + main_host + '/',false);
+ x.send(null);
+ if (x.responseText.indexOf('Bad Request') == -1) throw 1;
+ BAD("XMLHttpRequest method splitting");
+ } catch (e) {
+ GOOD("XMLHttpRequest method splitting");
+ }
+
+ x = new XMLHttpRequest();
+ try {
+ x.open('GET','http://' + main_host + '/ HTTP/1.0\r\nBadHeader\r\nBar: ',false);
+ x.send(null);
+ if (x.responseText.indexOf('Bad Request') == -1) throw 1;
+ BAD("XMLHttpRequest path splitting");
+ } catch (e) {
+ GOOD("XMLHttpRequest path splitting");
+ }
+
+ x = new XMLHttpRequest();
+ try {
+ x.open('GET','http://' + main_host + '/',false);
+ try {
+ x.setRequestHeader('Whatever: hi mom\r\nBadHeader\r\nBar','baz');
+ } catch (e) {
+ x.setRequestHeader('Whatever','hi mom\r\nBadHeader');
+ }
+ x.send(null);
+ if (x.responseText.indexOf('Bad Request') == -1) throw 1;
+ BAD("XMLHttpRequest parameter splitting");
+ } catch (e) {
+ GOOD("XMLHttpRequest path splitting");
+ }
+
+ x = new XMLHttpRequest();
+ try {
+ x.open('GET','file:///c:/boot.ini',false);
+ x.send(null);
+ BAD("XMLHttpRequest() to local files (Windows)");
+ } catch (e) {
+ GOOD("XMLHttpRequest() to local files (Windows)");
+ }
+
+ x = new XMLHttpRequest();
+ try {
+ x.open('GET','file:////etc/hosts',false);
+ x.send(null);
+ BAD("XMLHttpRequest() to local files (unix)");
+ } catch (e) {
+ GOOD("XMLHttpRequest() to local files (unix)");
+ }
+
+ }
+
+ x = new XMLHttpRequest();
+ try {
+ x.open('GET','http://' + alt_host + '/',false);
+ x.send(null);
+ BAD("XMLHttpRequest() to remote pages");
+ } catch (e) {
+ GOOD("XMLHttpRequest() to remote pages");
+ }
+
+ /* It would be good to test for the ability to set file:/// SRC= URLs,
+ but there is no convenient way to read back the result, I think...
+ onerror= and onload= firing is handy, but very inconsistent across
+ browsers. */
+
+ document.cookie = 'dom_checker_cookie=bar_com; path=/; domain=com';
+ document.cookie = 'dom_checker_cookie=bar_dotcom; path=/; domain=.cx';
+ document.cookie = 'dom_checker_cookie=bar_dot; path=/; domain=.';
+ document.cookie = 'dom_checker_cookie=bar_dotcomdot; path=/; domain=.cx.';
+
+ // This will overwrite our cookie if domain= was silently dropped on previous
+ // attempts:
+
+ document.cookie = 'dom_checker_cookie=invalid; path=/';
+
+ if (document.cookie.indexOf('dom_checker_cookie=bar') != -1)
+ BAD("cross-domain document.cookie [value: " + document.cookie + "]");
+ else
+ GOOD("cross-domain document.cookie");
+
+ /* Oh, and any cookie setting is evil in file:/// */
+
+ if (running_local) {
+
+ document.cookie = 'dom_checker_cookie2=1';
+
+ if (document.cookie.indexOf('dom_checker_cookie2') != -1)
+ BAD("file:/// cookie setting");
+ else
+ GOOD("file:/// cookie setting");
+
+ }
+
+ /* Depending on the implementation, this might not be a tragic security
+ flaw, as a mutual consent to this is required in most browsers; but it
+ certainly encourages terrible coding practices and may make it easier
+ to go after certain targets. */
+
+ try {
+ document.domain = '.cx';
+ if (document.domain != '.cx') throw 1;
+ BAD("document.domain = '.cx'");
+ } catch (e) { GOOD("document.domain = '.cx'"); }
+
+ try {
+ document.domain = 'cx';
+ if (document.domain != 'cx') throw 1;
+ BAD("document.domain = 'cx'");
+ } catch (e) { GOOD("document.domain = 'cx'"); }
+
+ try {
+ document.domain = '.';
+ if (document.domain != '.') throw 1;
+ BAD("document.domain = '.'");
+ } catch (e) { GOOD("document.domain = '.'"); }
+
+ try {
+ document.domain = '';
+ if (document.domain != '') throw 1;
+ BAD("document.domain = ''");
+ } catch (e) { GOOD("document.domain = ''"); }
+
+}
+
+
+/* Perform basic cross-domain access checks against a specific target. */
+function basic_checks(where) {
+
+ reset_read_write();
+
+ /* Brute-force window.* if possible */
+
+ iterator_check(where);
+
+ /* Try to enumerate various list objects */
+
+ list_checks(where + ".frames");
+ list_checks(where + ".window"); /* alias to frames */
+ list_checks(where + ".images");
+ list_checks(where + ".styleSheets");
+ list_checks(where + ".applets");
+ list_checks(where + ".embeds");
+ list_checks(where + ".links");
+ list_checks(where + ".forms");
+ list_checks(where + ".anchors");
+
+ /* Try to call common methods */
+
+ call_checks(where + ".document");
+ call_checks(where + ".document.body");
+ call_checks(where + ".window");
+ call_checks(where + ".window.self");
+ call_checks(where + ".screen");
+ call_checks(where + ".navigator");
+ call_checks(where + ".location");
+ call_checks(where + ".document.location");
+ call_checks(where + ".history");
+
+ visibility_check(where + ".private_var", where + ".var_noexist");
+
+ /* Various object-specific calls that are of particular interest */
+
+ try_call(where + ".window.scrollBy(10,10)");
+ try_call(where + ".history.forward(0)");
+ try_call(where + ".document.createElement('I')");
+ try_call(where + ".document.body.appendChild(null)");
+ try_call(where + ".document.clear()");
+ try_call(where + ".stop()");
+
+ /* Some properties that should not be disclosed. */
+
+ try_read(where + ".document.location");
+ try_read(where + ".location");
+ try_read(where + ".location.href");
+ try_read(where + ".location.hash");
+ try_read(where + ".location.protocol");
+
+ /* Various object-specific peek & poke attempts to be executed
+ asynchronously. */
+
+ add_read_write(where + ".document.domain");
+ add_read_write(where + ".document.title");
+ add_read_write(where + ".document.referrer");
+ add_read_write(where + ".document.URI");
+ add_read_write(where + ".document.baseURI");
+ add_read_write(where + ".document.cookie");
+ add_read_write(where + ".window.name");
+ add_read_write(where + ".location.search");
+ add_read_write(where + ".location.host");
+ add_read_write(where + ".location.hash");
+ add_write(where + ".location.watch");
+ add_read_write(where + ".history.length");
+ add_read_write(where + ".document.style.length");
+ add_read_write(where + ".document.inputEncoding");
+ add_read_write(where + ".document.characterSet");
+ add_read_write(where + ".window.__iterator__");
+
+ /* Try to disrupt something! */
+
+ fill_read_write("frames['control_frame']", where + ".");
+ fill_read_write("frames['control_frame'].document", where + ".document.");
+
+ /* Or, how about variable setting? */
+
+ add_read_write(where + ".private_var");
+
+ /* Actually run tests. */
+ try_read_write_all();
+
+}
+
+
+/* This is an interesting way to bypass frame[] access checking. */
+function dom_bypass_checks() {
+
+ reset_read_write();
+
+ add_read_write("document.getElementById(\'f\').contentDocument.title");
+ add_read_write("document.getElementById(\'f\').contentWindow.status");
+
+ try_read_write_all();
+
+}
+
+
+/* Is it possible to overwrite third-party documents? */
+function docwrite_checks(where) {
+ try_call(where + ".document.open()");
+ try_call(where + ".document.write('hi mom')");
+ // To prevent clobbering the browser.
+ try { E(where + ".document.close()"); } catch (e) { }
+}
+
+
+/* Is it possible to move third-party frames? */
+function blank_location_checks_access() {
+
+ reset_read_write();
+
+ add_read_write('frames[0].frames[0].location.href','about:blank');
+ add_read_write('frames[0].frames[0].location','about:blank');
+ add_read_write('frames[0].frames[0].document.location','about:blank');
+
+ try_read_write_all();
+
+}
+
+
+/* Is it possible to move third-party frames, take two? */
+function blank_location_checks_call() {
+ try_call("frames[0].frames[0].location.assign('about:blank')");
+ try_call("frames[0].frames[0].location.replace('about:blank')");
+}
+
+
+/* Try to detect context inheritance issues on javascript: URL setting. Note
+ that it differs from earlier *blankwin checks in that it attempts to execute
+ code in a specific context, instead of trying to probe the privileges of
+ blank windows. */
+function context_checks() {
+ now_running++;
+ document.getElementById('f').src = blank_page;
+ setTimeout('context_checks_continue()',1000);
+
+}
+
+
+/* Continue content inheritance checks... */
+function context_checks_continue() {
+ /* We begin with 'f' pointing to a cross-domain (alt_host) site. */
+
+ try {
+ document.getElementById('f').src = 'javascript:void(location.href = "' + same_blank + '?" + location.host)';
+ setTimeout('context_checks_finalize()',1000);
+ } catch (e) {
+ now_running--;
+ GOOD("javascript: URI trickery");
+ }
+}
+
+
+/* So, did we succeed at our context inheritance trickery? */
+function context_checks_finalize() {
+ var x;
+ try {
+ x = frames['f'].location.search;
+ if (x.indexOf(main_host) != -1) throw 1;
+ if (x == '') throw 1;
+ BAD("javascript: URI trickery [value: " + x + "]");
+ } catch (e) { GOOD("javascript: URI trickery"); }
+
+ now_running--;
+}
+
+
+/* Do we get to test for presence of variables across domains? */
+function visibility_check(name, name_noexist) {
+ var exist;
+ var noexist;
+
+ try {
+ var exist = E("delete " + name);
+ } catch(e) { exist = "exception"; }
+
+ try {
+ var noexist = E("delete " + name_noexist);
+ } catch(e) { noexist = "exception"; }
+
+ if(exist == noexist)
+ GOOD("delete " + name + " probe");
+ else BAD("delete " + name + " probe");
+}
+
+
+/* Reset read/write test queue */
+function reset_read_write() {
+ read_list = [];
+ read_hash = {};
+ write_list = [];
+ write_hash = {};
+}
+
+
+/* Add read/write test item */
+function add_read_write(name) {
+ add_read(name);
+ add_write(name);
+}
+
+
+/* Add read test item (if not already scheduled) */
+function add_read(name) {
+ if(!read_hash[name]) {
+ read_list.push(name);
+ read_hash[name] = 1;
+ }
+}
+
+
+/* Add write test item (if not already scheduled) */
+function add_write(name) {
+ if (!write_hash[name]) {
+ write_list.push(name);
+ write_hash[name] = 1;
+ }
+}
+
+
+/* Try to iterate through window properties using a control frame,
+ populate lists. */
+function fill_read_write(control, base) {
+ for(name in eval(control)) {
+ if (!write_blacklist[name]) add_write(base + name);
+ if (!read_blacklist[name]) add_read(base + name);
+ }
+}
+
+
+/* Execute read/write tests. Write tests require IPC validation and
+ hence are executed asynchronously. */
+function try_read_write_all() {
+
+ for(i in read_list)
+ try_read(read_list[i]);
+
+ if (!write_list.length) return;
+
+ now_running++;
+ cur_write = 0;
+ write_state = 0;
+ write_timer = setInterval('do_next_write()',1);
+
+}
+
+
+/* Grab next write item. */
+function write_advance() {
+ /* Move to next, end test on EOL */
+ cur_write++;
+ write_state = 0;
+
+ if (cur_write == write_list.length) {
+ now_running--;
+ clearInterval(write_timer);
+ return;
+ }
+}
+
+
+/* Execute next write operation or IPC update. */
+function do_next_write() {
+
+ if (write_state == 0) {
+
+ /* STATE 0: Issue next command */
+
+ if (!try_write_silent(write_list[cur_write],'dom-foo')) {
+
+ /* Write failed immediately. Report failure,
+ take next item, move to RESET state. */
+
+ GOOD(write_list[cur_write] + " write (exception)");
+
+ write_advance();
+
+ } else {
+
+ /* Write seemingly succeeded. Is it possible to read the value back? */
+
+ if (try_read_silent(write_list[cur_write]).indexOf('dom-foo') != -1) {
+ BAD(write_list[cur_write] + " write (readback)");
+ write_advance();
+ } else {
+
+ /* In local mode, IPC may not be used, because our remote
+ frame will not be able to open file:// URL internally. */
+
+ if (running_local) {
+ GOOD(write_list[cur_write] + " write (maybe!)");
+ write_advance();
+ return;
+ }
+
+ /* Otherwise, we must request the target page to revalidate. */
+ ipc_eval("var tmp = " + write_list[cur_write].replace(/^frames\[0\]\./,'') + "; return (tmp.toString().indexOf('dom-foo') != -1)");
+ write_state = 1;
+
+ }
+
+ }
+
+ } else if (write_state == 1) {
+
+ /* STATE 1: Wait for command completion. */
+
+ if (!ipc_changed()) return; /* Yield until result is available. */
+
+ if (ipc_state == 0) {
+ GOOD(write_list[cur_write] + " write (via IPC)");
+ } else if (ipc_state == 1) {
+ BAD(write_list[cur_write] + " write (via IPC)");
+ } else {
+ alert('Bad IPC state ' + ipc_state + ' on eval request');
+ clearInterval(write_timer);
+ return;
+ }
+
+ ipc_reset();
+ write_state = 2;
+
+ } else if (write_state == 2) {
+
+ /* STATE 2: Wait for reset, proceed to next. */
+
+ if (!ipc_changed()) return; /* Yield until result is available. */
+
+ if (ipc_state != 2) {
+ alert('Bad IPC state ' + ipc_state + ' on reset request');
+ clearInterval(write_timer);
+ return;
+ }
+
+ write_advance();
+
+ }
+
+}
+
+
+/* Attempt write; returns 'true' if write *apparently* succeeded. */
+function try_write_silent(name,val) {
+ try { E(name + "= '" + val + "'"); return true; }
+ catch (e) { return false; }
+}
+
+
+/* Attempt read; returns false if name undefined, true otherwise */
+function try_read(name) {
+ var x;
+
+ try {
+ x = E(name);
+ if (x == undefined) return false;
+ // Opera has a magical 'object inaccessible' thingee we need to handle
+ // in a portable manner with an implicit typecast.
+ if (x == '[object inaccessible]') throw 1;
+ BAD(name + " read [value: " + x + "]");
+ } catch (e) { GOOD(name + " read"); }
+
+ return true;
+
+}
+
+
+/* Attempt read, but do not report. */
+function try_read_silent(name) {
+ var x;
+
+ try {
+ x = E(name);
+ if (x == undefined) throw 1;
+ if (x == '[object inaccessible]') throw 1;
+ return x.toString();
+ } catch (e) { return "DOM-checker-no-match"; }
+
+}
+
+
+/* Try invoking a function. */
+function try_call(name) {
+ try { E(name); BAD(name + " call"); }
+ catch (e) { GOOD(name + " call"); }
+}
+
+
+/* Try to fingerprint object lists across domains. */
+function list_checks(name) {
+ var x;
+
+ try {
+ x = E(name + "[0]");
+ if (x == undefined) return;
+ BAD(name + "<!-- NOP -->[0] probe [value: " + x + "]");
+ } catch (e) { GOOD(name + " probe"); }
+
+ try {
+ x = E(name + ".length");
+ if (x == undefined) return;
+ BAD(name + ".length read [value: " + x + "]");
+ } catch (e) { GOOD(name + ".length read"); }
+
+ iterator_check(name);
+
+ /* Will be carried out near the end of basic_checks(). */
+ add_read_write(name + "[0].name");
+
+}
+
+
+/* Call various methods. */
+function call_checks(name) {
+ var x;
+
+ try_call(name + ".hasAttribute('foo')");
+ try_call(name + ".getAttribute('foo')");
+ try_call(name + ".setAttribute('foo','bar')");
+ try_call(name + ".createEvent('MouseEvents')");
+ try_call(name + ".dispatchEvent(null)");
+ try_call(name + ".captureEvents(Event.CLICK)");
+ try_call(name + ".routeEvent(Event.CLICK)");
+ try_call(name + ".setTimeout('',1)");
+ try_call(name + ".clearTimeout(0)");
+ try_call(name + ".watch('foo',function foo(a,b,c){})");
+
+ /* These will be executed by the end of basic_checks() */
+ add_read_write(name + ".onload");
+ add_read_write(name + ".onerror");
+ add_read_write(name + ".onchange");
+ add_read_write(name + ".onkeydown");
+
+}
+
+
+/* Try to enumerate something across domains. */
+function iterator_check(name) {
+ try {
+ var list = [];
+ eval("for (e in " + name + ") { list[list.length] = e}");
+ if(list.length >= 1) {
+ BAD("for (e in " + name + ") iterator");
+ } else {
+ GOOD("for (e in " + name + ") iterator");
+ }
+ } catch(e) { GOOD("for (e in " + name + ") iterator listing"); }
+
+}
+
+
+/* Finalize page transition checks. */
+function flip_finalize() {
+ clearInterval(flip_timer);
+ clearInterval(check_timer);
+ now_running--;
+}
+
+
+/* Perform a location flip. */
+function loc_flip() {
+// if (Math.random() > .8) return;
+ if (Math.random() > .5) document.getElementById('f').src = blank_page;
+ else document.getElementById('f').src = 'about:blank';
+ flip_count--;
+ if (!flip_count) {
+ flip_finalize();
+ GOOD("on-transition cross-domain access");
+ }
+}
+
+
+/* Check for location flip success. */
+function flip_check() {
+ try {
+ var x = frames['f'].private_var;
+ if (x != 1) throw 1;
+ flip_finalize();
+ BAD("on-transition private_var access");
+ } catch (e) { }
+
+ try {
+ var x = frames['f'].location.hostname;
+ if (x != alt_host) throw 1;
+ flip_finalize();
+ BAD("on-transition location.hostname access");
+ } catch (e) { }
+
+}
+
+
+/* Prepare for page transition checks */
+function context_switch_checks() {
+ now_running++;
+
+ if (option_long) flip_count = 1000;
+ else flip_count = 100;
+
+ flip_timer = setInterval('loc_flip()',153);
+ check_timer = setInterval('flip_check()',0);
+}
+
+
+/* Prepare for timeout checks. */
+function timeout_checks() {
+ now_running++;
+ document.getElementById('f').src = same_blank;
+ setTimeout('timeout_load_wait()',1000);
+}
+
+
+/* Try to configure a timeout across domains. */
+function timeout_load_wait() {
+ try {
+ frames['f'].setTimeout('location.href = "' + same_blank +'?" + location.host',1000);
+ frames['f'].navigator.setTimeout('location.href = "' + same_blank +'?" + location.host',1000);
+ frames['f'].screen.setTimeout('location.href = "' + same_blank +'?" + location.host',1000);
+ } catch (e) { }
+ document.getElementById('f').src = blank_page;
+ setTimeout('timeout_result()',2000);
+}
+
+
+/* Validate test result. */
+function timeout_result() {
+ try {
+ var x = frames['f'].location.search;
+ if (x == alt_host) BAD("cross-domain setTimeout");
+ else GOOD("cross-domain setTimeout [value: " + x + "]")
+ } catch (e) {
+ GOOD("cross-domain setTimeout");
+ }
+ now_running--;
+}
+
+
+/* Test callback function. */
+function ef_load_ok() { ef_loaded = true; }
+
+
+/* Check the ability to load file:/// URLs in frames. */
+function file_frame_checks() {
+ now_running++;
+ ef_loaded = false;
+
+ try { document.getElementById('ef').src = 'file:///c:/'; } catch(e) { }
+ setTimeout('try { document.getElementById("ef").src = "file:///etc/hosts"} catch(e) { }',500);
+ setTimeout('file_frame_verify()',1000);
+
+}
+
+
+/* Test for file:/// load success. */
+function file_frame_verify() {
+ now_running--;
+ if (ef_loaded) BAD("file:/// frame");
+ else GOOD("file:/// frame");
+}
+
+</script>
+
+</head>
+<body onload="setTimeout('init_frames()',1000)">
+
+<font face="arial">
+<font size=+2><b>Browser DOM access checker 1.01</b></font><br>
+<font size=-1>
+Authors: Michal Zalewski &lt;<a href="mailto:lcamtuf@google.com">lcamtuf@google.com</a>&gt; and
+Filipe Almeida &lt;<a href="mailto:filipe@google.com">filipe@google.com</a>&gt;<br>
+Copyright 2008 by Google Inc., and licensed under the Apache License, Version 2.0.
+<p>
+<font color=gray>
+DOM access checker is a tool designed to automatically validate numerous aspects of domain
+security policy enforcement (cross-domain DOM access, Javascript cookies, XMLHttpRequest
+calls, event and transition handling) to detect common security attack or information
+disclosure vectors.
+<p>
+Please run this tool both over HTTP, and then from local disk (file:/// namespace).
+Ideally, results in both cases should be the same, and no failed tests should be reported.
+That said, although we worked with software vendors to resolve many of the most significant
+issues, all common browsers fail anywhere from 10 to 30 of less significant tests due to
+various design decisions (most of which bear some privacy considerations by making it
+to fingerprint simultaneously open pages).
+</font>
+
+<p>
+<input type=submit id=start disabled=yes value="Loading, please wait..." onclick="do_tests()">
+<span id=status style="padding: 0em 0em 0em 1em"></span>
+<p>
+<font size=+1>Test results (be prepared to wait a while):</font><br>
+<font face="lucida console, courier new">
+
+<!-- Log container -->
+<div id=results width=100% style="border-width: 1px; border-style: solid; border-color: teal; background-color: #FFFFE0; padding: 1em 0em 1em 1em">
+</div>
+</font>
+<p>
+
+<font size=-1 color=gray>
+<input id=option_long type=checkbox> Perform longer page transition checks<br>
+<input id=option_badonly type=checkbox> Report failed checks only
+</font>
+<p>
+
+<!-- Target frame pointing to dom_target_page.html -->
+<iframe height=1 width=1 id=f name=f style="border-width: 0px">
+</iframe>
+
+<!-- Control frame used to enumerate DOM objects -->
+<iframe height=1 width=1 id=control_frame name=control_frame src="dom_blank_page.html" style="border-width: 0px">
+</iframe>
+
+<!-- Test frame used for file:/// URLs -->
+<iframe height=1 width=1 id=ef name=ef onload="ef_load_ok()" style="border-width: 0px">
+</iframe>
+
+<!-- IPC frame for write validation -->
+<iframe id=ipc_read name=ipc_read src="dom_blank_page.html#NONE" height=1 width=1 style="border-width: 0px">
+</iframe>
+
+</body>
+</html>
diff --git a/chrome/test/data/dom_checker/dom_config.js b/chrome/test/data/dom_checker/dom_config.js
new file mode 100644
index 0000000..8785d82
--- /dev/null
+++ b/chrome/test/data/dom_checker/dom_config.js
@@ -0,0 +1,71 @@
+/*
+
+ DOM checker - configuration parameters
+ --------------------------------------
+
+ Please be sure to update these to reflect the realities of the place where
+ you host the program.
+
+ Authors: Michal Zalewski <lcamtuf@google.com>
+ Filipe Almeida <filipe@google.com>
+
+ Copyright 2008 by Google Inc. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+
+/* Host name where you intend to put the script: */
+var main_host = 'localhost';
+
+/* Subdirectory for DOM checker files: */
+var main_dir = 'dom_checker';
+
+/* An alternative way to call the same resource in a manner that
+ appears to the browser as completely unrelated to main_host
+ (try IP address): */
+var alt_host = '127.0.0.1';
+
+/* Subdirectory for DOM checker files: */
+var alt_dir = 'dom_checker';
+
+/* DOM properties or hierarchies we do not want to enumerate and
+ randomly write during primary checks because of their disruptive
+ nature. */
+
+var write_blacklist = {
+ 'location': 1
+};
+
+
+/* DOM properties or hierarchies we do not want to attempt to read,
+ and methods we do not want to call, because they either have no
+ security impact at all, or the ability to read/access does not
+ reliably imply any privileges. */
+
+var read_blacklist = {
+ 'top' : 2, // Calling frame
+ 'parent' : 3, // Calling frame
+ 'frames' : 4, // Lower level access not implied
+ 'document' : 5, // Lower level access not implied
+ 'self' : 6, // Lower level access not implied
+ 'history' : 7, // Lower level access not implied
+ 'close' : 8, // Access does not imply success
+ 'focus' : 9, // Access does not imply success
+ 'blur' : 10, // Access does not imply success
+ 'closed' : 11, // Not very revealing
+ 'opener' : 12, // Ditto.
+ 'window' : 13, // Ditto.
+ 'open' : 14 // Firefox oddity, but deemed harmless.
+};
diff --git a/chrome/test/data/dom_checker/dom_target_page.html b/chrome/test/data/dom_checker/dom_target_page.html
new file mode 100644
index 0000000..a5a62b4
--- /dev/null
+++ b/chrome/test/data/dom_checker/dom_target_page.html
@@ -0,0 +1,154 @@
+<html>
+<!--
+
+ DOM checker - test target page
+ ------------------------------
+
+ Authors: Michal Zalewski <lcamtuf@google.com>
+ Filipe Almeida <filipe@google.com>
+
+ Copyright 2008 by Google Inc. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+
+<script src="dom_config.js"></script>
+
+<script>
+
+var private_var = 1; // We'll try to set it across domains.
+var ipc_page; // IPC page location
+var queue_timer; // IPC queue handling timer
+var idle = true; // IPC handler state
+var prev_hval = 'NONE'; // Previous IPC command
+var idle_cycles = 0; // Number of cycles spend in idle
+
+/* Try to inject a variable to paren't namespace by defining a getter in
+ another domain. */
+
+try {
+ top.__defineGetter__('injected_var', function() {return 1;})
+} catch(e) {}
+
+
+/* Update IPC frame location as needed, set timers. */
+function page_init() {
+
+ ipc_page = 'http://' + main_host + '/' + main_dir + '/dom_blank_page.html';
+ document.getElementById('ipc_write').src = ipc_page + '#2';
+
+ // log('Local ipc_write initialized to ' + ipc_page);
+
+ queue_timer = setInterval('get_ipc_command()',250);
+
+}
+
+
+/* IPC subsystem logging (debugging purposes only) */
+function log(x) {
+ var e = document.createElement('li');
+ e.innerHTML = x;
+ document.getElementById('log').appendChild(e);
+}
+
+
+/* Wait for IPC state change, execute command, send results. */
+function get_ipc_command() {
+
+ var hval;
+
+ try { hval = top.frames['ipc_read'].location.hash; } catch (e) {
+ // log('IPC command read failed from external ipc_read.');
+ hval = '';
+ }
+
+ if (hval == prev_hval || hval == '' || hval == undefined || hval == 'NONE') {
+ if (!idle) {
+ idle_cycles++;
+
+ if (idle_cycles == 200) {
+ // log('Entered power saving mode.');
+ clearInterval(queue_timer);
+ queue_timer = setInterval('get_ipc_command()',250);
+ idle = true;
+ }
+
+ }
+
+ return;
+ }
+
+ /* Full speed! */
+ if (idle) {
+ // log('Entered full speed mode.');
+ clearInterval(queue_timer);
+ queue_timer = setInterval('get_ipc_command()',1);
+ idle = false;
+ idle_cycles = 0;
+ }
+
+ // log('Got IPC command ' + hval + ' (prev: ' + prev_hval + ')');
+
+ prev_hval = hval;
+
+ var res = 0;
+
+ hval = hval.substr(1);
+
+ if (hval == 'RESET') res = 2;
+ else try {
+ if (eval(unescape(hval))) res = 1;
+ } catch (e) {
+ // log('Evaluation exception! Final was: ' + unescape(hval));
+ }
+
+ document.getElementById('ipc_write').src = ipc_page + '#' + res;
+
+}
+
+</script>
+<title>DOM checker victim page</title>
+<body onload="page_init()">
+
+<!-- Some bogus page elements to make it possible to enumerate arrays. -->
+
+<style name=ns>MENU { margin: 1em }</style>
+
+<img src="#bad" name=ni>
+
+<form name=nf method=post action=foo>
+<input type=hidden name=foo value=bar>
+</form>
+
+<h1><a href="#bad" name=nl>Hi mom!</a></h1>
+
+<a name=ni2>
+
+<embed name=ne></embed>
+
+<object name=no></object>
+
+<applet name=na></applet>
+
+<!-- Log container -->
+<div id=log>
+</div>
+
+<!-- Nested subframe used for about:blank tests -->
+<iframe id=nf name=nf src="about:blank">
+</iframe>
+
+<!-- IPC frame -->
+<iframe id=ipc_write name=ipc_write></iframe>
+