-
Notifications
You must be signed in to change notification settings - Fork 1
/
domnom.html
executable file
·530 lines (491 loc) · 18.6 KB
/
domnom.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
<!--
******************************************************************************
* domnom.js browser fuzzer by @chaseahiggins, loudeating24
******************************************************************************
* domnom is a simple javascript fuzzer that tries to find bugs by choosing
* parent/child combinations of various depths using several different
* techniques to reference the children. the goal is to find invalid access
* issues, uafs, null ptr deref etc
* inspired by lcamtuf's ref_fuzz, with some sauces for new ES6 stuff
* https://lcamtuf.blogspot.com/2010/06/announcing-reffuzz-2yo-fuzzer.html
******************************************************************************
* BUGS/TODO:
* maybe move logging to webworker to keep main thread free to fuzz
* 'arg builder' that builds random functions objs etc in a semi-intelligent
* way that is likely to cause problems (mostly with arraybuff resizing)
* NaNs and nulls getting through number while for arr len edit
* violation of cors causes script to stop if accessed locally via file://
* maybe move all fun vals to an iterable obj
* ie11 needs work, should probably keep it supported(maybe url mode opt?)
* el count should drift toward max_els but doesn't seem to
* what about using proxies to actually trap+fuzz windows ops on get, set etc
******************************************************************************
-->
<html>
<head>
<title>domnom.js by @chaseahiggins</title>
<link href="https://fonts.googleapis.com/css?family=Space+Mono:400,700" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
}
html, body {
font-family: 'Space Mono', monospace;
font-size: 18px;
background-color: #222;
color: #ddd;
}
#fuzzing {
margin: 0 1em;
}
a, a.link, a.visited {
color: red;
text-decoration: none;
}
#log {
margin: 0.5em 0 1em 0;
}
#log div {
border: 1px solid #111;
background-color: #333;
height: 40%;
width: 50%;
padding: 5px;
font-size: 14px;
overflow-y: scroll;
}
#log::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
}
#log::-webkit-scrollbar-thumb {
background-color: darkgrey;
outline: 1px solid slategrey;
}
#test_area {
border: 1px solid #111;
background-color: #333;
height: 33%;
width: 33%;
font-size: 14px;
}
</style>
<!--[if IE]>
<style>
#log div {
height: 500px;
}
</style>
<![endif]-->
<script>
var domnom = domnom || {};
// options
domnom.opts = {
odds: 5, // how random should we be
max_log: 16384, // 16k
max_els: 7, // can get out of hand quickly
func_argc: 7, // max number of args for func calls
max_bad_els: 7, // max num of bad accesses to tolerate
max_depth: 5, // max trips into objects when reading
max_type_misses: 10 // max misses on type preservation
};
// runtime values
domnom.run = {};
// elements, properties, attributes etc
// fun values for things
domnom.fun_vals = [
Infinity,
-Infinity,
undefined,
null,
false,
true,
eval,
-1,
navigator,
new Array,
0.1333333333333337,
0x13371337,
7e7,
-7e7,
7e-7,
1e101,
-1e101,
"aqua"
];
// functions that have a history of being problematic, mostly new stuff from es6
// these functions come from bug reports on p0, and other disclosed browser errs
domnom.fun_objs = [
Object,
JSON,
Array,
DataView,
ArrayBuffer,
Uint8ClampedArray
];
// html
domnom.html = {};
domnom.html.tags = [
"div", "p", "img", "object", "video",
"audio", "br", "canvas", "svg", "link",
"span", "figcaption", "time", "meta", "embed",
"textarea", "iframe", "nav", "area", "fieldset",
"applet", "html", "a", "bdi", "keygen",
"noframes", "progress", "sup", "table", "wbr",
"cron", "marquee", "aside"
];
domnom.html.attributes =[
"tabindex"
//"aria-*"
];
// css
domnom.css = {};
domnom.css.properties = [
"border-top"
];
// js
domnom.js = {};
domnom.js.bad_props = {
// avoid these to prevent mangling the dom, from lcamtuf's ref_fuzz
"parentNode" : 1,
"nextSibling" : 1,
"previousSibling" : 1,
"top" : 1,
"parent" : 1,
"opener" : 1,
"ownerDocument" : 1,
"parentElement" : 1,
"parentTextEdit" : 1,
"offsetParent" : 1,
"DOCUMENT_POSITION_PRECEDING" : 1,
"document" : 1,
"activeElement" : 1,
"documentElement" : 1,
"onbeforeunload" : 1
};
// the things
domnom.util = {};
domnom.util.heapSpray = function() {
// at start of tests, after every round, and whenever you're just feeling like it
// this is essentially from lcamtuf's ref_fuzz
// we'll start with 32MB(22) spray given how much memory most machines now have
// instead of the 16MB(21) spray used in ref_fuzz
var spray_str = "ABCDABCD";
for (var i = 0; i < 22; i++) // you end up spraying spray_str.length * (2 ^ loop_count) bytes
spray_str += spray_str;
return 1;
};
domnom.util.log = function(msg) {
setTimeout(function() { // keep this channel clear
if (domnom.run.log.innerHTML.length > domnom.opts.max_log) // big one...
domnom.run.log.innerHTML = domnom.run.log.innerHTML.slice(domnom.run.log.innerHTML.length - domnom.opts.max_log);
domnom.run.log.innerHTML += msg + "<br />";
domnom.run.log.scrollTop = domnom.run.log.scrollHeight; // scroll on add
}, 0);
}; // end domnom.util.log()
domnom.util.rand = function(mod) {
return Math.floor(Math.random() * mod);
}; // end rand()
domnom.util.addRef = function(obj, origin) {
// we need to store refs if we're ever going to confusulate them
// potentially, we make a decision here as to whether it's worth our time to store it
// let's add the reference
//domnom.util.log("storing ref: " + obj + "; origin: " + origin + "; round: " + domnom.run.round + "; ref#: " + domnom.run.ref_stack.length);
// parsh it
if (!obj && domnom.util.rand(domnom.opts.odds) != 0) return; // prevent empty ref assault
domnom.run.ref_stack.push(obj);
}; // end addref()
domnom.util.readObj = function(obj, prop) {
// see if we can read this object
prop = prop || 0;
var val;
try {
if (prop) {
val = obj[prop];
//domnom.util.log(prop + ": " + val + "; type: " + typeof(val));
} else {
val = obj;
}
} catch(e) {
return false;
}
return {
val: val,
type: typeof(val)
}
}; // end readObj()
domnom.util.setObj = function(obj, prop, type) {
// set the property of this object
// this is where the magic happens basically, where we
// try and pick the craziest shit possible to ref
var val;
switch (domnom.util.rand(4)) { // higher number, more chance of ref_stack pick
case 0: // do a fun_val
val = domnom.fun_vals[domnom.util.rand(domnom.fun_vals.length)];
break;
case 1: // pick random property from a fun_objs
val = domnom.fun_objs[domnom.util.rand(domnom.fun_objs.length)];
var props = Object.getOwnPropertyNames(val);
val = val[props[domnom.util.rand(props.length)]];
break;
case 2: // we just found a new home for a function
var f;
for (var i = 0; i < domnom.opts.max_type_misses; i++) {
f = domnom.run.ref_stack[domnom.util.rand(domnom.run.ref_stack.length)];
if (typeof(f) == "function") break; // break loop
} // couldn't find one, move on
if (typeof(f) == "function") {
val = f;
break; // break case, otherwise fall through to any ref_stack obj
}
default: // pick from the ref stack
val = domnom.run.ref_stack[domnom.util.rand(domnom.run.ref_stack.length)];
} // end switch
if (type && typeof(val) !== type && domnom.run.type_misses < domnom.opts.max_type_misses) {
domnom.run.type_misses++;
return domnom.util.setObj(obj, prop, type); // try again, yeah it's lazy
} else if (type && typeof(val) === type) domnom.run.type_misses = 0; // reset it
try {
obj[prop] = val;
} catch(e) {
domnom.util.log("could not change property: " + prop);
}
}; // end setObj()
domnom.util.walkObj = function(obj, origin) {
// let's review this object's properties and potentially store refs
var prop;
for (prop in obj) {
var val;
val = domnom.util.readObj(obj, prop);
if (!val) continue; // skip a bad prop
val = val.val; // maybe we'll make the obj not redundant at somme point
// store a ref maybe, we'll have a few cases where no
if (typeof(val) === "string" && domnom.util.rand(domnom.opts.odds) !== 0)
continue; // strangs can be so boring, advice from ref_fuzz
if (val != null && domnom.js.bad_props[prop] != 1) {
if (typeof(val) == "function" && domnom.util.rand(domnom.opts.odds) != 0) continue;
domnom.util.addRef(val, origin + '.' + prop);
}
} // finished with props
}; // end walkObj()
domnom.util.callFunctions = function(obj, origin) {
// try and walk a potentially freed obj and call functions
var prop;
var rets = [];
for (prop in obj) {
var val;
val = domnom.util.readObj(obj, prop);
if (!val) continue; // skip a bad prop
val = val.val;
// see if we should keep looking at this one
if (typeof(val) != "function") continue;
// see if we can call it
try {
// build a dummy param list
// js gives us flexibility to do too many args
// do a dumb arg builder for now
var argc = domnom.util.rand(domnom.opts.func_argc);
var args = [];
for (var i = 0; i < argc; i++)
args.push(domnom.run.ref_stack[domnom.util.rand(domnom.run.ref_stack.length)]);
// call it
var ret = val.apply(obj, args);
//domnom.util.log(prop + " function returned: " + ret);
// let's pop it in the rets stack
/*
if (domnom.util.rand(domnom.opts.odds) % 2 == 0 && typeof(ret) == "object") {
// maybe change odds later, remove mod. might also allow other types in
domnom.util.addRef(ret, "funcreturn");
}
*/
rets.push(ret);
} catch(e) {
domnom.util.log("couldn't call " + prop + ": " + e);
}
// if we get a valid return val, save that ref
}
return rets;
}; // end callFunctions()
domnom.util.objThrasher = function(obj, origin) {
// let's go through and fuckulate some stuff
// again we step through props
var prop;
for (prop in obj) {
// first, we know we can skip the blacklist props
// also skip a null prop etc, or just randomly
if (!prop || domnom.js.bad_props[prop] == 1) continue; // could be combined
if (domnom.util.rand(domnom.opts.odds) === 0) continue; // were already long
if ((prop == "length" || Array.isArray(obj[prop])) && domnom.util.rand(domnom.opts.odds) % 2 == 0) {
// we specifically want to try and mess with lengths
var fval;
do {
fval = domnom.fun_vals[domnom.util.rand(domnom.fun_vals.length)];
}
while (isNaN(fval));
if (Math.abs(fval) > 0x10000) fval %= domnom.util.rand(0x10000); // was causing too many large allocs
try {
(prop == "length") ? obj[prop] = fval : obj[prop].length = fval;
} catch (e) {
console.log("can't set len: " + fval);
console.log(e);
}
continue; // no further action needed
}
var val;
val = domnom.util.readObj(obj, prop);
if (!val) continue; // skip a bad prop
val = val.val;
// decide if we want to delete the prop or change it
if (domnom.util.rand(domnom.opts.odds) === 0) {
// 1/odds chance, and we hit it. delete the prop
try {
//domnom.util.log("deleting propery: " + prop);
delete obj[prop];
} catch (e) {
domnom.util.log("could not delete property: " + prop);
}
}
else {
// let's just change it to something fun
//domnom.util.log("changing property: " + prop);
if (domnom.util.rand(domnom.opts.odds) % 2 == 0) // preserve prop type
domnom.util.setObj(obj, prop, typeof(obj[prop]));
else
domnom.util.setObj(obj, prop, null); // just let it go crazy
}
} // end prop walk
}; // end objThrasher()
domnom.util.newElement = function() {
// add a new element to testing tree
try {
// picks a random el from the list of allowed tags at domnom.html.tags
var tag = document.createElement(domnom.html.tags[domnom.util.rand(domnom.html.tags.length)]);
// pop it into the missile range
if (domnom.util.rand(domnom.opts.odds) == 0) { // pick a rand child as parent
var r = domnom.util.rand(domnom.opts.max_els)
domnom.run.test_area.children[r].appendChild(tag);
}
else // just add to root
domnom.run.test_area.appendChild(tag);
// make it have stuff
tag.innerHTML = "One count of being a bear.<br />And one count of being an accessory to being a bear.";
tag.value = "stonecutters";
} catch(e) {
// we had a problem
console.log("couldn't create a new tag: " + e);
domnom.util.log("couldn't create a new tag: " + e);
return false;
}
// return the new tag for if caller wants to verify what was added
return tag;
}; // end newElement()
domnom.util.reload = function() {
//alert("reloading!"); // block to pause reload
location.reload(true);
}; // end reload()
// the testing
domnom.init = function() {
// setup
domnom.run.log = document.getElementById("log").getElementsByTagName("div")[0]; // log div, but why?
domnom.run.log.innerHTML = "";
domnom.util.log("domnom.js - loudeating");
domnom.util.log("initializing fuzzer...");
domnom.run.test_area = document.getElementById("test_area"); // tests div
domnom.run.test_area.innerHTML = "";
domnom.run.ref_stack = []; // a place for references™
domnom.run.round = 0; // round count for our references
domnom.run.bad_els = 0; // count of unremovable dom els
domnom.run.type_misses = 0; // maximum number of times to retry type perservation
// fill the heap up a little
domnom.util.heapSpray();
// let's go
domnom.util.log("let's get started :)");
domnom.next();
}; // end domnom.init()
domnom.next = function() {
// time do do something
// depending on our fuzzing state, we might want to add, test or destroy refs
// we might also try various other strategies to fuckulate the dom
domnom.run.curr_tags = domnom.run.test_area.children.length;
if ((domnom.util.rand(domnom.opts.odds) % 2 === 0 && domnom.run.curr_tags < domnom.opts.max_els) || domnom.run.ref_stack.length === 0) { // add one
var el = domnom.util.newElement(); // easy to add, VERY expensive to work with
// store refs
domnom.util.walkObj(el, "origin");
}
// choose an element in the list to mess with
if (domnom.run.curr_tags > 0) {
var r = domnom.util.rand(domnom.run.curr_tags);
var el = domnom.run.test_area.children[r];
domnom.util.objThrasher(el, "something");
}
// call some funcs, it's a farty afterall
if (domnom.run.curr_tags > 0) {
var new_refs = [];
var r = domnom.util.rand(domnom.run.curr_tags);
var el = domnom.run.test_area.children[r];
//domnom.util.callFunctions(el, "somewhere");
for (var i = 0; i < domnom.opts.max_depth; i++) {
var rets = domnom.util.callFunctions(el, "somewhere");
rets.map(function(ret) {
if (ret) new_refs.push(ret);
});
}
domnom.run.ref_stack = domnom.run.ref_stack.concat(new_refs);
}
// while we're calling funcs, let's pick some from the refs stack too
// maybe delete one I do what I want
if (domnom.util.rand(domnom.opts.odds) === 0) {
// delete's job is also to check if too many bad els exist
if (domnom.run.bad_els > domnom.opts.max_bad_els)
domnom.util.reload();
try {
var r = domnom.util.rand(domnom.run.curr_tags);
// peel off the dom
domnom.run.test_area.removeChild(domnom.run.test_area.children[r]);
delete domnom.run.test_area.children[r];
} catch (e) {
domnom.run.bad_els++;
domnom.util.log("could not remove element: " + e);
}
}
// cleanup textnodes shards left over not in html objs
for (var i = 0; i < domnom.run.test_area.childNodes.length; i++) {
var node = domnom.run.test_area.childNodes[i];
if (node.nodeType == 3) {
domnom.run.test_area.removeChild(node);
delete node;
}
} // end cleanup
// let's get ready for the next round
//if (domnom.run.round > 6) return;
// inc round count
//console.log("end round: " + domnom.run.round);
domnom.run.round++;
domnom.util.heapSpray();
// here goes nothing..
//domnom.next();
setTimeout(function() {
domnom.next();
}, 0);
}; // end domnom.next()
</script>
</head>
<body onload="domnom.init();">
<div id="fuzzing">
<span style="border-bottom: 1px solid red;">
<span style="font-size: 2em;">domnom.js</span> by <a href="https://twitter.com/chaseahiggins">@chaseahiggins</a>
</span>
<div id="log">
log:<br />
<div>
</div>
</div>
<!-- test area -->
missile range:
<div id="test_area">
</div>
</div>
</body>
</html>