Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / test / data / dom_checker / dom_checker.html
blobb27793fa616ba51d0cb4e57af8fad5d113a71732
1 <html>
2 <!--
4 DOM checker - browser domain context separation validator
5 ----------------------------------------------------------
7 Authors: Michal Zalewski <lcamtuf@google.com>
8 Filipe Almeida <filipe@google.com>
10 Copyright 2008 by Google Inc. All Rights Reserved.
12 Licensed under the Apache License, Version 2.0 (the "License");
13 you may not use this file except in compliance with the License.
14 You may obtain a copy of the License at
16 http://www.apache.org/licenses/LICENSE-2.0
18 Unless required by applicable law or agreed to in writing, software
19 distributed under the License is distributed on an "AS IS" BASIS,
20 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 See the License for the specific language governing permissions and
22 limitations under the License.
24 -->
26 <head>
27 <title>DOM checker - browser domain context separation validator</title>
29 <script src="../json2.js"></script>
30 <script src="automation.js"></script>
32 <script src="dom_config.js"></script>
34 <script>
36 var option_long = false; // Run more timing tests?
37 var option_badonly = false; // Report failed tests only?
38 var running_local = false; // Running from file:///?
40 var bad = 0; // Number of failed tests
41 var now_running = 0; // Run-level nesting counter
42 var target_page; // Target page location
43 var same_blank; // Same-domain blank page location
44 var blank_page; // Blank page location
46 var blank_win; // about:blank window handle
48 var init_timer; // Initialization timer
49 var check_timer; // Check execution timer
50 var flip_timer; // Page transition check timer
51 var write_timer; // IPC write test timer
53 var flip_count = 0; // Page transition counter
55 var disable_ipc = false; // IPC abort flag
56 var write_state; // IPC write state
57 var cur_write; // IPC write pointer
59 var ef_loaded = false; // Local file load test flag
61 var test_list = []; // Test schedule
62 var test_size = 0;
64 var test_count = 1; // Test counter
66 var read_list = []; // Test data sets
67 var read_hash = {};
68 var write_list = [];
69 var write_hash = [];
71 var last_ipc; // Last IPC command
72 var ipc_wait_count; // IPC wait cycle count
73 var ipc_count = 0; // IPC total command count
74 var ipc_state = 2; // Current IPC state
75 var fail_cycles = 0; // IPC failure count
77 var output; // Output container.
79 /* Send a reset command to IPC peer. */
80 function ipc_reset() {
82 last_ipc = 'RESET';
83 if (disable_ipc) return;
85 ipc_wait_count = 0;
86 ipc_count++;
87 document.getElementById('ipc_read').src = blank_page + '#RESET';
92 /* Send evaluation request to IPC peer. */
93 function ipc_eval(expr) {
95 last_ipc = 'EVAL(' + expr + ')';
96 if (disable_ipc) return;
98 ipc_wait_count = 0;
99 ipc_count++;
100 document.getElementById('ipc_read').src = blank_page + '#' + escape(expr);
105 /* Test for IPC state change, handle errors and timeouts. */
106 function ipc_changed() {
108 if (disable_ipc) {
109 if (last_ipc == 'RESET') ipc_state = '2'; else ipc_state = '0';
110 return true;
113 try {
115 ipc_value = frames[0].frames['ipc_write'].location.hash.substr(1);
117 } catch (e) {
119 fail_cycles++;
120 if (fail_cycles == 500) {
122 /* So, Opera is a bit naughty and won't let us do that. Oh well. */
124 alert('Unable to get ipc_write frame hash in response to ' + last_ipc +
125 ' (' + ipc_count + ') in state ' + ipc_state +
126 '\nWARNING: Disabling side channel IPC, results may be less accurate!');
128 disable_ipc = true;
131 return false;
135 fail_cycles = 0;
137 if (ipc_value == ipc_state) {
138 ipc_wait_count++;
140 if (ipc_wait_count == 500)
141 alert('Waited more than 500 cycles on IPC command ' + last_ipc + ' (' +
142 ipc_count + ') in state ' + ipc_state);
144 return false;
147 ipc_state = ipc_value;
148 return true;
153 /* Point frames to preconfigured locations, initialize other stuff. */
154 function init_frames() {
156 try {
157 if (main_host == undefined) throw 1;
158 } catch (e) {
159 alert('Config file dom_checker.js could not be loaded. Please check your configuration.');
160 return;
163 if (location.protocol == 'http:' && main_host != location.hostname &&
164 main_host != location.host) {
165 alert('Config file dom_checker.js specifies main_host that does not match current hostname.\n' +
166 'Please check your configuration.');
167 return;
170 if (location.href.indexOf('/' + main_host + '/') == -1)
171 running_local = true;
173 target_page = 'http://' + alt_host + '/' + alt_dir + '/dom_target_page.html';
174 document.getElementById('f').src = target_page;
176 same_blank = 'http://' + main_host + '/' + main_dir + '/dom_blank_page.html';
177 blank_page = 'http://' + alt_host + '/' + alt_dir + '/dom_blank_page.html';
178 document.getElementById('ipc_read').src = blank_page;
180 init_timer = setInterval('check_frame()',1000);
185 /* Wait for 'f' location to be updated; this is actually kind of dodgy,
186 but so is onload= behavior when frame location is modified by scripts. */
187 function check_frame() {
189 try {
191 var x = frames['f'].location.hostname;
192 if (x != main_host) throw 1;
194 } catch (e) {
196 clearInterval(init_timer);
197 init_button();
204 /* Make the page runnable. */
205 function init_button() {
206 output = document.getElementById('results');
207 do_tests(); // Start the tests automatically at page load.
211 /* Shorthand notation... */
212 function E(x) { return eval(x); }
215 /* Open a new javascript: window, see if it gets to read window properties to
216 other sites' about:blank windows, and phone back by pointing the window back
217 to our domain. */
218 function open_blankwin() {
220 /* XXX: Is opener.* the best route to follow? Maybe open('',name) instead? */
222 try {
223 blank_win = open('javascript:void(location = "' + same_blank +
224 '?" + opener.frames[0].frames[0].location)','blank_win',
225 'height=100,width=100,location=no,menubar=no,resizable=no,scrollbars=no,status=no,toolbar=no');
226 } catch (e) {
227 blank_win = open('javascript:void(location = "' + same_blank +
228 '?" + opener.frames[0].frames[0].location)','blank_win',
229 'height=100,width=100,location=no,menubar=no,resizable=no,scrollbars=no,status=no,toolbar=no');
235 /* See if we get to access that window's properties, and whether it succeeded at
236 retrieving the data. */
237 function blankwin_verify() {
239 try {
240 var x = blank_win.location.search;
241 if (x.indexOf('about:blank') == -1) throw 1;
242 BAD("about:blank crosstalk");
243 } catch (e) {
244 GOOD("about:blank crosstalk");
247 blank_win.close();
248 now_running--;
253 /* Prepare blank window checks... */
254 function blankwin_checks() {
255 now_running++;
256 setTimeout('blankwin_verify()',2000);
260 /* Main test entry point. Set up all tests, get the ball rolling. */
261 function do_tests() {
263 try {
264 if (frames['control_frame'].location.pathname.indexOf('/dom_blank_page.html') == -1) throw 1;
265 } catch (e) {
266 alert('Unable to load control frame - perhaps dom_blank_page.html not present?');
267 return;
270 document.getElementById('start').disabled = true;
271 document.getElementById('start').value = "Tests in progress...";
273 if (document.getElementById('option_badonly').checked)
274 option_badonly = true;
276 if (document.getElementById('option_long').checked)
277 option_long = true;
279 /* We need to call this first, as the ability to open new windows expires pretty
280 quickly after UI events in some browsers */
282 open_blankwin();
283 schedule_test('blankwin_checks()');
285 // First, try some non-destructive ways of stacking odds in our favor...
287 schedule_test('opening_tricks()');
289 // Try to look up frames by name...
291 schedule_test('name_lookup_test()');
293 schedule_test('variable_injection_check()');
295 // Then, try non-intrusive checks on the third-party domain frame
297 schedule_test('basic_checks("frames[0]")');
299 // Proceed with checks against nested about:blank frame
301 schedule_test('basic_checks("frames[0].frames[0]")');
303 // Try to bypass IFRAME access checking by going through document.*
305 schedule_test('dom_bypass_checks()');
307 // Try to perform no-op location updates for that frame (this should be non-disruptive,
308 // and is required because we otherwise blacklist location.* writes).
310 // Temporarily disable these tests to see if it has any effect on
311 // non-determinism.
312 //schedule_test('blank_location_checks_access()');
313 //schedule_test('blank_location_checks_call()');
315 // Now that we're done with r/w checks, try destructive overwrites of the nested
316 // about:blank frame we put in the target window:
318 schedule_test('docwrite_checks("frames[0].frames[0]")');
320 // Let's try the same against the primary target frame itself:
322 schedule_test('docwrite_checks("frames[0]")');
324 // Try javascript: context inheritance tricks to see if we get to run javascript:
325 // in third party domains.
327 schedule_test('context_checks()');
329 // Clown around with our own settings for a while to spot more general issues.
331 schedule_test('closing_tricks()');
333 // Try transition attacks (this is slow and most certainly disruptive).
335 schedule_test('context_switch_checks()');
337 // Can setTimeout be smuggled across page transitions?
339 schedule_test('timeout_checks()');
341 // Can IFRAMEs access file:// URLs?
343 if (!running_local)
344 schedule_test('file_frame_checks()');
346 // That's all, folks...
347 run_tests();
352 // Queue a test for running.
353 function schedule_test(cmd) {
354 test_list.push(cmd);
358 // Begin test execution.
359 function run_tests() {
360 test_size = test_list.length;
361 next_test();
365 // Try to see if we get to look up frames by name across sites.
366 function name_lookup_test() {
367 var x;
369 try {
370 x = open('','nf');
371 if (x == null || x == undefined) throw 1;
372 BAD("open() frame name lookup");
373 } catch (e) {
374 GOOD("open() frame name lookup");
380 // Fetch test from list, run, schedule next.
381 function next_test() {
383 /* If previous timer-based test is still running, yield. */
385 if (now_running > 0) {
386 setTimeout(next_test, 100);
387 return;
390 var cmd = test_list.shift();
391 var e = document.getElementById('status');
392 test_count++;
394 if (cmd) {
395 e.innerHTML = "Running test " + (test_count-1) + "/" + test_size +
396 " <font color=gray>(" + cmd + ")</font>";
397 eval(cmd);
398 setTimeout(next_test, 100); // yield
399 } else {
400 e.innerHTML = "";
401 update_finalize();
406 function update_finalize() {
408 document.getElementById('start').value = "Testing complete!";
410 if (bad)
411 document.getElementById('status').innerHTML =
412 "<font color=red>Failed checks: " + bad + "</font>";
413 else
414 document.getElementById('status').innerHTML =
415 "<font color=green>All checks passed (whoa!)</font>";
417 // Let the automation know the test is finished.
418 automation.SetDone();
423 /* Append a message to test log */
424 function log(message) {
425 var e = document.createElement('li');
426 e.innerHTML = message;
427 output.appendChild(e);
431 /* Log passed test event. */
432 function GOOD(x) {
433 automation.IncrementTestCount();
435 if (option_badonly) return;
436 x = x.replace('frames[0].frames[0]','(blank)').replace('frames[0]','(third-party)');
437 log('<font color=teal>Check passed : ' + x + ' access denied.</font>');
441 /* Log test failure. */
442 function BAD(x) {
443 automation.IncrementTestCount();
445 bad++;
446 x = x.replace('frames[0].frames[0]','(blank)').replace('frames[0]','(third-party)');
447 log('<font color=red>CHECK FAILED : ' + x + ' is possible!</font>');
449 automation.AddFailure(x);
453 /* For debugging and notification purposes... */
454 function DEBUG(x) {
455 log('<font color=black>Debug output : ' + x + '</font>');
459 /* Let's see if the target page succeeded at planting a variable in our
460 context (this test is actually carried out on load by target page). */
461 function variable_injection_check() {
462 var message = "(third-party).__defineGetter__('injected_var', ...)";
464 try {
466 if (injected_var == 1) { }
467 BAD(message);
469 } catch(e) {
471 GOOD(message);
478 /* See if we may just set document.domain to whatever we want. */
479 function opening_tricks() {
480 try {
481 document.domain = alt_host;
482 if (document.domain != alt_host) throw 1;
483 BAD("arbitrary document.domain");
484 } catch (e) { GOOD("arbitrary document.domain"); }
488 /* Try some tests that do not involve cross-frame access, but are nevertheless
489 pretty common. */
490 function closing_tricks() {
491 var x;
493 if (!running_local) {
495 /* NOTE: This requires Apache or IIS server and a generic 'Bad request'
496 response page. If you host the script at a place where these conditions
497 are not met, tough. */
499 x = new XMLHttpRequest();
500 try {
501 x.open('GET /? HTTP/1.0\r\nBadHeader\r\nBar: ','http://' + main_host + '/',false);
502 x.send(null);
503 if (x.responseText.indexOf('Bad Request') == -1) throw 1;
504 BAD("XMLHttpRequest method splitting");
505 } catch (e) {
506 GOOD("XMLHttpRequest method splitting");
509 x = new XMLHttpRequest();
510 try {
511 x.open('GET','http://' + main_host + '/ HTTP/1.0\r\nBadHeader\r\nBar: ',false);
512 x.send(null);
513 if (x.responseText.indexOf('Bad Request') == -1) throw 1;
514 BAD("XMLHttpRequest path splitting");
515 } catch (e) {
516 GOOD("XMLHttpRequest path splitting");
519 x = new XMLHttpRequest();
520 try {
521 x.open('GET','http://' + main_host + '/',false);
522 try {
523 x.setRequestHeader('Whatever: hi mom\r\nBadHeader\r\nBar','baz');
524 } catch (e) {
525 x.setRequestHeader('Whatever','hi mom\r\nBadHeader');
527 x.send(null);
528 if (x.responseText.indexOf('Bad Request') == -1) throw 1;
529 BAD("XMLHttpRequest parameter splitting");
530 } catch (e) {
531 GOOD("XMLHttpRequest path splitting");
534 // Disabled, causes the page to become unresponsive. See:
535 // http://code.google.com/p/chromium/issues/detail?id=8401
537 x = new XMLHttpRequest();
538 try {
539 x.open('GET','file:///c:/boot.ini',false);
540 x.send(null);
541 BAD("XMLHttpRequest() to local files (Windows)");
542 } catch (e) {
543 GOOD("XMLHttpRequest() to local files (Windows)");
546 x = new XMLHttpRequest();
547 try {
548 x.open('GET','file:////etc/hosts',false);
549 x.send(null);
550 BAD("XMLHttpRequest() to local files (unix)");
551 } catch (e) {
552 GOOD("XMLHttpRequest() to local files (unix)");
558 x = new XMLHttpRequest();
559 try {
560 x.open('GET','http://' + alt_host + '/',false);
561 x.send(null);
562 BAD("XMLHttpRequest() to remote pages");
563 } catch (e) {
564 GOOD("XMLHttpRequest() to remote pages");
567 /* It would be good to test for the ability to set file:/// SRC= URLs,
568 but there is no convenient way to read back the result, I think...
569 onerror= and onload= firing is handy, but very inconsistent across
570 browsers. */
572 document.cookie = 'dom_checker_cookie=bar_com; path=/; domain=com';
573 document.cookie = 'dom_checker_cookie=bar_dotcom; path=/; domain=.cx';
574 document.cookie = 'dom_checker_cookie=bar_dot; path=/; domain=.';
575 document.cookie = 'dom_checker_cookie=bar_dotcomdot; path=/; domain=.cx.';
577 // This will overwrite our cookie if domain= was silently dropped on previous
578 // attempts:
580 document.cookie = 'dom_checker_cookie=invalid; path=/';
582 if (document.cookie.indexOf('dom_checker_cookie=bar') != -1)
583 BAD("cross-domain document.cookie [value: " + document.cookie + "]");
584 else
585 GOOD("cross-domain document.cookie");
587 /* Oh, and any cookie setting is evil in file:/// */
589 if (running_local) {
591 document.cookie = 'dom_checker_cookie2=1';
593 if (document.cookie.indexOf('dom_checker_cookie2') != -1)
594 BAD("file:/// cookie setting");
595 else
596 GOOD("file:/// cookie setting");
600 /* Depending on the implementation, this might not be a tragic security
601 flaw, as a mutual consent to this is required in most browsers; but it
602 certainly encourages terrible coding practices and may make it easier
603 to go after certain targets. */
605 try {
606 document.domain = '.cx';
607 if (document.domain != '.cx') throw 1;
608 BAD("document.domain = '.cx'");
609 } catch (e) { GOOD("document.domain = '.cx'"); }
611 try {
612 document.domain = 'cx';
613 if (document.domain != 'cx') throw 1;
614 BAD("document.domain = 'cx'");
615 } catch (e) { GOOD("document.domain = 'cx'"); }
617 try {
618 document.domain = '.';
619 if (document.domain != '.') throw 1;
620 BAD("document.domain = '.'");
621 } catch (e) { GOOD("document.domain = '.'"); }
623 try {
624 document.domain = '';
625 if (document.domain != '') throw 1;
626 BAD("document.domain = ''");
627 } catch (e) { GOOD("document.domain = ''"); }
632 /* Perform basic cross-domain access checks against a specific target. */
633 function basic_checks(where) {
635 reset_read_write();
637 /* Brute-force window.* if possible */
639 iterator_check(where);
641 /* Try to enumerate various list objects */
643 list_checks(where + ".frames");
644 list_checks(where + ".window"); /* alias to frames */
645 list_checks(where + ".images");
646 list_checks(where + ".styleSheets");
647 list_checks(where + ".applets");
648 list_checks(where + ".embeds");
649 list_checks(where + ".links");
650 list_checks(where + ".forms");
651 list_checks(where + ".anchors");
653 /* Try to call common methods */
655 call_checks(where + ".document");
656 call_checks(where + ".document.body");
657 call_checks(where + ".window");
658 call_checks(where + ".window.self");
659 call_checks(where + ".screen");
660 call_checks(where + ".navigator");
661 call_checks(where + ".location");
662 call_checks(where + ".document.location");
663 call_checks(where + ".history");
665 visibility_check(where + ".private_var", where + ".var_noexist");
667 /* Various object-specific calls that are of particular interest */
669 try_call(where + ".window.scrollBy(10,10)");
670 try_call(where + ".history.forward(0)");
671 try_call(where + ".document.createElement('I')");
672 try_call(where + ".document.body.appendChild(null)");
673 try_call(where + ".document.clear()");
674 try_call(where + ".stop()");
676 /* Some properties that should not be disclosed. */
678 try_read(where + ".document.location");
679 try_read(where + ".location");
680 try_read(where + ".location.href");
681 try_read(where + ".location.hash");
682 try_read(where + ".location.protocol");
684 /* Various object-specific peek & poke attempts to be executed
685 asynchronously. */
687 add_read_write(where + ".document.domain");
688 add_read_write(where + ".document.title");
689 add_read_write(where + ".document.referrer");
690 add_read_write(where + ".document.URI");
691 add_read_write(where + ".document.baseURI");
692 add_read_write(where + ".document.cookie");
693 add_read_write(where + ".window.name");
694 add_read_write(where + ".location.search");
695 add_read_write(where + ".location.host");
696 add_read_write(where + ".location.hash");
697 add_write(where + ".location.watch");
698 add_read_write(where + ".history.length");
699 add_read_write(where + ".document.style.length");
700 add_read_write(where + ".document.inputEncoding");
701 add_read_write(where + ".document.characterSet");
702 add_read_write(where + ".window.__iterator__");
704 /* Try to disrupt something! */
706 fill_read_write("frames['control_frame']", where + ".");
707 fill_read_write("frames['control_frame'].document", where + ".document.");
709 /* Or, how about variable setting? */
711 add_read_write(where + ".private_var");
713 /* Actually run tests. */
714 try_read_write_all();
719 /* This is an interesting way to bypass frame[] access checking. */
720 function dom_bypass_checks() {
722 reset_read_write();
724 add_read_write("document.getElementById(\'f\').contentDocument.title");
725 add_read_write("document.getElementById(\'f\').contentWindow.status");
727 try_read_write_all();
732 /* Is it possible to overwrite third-party documents? */
733 function docwrite_checks(where) {
734 try_call(where + ".document.open()");
735 try_call(where + ".document.write('hi mom')");
736 // To prevent clobbering the browser.
737 try { E(where + ".document.close()"); } catch (e) { }
741 /* Is it possible to move third-party frames? */
742 function blank_location_checks_access() {
744 reset_read_write();
746 add_read_write('frames[0].frames[0].location.href','about:blank');
747 add_read_write('frames[0].frames[0].location','about:blank');
748 add_read_write('frames[0].frames[0].document.location','about:blank');
750 try_read_write_all();
755 /* Is it possible to move third-party frames, take two? */
756 function blank_location_checks_call() {
757 try_call("frames[0].frames[0].location.assign('about:blank')");
758 try_call("frames[0].frames[0].location.replace('about:blank')");
762 /* Try to detect context inheritance issues on javascript: URL setting. Note
763 that it differs from earlier *blankwin checks in that it attempts to execute
764 code in a specific context, instead of trying to probe the privileges of
765 blank windows. */
766 function context_checks() {
767 now_running++;
768 document.getElementById('f').src = blank_page;
769 setTimeout('context_checks_continue()',1000);
774 /* Continue content inheritance checks... */
775 function context_checks_continue() {
776 /* We begin with 'f' pointing to a cross-domain (alt_host) site. */
778 try {
779 document.getElementById('f').src = 'javascript:void(location.href = "' + same_blank + '?" + location.host)';
780 setTimeout('context_checks_finalize()',1000);
781 } catch (e) {
782 now_running--;
783 GOOD("javascript: URI trickery");
788 /* So, did we succeed at our context inheritance trickery? */
789 function context_checks_finalize() {
790 var x;
791 try {
792 x = frames['f'].location.search;
793 if (x.indexOf(main_host) != -1) throw 1;
794 if (x == '') throw 1;
795 BAD("javascript: URI trickery [value: " + x + "]");
796 } catch (e) { GOOD("javascript: URI trickery"); }
798 now_running--;
802 /* Do we get to test for presence of variables across domains? */
803 function visibility_check(name, name_noexist) {
804 var exist;
805 var noexist;
807 try {
808 var exist = E("delete " + name);
809 } catch(e) { exist = "exception"; }
811 try {
812 var noexist = E("delete " + name_noexist);
813 } catch(e) { noexist = "exception"; }
815 if(exist == noexist)
816 GOOD("delete " + name + " probe");
817 else BAD("delete " + name + " probe");
821 /* Reset read/write test queue */
822 function reset_read_write() {
823 read_list = [];
824 read_hash = {};
825 write_list = [];
826 write_hash = {};
830 /* Add read/write test item */
831 function add_read_write(name) {
832 add_read(name);
833 add_write(name);
837 /* Add read test item (if not already scheduled) */
838 function add_read(name) {
839 if(!read_hash[name]) {
840 read_list.push(name);
841 read_hash[name] = 1;
846 /* Add write test item (if not already scheduled) */
847 function add_write(name) {
848 if (!write_hash[name]) {
849 write_list.push(name);
850 write_hash[name] = 1;
855 /* Try to iterate through window properties using a control frame,
856 populate lists. */
857 function fill_read_write(control, base) {
858 for(name in eval(control)) {
859 if (!write_blacklist[name]) add_write(base + name);
860 if (!read_blacklist[name]) add_read(base + name);
865 /* Execute read/write tests. Write tests require IPC validation and
866 hence are executed asynchronously. */
867 function try_read_write_all() {
869 for(i in read_list)
870 try_read(read_list[i]);
872 if (!write_list.length) return;
874 now_running++;
875 cur_write = 0;
876 write_state = 0;
877 write_timer = setInterval('do_next_write()',1);
882 /* Grab next write item. */
883 function write_advance() {
884 /* Move to next, end test on EOL */
885 cur_write++;
886 write_state = 0;
888 if (cur_write == write_list.length) {
889 now_running--;
890 clearInterval(write_timer);
891 return;
896 /* Execute next write operation or IPC update. */
897 function do_next_write() {
899 if (write_state == 0) {
901 /* STATE 0: Issue next command */
903 if (!try_write_silent(write_list[cur_write],'dom-foo')) {
905 /* Write failed immediately. Report failure,
906 take next item, move to RESET state. */
908 GOOD(write_list[cur_write] + " write (exception)");
910 write_advance();
912 } else {
914 /* Write seemingly succeeded. Is it possible to read the value back? */
916 if (try_read_silent(write_list[cur_write]).indexOf('dom-foo') != -1) {
917 BAD(write_list[cur_write] + " write (readback)");
918 write_advance();
919 } else {
921 /* In local mode, IPC may not be used, because our remote
922 frame will not be able to open file:// URL internally. */
924 if (running_local) {
925 GOOD(write_list[cur_write] + " write (maybe!)");
926 write_advance();
927 return;
930 /* Otherwise, we must request the target page to revalidate. */
931 ipc_eval("var tmp = " + write_list[cur_write].replace(/^frames\[0\]\./,'') + "; return (tmp.toString().indexOf('dom-foo') != -1)");
932 write_state = 1;
938 } else if (write_state == 1) {
940 /* STATE 1: Wait for command completion. */
942 if (!ipc_changed()) return; /* Yield until result is available. */
944 if (ipc_state == 0) {
945 GOOD(write_list[cur_write] + " write (via IPC)");
946 } else if (ipc_state == 1) {
947 BAD(write_list[cur_write] + " write (via IPC)");
948 } else {
949 alert('Bad IPC state ' + ipc_state + ' on eval request');
950 clearInterval(write_timer);
951 return;
954 ipc_reset();
955 write_state = 2;
957 } else if (write_state == 2) {
959 /* STATE 2: Wait for reset, proceed to next. */
961 if (!ipc_changed()) return; /* Yield until result is available. */
963 if (ipc_state != 2) {
964 alert('Bad IPC state ' + ipc_state + ' on reset request');
965 clearInterval(write_timer);
966 return;
969 write_advance();
976 /* Attempt write; returns 'true' if write *apparently* succeeded. */
977 function try_write_silent(name,val) {
978 try { E(name + "= '" + val + "'"); return true; }
979 catch (e) { return false; }
983 /* Attempt read; returns false if name undefined, true otherwise */
984 function try_read(name) {
985 var x;
987 try {
988 x = E(name);
989 if (x == undefined) return false;
990 // Opera has a magical 'object inaccessible' thingee we need to handle
991 // in a portable manner with an implicit typecast.
992 if (x == '[object inaccessible]') throw 1;
993 BAD(name + " read [value: " + x + "]");
994 } catch (e) { GOOD(name + " read"); }
996 return true;
1001 /* Attempt read, but do not report. */
1002 function try_read_silent(name) {
1003 var x;
1005 try {
1006 x = E(name);
1007 if (x == undefined) throw 1;
1008 if (x == '[object inaccessible]') throw 1;
1009 return x.toString();
1010 } catch (e) { return "DOM-checker-no-match"; }
1015 /* Try invoking a function. */
1016 function try_call(name) {
1017 try { E(name); BAD(name + " call"); }
1018 catch (e) { GOOD(name + " call"); }
1022 /* Try to fingerprint object lists across domains. */
1023 function list_checks(name) {
1024 var x;
1026 try {
1027 x = E(name + "[0]");
1028 if (x == undefined) return;
1029 BAD(name + "<!-- NOP -->[0] probe [value: " + x + "]");
1030 } catch (e) { GOOD(name + " probe"); }
1032 try {
1033 x = E(name + ".length");
1034 if (x == undefined) return;
1035 BAD(name + ".length read [value: " + x + "]");
1036 } catch (e) { GOOD(name + ".length read"); }
1038 iterator_check(name);
1040 /* Will be carried out near the end of basic_checks(). */
1041 add_read_write(name + "[0].name");
1046 /* Call various methods. */
1047 function call_checks(name) {
1048 var x;
1050 try_call(name + ".hasAttribute('foo')");
1051 try_call(name + ".getAttribute('foo')");
1052 try_call(name + ".setAttribute('foo','bar')");
1053 try_call(name + ".createEvent('MouseEvents')");
1054 try_call(name + ".dispatchEvent(null)");
1055 try_call(name + ".captureEvents(Event.CLICK)");
1056 try_call(name + ".routeEvent(Event.CLICK)");
1057 try_call(name + ".setTimeout('',1)");
1058 try_call(name + ".clearTimeout(0)");
1059 try_call(name + ".watch('foo',function foo(a,b,c){})");
1061 /* These will be executed by the end of basic_checks() */
1062 add_read_write(name + ".onload");
1063 add_read_write(name + ".onerror");
1064 add_read_write(name + ".onchange");
1065 add_read_write(name + ".onkeydown");
1070 /* Try to enumerate something across domains. */
1071 function iterator_check(name) {
1072 try {
1073 var list = [];
1074 eval("for (e in " + name + ") { list[list.length] = e}");
1075 if(list.length >= 1) {
1076 BAD("for (e in " + name + ") iterator");
1077 } else {
1078 GOOD("for (e in " + name + ") iterator");
1080 } catch(e) { GOOD("for (e in " + name + ") iterator listing"); }
1085 /* Finalize page transition checks. */
1086 function flip_finalize() {
1087 clearInterval(flip_timer);
1088 clearInterval(check_timer);
1089 now_running--;
1093 /* Perform a location flip. */
1094 function loc_flip() {
1095 // if (Math.random() > .8) return;
1096 if (Math.random() > .5) document.getElementById('f').src = blank_page;
1097 else document.getElementById('f').src = 'about:blank';
1098 flip_count--;
1099 if (!flip_count) {
1100 flip_finalize();
1101 GOOD("on-transition cross-domain access");
1106 /* Check for location flip success. */
1107 function flip_check() {
1108 try {
1109 var x = frames['f'].private_var;
1110 if (x != 1) throw 1;
1111 flip_finalize();
1112 BAD("on-transition private_var access");
1113 } catch (e) { }
1115 try {
1116 var x = frames['f'].location.hostname;
1117 if (x != alt_host) throw 1;
1118 flip_finalize();
1119 BAD("on-transition location.hostname access");
1120 } catch (e) { }
1125 /* Prepare for page transition checks */
1126 function context_switch_checks() {
1127 now_running++;
1129 if (option_long) flip_count = 1000;
1130 else flip_count = 100;
1132 flip_timer = setInterval('loc_flip()',153);
1133 check_timer = setInterval('flip_check()',0);
1137 /* Prepare for timeout checks. */
1138 function timeout_checks() {
1139 now_running++;
1140 document.getElementById('f').src = same_blank;
1141 setTimeout('timeout_load_wait()',1000);
1145 /* Try to configure a timeout across domains. */
1146 function timeout_load_wait() {
1147 try {
1148 frames['f'].setTimeout('location.href = "' + same_blank +'?" + location.host',1000);
1149 frames['f'].navigator.setTimeout('location.href = "' + same_blank +'?" + location.host',1000);
1150 frames['f'].screen.setTimeout('location.href = "' + same_blank +'?" + location.host',1000);
1151 } catch (e) { }
1152 document.getElementById('f').src = blank_page;
1153 setTimeout('timeout_result()',2000);
1157 /* Validate test result. */
1158 function timeout_result() {
1159 try {
1160 var x = frames['f'].location.search;
1161 if (x == alt_host) BAD("cross-domain setTimeout");
1162 else GOOD("cross-domain setTimeout [value: " + x + "]")
1163 } catch (e) {
1164 GOOD("cross-domain setTimeout");
1166 now_running--;
1170 /* Test callback function. */
1171 function ef_load_ok() { ef_loaded = true; }
1174 /* Check the ability to load file:/// URLs in frames. */
1175 function file_frame_checks() {
1176 now_running++;
1177 ef_loaded = false;
1179 try { document.getElementById('ef').src = 'file:///c:/'; } catch(e) { }
1180 setTimeout('try { document.getElementById("ef").src = "file:///etc/hosts"} catch(e) { }',500);
1181 setTimeout('file_frame_verify()',1000);
1186 /* Test for file:/// load success. */
1187 function file_frame_verify() {
1188 now_running--;
1189 if (ef_loaded) BAD("file:/// frame");
1190 else GOOD("file:/// frame");
1193 </script>
1195 </head>
1196 <body onload="setTimeout('init_frames()',1000)">
1198 <font face="arial">
1199 <font size=+2><b>Browser DOM access checker 1.01</b></font><br>
1200 <font size=-1>
1201 Authors: Michal Zalewski &lt;<a href="mailto:lcamtuf@google.com">lcamtuf@google.com</a>&gt; and
1202 Filipe Almeida &lt;<a href="mailto:filipe@google.com">filipe@google.com</a>&gt;<br>
1203 Copyright 2008 by Google Inc., and licensed under the Apache License, Version 2.0.
1205 <font color=gray>
1206 DOM access checker is a tool designed to automatically validate numerous aspects of domain
1207 security policy enforcement (cross-domain DOM access, Javascript cookies, XMLHttpRequest
1208 calls, event and transition handling) to detect common security attack or information
1209 disclosure vectors.
1211 Please run this tool both over HTTP, and then from local disk (file:/// namespace).
1212 Ideally, results in both cases should be the same, and no failed tests should be reported.
1213 That said, although we worked with software vendors to resolve many of the most significant
1214 issues, all common browsers fail anywhere from 10 to 30 of less significant tests due to
1215 various design decisions (most of which bear some privacy considerations by making it
1216 to fingerprint simultaneously open pages).
1217 </font>
1220 <input type=submit id=start disabled=yes value="Loading, please wait..." onclick="do_tests()">
1221 <span id=status style="padding: 0em 0em 0em 1em"></span>
1223 <font size=+1>Test results (be prepared to wait a while):</font><br>
1224 <font face="lucida console, courier new">
1226 <!-- Log container -->
1227 <div id=results width=100% style="border-width: 1px; border-style: solid; border-color: teal; background-color: #FFFFE0; padding: 1em 0em 1em 1em">
1228 </div>
1229 </font>
1232 <font size=-1 color=gray>
1233 <input id=option_long type=checkbox> Perform longer page transition checks<br>
1234 <input id=option_badonly type=checkbox> Report failed checks only
1235 </font>
1238 <!-- Target frame pointing to dom_target_page.html -->
1239 <iframe height=1 width=1 id=f name=f style="border-width: 0px">
1240 </iframe>
1242 <!-- Control frame used to enumerate DOM objects -->
1243 <iframe height=1 width=1 id=control_frame name=control_frame src="dom_blank_page.html" style="border-width: 0px">
1244 </iframe>
1246 <!-- Test frame used for file:/// URLs -->
1247 <iframe height=1 width=1 id=ef name=ef onload="ef_load_ok()" style="border-width: 0px">
1248 </iframe>
1250 <!-- IPC frame for write validation -->
1251 <iframe id=ipc_read name=ipc_read src="dom_blank_page.html#NONE" height=1 width=1 style="border-width: 0px">
1252 </iframe>
1254 </body>
1255 </html>