App-DubiousHTTP
view release on metacpan or search on metacpan
lib/App/DubiousHTTP/Tests.pm view on Meta::CPAN
instead it analyzes which responses will successfully be interpreted as
JavaScript by the browser, i.e. by using the "script" tag.
</p>
<p id=test_js class=runtest><a href="/autojs/all/set_success.js">Run Test with
innocent JavaScript payload</a></p>
<a name=img>
<h2>Bulk test with innocent Image</h2>
</a>
<p>
This bulk test will use "img" tags to download an innocent image to check which
uncommon responses can be used to load images.
</p>
<p id=test_js class=runtest><a href="/autoimg/all/ok.png">Run Test with
innocent image payload</a></p>
<a name=iframe>
<h2>Bulk test with innocent Iframe</h2>
</a>
<p>
This bulk test will use "iframe" tags to download an innocent HTML to check which
uncommon responses can be used to load iframes. <b>Warning!</b>: IE and Edge seem
to have serious problems with some test cases here and will render the page
unresponsive.
</p>
<p id=test_iframe class=runtest><a href="/autohtml/all/parent_set_success.html">Run Test with
innocent iframe payload</a></p>
<a name=other>
<h2>Non-Bulk tests</h2>
</a>
<p>
The following tests analyze the behavior of browsers in specific cases, like
loading an image, loading a script and loading HTML into an iframe. They offer a
download for the EICAR test virus. The subtests in these tests all follow the
same style: If the browser behaves like expected (i.e. fails or succeeds) the
relevant element (IMAGE, SCRIPT or HTML) will turn green, if it behaves
differently it will turn red. Yellow is similar successful as green but marks an
uncommon behavior. If this uncommon behavior is not implemented (i.e. load of
image or script failed) the element will be grey.
When trying to load HTML into an iframe it can happen that the iframe stays
empty or contains some error message or garbage instead of "HTML". In this case
it failed to load the content.
</p>
<p>
Which behavior is expected can be seen from the header preceding
the relevant section of subtests: if it says that the following requests are
VALID it is expected that loading succeeds, on INVALID requests it is expected
that they fail. In other words: anything turning red is bad and more so if it is
for INVALID requests. Because in this case the browser executes the payload even
if the HTTP response was invalid which might often be used to bypass firewalls
which behave differently.
</p>
HTML
$page =~s{href="(/[^"]+)"}{ 'href="'. garble_url($1). '"' }eg;
for( grep { $_->TESTS } @cat ) {
$page .= "<h3>".html_escape($_->SHORT_DESC)."</h3>";
$page .= $_->LONG_DESC_HTML;
$page .= "<p class=runtest><a href=/".$_->ID.">Run Test</a></p>\n";
}
$page .= "</body></html>";
return "HTTP/1.0 200 ok\r\n".
"Content-type: text/html\r\n".
"Content-length: ".length($page)."\r\n".
"\r\n".
$page;
}
sub auto {
my $self = shift;
my $type = shift;
return $self->auto_xhr(@_) if $type eq 'xhr';
return $self->auto_js(@_) if $type eq 'js';
return $self->auto_img(@_) if $type eq 'img';
return $self->auto_html(@_) if $type eq 'html';
die;
}
sub auto_xhr {
my ($self,$cat,$pages,$spec,$qstring,$rqhdr) = @_;
$pages ||= [qw(eicar.zip eicar.txt novirus.txt)];
if (!ref $pages) {
$pages = 'eicar.zip|eicar.txt' if $pages eq 'eicar';
$pages = [ split(qr{\|},$pages) ];
}
my $html = _auto_static_html();
my (@bad,$good);
for(@$pages) {
$_ =~m{^[\w\-.]+$} or return; # bad name
my ($hdr,$body,$isbad) = content($_);
if ($isbad) {
push @bad, { file => $_, body => $body, isbad => $isbad }
} else {
$good = { file => $_, body => $body }
}
}
$good ||= do {
my ($hdr,$body,$isbad) = content('novirus.txt');
$isbad and die "novirus.txt should not be bad!";
{ file => 'novirus.txt', body => $body }
};
my @bad_pages = map { $_->{file} } @bad;
my $good_page = $good->{file};
$html .= "<script>\n";
if (@bad_pages and my ($vendor,$msg) = vendor_notice($CLIENTIP)) {
warn "VENDOR_NOTICE($CLIENTIP) $vendor\n";
$msg =~s{(\\|")|(\r)|(\n)|(\t)}{ "\\".($1||($2?'r':$3?'n':'t'))}eg;
$html .= "vendor_notice(\"$msg\");\n";
}
my ($accept) = $rqhdr =~m{^Accept:[ \t]*([^\r\n]+)}mi;
if ($qstring =~m{(?:^|\&)accept=([^&]*)}) {
($accept = $1) =~s{(?:%([a-f\d]{2})|(\+))}{ $2 ? ' ' : chr(hex($1)) }esg;
}
$html .= "accept = '".quotemeta($accept)."';\n" if $accept;
$html .= "fast_feedback = 16384;\n" if $FAST_FEEDBACK;
if (!@bad_pages) {
$html .= "div_title.innerHTML = '<h1>Browser behavior test with XMLHTTPRequest</h1>';\n";
} else {
my $name = $bad[-1]{isbad};
$html .= "bad_name='$name';\n";
$html .= "div_title.innerHTML = '<h1>Firewall evasion test with $name</h1>';\n";
}
$html .= "expect64_harmless = '".encode_base64($good->{body},'')."';\n";
$html .= "file_harmless = '$good_page';\n";
if (@bad_pages) {
$html .= "expect64_bad = [\n".
join(",\n", map { " '".encode_base64($_->{body},'')."'" } @bad).
"\n];\n";
}
$html .= "files_bad = [ ".join(",", map { "'$_'" } @bad_pages)."];\n"
if @bad_pages;
$html .= 'results = "V | '.App::DubiousHTTP->VERSION.'\n";' . "\n";
if (@bad_pages) {
# sanity check with good version
$html .= "checks.push({ num:0, harmless:'". garble_url("/clen/$good_page/close,clen,content").
"', desc:'sanity check without test virus', valid:2, log_header:1, file: '$good_page' });\n";
my $bad_urls = "[".join(",", map { "'".garble_url("/clen/$_/close,clen,content")."'" } @bad_pages)."]";
$html .= "checks.push({ num:0, bad: $bad_urls, ".
"desc:'sanity check with test virus', valid:2, log_header:1, expect_bad: 1 });\n";
if ($bad_pages[-1] eq 'eicar.txt') {
my $junkbody = encode_base64((content('junk-eicar.txt'))[1],'');
$html .= "checks.push({ num:0, harmless:'". garble_url("/clen/junk-eicar.txt/close,clen,content").
"', desc:'junk before test virus', valid:99, log_header:1, file: 'junk-eicar.txt', expect: '$junkbody' });\n";
$junkbody = encode_base64((content('eicar-junk.txt'))[1],'');
$html .= "checks.push({ num:0, harmless:'". garble_url("/clen/eicar-junk.txt/close,clen,content").
"', desc:'junk after test virus', valid:99, log_header:1, file: 'eicar-junk.txt', expect: '$junkbody' });\n";
}
} else {
$html .= "checks.push({ num:0, harmless:'". garble_url("/clen/$good_page/close,clen,content").
"', desc:'sanity check', valid:2, log_header:1, file: '$good_page' });\n";
}
my $limit;
for(@cat) {
next if $cat ne 'all' && $_->ID ne $cat;
for my $t ($_->TESTS) {
last if defined $limit && --$limit <= 0;
if (!@bad_pages) {
$html .= sprintf("checks.push({ num:%s, harmless:'%s', desc:'%s', valid:%d, file:'%s' });\n",
$t->NUM_ID, url_encode($t->url($good_page)), quotemeta(html_escape($t->DESCRIPTION)), $t->VALID,$good_page)
} else {
my $bad_urls = "[".join(",", map { "'".url_encode($t->url($_))."'" } @bad_pages)."]";
$html .= sprintf("checks.push({ num:%s, bad:%s, harmless: '%s', desc:'%s', valid:%d });\n",
$t->NUM_ID, $bad_urls, url_encode($t->url($good_page)), quotemeta(html_escape($t->DESCRIPTION)), $t->VALID)
}
}
}
$html .= sprintf("reference='%x' + Math.floor(time()/1000).toString(16);\n", rand(2**32));
$html .= "runtests();\n</script>\n";
return "HTTP/1.0 200 ok\r\n".
"Content-type: text/html\r\n".
"Content-length: ".length($html)."\r\n".
"ETag: ".App::DubiousHTTP->VERSION."\r\n".
"\r\n".
$html;
}
sub auto_img {
my ($self,$cat) = @_;
_auto_imgjshtml($cat, 'Browser behavior test with img tag', 'ok.png', sub {
my ($url,$id) = @_;
return "<img id='$id' src='$url' onload='set_success(\"$id\",\"img\");' onerror='set_fail(\"$id\",\"img\");' />";
});
}
sub auto_js {
my ($self,$cat) = @_;
_auto_imgjshtml($cat, 'Browser behavior test with script tag', 'set_success.js', sub {
my ($url,$id) = @_;
#return "<script id='$id' src='$url' onload='set_load(\"$id\",\"js\");' onerror='set_fail(\"$id\",\"js\");' onreadystatechange='set_load(\"$id\",\"js\");'></script>";
return <<"JS"
function(div) {
var s = document.createElement('script');
s.setAttribute('src','$url');
s.setAttribute('id','$id');
s.setAttribute('onload','set_load(\"$id\",\"js\");');
s.setAttribute('onreadystatechange','set_load(\"$id\",\"js\");');
s.setAttribute('onerror','set_fail(\"$id\",\"js\");');
div.appendChild(s);
}
JS
});
}
sub auto_html {
my ($self,$cat) = @_;
_auto_imgjshtml($cat, 'Browser behavior test with iframe including HTML', 'parent_set_success.html', sub {
my ($url,$id) = @_;
return "<iframe id='$id' src='$url' onload='set_load(\"$id\",\"html\");' onerror='set_fail(\"$id\",\"html\");' onreadystatechange='set_load(\"$id\",\"html\");'></iframe>";
});
}
sub _auto_imgjshtml {
my ($cat,$title,$page,$mkhtml) = @_;
my $jsglob = '';
$jsglob .= sprintf("reference='%x' + Math.floor(time()/1000).toString(16);\n", rand(2**32));
$jsglob .= "fast_feedback = 16384;\n" if $FAST_FEEDBACK;
my $rand = rand();
for(@cat) {
next if $cat ne 'all' && $_->ID ne $cat;
for($_->TESTS) {
my $num = $_->NUM_ID;
my $xid = quotemeta(html_escape($_->LONG_ID));
my $url = url_encode($_->url($page));
my $html = $mkhtml->("$url?rand=$rand",$xid);
$jsglob .= "checks.push({ "
. "num: $num, page: '$url', xid: '$xid', "
. 'desc: "'.quotemeta(html_escape($_->DESCRIPTION)) .'",'
. 'valid: '.$_->VALID .','
. 'html: '.($html =~m{^function} ? $html : '"'.quotemeta($html).'"')
."});\n";
}
}
$jsglob .= "div_title.innerHTML = '<h1>".html_escape($title)."</h1>';";
$jsglob .= "runtests()\n";
my $html = _auto_static_html()."<script>$jsglob</script>\n";
return "HTTP/1.0 200 ok\r\n".
"Content-type: text/html\r\n".
"Content-length: ".length($html)."\r\n".
"ETag: ".App::DubiousHTTP->VERSION."\r\n".
"\r\n".
$html;
}
sub _auto_static_html { return <<'HTML'; }
<!doctype html>
<meta charset="utf-8">
<style>
body { font-family: Verdana, sans-serif; }
#title { padding: 1em; margin: 1em; border: 1px; border-style: solid; color: #000; background: #eee; }
#title h1 { font-size: 190%; }
#vendor_notice { padding: 2em; margin: 1em; background: #000000; color: #ff0000; font-size: 150%; display: none; }
#nobad { padding: 2em; margin: 1em; background: #ff3333; display: none; }
#nobad div { font-size: 150%; margin: 0.5em; }
#noevade { padding: 1em; margin: 1em; background: green; display: none; }
#overblock { padding: 1em; margin: 1em; background: #ff9933; display: none; }
#evadable { padding: 1em; margin: 1em; background: #ff3333; display: none; }
#urlblock { padding: 1em; margin: 1em; background: #ffff00 ; display: none; }
#urlblock div { font-size: 150%; margin: 0.5em; }
#notice { padding: 1em; margin: 1em; background: #e9f2e1; display: none; }
#warnings { padding: 1em; margin: 1em; background: #e3a79f; display: none; }
#process { padding: 1em; margin: 1em; background: #f2f299; }
#debug { padding: 1em; margin: 1em; }
.desc { font-size: 110%; }
.srclink { font-variant: small-caps; }
.trylink { font-variant: small-caps; }
#eicar { font-family: Lucida Sans Typewriter,Lucida Console,monaco,Bitstream Vera Sans Mono,monospace; padding: 0.5em; margin: 0.5em; border-style: solid; border-width: 1px; }
</style>
<div id=noscript>
You need to have JavaScript enabled to run this tests.
</div>
<div id=title></div>
<div id=vendor_notice> </div>
<div id=nobad> </div>
<div id=urlblock> </div>
<div id=evasions></div>
<div id=process></div>
<div id=evadable> </div>
<div id=overblock> </div>
<div id=noevade> </div>
<div id=warnings><h1>Serious Problems</h1></div>
<div id=notice><h1>Behavior in Uncommon Cases</h1></div>
<div id=debug><h1>Debug</h1></div>
<div id=work style='display:none;'></div>
<script>
'use strict';
var time = Date.now || function() { return +new Date; };
function vendor_notice(msg) {
var div = document.getElementById('vendor_notice');
if (!div) return;
div.innerHTML = msg;
lib/App/DubiousHTTP/Tests.pm view on Meta::CPAN
var div_notice = document.getElementById('notice');
var div_warnings = document.getElementById('warnings');
var div_nobad = document.getElementById('nobad');
var div_evasions = document.getElementById('evasions');
var div_process = document.getElementById('process');
var div_work = document.getElementById('work');
var div_title = document.getElementById('title');
var fast_feedback = 0;
var checks = [];
var current_test = null;
var results = '';
var done = 0;
var reference;
var expect64_harmless;
var file_harmless;
var expect64_bad;
var files_bad = null;
var skip_bad = 0;
var bad_name = null;
var accept = null;
var have_warn = {};
var evasions = 0;
var evasions_blocked = 0;
var overblocked = 0;
var maybe_overblocked = 0;
var browser_invalid = 0;
var rand = Math.random();
function add_warning(m,test) {
var id = test['num'];
div_warnings.innerHTML = div_warnings.innerHTML + "[" + id + "] " + m + ": <span class=desc>" + test['desc'] + "</span>" +
" <a class=trylink target=_blank download='" + test['file'] + "' href=" + test['page'] + ">try</a>" +
" <a class=srclink target=_blank href=/src" + test['page'] + ">src</a>" +
"<br>";
div_warnings.style.display = 'block';
}
function add_notice(m,test) {
var id = test['num'];
div_notice.innerHTML = div_notice.innerHTML + "[" + id + "] " + m + ": <span class=desc>" + test['desc'] + "</span>" +
" <a class=trylink target=_blank download='" + test['file'] + "' href=" + test['page'] + ">try</a>" +
" <a class=srclink target=_blank href=/src" + test['page'] + ">src</a>" +
"<br>";
div_notice.style.display = 'block';
}
function add_debug(m,test) {
if (test) {
m = "[" + test['num'] + "] " + m;
}
div_debug.innerHTML = div_debug.innerHTML + m + (test ?
" <a class=trylink target=_blank download='" + test['file'] + "' href=" + test['page'] + ">try</a>" +
" <a class=srclink target=_blank href=/src" + test['page'] + ">src</a>"
: "" ) + "<br>";
}
function escapeAttribute(attr) {
return attr
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function _log(m) {
try { console.log(m) }
catch(e) {}
}
function xhr(method,page,payload,callback) {
var req = null;
try { req = new XMLHttpRequest(); }
catch(e) {
try { req = new ActiveXObject("Microsoft.XMLHTTP"); }
catch(e) {
try { req = new ActiveXObject("Msxml2.XMLHTTP"); }
catch(e) { req = null; }
}
}
if (!req) {
return null;
}
try {
try { req.overrideMimeType('text/plain; charset=x-user-defined'); }
catch(e) { _log("no support for overrideMimeType"); }
if (callback) {
var done = 0;
req.ontimeout = function() {
if (!done) {
done = 1;
callback(req,'timeout');
}
};
req.onreadystatechange = function() {
if (!done && req.readyState == 4) {
done = 1;
callback(req);
}
};
}
req.open(method, page, true);
if (accept != null) {
try { req.setRequestHeader('Accept',accept); }
catch(e) { _log("no support for setRequestHeader") }
}
try { req.timeout = 5000; }
catch(e) { _log("no support for xhr timeout") }
req.send(payload);
} catch(e) {
_log(e);
req = null;
}
return req;
}
( run in 0.480 second using v1.01-cache-2.11-cpan-e1769b4cff6 )