Загрузка данных
#!/usr/bin/perl
# Copyright Andrew Gavin 2009-2012
#
# This file is part of OpenDLP.
#
# OpenDLP 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, either version 3 of the License, or
# (at your option) any later version.
#
# OpenDLP 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 OpenDLP. If not, see <http://www.gnu.org/licenses/>.
use DBI;
use Algorithm::LUHN qw/check_digit is_valid/;
use POSIX qw( floor );
use Time::HiRes qw( gettimeofday );
# Status codes:
# -1 = deploying
# 0 = this script started
# 1 = schema enumeration complete
# 2 = in process of grepping stuff
# 3 = done
my $profile = $ARGV[0];
my $target = $ARGV[1];
my $tracker = $ARGV[2];
my $scanname = $ARGV[3];
my $pid = $$;
my $db_admin_file = "../etc/db_admin";
my( $db_username, $db_password ) = "";
my( $username, $password, $mask, $ignore_dbs, $dbs, $ignore_tables, $tables, $ignore_columns, $columns, $rows, $scantype, $regex, $creditcards, $debug ) = "";
my $total_databases = $total_tables = $total_columns = 0;
my $databases_done = $tables_done = $columns_done = 0;
my @db_array = @table_array = @column_array = @creditcard_array = ();
my %data = %whitelist_data = %regexes = ();
open( DB, $db_admin_file ) or die "BLAH";
my $db_line = <DB>;
close( DB );
chomp $db_line;
($db_username, $db_password) = split( ":", $db_line );
my $dbh_local = DBI->connect("DBI:mysql:database=OpenDLP;host=localhost",$db_username,$db_password);
my $string = "UPDATE systems SET pid=?,status=0 WHERE tracker=?";
my $sth_local = $dbh_local->prepare( $string );
$sth_local->execute( $pid, $tracker );
my $string = "SELECT username,password,mask,ignore_dbs,dbs,ignore_tables,tables,ignore_columns,columns,rows,scantype,regex,creditcards,debug FROM profiles where profile=?";
my $sth_local = $dbh_local->prepare( $string );
$sth_local->execute( $profile );
# update log
update_log( "Attempting to start discovery" );
my $results = $sth_local->fetchrow_arrayref();
if( $$results[0] ne "" )
{
$username = $$results[0];
$password = $$results[1];
$mask = $$results[2];
$ignore_dbs = $$results[3];
$dbs = $$results[4];
$ignore_tables = $$results[5];
$tables = $$results[6];
$ignore_columns = $$results[7];
$columns = $$results[8];
$rows = $$results[9];
$scantype = $$results[10];
$regex = $$results[11];
$creditcards = $$results[12];
$debug = $$results[13];
@db_array = split( "\n", $dbs );
@table_array = split( "\n", $tables );
@column_array = split( "\n", $columns );
@creditcard_array = split( "\n", $creditcards );
foreach( @db_array )
{
$_ =~ s/\r$//g;
$_ =~ s/\n$//g;
}
foreach( @table_array )
{
$_ =~ s/\r$//g;
$_ =~ s/\n$//g;
}
foreach( @column_array )
{
$_ =~ s/\r$//g;
$_ =~ s/\n$//g;
}
foreach( @creditcard_array )
{
$_ =~ s/\r$//g;
$_ =~ s/\n$//g;
}
my @regex_array = split( ",", $regex );
foreach( @regex_array )
{
my $string2 = "SELECT name,pattern FROM regexes WHERE number = ?";
my $sth_local2 = $dbh_local->prepare( $string2 );
$sth_local2->execute( $_ );
my $results2 = $sth_local2->fetchrow_arrayref();
$regexes{$$results2[0]} = $$results2[1];
$sth_local2->finish();
}
update_log( "Retrieved all profile information" );
if( $scantype eq "mssql_agentless" )
{
update_log( "Starting Microsoft SQL server scan" );
do_mssql_agentless();
update_log( "Done with Microsoft SQL server scan" );
}
elsif( $scantype eq "mysql_agentless" )
{
update_log( "Starting MySQL scan" );
do_mysql_agentless();
update_log( "Done with MySQL scan" );
}
my $string = "UPDATE systems SET status=3,control=? WHERE tracker=?";
my $sth_local = $dbh_local->prepare( $string );
$sth_local->execute( 'finished', $tracker );
}
else
{
# could not find policy info
update_log( "Could not find policy...quitting" );
my $string = "UPDATE systems SET status=3,control=? WHERE tracker=?";
my $sth_local = $dbh_local->prepare( $string );
$sth_local->execute( 'finished', $tracker );
$sth_local->finish();
}
$sth_local->finish();
$dbh_local->disconnect();
sub do_mysql_agentless
{
# get all dbs/tables/columns
my $dbh = DBI->connect("DBI:mysql:database=information_schema;host=$target",$username,$password);
if( $dbh )
{
update_log( "Successfully authenticated to database server" );
}
else
{
update_log( "Could not authenticate to MySQL database server...quitting" );
my $string = "UPDATE systems SET status=3,control=? WHERE tracker=?";
my $sth_local = $dbh_local->prepare( $string );
$sth_local->execute( 'finished', $tracker );
$sth_local->finish();
$dbh_local->disconnect();
exit(0);
}
my $string = "SELECT DISTINCT table_schema,table_name,column_name FROM columns";
my $sth = $dbh->prepare( $string );
$sth->execute();
while( my $results = $sth->fetchrow_arrayref() )
{
my $database = $$results[0];
my $table = $$results[1];
my $column = $$results[2];
push @{$data{$database}{$table}}, $column;
}
$sth->finish();
my $string = "UPDATE systems SET status=1 WHERE tracker=?";
my $sth_local = $dbh_local->prepare( $string );
$sth_local->execute( $tracker );
$sth_local->finish();
update_log( "Done enumerating entire database server schema" );
# figure out what we're going to scan based on policy
whitelist_schema();
update_log( "Done whitelisting/blacklisting database server schema...beginning to parse actual data" );
my $string = "UPDATE systems SET status=?,dbtotal=?,tabletotal=?,columntotal=? WHERE tracker=?";
my $sth_local = $dbh_local->prepare( $string );
$sth_local->execute( "2", $total_databases, $total_tables, $total_columns, $tracker );
$sth_local->finish();
# grab actual data
foreach my $db( sort( keys( %whitelist_data )))
{
update_log( "Starting with database \"$db\"" );
$dbh->do( "use \`$db\`" );
foreach my $tbl( sort( keys( %{$whitelist_data{$db}} )))
{
my $length_col = @{$whitelist_data{$db}{$tbl}};
if( $length_col > 0 )
{
my $select = "SELECT ";
my $counter = 0;
foreach my $col( @{$whitelist_data{$db}{$tbl}} )
{
$select .= "\`$col\`";
$counter++;
if( $counter < $length_col )
{
$select .= ",";
}
}
$select .= " FROM \`$tbl\`";
if( $rows > 0 )
{
$select .= " LIMIT $rows";
}
my $sth = $dbh->prepare( $select );
$sth->execute();
# MySQL considers the first row as 1, not 0
my $row_counter = 1;
while( my $results = $sth->fetchrow_arrayref() )
{
my $col_counter = 0;
foreach my $coldata( @{$results} )
{
my $curr_column = $whitelist_data{$db}{$tbl}[$col_counter];
foreach my $key( sort( keys( %regexes )))
{
my( $result, $possible_string ) = find_data( $coldata, $key );
if( $result == 1 )
{
my $string_copy = "";
if( $mask == 1 )
{
$string_copy = filterme( $possible_string );
}
else
{
$string_copy = $possible_string;
}
my $string = "INSERT INTO results SET scan=?,system=?,type=?,pattern=?,tracker=?,db=?,tbl=?,col=?,row=?,is_false=\"0\"";
my $sth_local = $dbh_local->prepare( $string );
$sth_local->execute( $scanname, $target, $key, $string_copy, $tracker, $db, $tbl, $curr_column, $row_counter );
$sth_local->finish();
}
}
$col_counter++;
}
$row_counter++;
}
$columns_done += @{$whitelist_data{$db}{$tbl}};
}
# update column, table counter in systems table
$tables_done++;
my $localtime = time();
my $update = "UPDATE systems SET tabledone=?,columndone=?,updated=? WHERE tracker=?";
my $sth_local = $dbh_local->prepare( $update );
$sth_local->execute( $tables_done, $columns_done, $localtime, $tracker );
$sth_local->finish();
update_log( "Done with table \"$tbl\" and $length_col column(s)" );
}
# update database counter in systems table
$databases_done++;
my $localtime = time();
my $update = "UPDATE systems SET dbdone=?,updated=? WHERE tracker=?";
my $sth_local = $dbh_local->prepare( $update );
$sth_local->execute( $databases_done, $localtime, $tracker );
$sth_local->finish();
update_log( "Done with database \"$db\"" );
}
$dbh->disconnect();
}
sub update_log
{
my $log_message = shift;
my $localtime = getmicroseconds();
my $string = "INSERT INTO logs SET tracker=?,data=?,updated=?,scan=?,profile=?";
my $sth_local = $dbh_local->prepare( $string );
$sth_local->execute( $tracker, $log_message, $localtime, $scanname, $profile );
$sth_local->finish();
}
sub do_mssql_agentless
{
# get all DBs, tables, columns
my $dsn = "DBI:Sybase:server=$target";
my $dbh = DBI->connect( $dsn, $username, $password );
if( $dbh )
{
update_log( "Successfully authenticated to database server" );
}
else
{
update_log( "Could not authenticate to database server...quitting" );
my $string = "UPDATE systems SET status=3,control=? WHERE tracker=?";
my $sth_local = $dbh_local->prepare( $string );
$sth_local->execute( 'finished', $tracker );
$sth_local->finish();
$dbh_local->disconnect();
exit(0);
}
my $query = "SELECT name FROM master..sysdatabases";
my $sth = $dbh->prepare( $query ) or die "prepare failed\n";
$sth->execute() or die "unable to execute query $query error $DBI::errstr\n";
while( my $results = $sth->fetchrow_arrayref() )
{
my $database = $$results[0];
my $query2 = "SELECT \"TABLE_NAME\",\"COLUMN_NAME\" FROM \"$database\".\"information_schema\".\"columns\"";
my $sth2 = $dbh->prepare( $query2 ) or die "prepare failed\n";
$sth2->execute();
while( my $results2 = $sth2->fetchrow_arrayref() )
{
my $table = $$results2[0];
my $column = $$results2[1];
# print BLAH "DB: $database\tTable: $table\tColumn: $column\n";
push @{$data{$database}{$table}}, $column;
}
$sth2->finish();
}
$sth->finish();
my $string = "UPDATE systems SET status=1 WHERE tracker=?";
my $sth_local = $dbh_local->prepare( $string );
$sth_local->execute( $tracker );
$sth_local->finish();
update_log( "Done enumerating entire database server schema" );
# figure out what we're going to scan based on policy
whitelist_schema();
update_log( "Done whitelisting/blacklisting database server schema...beginning to parse actual data" );
my $string = "UPDATE systems SET status=?,dbtotal=?,tabletotal=?,columntotal=? WHERE tracker=?";
my $sth_local = $dbh_local->prepare( $string );
$sth_local->execute( "2", $total_databases, $total_tables, $total_columns, $tracker );
$sth_local->finish();
# grab actual data
foreach my $db( sort( keys( %whitelist_data )))
{
$dbh->do( "use \"$db\"" );
foreach my $tbl( sort( keys( %{$whitelist_data{$db}} )))
{
my $length_col = @{$whitelist_data{$db}{$tbl}};
if( $length_col > 0 )
{
my $select = "SELECT ";
if( $rows > 0 )
{
$select .= "TOP $rows ";
}
my $counter = 0;
foreach my $col( @{$whitelist_data{$db}{$tbl}} )
{
$select .= "\"$col\"";
$counter++;
if( $counter < $length_col )
{
$select .= ",";
}
}
# $select .= " FROM \"$db\".\"$tbl\"";
$select .= " FROM \"$tbl\"";
my $sth = $dbh->prepare( $select );
$sth->execute();
# microsoft SQL server considers the first row as 1, not 0
my $row_counter = 1;
while( my $results = $sth->fetchrow_arrayref() )
{
my $col_counter = 0;
foreach my $coldata( @{$results} )
{
my $curr_column = $whitelist_data{$db}{$tbl}[$col_counter];
# print BLAH "table: $tbl\tcol: $curr_column\tdata: $coldata\n";
foreach my $key( sort( keys( %regexes )))
{
my( $result, $possible_string ) = find_data( $coldata, $key );
if( $result == 1 )
{
# print_log( "PID: $pid || Scanname: $scanname || Target: $target || Tracker: $tracker || DB: $db || Table: $tbl || Col: $curr_column || Row: $row_counter || Pattern: $key || Data: $possible_string\n\n" );
my $string_copy = "";
if( $mask == 1 )
{
$string_copy = filterme( $possible_string );
}
else
{
$string_copy = $possible_string;
}
my $string = "INSERT INTO results SET scan=?,system=?,type=?,pattern=?,tracker=?,db=?,tbl=?,col=?,row=?,is_false=\"0\"";
my $sth_local = $dbh_local->prepare( $string );
$sth_local->execute( $scanname, $target, $key, $string_copy, $tracker, $db, $tbl, $curr_column, $row_counter );
$sth_local->finish();
}
}
$col_counter++;
}
$row_counter++;
}
$columns_done += @{$whitelist_data{$db}{$tbl}};
}
# update column, table counter in systems table
$tables_done++;
my $localtime = time();
my $update = "UPDATE systems SET tabledone=?,columndone=?,updated=? WHERE tracker=?";
my $sth_local = $dbh_local->prepare( $update );
$sth_local->execute( $tables_done, $columns_done, $localtime, $tracker );
$sth_local->finish();
update_log( "Done with table \"$tbl\" and $length_col column(s)" );
}
# update database counter in systems table
$databases_done++;
my $localtime = time();
my $update = "UPDATE systems SET dbdone=?,updated=? WHERE tracker=?";
my $sth_local = $dbh_local->prepare( $update );
$sth_local->execute( $databases_done, $localtime, $tracker );
$sth_local->finish();
update_log( "Done with database \"$db\"" );
}
$dbh->disconnect();
}
sub find_data
{
my $coldata = shift;
my $regex_name = shift;
my $regex_pattern = $regexes{$regex_name};
my $possible_find = 0;
my $is_cc_regex = 0;
my $found_valid_cc = "";
my $possible_string = "";
if( $coldata =~ /($regex_pattern)/ )
{
$possible_string = $1;
$possible_find = 1;
foreach( @creditcard_array )
{
if( $_ eq $regex_name )
{
$is_cc_regex = 1;
my $length_match = length($possible_string);
my $match_copy = "";
my $x = 0;
while( $x < $length_match )
{
if( substr( $possible_string, $x, 1 ) =~ /[0-9]/ )
{
$match_copy .= substr( $possible_string, $x, 1 );
}
$x++;
}
if( do_luhn( $match_copy ))
{
$found_valid_cc = 1;
}
else
{
$found_valid_cc = 0;
}
}
}
}
if( $possible_find == 0 )
{
return( 0, "" );
}
elsif( $found_valid_cc == 1 )
{
return( 1, $possible_string );
}
elsif( $is_cc_regex == 1 && $found_valid_cc == 0 )
{
return( 0, "" );
}
elsif( $is_cc_regex == 0 && $possible_find == 1 )
{
return( 1, $possible_string );
}
else
{
return NULL;
}
}
sub print_log
{
my $string = shift;
open( BLAH, ">>/tmp/blah.txt" );
print BLAH $string;
close( BLAH );
}
sub do_luhn
{
my $number = shift;
my $c = check_digit( $number );
if( is_valid( $number ))
{
return 1;
}
else
{
return 0;
}
}
sub whitelist_schema
{
foreach my $dbs( sort( keys( %data )))
{
if( !is_blacklisted( "db", $dbs ))
{
$total_databases++;
foreach my $tbl( sort( keys( %{$data{$dbs}} )))
{
if( !is_blacklisted( "table", $tbl ))
{
$total_tables++;
foreach my $col( @{$data{$dbs}{$tbl}} )
{
if( !is_blacklisted( "column", $col ))
{
$total_columns++;
push @{$whitelist_data{$dbs}{$tbl}}, $col;
}
}
}
}
}
}
}
sub is_blacklisted
{
# ignore, allow, everything
my $type = shift;
my $value = shift;
if( $type eq "db" )
{
if( $ignore_dbs eq "everything" )
{
return 0;
}
elsif( $ignore_dbs eq "ignore" )
{
foreach( @db_array )
{
if( $_ eq $value )
{
return 1;
}
}
}
elsif( $ignore_dbs eq "allow" )
{
my $rc = 1;
foreach( @db_array )
{
if( $_ eq $value )
{
$rc = 0;
}
}
return $rc;
}
}
elsif( $type eq "table" )
{
if( $ignore_tables eq "everything" )
{
return 0;
}
elsif( $ignore_tables eq "ignore" )
{
foreach( @table_array )
{
if( $_ eq $value )
{
return 1;
}
}
}
elsif( $ignore_tables eq "allow" )
{
my $rc = 1;
foreach( @table_array )
{
if( $_ eq $value )
{
$rc = 0;
}
}
return $rc;
}
}
elsif( $type eq "column" )
{
if( $ignore_columns eq "everything" )
{
return 0;
}
elsif( $ignore_columns eq "ignore" )
{
foreach( @column_array )
{
if( $_ eq $value )
{
return 1;
}
}
}
elsif( $ignore_columns eq "allow" )
{
my $rc = 1;
foreach( @column_array )
{
if( $_ eq $value )
{
$rc = 0;
}
}
return $rc;
}
}
}
sub filterme
{
my $text = shift;
my $filtered = "";
for( my $x = 0; $x < length( $text ); $x++ )
{
my $char = substr( $text, $x, 1 );
if( $x <= (floor( length( $text ) * .75)) )
{
$filtered .= "X";
}
elsif( $char !~ /[0-9A-Z\~\`\!\@\#\$\%\^\&\*\(\)\_\-\=\+\[\{\]\}\\\|\;\:\'\"\,\<\.\>\/\?\ ]/i )
{
$filtered .= "?";
}
else
{
$filtered .= $char;
}
}
return $filtered;
}
sub getmicroseconds
{
my( $seconds, $microseconds ) = gettimeofday;
while( length( $microseconds ) < 6 )
{
$microseconds = "0" . $microseconds;
}
return "$seconds.$microseconds";
}