package functions;
# This file is part of sqlsus
#
# Copyright (c) 2008-2011 Jérémy Ruffet (sativouf)
# http://sqlsus.sourceforge.net/
#
# Licensed under GPLv3+: GNU GPL version 3 or later
# See LICENSE file or http://www.gnu.org/licenses/gpl.html
use strict;
use warnings;
use Unicode::Normalize;
use Time::HiRes qw/sleep/;
use constant LENGTH_URL => 1;
use constant LENGTH_INJVAR => 2;
##################################
#-- ask a question to the user
sub question {
my $question = shift;
my @choices = @_;
my $answer = "";
do {
print "$question [" . join ("|", @choices) . "] ? ";
$answer = <STDIN>;
return unless defined $answer;
chomp $answer;
} until grep(/$answer/i, @choices);
return lc $answer;
}
##################################
sub length_above_max_sendable {
my $inject_callback = shift;
my @args = @_;
if ($conf::max_inj_length) {
return &$inject_callback(@args, LENGTH_INJVAR) > $conf::max_inj_length;
} elsif ($conf::max_url_length) {
return &$inject_callback(@args, LENGTH_URL) > $conf::max_url_length;
}
return 0;
}
##################################
sub get_injvar_length {
my $url = shift;
$url =~ s/(%([a-fA-F0-9]{2}))/chr(hex $2)/eg;
my @args = split /[\?&][^\?&=]+?=/, $url;
shift @args;
my $max_length = 0;
for (@args) {
$max_length = length($_) if length($_) > $max_length;
}
return $max_length;
}
#-- hit the server with a GET/POST request
# RETURN:
# if dry_run : $length_request
# else : return value of $browser->get/post
##################################
sub get_url {
my ($url, $form, $dry_run) = @_;
my $res;
my $hits = 0;
$url =~ s# #/**/#g if $conf::convert_spaces;
# will be done anyway, so better do it now to calculate an accurate length_request
$url =~ s# #%20#g;
$url =~ s/#/%23/g;
$url =~ s/\+/%2B/g;
my $length_request;
my $length_injvar;
# POST
if ($form and $form ne LENGTH_URL and $form ne LENGTH_INJVAR) {
my %hash = %{$form};
if (not $dry_run) {
my $post_content;
for (keys %hash) { $post_content .= " [$_]=> $hash{$_} " }
&msg::debug("[POST] $url : $post_content");
}
# full_url is only used for length calculation purpose
my $full_url = $url;
for (keys %hash) {
my $arg = $hash{$_};
# &get_injvar_length may wrongly cut otherwise
$arg =~ s/[\?&=]/x/g;
$full_url .= "?$_=$arg";
}
$length_request = length($full_url);
$length_injvar = &get_injvar_length($full_url);
return $length_injvar if $dry_run and ($dry_run == LENGTH_INJVAR);
return $length_request if $dry_run and ($dry_run == LENGTH_URL);
do {
$res = $main::browser->post($url, \%{$form});
&hits_add();
} while (&is_error_code($res->code) and ($hits++ < $conf::http_error_retries) and not &main::is_interrupted());
# GET
} else {
&msg::debug("[GET] $url") unless $dry_run;
$length_request = length($url);
$length_injvar = &get_injvar_length($url);
return $length_injvar if $dry_run and ($dry_run == LENGTH_INJVAR);
return $length_request if $dry_run and ($dry_run == LENGTH_URL);
do {
$res = $main::browser->get($url);
&hits_add();
} while (&is_error_code($res->code) and ($hits++ < $conf::http_error_retries) and not &main::is_interrupted());
}
return if &main::is_interrupted();
if ($hits == $conf::http_error_retries) {
&msg::error("http_error_retries hit");
}
if (($conf::max_url_length > 0) and ($length_request > $conf::max_url_length)) {
&msg::error("max_url_length exceeded (requested URL is " . $length_request . " bytes long), this will probably fail.");
}
if (($conf::max_inj_length > 0) and ($length_injvar > $conf::max_inj_length)) {
&msg::error("max_inj_length exceeded (requested injection point is " . $length_injvar . " bytes long), this will probably fail.");
}
sleep($conf::sleep_after_hit);
return($res);
}
##################################
#-- show / set global variables
# "set" alone will display all the set variables
sub set_var {
my $query_args = shift;
my ($var, @values) = split /\s+/, $query_args;
$var = "" unless $var;
my @to_print;
if ($var =~ /^(|database|db)$/i) {
$main::database = join('', @values) if @values;
if (defined $main::database) {
$main::database =~ s/^(['"])(.*)\1$/$2/;
push @to_print, "database = \"$main::database\"";
}
}
if ($var =~ /^(|columns)$/i) {
# '' or "" to clear
@conf::columns = split /[ ,]/, join ' ', grep (!/^(['"])\1$/, @values) if @values;
push @to_print, "columns = " . join (",", @conf::columns) . "";
}
if ($var =~ /^(|binary)$/i) {
$conf::binary = join('', @values) if @values;
if ($conf::binary) {
$conf::hex_start = "HEX(BINARY ";
$conf::hex_end = ")";
} else {
$conf::hex_start = $conf::hex_end = "";
}
push @to_print, "binary = $conf::binary";
}
if ($var =~ /^(|blind_max_length)$/i) {
$conf::blind_max_length = join('', @values) if @values;
push @to_print, "blind_max_length = $conf::blind_max_length";
}
if ($var =~ /^(|proxy)$/i) {
$conf::proxy = join('', @values) if @values;
$conf::proxy =~ s/^(['"])(.*)\1$/$2/;
&functions::load_proxy_in_browser;
push @to_print, "proxy = \"$conf::proxy\"";
}
if ($var =~ /^(|cookie)$/i) {
if ($conf::use_cookie_jar) {
$conf::cookie = join('', @values) if @values;
$conf::cookie =~ s/^(['"])(.*)\1$/$2/;
&set_cookie($conf::cookie);
push @to_print, "cookie = \"$conf::cookie\"";
} else {
push @to_print, "cookie = DISABLED_BY_CONFIG";
}
}
if ($var =~ /^(|debug)$/i) {
$conf::debug = join('', @values) if @values;
push @to_print, "debug = $conf::debug";
}
if ($var =~ /^(|max_returned_length)$/i) {
$conf::max_returned_length = join('', @values) if @values;
push @to_print, "max_returned_length = $conf::max_returned_length";
}
if ($var =~ /^(|max_url_length)$/i) {
$conf::max_url_length = join('', @values) if @values;
push @to_print, "max_url_length = $conf::max_url_length";
}
if ($var =~ /^(|max_inj_length)$/i) {
$conf::max_inj_length = join('', @values) if @values;
push @to_print, "max_inj_length = $conf::max_inj_length";
}
if ($var =~ /^(|max_subqueries)$/i) {
$conf::max_subqueries = join('', @values) if @values;
push @to_print, "max_subqueries = $conf::max_subqueries";
}
if ($var =~ /^(|processes)$/i) {
$conf::processes = join('', @values) if @values;
push @to_print, "processes = $conf::processes";
}
if ($var =~ /^(|sleep_after_hit)$/i) {
$conf::sleep_after_hit = join('', @values) if @values;
push @to_print, "sleep_after_hit = $conf::sleep_after_hit";
}
if ($var =~ /^(|table_prefix)$/i) {
$conf::table_prefix = join('', @values) if @values;
$conf::table_prefix =~ s/^(['"])(.*)\1$/$2/;
push @to_print, "table_prefix = \"$conf::table_prefix\"";
}
if ($var =~ /^(|http_error_retries)$/i) {
$conf::http_error_retries = join('', @values) if @values;
push @to_print, "http_error_retries = $conf::http_error_retries";
}
if ($var =~ /^(|document_root)$/i) {
$conf::document_root = join('', @values) if @values;
$conf::document_root =~ s/^(['"])(.*)\1$/$2/;
push @to_print, "document_root = \"$conf::document_root\"";
}
if ($var =~ /^(|crawler_depth)$/i) {
$conf::crawler_depth = join('', @values) if @values;
push @to_print, "crawler_depth = $conf::crawler_depth";
}
if ($var =~ /^(|uploader)$/i) {
$conf::uploader = join('', @values) if @values;
$conf::uploader =~ s/^(['"])(.*)\1$/$2/;
push @to_print, "uploader = \"$conf::uploader\"";
}
if ($var =~ /^(|backdoor)$/i) {
$conf::backdoor = join('', @values) if @values;
$conf::backdoor =~ s/^(['"])(.*)\1$/$2/;
push @to_print, "backdoor = \"$conf::backdoor\"";
}
if (@to_print) {
print join ("\n", @to_print ). "\n";
} else {
&msg::error("No such variable : $var");
}
}
##################################
#-- converts string to 0xHEX
sub string_to_hex {
my $str = shift;
$str =~ s/(.)/ sprintf '%02x', ord $1 /seg;
return "0x" . $str;
}
#-- and the opposite..
sub hex_to_string {
(my $str = shift) =~ s/([a-fA-F0-9]{2})/chr(hex $1)/eg;
$str =~ s/^0x//;
return $str;
}
##################################
#-- gen random string
sub generate_random_string {
my $length = shift;
my @keyspace = @_;
my $random_string;
for (1..$length) {
$random_string .= $keyspace[rand @keyspace];
}
return $random_string;
}
##################################
sub is_error_code {
my $code = shift;
if (grep($_ eq $code, @conf::http_error_codes)) {
&msg::debug("HTTP Error: $code") if not &main::is_interrupted();
return 1;
} else {
return 0;
}
}
##################################
#-- find the tables that have a column that matches the given pattern
sub find_tables {
my $pattern = shift;
if ($pattern) {
$pattern =~ s/^(['"])(.*)\1/$2/;
if (defined $main::database) {
my @result = &select::mass_query("SELECT DISTINCT table_name FROM information_schema.columns WHERE table_schema = \"$main::database\" AND column_name LIKE \"$pattern\"", 1);
if (@result) {
&functions::print_array_in_a_box(&hits_counter(), [ qw(table_name) ], @result);
} else {
&msg::info("No table found with a column matching this pattern");
}
} else {
&msg::error("Current database not defined, use \"start\" first");
}
} else {
print "Please specify a column pattern\n";
}
}
##################################
sub print_table_in_a_box {
my $hits = shift;
my $database = shift;
my $result_table = shift;
my @header_row = &db::get_header($result_table);
if (not $result_table or &db::count_rows($database, $result_table) == 0) {
print "Empty set\n\n";
return 0;
}
my $hits_info = "";
$hits_info = "($hits hit" . ($hits == 1 ? "" : "s") . ")" if defined $hits;
my @cols_size = ();
my $header_decoration_line;
my $header_line;
# Get max size for every column
# set it to header for the minimum value
for (@header_row) {
push @cols_size, length $_ if $_;
}
my $i = 0;
for my $col (@header_row) {
my $longest = &db::longest($database, $result_table, $col);
($cols_size[$i]) = $longest if not defined $cols_size[$i] or $longest > $cols_size[$i];
$i++;
}
for my $i (0..$#cols_size) {
$header_decoration_line .= "+" . "-" x ($cols_size[$i] + 2);
$header_line .= "| " . $header_row[$i] . " " x ($cols_size[$i] - length ($header_row[$i]) + 1);
}
print "$header_decoration_line" . "+\n";
print "$header_line" . "|\n";
print "$header_decoration_line" . "+\n";
my $dbh = DBI->connect("dbi:SQLite:dbname=$database","","");
my $sql = "SELECT * FROM $result_table ORDER BY ABS(row)";
my $sth = $dbh->prepare($sql);
$sth->execute();
while (my @row = $sth->fetchrow_array()) {
for my $i (0..$#cols_size) {
my $item = $row[$i+1];
$item = "" if not defined $item;
$item =~ s/\x06/$conf::null_substitute/g;
# my $padding_len = length(NFD $item) - length($item);
my $padding_len = 0;
if ($item =~ /^-?[\d\.~]+$/) {
print "| " . " " x ($cols_size[$i] - length ($item) + $padding_len) . $item . " ";
} else {
print "| " . $item . " " x ($cols_size[$i] - length ($item) + $padding_len + 1);
}
}
print "|\n";
}
print "$header_decoration_line" . "+\n";
my $rows = $sth->rows;
my $rows_word = "row";
$rows_word .= "s" if $rows > 1;
print "$rows $rows_word in set $hits_info\n\n";
return 1;
}
##################################
sub print_array_in_a_box {
print &array_in_a_box(@_);
}
##################################
sub array_in_a_box {
my $hits = shift;
my @header_row = @{ shift(@_) };
my @contents = @_;
my $result;
my $hits_info = "";
$hits_info = "($hits hit" . ($hits == 1 ? "" : "s") . ")" if defined $hits;
if (scalar (@contents) == 0) {
return "Empty set\n\n";
}
my @cols_size = ();
my $header_decoration_line;
my $header_line;
# Get max size for every column
# set it to header to start
for (@header_row) {
push @cols_size, length $_;
}
for (my $i = 0; $i < $#contents + 1; $i += $#cols_size + 1) {
for my $j (0..$#cols_size) {
$contents[$i+$j] = "" unless defined $contents[$i+$j];
my $len = length $contents[$i+$j];
if ($cols_size[$j] < $len) {
$cols_size[$j] = $len;
}
}
}
for my $i (0..$#cols_size) {
$header_decoration_line .= "+" . "-" x ($cols_size[$i] + 2);
$header_line .= "| " . $header_row[$i] . " " x ($cols_size[$i] - length ($header_row[$i]) + 1);
}
$result .= "$header_decoration_line" . "+\n";
$result .= "$header_line" . "|\n";
$result .= "$header_decoration_line" . "+\n";
for (my $i = 0; $i < $#contents + 1; $i += $#cols_size + 1) {
for my $j (0..$#cols_size) {
my $item = $contents[$i+$j];
$item = "" if not defined $item;
$item =~ s/\x06/$conf::null_substitute/g;
# my $padding_len = length(NFD $item) - length($item);
my $padding_len = 0;
if ($item =~ /^-?[\d\.~]+$/) {
$result .= "| " . " " x ($cols_size[$j] - length ($item) + $padding_len) . $item . " ";
} else {
$result .= "| " . $item . " " x ($cols_size[$j] - length ($item) + $padding_len + 1);
}
}
$result .= "|\n";
}
$result .= "$header_decoration_line" . "+\n";
my $rows = int (($#contents + 1) / ($#cols_size + 1));
my $rows_word = "row";
$rows_word .= "s" if $rows > 1;
$result .= "$rows $rows_word in set $hits_info\n\n";
return $result;
}
##################################
sub set_cookie {
my $cookie = shift;
if (not $conf::use_cookie_jar) {
&msg::error("conf::use_cookie_jar set to 0, not setting cookie.");
return 0;
}
$main::browser->cookie_jar->clear;
$cookie .= ";" if ($cookie =~ /.*=.*[^;]\s*$/);
for (split /\s*;\s*/, $cookie) {
my ($key, $value) = /(.*?)=(.*)/;
my $tld = "";
$tld = $1 if ($main::server =~ /([^\.]+)$/);
$main::browser->cookie_jar->set_cookie(0, $key, $value, "/", ".$tld",,,,,);
}
}
##################################
sub hits_counter {
$main::control_child->send("hits print");
$main::control_child->recv(my $buf, 100000);
return $buf;
}
##################################
sub hits_reset {
$main::control_child->send("hits reset");
}
##################################
sub hits_add {
$main::control_child->send("hits +");
}
##################################
sub interrupt_hit {
$main::control_child->send("interrupt +");
}
##################################
sub interrupt_reset {
$main::control_child->send("interrupt reset");
}
##################################
sub interrupt {
$main::control_child->send("interrupt print");
$main::control_child->recv(my $buf, 100);
return $buf;
}
##################################
sub my_fork {
$SIG{'INT'} = 'IGNORE';
return fork();
}
##################################
sub load_proxy_in_browser {
if ($conf::proxy) {
if ($conf::proxy =~ /^socks/) {
eval { require LWP::Protocol::socks };
if ($@) { &msg::warning("socks proxy disabled, please install LWP::Protocol::socks (\"cpan LWP::Protocol::socks\" or \"apt-get install liblwp-protocol-socks-perl\")") }
}
eval { $main::browser->proxy([qw/ http https /], $conf::proxy) };
if ($@) {
&msg::error("Incorrect proxy settings detected, no proxy will be used.");
$main::browser->proxy([qw/ http https /], "");
};
}
}
1