#!/usr/bin/perl
# Apache profiler script
# Written by Eldar "Wireghoul" Marcussen - http://www.justanotherhacker.com

use strict;
use warnings;
use IO::Socket::INET;
use IO::Socket::SSL;
use Getopt::Long;

my $VERSION = '0.3';
my $AUTHOR = 'Eldar "Wireghoul" Marcussen';
my $timeout = 20;
my $path = '/';
my ($sock, $force);

my $opts = GetOptions(
    "basepath=s" => \$path,
    "timeout"    => \$timeout,
    "force"      => \$force,
    "version"    => sub { print "aprof version: $VERSION\n"; exit 0; },
    "help"       => sub { &show_help },
);

&show_help unless $ARGV[0];
my ($ssl, $target, $port) = &_parse_uri($ARGV[0]);
&banner;
# General stuff here
$path = "/".$path if $path !~ /^\//;
$path = $path."/" if $path !~ /\/$/;
&print_msg('Analysing: '. ($ssl ? 'https' : 'http') . "://$target$path\n");
if (!$force) {
    my $tmp = &check_server;
    chop($tmp);
    if ($tmp !~ /Apache/) {
        &error_msg("Not Apache ($tmp)! Use --force if you think the server is faking it.\n");
        exit;
    }
    &status_msg("Server signature: $tmp\n");
}

# Module detection here
&status_msg("Attempting to identify modules:\n");
&check_default_host;
&check_mod_alias;
&check_mod_caucho;
&check_mod_cgi;
&check_mod_dav;
&check_mod_deflate;
&check_embperl;
&check_mod_imagemap;
&check_mod_info;
&check_mod_mime;
&check_mod_php;
&check_mod_proxy_balancer;
&check_mod_python;
&check_mod_rivet;
&check_mod_ruby;
&check_mod_status;
&check_mod_setenvif;
&check_Frontpage;
&status_msg("Done!\n\n");

sub check_server {
    if ( &request("GET $path HTTP/1.1\r\nHost: $target") =~ m{^Server: (.*)$}m ) {
        return $1;
    }
}

sub check_default_host {
    my $hreq = &request("GET $path HTTP/1.1\r\nHost: $target");
    my $nhreq = &request("GET $path HTTP/1.1\r\nHost: KJAISJNKDCIUW9123ejkasdnKZ");
    if ($hreq =~ /title>(.*)<\/title/i) {
        my $htitle = $1;
        if ($nhreq =~ /title>(.*)<\/title/i) {
            if ($htitle eq $1) {
                &success_msg("Host $target is the default virtual host\n");
                return;
            }
        }
    }
    # in all other cases
    &error_msg("Host $target is NOT the default virtual host\n");
}

sub check_mod_alias {
    return;
}

sub check_mod_php {
    my $php = 0;
    if ( &request("GET $path HTTP/1.1\r\nHost: $target") =~ m{^(X-Powered-By: PHP|Set-Cookie: PHPSESSID|Server: .* PHP/)}m ) {
        $php = 1;
    } elsif ( &request("GETSPHP ${path}index.php HTTP/1.1\r\nHost: $target") =~ m{^HTTP/1.. (200|30[12]|404|500)} && &request("GETSPHP ${path}notaphp.file HTTP/1.1\r\nHost: $target") =~ m{^HTTP/1.. 501} ) {
        $php = 1;
    }
    if ($php) {
        &success_msg("mod_php detected\n");
        &status_msg("Checking what extensions have php bindings:\n");
        foreach my $ext ('.php','.php3','.php4','.php5','.php7','.pht','.phtm','.phtml','.phps') {
            if (&request("GETSPHP ${path}index$ext HTTP/1.1\r\nHost: $target") =~ m{^HTTP/1.. (200|30[12]|404|500)}) {
                &success_msg("\t$ext in use\n");
            }
        }
    }
}

sub check_mod_caucho {
    if ( &request("GET ${path}caucho-status HTTP/1.0") =~ m{(Server: .* Resin/|Status : Caucho Servlet Engine)}m) {
        &success_msg("mod_caucho detected\n");
    }
}

sub check_mod_cgi {
    if ( &request("GETCGI ${path}cgi-bin/ HTTP/1.1\r\nHost: $target") =~ m{^HTTP/1.. (200|30[12]|403|500)} && &request("GETCGI ${path}notacgi.gob HTTP/1.1\r\nHost: $target") =~ m{^HTTP/1.. 501} ) {
        &success_msg("mod_cgi detected\n");
    }
}

sub check_mod_dav {
    return;
}

sub check_mod_deflate {
    return;
}

sub check_embperl {
    if ( &request("GETSPL ${path}somefile.epl HTTP/1.1\r\nHost: $target") =~ m{^HTTP/1.. (200|404|500)} ) {
        &success_msg("mod_perl => Embperl detected\n");
    }
}

sub check_mod_mime {
    if ( &request("GETSVAR ${path}somefile.var HTTP/1.1\r\nHost: $target") =~ m{^HTTP/1.. (200|404|500)} ) {
        &success_msg("mod_mime detected\n");
    }
}


sub check_mod_imagemap {
    if ( &request("GETSMAP ${path}image.map HTTP/1.1\r\nHost: $target") =~ m{^HTTP/1.. (200|404|500)} ) {
        &success_msg("mod_imagemap detected\n");
    }
}

sub check_mod_info {
    my $response = &request("GET ${path}server-info HTTP/1.1\r\nHost: $target");
    if ( $response =~ m{^HTTP/1.. 403} ) {
        &success_msg("mod_info (restricted access) detected\n");
    } elsif ( $response =~ m{Apache Server Information} ) {
        &success_msg("mod_info (public access) detected - http://$target:$port/server-info\n"); # Should be parsed for 100% accurate information
    }
}

sub check_mod_rivet {
    if ( &request("GET ${path}index.rvt HTTP/1.1\r\nHost: $target") =~ m{(Server: .*Rivet)} ) {
      &success_msg("mod_rivet detected\n");
    }
}

sub check_mod_ruby {
    if ( &request("GET ${path}index.rb HTTP/1.1\r\nHost: $target") =~ m{(Server: .*mod_ruby)}m) {
        &success_msg("mod_ruby detected\n");
    }
}

sub check_mod_python { #TODO add check for server headers and other extension bindings
    if ( &request("GETPY ${path}index.psp HTTP/1.1\r\nHost: $target") =~ m{(Server: .*mod_python)}m) {
        &success_msg("mod_python detected\n");
    }
}


sub check_mod_ldap {
    my $response = &request("GET ${path}ldap-status HTTP/1.1\r\nHost: $target");
    if ( $response =~ m{^HTTP/1.. 403} ) {
        &success_msg("mod_ldap (restricted access) detected\n");
    } elsif ( $response =~ m{Apache ldap} ) {
        &success_msg("mod_ldap (public access) detected\n");
    }
}

sub check_mod_qos {
    if ( &request("GET ${path}qos HTTP/1.1\r\nHost: $target") =~ m{^HTTP/1.. (200|403)} ) {
        &success_msg("mod_qos detected\n");
    }
}

sub check_mod_status {
    my $response = &request("GET ${path}server-status/ HTTP/1.1\r\nHost: $target");
    if ( $response =~ m{^HTTP/1.. 403} ) { 
        &success_msg("mod_status (restricted access) detected\n");
    } elsif ( $response =~ m{Apache Status} ) {
        &success_msg("mod_status (public access");
        if ($response !~ m{ExtendedStatus On}) {
            &print_msg(" - full status");
        } else {
            &print_msg(" - limited status");
        }
    &print_msg(") detected http://$target:$port/server-status/\n");
    }
}

sub check_mod_setenvif {
    return;
}

sub check_Frontpage {
    if ( &request("GET ${path}_vti_bin/ HTTP/1.1\r\nHost: $target") =~ m{^HTTP/1.. 200} ) {
        &success_msg("Frontpage detected\n");
    }
}

sub check_mod_proxy_balancer {
    my $response = &request("GET ${path}balancer-manager HTTP/1.1\r\nHost: $target");
    if ( $response =~ m{^HTTP/1.. 403} ) {
        &success_msg("mod_proxy_balancer (restricted access) detected\n");
    }
}


sub request {
# Need timeout
    my $request = shift;
    my $resp = '';
    if (&connect($target, $port, $timeout)) {
        print $sock "$request\r\nConnection: Close\r\n\r\n";
        while (<$sock>) {
            $resp .= $_;
        }
    }
#    warn "DEBUG:\n".$resp;
    return $resp;
}

sub connect {
    my ($target, $port, $timout) = @_;
    if ($ssl) {
        $sock = IO::Socket::SSL->new("$target:$port") or die "Unable to connect to $target:$port: $!\n";
    } else {
        $sock = IO::Socket::INET->new("$target:$port") or die "Unable to connect to $target:$port: $!\n";
    }
    # Reconnect attempts?
    return 1 if ($sock);
}

sub _parse_uri {
    my $uri = shift;
    my @p = (0, '', 80); #Defaults
    # Quick fix to handle uri's without protocol designation
    $uri = "http://$uri" if ($uri !~ m!://!);
    $p[0] = 1 if ($uri =~ m!^https://!);
    $uri =~ m!https?://([^:/]+):?(\d+)?/?!;
    $p[1] = $1;
    if ($2) {
        $p[2]=$2;
    } elsif ($p[0]) {
        $p[2]=443;
    }
    return @p;
}

sub show_help {
    &banner;
    print "Usage: $0 [options] <uri>\n";
    print "Options:\n";
    print "  --basepath\tUse directory as root (default /)\n";
    print "  --force\tIgnore apache server banner check\n";
    print "  --timeout\tTimeout in seconds (default: 20)\n";
    print "  --version\tPrints aprof version number\n";
    print "  --help\tThis screen\n";
    exit;
}

sub banner {
    print "aprof - Apache profiler tool v$VERSION by $AUTHOR\n";
    print "================================[ www.justanotherhacker.com ]===\n";
}

sub success_msg {
    print "[+] ". shift;
}

sub error_msg {
    print "[-] ". shift;
}

sub status_msg {
    print "[*] ". shift;
}

sub print_msg {
    print shift;
}
