App-DubiousHTTP

 view release on metacpan or  search on metacpan

lib/App/DubiousHTTP/Tests.pm  view on Meta::CPAN

use strict;
use warnings;
package App::DubiousHTTP::Tests;
use App::DubiousHTTP::Tests::Common;
use App::DubiousHTTP;
use MIME::Base64 'encode_base64';

my @cat;
for my $cat ( qw( Chunked Compressed Clen Broken Mime MessageRfc822 Range ) ) {
    my $mod = 'App::DubiousHTTP::Tests::'.$cat;
    eval "require $mod" or die "cannot load $mod: $@";
    push @cat, $mod;
}

sub categories { @cat }
sub make_response {
    my $page = <<'HTML';
<!doctype html><html><body>
<link rel="stylesheet" href="/stylesheet.css">
<link rel="stylesheet" href="/stylesheet.css">
<link rel="icon" href="/favicon.ico" type="image/vnd.microsoft.icon">
<h1>HTTP standard conformance tests - HTTP evader</h1>

<p>
While HTTP seems to be a simple protocol it is in reality complex enough that
different implementations of the protocol vary how the behave in case of HTTP
responses which are either slightly invalid or valid but uncommon.
These interpretation differences is critical if a firewall behaves
differently then the browser it should protect because it can be abused to
bypass the protection of the firewall.
</p>

<p>
The following tests are intended to test the behavior of browsers regarding
invalid or uncommon HTTP responses. And if there is a firewall or proxy between
the test server and the browser then it can be seen how this affects the results
and if a bypass of the protection would be possible.
More information about bypassing firewalls using interpretation differences can
be found <a href="http://noxxi.de/research/semantic-gap.html">here</a>.
</p>

<ul>
<li><a href=#xhr_eicar>Firewall evasion test - Bulk test with virus payload using XMLHttpRequest</a></li>
<li><a href=#xhr_novirus>Bulk test with innocent payload using XMLHttpRequest</a></li>
<li><a href=#js>Bulk test with innocent payload using script tag</a></li>
<li><a href=#img>Bulk test with innocent payload using img tag</a></li>
<li><a href=#iframe>Bulk test with innocent payload using iframe tag</a></li>
<li><a href=#other>Various non-bulk tests</a></li>
</ul>

<hr>

<a name=xhr_eicar>
<h2>Firewall evasion test - Bulk test with virus payload (XHR)</h2>
</a>

<p>
This bulk test tries to transfer the <a
href="http://www.eicar.org/86-0-Intended-use.html">EICAR test virus</a> from the
server to the client. This test virus is commonly used for basic tests of
antivirus and should be detected by every firewall which does deep
inspection to filter out malware. Since this virus itself is not malicious it is
safe to run this test.
</p><p>
But, the transfer is done with various kinds of uncommon or even invalid HTTP
responses to check if the inspection of the firewall can be bypassed this way.
The response from the server will then compared to the expected payload and
hopefully all transfers will be blocked either by the firewall or are considered
invalid by the browser.
</p><p>
The test uses XMLHttpRequests to issue the request and get the response. In most but
not all cases this shows the same behavior as other HTTP requests by the browser
(i.e. loading image, script,...). But to verify that an evasion is actually
possible with normal download one should use the provided link to actually test
the evasion.
</p>
<p id=test_virus class=runtest><a href="/auto/all/eicar">Run Test with <strong>EICAR test virus</strong> payload</a></p>

<a name=xhr_novirus>
<h2>Bulk test with innocent payload (XHR)</h2>
</a>

<p>
This is the same bulk test as the previous one but this time the payload is
completely innocent. This test can be used to find out the behavior of the
browsers itself, i.e. how uncommon or invalid HTTP responses are handled by the
browser. It can also be used to check if the use of proxies changes this
behavior and if firewalls block innocent payload if it is transferred using an
uncommon or invalid HTTP response.
</p>
<p id=test_novirus class=runtest><a href="/auto/all/novirus.txt">Run Test with <strong>innocent</strong> payload</a></p>

<a name=js>
<h2>Bulk test with innocent Javascript</h2>
</a>

<p>
Contrary to the previous bulk tests this one is not done with XMLHttpRequest but
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!";

lib/App/DubiousHTTP/Tests.pm  view on Meta::CPAN

		"', 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; }



( run in 2.282 seconds using v1.01-cache-2.11-cpan-e1769b4cff6 )