Exploit-DB #22524
Description: ZenPhoto 1.4.3.3 - Multiple Vulnerabilities
Type: webapps
Platform: php
Date: 2012-11-06
Author: waraxe

Exploit:
[waraxe-2012-SA#096] - Multiple Vulnerabilities in Zenphoto 1.4.3.3
===============================================================================

Author: Janek Vind "waraxe"
Date: 03. November 2012
Location: Estonia, Tartu
Web: http://www.waraxe.us/advisory-96.html


Description of vulnerable software:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Zenphoto is a standalone CMS for multimedia focused websites. Our focus lies on
being easy to use and having all the features there when you need them (but out
of the way if you do not.)
Zenphoto features support for images, video and audio formats, and the Zenpage
CMS plugin provides a fully integrated news section (blog) and custom pages to
run entire websites. 

http://www.zenphoto.org/

https://code.google.com/p/zenphoto/

Affected versions: Zenphoto 1.4.3.3 and older
Patched version: Zenphoto 1.4.3.4


###############################################################################
1. SQL Injection in "zp-core/zp-extensions/failed_access_blocker.php"
###############################################################################

Reason: insufficient sanitization of user-supplied data
Attack vector: user-supplied HTTP header "X_FORWARDED_FOR"
Preconditions:
 1. plugin "failed_access_blocker" activated (disabled by default)

"failed_access_blocker" plugin will log every failed authentication attempt:
 
Php script "zp-core/zp-extensions/failed_access_blocker.php" line 75:
------------------------[ source code start ]----------------------------------
function failed_access_blocker_adminGate($allow, $page) {
...
 //    add this attempt
 $sql = 'INSERT INTO '.prefix('plugin_storage').' (`type`, `aux`,`data`) VALUES
   ("failed_access", "'.time().'","'.getUserIP().'")';
 query($sql);
 //    check how many times this has happened recently
 count = db_count('plugin_storage','WHERE `type`="failed_access" AND 
   `data`="'.getUserIP().'"');
------------------------[ source code end ]------------------------------------

IP address of the user comes from function "getUserIP()" and is used in SQL
query. Let's look at the function "getUserIP()".

Php script "zp-core/functions.php" line 1979:
------------------------[ source code start ]----------------------------------
function getUserIP() {
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        return sanitize($_SERVER['HTTP_X_FORWARDED_FOR'], 0);
    } else {
        return sanitize($_SERVER['REMOTE_ADDR'], 0);
------------------------[ source code end ]------------------------------------

Function "sanitize()" does following things to the input data:
 1. strips slashes if magic_quotes_gpc=on
 2. strips null bytes
 3. strips html tags
 
So we can see, that function "sanitize()" will prevent null byte tricks and
most of the XSS exploits, but it does not escape or delete single and double
quotes, therefore SQL Injection may still be possible. Actually this function
makes SQL Injection more likely to occur because it reverts effects of the
"magic_quotes_gpc". As result of such insuffient input data sanitization,
attacker can use HTTP header "X_FORWARDED_FOR" for SQL Injection.

Test:

Let's use Firefox browser with Tamper Data Add-on.

 1. Open admin page:
 
 http://localhost/zenphoto1433/zp-core/admin.php
 
 2. Activate Tamper data (Start Tamper)
 3. Try to log in with bogus credentials, Tamper Data triggers
 4. "Tamper with request?" -> "Tamper"
 5. "Add element" -> X_FORWARDED_FOR=war"axe
 6. Click "OK" and tampered request will go to the server 

As result we will see blank page (OK 200 response code, content length 0).
But let's look at "debug.log" in "zp-data":

Backtrace: USER ERROR: MySql Error: ( <em>INSERT INTO `[prefix]plugin_storage`
(`type`, `aux`,`data`) VALUES ("failed_access", "[PII_REDACTED]","war"axe")</em> )
failed. MySql returned the error <em>You have an error in your SQL syntax; 
check the manual that corresponds to your MySQL server version for the right
syntax to use near 'axe")'


###############################################################################
2. SQL Injection in "zp-core/zp-extensions/search_statistics.php"
###############################################################################

Reason: insufficient sanitization of user-supplied data
Attack vector: user-supplied HTTP header "X_FORWARDED_FOR"
Preconditions:
 1. plugin "search_statistics" activated (disabled by default)


Php script "zp-core/zp-extensions/search_statistics.php" line 101:
------------------------[ source code start ]----------------------------------
static function handler($search_statistics, $type, $success, $dynamic,
    $iteration) {
...
 $sql = 'INSERT INTO '.prefix('plugin_storage').' (`type`, `aux`,`data`) VALUES
  ("search_statistics", "'.getUserIP().'",'.db_quote(serialize($store)).')';
 query($sql);
------------------------[ source code end ]------------------------------------

User's IP address comes from function "getUserIP()" and is used in SQL query.
As shown in previous case, it is possible to use HTTP header "X_FORWARDED_FOR"
for SQL Injection, because "getUserIP()" does not sufficiently sanitize
user-supplied input data.


###############################################################################
3. IP address spoofing vulnerability via HTTP header "X_FORWARDED_FOR"
###############################################################################

Reason: trusting spoofable input data
Attack vector: user-supplied HTTP header "X_FORWARDED_FOR"
Preconditions: none

We saw in two previous cases, that function "getUserIP()" can't be trusted,
because attacker can easily spoof his/her IP addresss by using HTTP header
"X_FORWARDED_FOR". Vulnerable function "getUserIP()" is heavily used in logging
functionality.
Example code lines from "zp-core/zp-extensions/security-logger.php":

security_logger::Logg