Showing posts with label perl. Show all posts
Showing posts with label perl. Show all posts

Tuesday, 25 January 2011

Perl Tip: finding module path

I thought I'd share a little Perl Tip/Trick that saves me a lot of trouble. Say you have a perl module installed and you need to make a correction to the source code. For some reason you can't find where the module installed to though. You could spend time using 'find' searching through folders or do this:

perl -M'Data::Dumper' -M'' -e 'print Dumper(\%INC)' 

This will dump all the modules loaded out to screen for you. You can of course pipe this into grep to look for the specific module or modules you want. You will then get the path.

Observe:



c0malod@Takeshi:/Tools$ perl -M'Data::Dumper' -M'Time::HiRes' -e 'print Dumper(\%INC)'
$VAR1 = {
          'warnings/register.pm' => '/usr/share/perl/5.10/warnings/register.pm',
          'bytes.pm' => '/usr/share/perl/5.10/bytes.pm',
          'XSLoader.pm' => '/usr/lib/perl/5.10/XSLoader.pm',
          'Carp.pm' => '/usr/share/perl/5.10/Carp.pm',
          'Exporter/Heavy.pm' => '/usr/share/perl/5.10/Exporter/Heavy.pm',
          'vars.pm' => '/usr/share/perl/5.10/vars.pm',
          'strict.pm' => '/usr/share/perl/5.10/strict.pm',
          'Time/HiRes.pm' => '/usr/lib/perl/5.10/Time/HiRes.pm',
          'Exporter.pm' => '/usr/share/perl/5.10/Exporter.pm',
          'warnings.pm' => '/usr/share/perl/5.10/warnings.pm',
          'AutoLoader.pm' => '/usr/share/perl/5.10/AutoLoader.pm',
          'overload.pm' => '/usr/share/perl/5.10/overload.pm',
          'Config.pm' => '/usr/lib/perl/5.10/Config.pm',
          'DynaLoader.pm' => '/usr/lib/perl/5.10/DynaLoader.pm',
          'Data/Dumper.pm' => '/usr/local/lib/perl/5.10.1/Data/Dumper.pm'
        };


OR:
c0malod@Takeshi:/Tools$ perl -M'Data::Dumper' -M'Time::HiRes' -e 'print Dumper(\%INC)' | grep 'HiRes'
          'Time/HiRes.pm' => '/usr/lib/perl/5.10/Time/HiRes.pm',

Simple little trick, but it works wonders. Cheers.


Friday, 5 March 2010

Monitoring those NTLM authentication Proxies

So, now that we have discussed how to overcome the challenge of testing those NTLM proxies, we move on to a better use. Load testing is fine and good, but how often do you really need to load test. Let's say though, that you have a couple dozen of these proxies spread out all over the globe, and for some reason MOM just doesn't cut it with monitoring the actual request performance on these proxies.

Using the base design of the previous script, I created one that is set to test each proxy in the environment once, through the same URL, and measure the delay in response. This is not 100% accurate as internal networking issues can cause some unaccounted fluctuation, but it is good enough for general purposes. So I created a mysql database with two tables. One is a status table, which contains the proxy, a counter, and the current known status. This is especially useful as the script pulls the proxies to test from this table, so adding or removing proxies is just a matter of doing it in the database, instead of altering code. the other table is a simple log file.

The script times the delay in the final response from the initiation of the request and then assigns a status based on this result. It compares it to the current status listed for that proxy, if it is different, it updates the table and emails out an alert. If it continues in a persistent bad state, it will send out a new alert again on the 12 straight return of that bad status. This ensures we are notified that the status is persisting, but doesn't flood us every 10 minutes, which is how frequently the script runs. Anyways, without further ado, here is my simplistic little proxy monitoring script

----------------code--------------------

#!/usr/bin/perl -w
use threads;
use DBI;
use LWP;
use LWP::UserAgent;
use HTTP::Request;
use Authen::NTLM(nt_hash, lm_hash);
use Authen::NTLM::HTTP;
use Time::HiRes qw( gettimeofday );
use Math::Round;
use Net::SMTP;

#Opens the connection to the datbase and prepares the statement handles we will need to call on.
our $dbh = DBI->connect("DBI:mysql:Proxy_Health", , ) or die "Unable to connect to database $DBI::errstr";
our $statuschk = $dbh->prepare("SELECT * from status WHERE proxy=?");
our $statusupd = $dbh->prepare("UPDATE status SET status=? , count=? where proxy=?");
our $logsth=$dbh->prepare("INSERT INTO chklog(proxy,delay) VALUES(?,?)");

#pulls the lsit of proxies from the datbase and maps them to a hash
%proxies= map {$_ => 0 } @{ $dbh->selectcol_arrayref("Select proxy from status" )};

#generates a worker thread for each proxy to test
 my $threadcount = 0;
 foreach (keys %proxies){
$threadcount+=1;
$thrs[$threadcount]= threads->create(\&Test, $_);

}
#performs blcoking for the threads, and returns the result of each test and inserts them into the chklog table
 foreach (keys %proxies){
$proxies{$_}= $thrs[$threadcount]->join;
$proxy_human = $_ ;
$proxy_human=~s/http:\/\///;
$proxy_human=~s/:80//;
$logsth->execute($proxy_human, $proxies{$_});
$threadcount-=1;
}

#Takes the results, and comapres the current status of the proxy to the last recorded status of the proxy. If the status has changed, it updates the status table and sends an alert. If the status has remained the same but is in a negative state, it increments a counter. Every 12 checks that return that negative result will generate a new Alert.
foreach (keys %proxies){
my $scount = 0;
if ($proxies{$_}>= 120){ $status = 'DOWN';}
elsif ($proxies{$_}>= 90){ $status = 'CRITICAL';}
elsif ($proxies{$_}>= 60){ $status = 'MAJOR';}
elsif ($proxies{$_}>= 40){ $status = 'MINOR';}
elsif ($proxies{$_}>= 20){ $status = 'SLOW';}
else{$status = 'GOOD';}
$statuschk->execute($_);
my @statusline = $statuschk->fetchrow_array;

if ($status eq $statusline[1]){
if ($status eq'GOOD'){last;}
elsif ($statusline[2]==11){
$scount =1;
&Alert($_, $status);
print "ALERT $_ !\n";
}
else{
$scount= $statusline[2] +1;
}
if ($scount==1){
&Alert($_, $status);
print "ALERT $_ !\n";
}
$statusupd->execute($status,$scount,$_);
}
else{
if ($status eq'GOOD'){$scount=0;}
else{$scount=1;}
$statusupd->execute($status,$scount,$_);
&Alert($_, $status);
print "ALERT $_ !\n";
}
}

 #

 #This function is what the worker threads run to test their given proxy.
sub Test{
#pulls the proxy from the passed parameters, sets the target as maps.google.com because that site is set to 'private' meaning the proxy will not cache it. It then retrieves the hostname of the local machine and the login credentials, so that it can properly negotiate NTLM authentication with the proxy server
my $proxy=$_[0];
my $url="http://maps.google.com";
our $workstation = `hostname` ;
my $user=;
my $my_pass = ;

#instanatiates the LWP user agent , sets the proxy, and sets the timeout to 120 seconds, because this is the timeout used on our ISA installs
my $ua =  new LWP::UserAgent(keep_alive=>1);
$ua->proxy('http', $proxy);
$ua->timeout(120);

#Creates the first request for the target website, starts the counter running and then fires off the request
my $req = HTTP::Request->new(GET => $url);
my $start = gettimeofday();
my $res = $ua->request($req);


#Sets up the data about the client to send the NTLM Authentication Negotiation Message
$client = new_client Authen::NTLM::HTTP(lm_hash($my_pass), nt_hash($my_pass),Authen::NTLM::HTTP::NTLMSSP_HTTP_PROXY, $user, , , $workstation, );

$flags = Authen::NTLM::NTLMSSP_NEGOTIATE_ALWAYS_SIGN | Authen::NTLM::NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED | Authen::NTLM::NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED | Authen::NTLM::NTLMSSP_NEGOTIATE_NTLM | Authen::NTLM::NTLMSSP_NEGOTIATE_OEM ;
$negotiate_msg = $client->http_negotiate($flags);

#Takes the negotiation message and sets it as a header in the request and resends the request
$negotiate_msg = "Proxy-" . $negotiate_msg ;
@pa = split(/:/,$negotiate_msg);
$req->header($pa[0] => $pa[1]);
$res = $ua->request($req);

#Strips the NTLM challenge message from the response header and parses it
my $challenge_msg = "Proxy-Authenticate: " . $res->header("Proxy-Authenticate");

($domain, $flags, $nonce, $ctx_upper, $ctx_lower) = $client->http_parse_challenge($challenge_msg);

if ($domain or $ctx_upper or $ctx_lower){$placeholder=1;}

#Takes the nonce and flags from the challenge message , calculates the final authentication message, sets it as a header and sends it in the final request, recieving the originally requested page in response
$flags = Authen::NTLM::NTLMSSP_NEGOTIATE_ALWAYS_SIGN | Authen::NTLM::NTLMSSP_NEGOTIATE_NTLM | Authen::NTLM::NTLMSSP_REQUEST_TARGET;
$auth_msg = $client->http_auth($nonce, $flags);

@pa = split(/:/,$auth_msg);
$req->header($pa[0] => $pa[1]);
$res = $ua->request($req);

#Stops the timer, calculates the elapsed time rounding to the nearest hudnredth of a second and returns that value to the main thread
my $end = gettimeofday();
my $delta = ($end - $start);
$delta= nearest(.01,$delta);
print "Finished getting $url through $proxy in $delta seconds! \n";
return $delta;

}

#This function actually handles the generation of the email alert for a status change. Depending on the status it picks from different wordings in the email subject and message.
sub Alert{
my $proxy = $_[0];
my $status=$_[1];


if ($status eq 'GOOD'){
$subject="Subject: $proxy has returned to Normal Operation";
$message = "The ProxyHealth Monitor has detected that proxy $proxy has returned to a 'GOOD' status and is retrieving pages within an acceptable timeframe.";
}
elsif ($status eq 'SLOW'){
$subject="Subject: $proxy is experiecing delay";
$message="The ProxyHealth Monitor has detected that the proxy $proxy is experiencing slowness in processing web requests. The system will continue to monitor and will send an update when the status changes.";
}
elsif($status eq 'MINOR'){
$subject="Subject: $proxy is experiencing a Performance Problem";
$message="The ProxyHealth Monitor has detected that the proxy $proxy is suffering noticeable slowness in processing web requests. It's current status is rated as 'MINOR'. The system will continue to monitor and will send an update when the status changes.";
}
elsif($status eq 'MAJOR'){
$subject="Subject: $proxy is experiencing a Major Performance Problem";
$message="The ProxyHealth Monitor has detected that the proxy $proxy is suffering serious slowness in processing web requests. It's current status is rated as 'MAJOR'. The system will continue to monitor and will send an update when the status changes.";
}
elsif($status eq 'CRITICAL'){
$subject="Subject: $proxy is experiencing a Critical Performance Problem";
$message="The ProxyHealth Monitor has detected that the proxy $proxy is facing a 'CRITICAL' performance decrease. Web traffic throguh this proxy will be extremely slow. The system will continue to monitor and will send an update when the status changes.";
}
elsif($status eq 'DOWN'){
$subject="Subject: $proxy is DOWN!";
$message="The ProxyHealth Monitor has detected that traffic through $proxy is exceeding the timeout limit of 2 minutes. This has led to the system declaring the proxy as being 'DOWN'. Web requests through this proxy will FAIL due to timeout. The system will continue to monitor and will send an update when the status changes.";
}



my $mailer= Net::SMTP->new(, Hello=> );
$mailer->mail();
$mailer->to();
$mailer->data();
#Sets the UK and US Security Team Distribution lists as the Recipients
$mailer->datasend('To: , ');
$mailer->datasend("\n");
$mailer->datasend('Return-Path:');
$mailer->datasend("\n");
#Sets a header that will tell the mail client that replies are to go to the Security Distribution lists and not back to the fake address used to send the alert.
$mailer->datasend('Reply-To:, ');
$mailer->datasend("\n");
$mailer->datasend('FROM:');
$mailer->datasend("\n");
#Sets the message importance to high
$mailer->datasend('Importance: High');
$mailer->datasend("\n");
$mailer->datasend($subject);
$mailer->datasend("\n\n");
$mailer->datasend($message);
$mailer->dataend();
$mailer->quit;



}


LWP and NTLM Proxy Authentication

During the course of my duties, I had a need to load test some proxy servers. To do this, we decided to use ISA logs as sources for test traffic. so the objective was seemingly simple, write a quick LWP script that parses an ISA log for urls, then goes and tries to retrieve them through the target proxy. Oh and , of course, make it multi-threaded so we can send tons and tons of traffic at a time. Where it gets a little more complicated is this: the proxies in question all use NTLM Authentication. I wasn't discouraged, at first, but soon discovered that I could not find anyone who had managed to make LWP work with an NTLM proxy. Sure, I could have kludged it together with something like CNTLM, but that didn't feel right, and didn't provide for solid re usability.

Fortunately, I did find Yee Man Chan's Authen::NTLM Module which I was able to appropriately adapt to my purposes.It  is important to not that this Yee Man Chan's module not the one with the same namespace . You can tell which is which by the version numbers. Anyways, the script I wrote takes a proxy address, an isa log file and a number of threads as arguments, and proceeds to slam said proxy into oblivion. Here it is.Please feel free to leave comments and/or feedback.


-------------------------------------code-------------------------------------------------------------

#!/usr/bin/perl


use threads;
use Thread::queue;
use LWP;
use LWP::UserAgent;
use HTTP::Request;
use Authen::NTLM(nt_hash, lm_hash);
use Authen::NTLM::HTTP;


#Checks to ensure the user has invoked the script correctly
unless(scalar(@ARGV) ==3){
print "Proper usage is proxytest.pl <# of threads> \n";
print "Proxy must be entered as http://: or http://:\n ";
exit;
}

#Begin instantiating our queues
our $users = new Thread::Queue;
our $urls = new Thread::Queue;

#Takes the apssed parameters and sets them. This is the ISA log file being parsed for test URLS, the number of threads to use in testing, and the proxy being tested
my $logfile = $ARGV[0];
my $numthreads = $ARGV[1];
my $proxy = $ARGV[2];

#Collect the hostname for the local machine, this is important for the NTLM Negotiation that will be happening later
our $workstation = `hostname` ;
our $placeholder = 0;

#Verifies that the proxy was entered in the correct format
unless ($proxy=~/^http:\/\/[A-Za-z0-9\.]+:\d+$/){
print "Proxy must be entered as http://: or http://:\n ";
exit;
}

#Enqueues the test accounts to use
$users->enqueue();

#Reads through the supplied log file, and collects all of the URLs and enqueues them for the worker threads to use
open ISALOG, "<$logfile";

while (){
chomp;
if($_=~/\banonymous\b/i){next;}
if($_=~/\bhttp:\/\/\S+\b/i){$urls->enqueue($&);}
}

close ISALOG;
print "\n Done Reading Log! \n\n";

#Instantiates a number of worker threads based on the parameter passed when invoking the script
for($tcount=1; $tcount<=$numthreads;$tcount++){
$thrs[$tcount]= threads->create(\&printoff, $tcount );
}
#Sets blockings joins for each one of these asynchronous worker threads
for($tcount=1; $tcount<=$numthreads;$tcount++){
$thrs[$tcount]->join;
}

#foreach(@thrs){$_->join;}


#The meat and potatoes
sub printoff{
#Dequeues a URL and username to use. It then re-enqueues the username, sticking back at the end of the Queue to be used over again
my $tid = $_[0];
my $url = $urls->dequeue_nb;
my $user = $users->dequeue;
$users->enqueue($user);

#While it had a valid URL, it will perform the below tests
while ($url){


#Password is set here. This password is static for all of the used test accounts
my $my_pass = ;

#Creates the LWP User Agent, tells it to use the supplied proxy, and sends the initial HTTP GET request for the supplied URL and takes in a response
my $ua =  new LWP::UserAgent(keep_alive=>1);
$ua->proxy('http', $proxy);
$ua->timeout(30);
my $req = HTTP::Request->new(GET => $url);
my $res = $ua->request($req);

#Once the initial request has been sent out, the proxy will send back an NTLM negotiate message
#We set up the NTLM authentication client response by passing ntlm hashes of the username, password, domain, and workstation hostname
$client = new_client Authen::NTLM::HTTP(lm_hash($my_pass), nt_hash($my_pass),Authen::NTLM::HTTP::NTLMSSP_HTTP_PROXY, $user, , , $workstation, );
#Here we set the NTLM protocol flags that we wish to be accepted
$flags = Authen::NTLM::NTLMSSP_NEGOTIATE_ALWAYS_SIGN | Authen::NTLM::NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED | Authen::NTLM::NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED | Authen::NTLM::NTLMSSP_NEGOTIATE_NTLM | Authen::NTLM::NTLMSSP_NEGOTIATE_OEM ;

#We then take the client data, and the flags and jam them into a header, and add it back to the original request, and resend it.
$negotiate_msg = $client->http_negotiate($flags);

$negotiate_msg = "Proxy-" . $negotiate_msg ;
@pa = split(/:/,$negotiate_msg);

$req->header($pa[0] => $pa[1]);
#The proxy then sends back an NTLM challenge response, which we strip from the message and parse using the NTLM methods provided by the module
$res = $ua->request($req);

my $challenge_msg = "Proxy-Authenticate: " . $res->header("Proxy-Authenticate");

($domain, $flags, $nonce, $ctx_upper, $ctx_lower) = $client->http_parse_challenge($challenge_msg);
#Kludged together fix. for some reason it generates errors if you do not do this. Possibly an oddity about the way we are using the NTLM module
if ($domain or $ctx_upper or $ctx_lower){$placeholder=1;}

#We set the next round of flags, take the Nonce which we gained from parsing the challenge message, and send back a final authentication message. Once the proxy recieves this, it processes the original GET request
$flags = Authen::NTLM::NTLMSSP_NEGOTIATE_ALWAYS_SIGN | Authen::NTLM::NTLMSSP_NEGOTIATE_NTLM | Authen::NTLM::NTLMSSP_REQUEST_TARGET;
$auth_msg = $client->http_auth($nonce, $flags);

@pa = split(/:/,$auth_msg);
$req->header($pa[0] => $pa[1]);
$res = $ua->request($req);
print "Finished getting $url \n";
#my $bytes = length $res->content;
#print " $url was $bytes bytes \n";
#print $res->code;
#print "\n\n" . $res->content;
#We then dequeue the next URL and continue on until there are no more URLs. The worker thread will then attempt to join. when all worker threads have joined, the code exits.
$url = $urls->dequeue_nb;


}

}






Wednesday, 3 March 2010

Lessons Learned: Self-referencing local file includes...

So I had a small incident at work today. I found a perl cgi script that had a local file include/os command injection vulnerability on it. After confirming this vulnerability, i decided to try and pull the source code for the vulnerable script, and the system choked. When I went to try something else, I was greeted by an ugly apache 500 server error. At first I just frowned and went back to a command string I had already validated worked. 500 error again. apparently somewhere in the mix, I am unsure if it is apache itself, mod_perl, or a condition created on the OS level, did not like the script trying to read itself and return it back out through apache. I suppose you could class this as an inadvertent denial of service attack