- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / test / data / dom_checker / dom_checker.html
1 <html>
2 <!--
3
4    DOM checker - browser domain context separation validator
5    ----------------------------------------------------------
6
7    Authors: Michal Zalewski <lcamtuf@google.com>
8             Filipe Almeida <filipe@google.com>
9
10    Copyright 2008 by Google Inc. All Rights Reserved.
11
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
15
16      http://www.apache.org/licenses/LICENSE-2.0
17
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.
23
24 -->
25
26 <head>
27 <title>DOM checker - browser domain context separation validator</title>
28
29 <script src="../json2.js"></script>
30 <script src="automation.js"></script>
31
32 <script src="dom_config.js"></script>
33
34 <script>
35
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:///?
39
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
45
46 var blank_win;                  // about:blank window handle
47
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
52
53 var flip_count = 0;             // Page transition counter
54
55 var disable_ipc = false;        // IPC abort flag
56 var write_state;                // IPC write state
57 var cur_write;                  // IPC write pointer
58
59 var ef_loaded = false;          // Local file load test flag
60
61 var test_list = [];             // Test schedule
62 var test_size = 0;
63
64 var test_count = 1;             // Test counter
65
66 var read_list = [];             // Test data sets
67 var read_hash = {};
68 var write_list = [];
69 var write_hash = [];
70
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
76
77 var output;                     // Output container.
78
79 /* Send a reset command to IPC peer. */
80 function ipc_reset() {
81
82   last_ipc = 'RESET';
83   if (disable_ipc) return;
84
85   ipc_wait_count = 0;
86   ipc_count++;
87   document.getElementById('ipc_read').src = blank_page + '#RESET';
88
89 }
90
91
92 /* Send evaluation request to IPC peer. */
93 function ipc_eval(expr) {
94
95   last_ipc = 'EVAL('  + expr + ')';
96   if (disable_ipc) return;
97
98   ipc_wait_count = 0;
99   ipc_count++;
100   document.getElementById('ipc_read').src = blank_page + '#' + escape(expr);
101
102 }
103
104
105 /* Test for IPC state change, handle errors and timeouts. */
106 function ipc_changed() {
107
108   if (disable_ipc) {
109     if (last_ipc == 'RESET') ipc_state = '2'; else ipc_state = '0';
110     return true;
111   }
112
113   try {
114
115     ipc_value = frames[0].frames['ipc_write'].location.hash.substr(1);
116
117   } catch (e) {
118
119     fail_cycles++;
120     if (fail_cycles == 500) {
121
122       /* So, Opera is a bit naughty and won't let us do that. Oh well. */
123
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!');
127
128       disable_ipc = true;
129
130     }
131     return false;
132
133   }
134
135   fail_cycles = 0;
136
137   if (ipc_value == ipc_state) {
138     ipc_wait_count++;
139
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);
143
144     return false;
145   }
146
147   ipc_state = ipc_value;
148   return true;
149
150 }
151
152
153 /* Point frames to preconfigured locations, initialize other stuff. */
154 function init_frames() {
155
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;
161   }
162
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;
168   }
169
170   if (location.href.indexOf('/' + main_host + '/') == -1)
171     running_local = true;
172
173   target_page = 'http://' + alt_host + '/' + alt_dir + '/dom_target_page.html';
174   document.getElementById('f').src = target_page;
175
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;
179
180   init_timer = setInterval('check_frame()',1000);
181
182 }
183
184
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() {
188
189   try {
190
191     var x = frames['f'].location.hostname;
192     if (x != main_host) throw 1;
193
194   } catch (e) {
195
196     clearInterval(init_timer);
197     init_button();
198
199   }
200
201 }
202
203
204 /* Make the page runnable. */
205 function init_button() {
206   output = document.getElementById('results');
207   do_tests();  // Start the tests automatically at page load.
208 }
209
210
211 /* Shorthand notation... */
212 function E(x) { return eval(x); }
213
214
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() {
219
220   /* XXX: Is opener.* the best route to follow? Maybe open('',name) instead? */
221
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');
230   }
231
232 }
233
234
235 /* See if we get to access that window's properties, and whether it succeeded at
236    retrieving the data. */
237 function blankwin_verify() {
238
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");
245   }
246
247   blank_win.close();
248   now_running--;
249
250 }
251
252
253 /* Prepare blank window checks... */
254 function blankwin_checks() {
255   now_running++;
256   setTimeout('blankwin_verify()',2000);
257 }
258
259
260 /* Main test entry point. Set up all tests, get the ball rolling. */
261 function do_tests() {
262
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;
268   }
269
270   document.getElementById('start').disabled = true;
271   document.getElementById('start').value = "Tests in progress...";
272
273   if (document.getElementById('option_badonly').checked)
274     option_badonly = true;
275
276   if (document.getElementById('option_long').checked)
277     option_long = true;
278
279   /* We need to call this first, as the ability to open new windows expires pretty
280      quickly after UI events in some browsers */
281
282   open_blankwin();
283   schedule_test('blankwin_checks()');
284
285   // First, try some non-destructive ways of stacking odds in our favor...
286
287   schedule_test('opening_tricks()');
288
289   // Try to look up frames by name...
290
291   schedule_test('name_lookup_test()');
292
293   schedule_test('variable_injection_check()');
294
295   // Then, try non-intrusive checks on the third-party domain frame
296
297   schedule_test('basic_checks("frames[0]")');
298
299   // Proceed with checks against nested about:blank frame
300
301   schedule_test('basic_checks("frames[0].frames[0]")');
302
303   // Try to bypass IFRAME access checking by going through document.*
304
305   schedule_test('dom_bypass_checks()');
306
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).
309
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()');
314
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:
317
318   schedule_test('docwrite_checks("frames[0].frames[0]")');
319
320   // Let's try the same against the primary target frame itself:
321
322   schedule_test('docwrite_checks("frames[0]")');
323
324   // Try javascript: context inheritance tricks to see if we get to run javascript:
325   // in third party domains.
326
327   schedule_test('context_checks()');
328
329   // Clown around with our own settings for a while to spot more general issues.
330
331   schedule_test('closing_tricks()');
332
333   // Try transition attacks (this is slow and most certainly disruptive).
334
335   schedule_test('context_switch_checks()');
336
337   // Can setTimeout be smuggled across page transitions?
338
339   schedule_test('timeout_checks()');
340
341   // Can IFRAMEs access file:// URLs?
342
343   if (!running_local)
344     schedule_test('file_frame_checks()');
345
346   // That's all, folks...
347   run_tests();
348
349 }
350
351
352 // Queue a test for running.
353 function schedule_test(cmd) {
354   test_list.push(cmd);
355 }
356
357
358 // Begin test execution.
359 function run_tests() {
360   test_size = test_list.length;
361   next_test();
362 }
363
364
365 // Try to see if we get to look up frames by name across sites.
366 function name_lookup_test() {
367   var x;
368
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");
375   }
376
377 }
378
379
380 // Fetch test from list, run, schedule next.
381 function next_test() {
382
383   /* If previous timer-based test is still running, yield. */
384
385   if (now_running > 0) {
386     setTimeout(next_test, 100);
387     return;
388   }
389
390   var cmd = test_list.shift();
391   var e = document.getElementById('status');
392   test_count++;
393
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();
402   }
403 }
404
405
406 function update_finalize() {
407
408   document.getElementById('start').value = "Testing complete!";
409
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>";
416
417   // Let the automation know the test is finished.
418   automation.SetDone();
419
420 }
421
422
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);
428 }
429
430
431 /* Log passed test event. */
432 function GOOD(x) {
433   automation.IncrementTestCount();
434
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>');
438 }
439
440
441 /* Log test failure. */
442 function BAD(x) {
443   automation.IncrementTestCount();
444
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>');
448
449   automation.AddFailure(x);
450 }
451
452
453 /* For debugging and notification purposes... */
454 function DEBUG(x) {
455   log('<font color=black>Debug output : ' + x + '</font>');
456 }
457
458
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', ...)";
463
464   try {
465
466     if (injected_var == 1) { }
467     BAD(message);
468
469   } catch(e) {
470
471     GOOD(message);
472
473   }
474
475 }
476
477
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"); }
485 }
486
487
488 /* Try some tests that do not involve cross-frame access, but are nevertheless
489    pretty common. */
490 function closing_tricks() {
491   var x;
492
493   if (!running_local) {
494
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. */
498
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");
507     }
508
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");
517     }
518
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');
526       }
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");
532     }
533
534     // Disabled, causes the page to become unresponsive. See:
535     //   http://code.google.com/p/chromium/issues/detail?id=8401
536     /*
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)");
544     }
545
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)");
553     }
554     */
555
556   }
557
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");
565   }
566
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. */
571
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.';
576
577   // This will overwrite our cookie if domain= was silently dropped on previous
578   // attempts:
579
580   document.cookie = 'dom_checker_cookie=invalid; path=/';
581
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");
586
587   /* Oh, and any cookie setting is evil in file:/// */
588
589   if (running_local) {
590
591     document.cookie = 'dom_checker_cookie2=1';
592
593     if (document.cookie.indexOf('dom_checker_cookie2') != -1)
594       BAD("file:/// cookie setting");
595     else
596       GOOD("file:/// cookie setting");
597
598   }
599
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. */
604
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'"); }
610
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'"); }
616
617   try {
618     document.domain = '.';
619     if (document.domain != '.') throw 1;
620     BAD("document.domain = '.'");
621   } catch (e) { GOOD("document.domain = '.'"); }
622
623   try {
624     document.domain = '';
625     if (document.domain != '') throw 1;
626     BAD("document.domain = ''");
627   } catch (e) { GOOD("document.domain = ''"); }
628
629 }
630
631
632 /* Perform basic cross-domain access checks against a specific target. */
633 function basic_checks(where) {
634
635   reset_read_write();
636
637   /* Brute-force window.* if possible */
638
639   iterator_check(where);
640
641   /* Try to enumerate various list objects */
642
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");
652
653   /* Try to call common methods */
654
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");
664
665   visibility_check(where + ".private_var", where + ".var_noexist");
666
667   /* Various object-specific calls that are of particular interest */
668
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()");
675
676   /* Some properties that should not be disclosed. */
677
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");
683
684   /* Various object-specific peek & poke attempts to be executed
685      asynchronously. */
686
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__");
703
704   /* Try to disrupt something! */
705
706   fill_read_write("frames['control_frame']", where + ".");
707   fill_read_write("frames['control_frame'].document", where + ".document.");
708
709   /* Or, how about variable setting? */
710
711   add_read_write(where + ".private_var");
712
713   /* Actually run tests. */
714   try_read_write_all();
715
716 }
717
718
719 /* This is an interesting way to bypass frame[] access checking. */
720 function dom_bypass_checks() {
721
722   reset_read_write();
723
724   add_read_write("document.getElementById(\'f\').contentDocument.title");
725   add_read_write("document.getElementById(\'f\').contentWindow.status");
726
727   try_read_write_all();
728
729 }
730
731
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) { }
738 }
739
740
741 /* Is it possible to move third-party frames? */
742 function blank_location_checks_access() {
743
744   reset_read_write();
745
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');
749
750   try_read_write_all();
751
752 }
753
754
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')");
759 }
760
761
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);
770
771 }
772
773
774 /* Continue content inheritance checks... */
775 function context_checks_continue() {
776   /* We begin with 'f' pointing to a cross-domain (alt_host) site. */
777
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");
784   }
785 }
786
787
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"); }
797
798   now_running--;
799 }
800
801
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;
806
807   try {
808     var exist = E("delete " + name);
809   } catch(e) { exist = "exception"; }
810
811   try {
812     var noexist = E("delete " + name_noexist);
813   } catch(e) { noexist = "exception"; }
814
815   if(exist == noexist)
816     GOOD("delete " + name + " probe");
817   else BAD("delete " + name + " probe");
818 }
819
820
821 /* Reset read/write test queue */
822 function reset_read_write() {
823   read_list = [];
824   read_hash = {};
825   write_list = [];
826   write_hash = {};
827 }
828
829
830 /* Add read/write test item */
831 function add_read_write(name) {
832   add_read(name);
833   add_write(name);
834 }
835
836
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;
842   }
843 }
844
845
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;
851   }
852 }
853
854
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);
861   }
862 }
863
864
865 /* Execute read/write tests. Write tests require IPC validation and
866    hence are executed asynchronously. */
867 function try_read_write_all() {
868
869   for(i in read_list)
870     try_read(read_list[i]);
871
872   if (!write_list.length) return;
873
874   now_running++;
875   cur_write = 0;
876   write_state = 0;
877   write_timer = setInterval('do_next_write()',1);
878
879 }
880
881
882 /* Grab next write item. */
883 function write_advance() {
884   /* Move to next, end test on EOL */
885   cur_write++;
886   write_state = 0;
887
888   if (cur_write == write_list.length) {
889     now_running--;
890     clearInterval(write_timer);
891     return;
892   }
893 }
894
895
896 /* Execute next write operation or IPC update. */
897 function do_next_write() {
898
899   if (write_state == 0) {
900
901     /* STATE 0: Issue next command */
902
903     if (!try_write_silent(write_list[cur_write],'dom-foo')) {
904
905       /* Write failed immediately. Report failure,
906          take next item, move to RESET state. */
907
908       GOOD(write_list[cur_write] + " write (exception)");
909
910       write_advance();
911
912     } else {
913
914       /* Write seemingly succeeded. Is it possible to read the value back? */
915
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 {
920
921         /* In local mode, IPC may not be used, because our remote
922            frame will not be able to open file:// URL internally. */
923
924         if (running_local) {
925           GOOD(write_list[cur_write] + " write (maybe!)");
926           write_advance();
927           return;
928         }
929
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;
933
934       }
935
936     }
937
938   } else if (write_state == 1) {
939
940     /* STATE 1: Wait for command completion. */
941
942     if (!ipc_changed()) return; /* Yield until result is available. */
943
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;
952     }
953
954     ipc_reset();
955     write_state = 2;
956
957   } else if (write_state == 2) {
958
959     /* STATE 2: Wait for reset, proceed to next. */
960
961     if (!ipc_changed()) return; /* Yield until result is available. */
962
963     if (ipc_state != 2) {
964       alert('Bad IPC state ' + ipc_state + ' on reset request');
965       clearInterval(write_timer);
966       return;
967     }
968
969     write_advance();
970
971   }
972
973 }
974
975
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; }
980 }
981
982
983 /* Attempt read; returns false if name undefined, true otherwise */
984 function try_read(name) {
985   var x;
986
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"); }
995
996   return true;
997
998 }
999
1000
1001 /* Attempt read, but do not report. */
1002 function try_read_silent(name) {
1003   var x;
1004
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"; }
1011
1012 }
1013
1014
1015 /* Try invoking a function. */
1016 function try_call(name) {
1017   try { E(name); BAD(name + " call"); }
1018   catch (e) { GOOD(name + " call"); }
1019 }
1020
1021
1022 /* Try to fingerprint object lists across domains. */
1023 function list_checks(name) {
1024   var x;
1025
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"); }
1031
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"); }
1037
1038   iterator_check(name);
1039
1040   /* Will be carried out near the end of basic_checks(). */
1041   add_read_write(name + "[0].name");
1042
1043 }
1044
1045
1046 /* Call various methods. */
1047 function call_checks(name) {
1048   var x;
1049
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){})");
1060
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");
1066
1067 }
1068
1069
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");
1079     }
1080   } catch(e) { GOOD("for (e in " + name + ") iterator listing"); }
1081
1082 }
1083
1084
1085 /* Finalize page transition checks. */
1086 function flip_finalize() {
1087   clearInterval(flip_timer);
1088   clearInterval(check_timer);
1089   now_running--;
1090 }
1091
1092
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");
1102   }
1103 }
1104
1105
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) { }
1114
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) { }
1121
1122 }
1123
1124
1125 /* Prepare for page transition checks */
1126 function context_switch_checks() {
1127   now_running++;
1128
1129   if (option_long) flip_count = 1000;
1130     else flip_count = 100;
1131
1132   flip_timer = setInterval('loc_flip()',153);
1133   check_timer = setInterval('flip_check()',0);
1134 }
1135
1136
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);
1142 }
1143
1144
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);
1154 }
1155
1156
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");
1165   }
1166   now_running--;
1167 }
1168
1169
1170 /* Test callback function. */
1171 function ef_load_ok() { ef_loaded = true; }
1172
1173
1174 /* Check the ability to load file:/// URLs in frames. */
1175 function file_frame_checks() {
1176   now_running++;
1177   ef_loaded = false;
1178
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);
1182
1183 }
1184
1185
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");
1191 }
1192
1193 </script>
1194
1195 </head>
1196 <body onload="setTimeout('init_frames()',1000)">
1197
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.
1204 <p>
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.
1210 <p>
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>
1218
1219 <p>
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>
1222 <p>
1223 <font size=+1>Test results (be prepared to wait a while):</font><br>
1224 <font face="lucida console, courier new">
1225
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>
1230 <p>
1231
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>
1236 <p>
1237
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>
1241
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>
1245
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>
1249
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>
1253
1254 </body>
1255 </html>