diff options
author | patrick@chromium.org <patrick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-09 18:39:03 +0000 |
---|---|---|
committer | patrick@chromium.org <patrick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-09 18:39:03 +0000 |
commit | 30cdf1b498549d19a9b39388831d5ac1a5c93b98 (patch) | |
tree | 7f38b048b6b39bca4473039f5724f4e234a3dd9a /chrome/test | |
parent | 36737bcca80d83c72288c2b854f9caa1671593f9 (diff) | |
download | chromium_src-30cdf1b498549d19a9b39388831d5ac1a5c93b98.zip chromium_src-30cdf1b498549d19a9b39388831d5ac1a5c93b98.tar.gz chromium_src-30cdf1b498549d19a9b39388831d5ac1a5c93b98.tar.bz2 |
Add a copy of DOM checker for testing. DOM checker is a tool to help automate
domain security policy enforcement.
This copy of DOM checker was fetched from:
http://lcamtuf.coredump.cx/dom_checker/
Some values in dom_config.js have been changed. There have been no other
modifications.
In order to automate this test, there will need to be a few more small changes.
To more easily see what changes have been made for Chromium, I'm first checking
in a clean copy.
BUG=6274
Review URL: http://codereview.chromium.org/40234
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@11264 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/test')
-rw-r--r-- | chrome/test/data/dom_checker/README.chromium | 7 | ||||
-rw-r--r-- | chrome/test/data/dom_checker/dom_blank_page.html | 2 | ||||
-rw-r--r-- | chrome/test/data/dom_checker/dom_checker.html | 1237 | ||||
-rw-r--r-- | chrome/test/data/dom_checker/dom_config.js | 71 | ||||
-rw-r--r-- | chrome/test/data/dom_checker/dom_target_page.html | 154 |
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 <<a href="mailto:lcamtuf@google.com">lcamtuf@google.com</a>> and +Filipe Almeida <<a href="mailto:filipe@google.com">filipe@google.com</a>><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> + |