Summary: PostNuke is a PHPNuke fork. It is a content management system written in PHP with a MySQL backend, focusing on style, appearance, and functionality. A security vulnerability in the product allows attackers that know an existing name of a username, to logon by that name without requiring to know its password.
Vulnerable systems: PostNuke version 0.62 PostNuke version 0.63 PostNuke version 0.64 PHPNuke version 5.2 (and earlier) contains the same code as PostNuke and could be vulnerable.
Impact: If an attacker knows the username and userid of a user on a PostNuke system, it is possible to log in as the user without specifying a password. Userids or usernames are usually available from the Members list. A fix is available.
Background: The vulnerable code is located in article.php and mainfile2.php (mainfile.php):
o article.php:
if ($save) { cookiedecode($user); mysql_query("update $pntable[users] set umode=`$mode`, uorder=`$order`, thold=`$thold` where uid=`$cookie[0]`"); getusrinfo($user); $info = base64_encode("$userinfo[uid]:$userinfo[uname]:$userinfo[pass]:$userinfo[storynum]:$userinfo[umode]:$userinfo[uorder]:$userinfo[thold]:$userinfo[noscore]"); setcookie("user","$info",time()+$cookieusrtime); }
o mainfile2.php (mainfile.php in PHPnuke and older versions of PostNuke):
function getusrinfo($user) { global $userinfo, $pntable; $user2 = base64_decode($user); $user3 = explode(":", $user2); $result = mysql_query("select uid, name, uname, email, femail, url, user_avatar, user_icq, user_occ, user_from, user_intrest, user_sig, user_viewemail, user_theme, user_aim, user_yim, user_msnm, pass, storynum, umode, uorder, thold, noscore, bio, ublockon, ublock, theme, commentmax, timezone_offset from $pntable[users] where uname=`$user3[1]` and pass=`$user3[2]`"); if(mysql_num_rows($result)==1) { $userinfo = mysql_fetch_array($result); } else { echo ""._MPROBLEM." "; } return $userinfo; }
The bug is a result of the following issues:
o It is possible to invoke the if-clause in article.php by just specifying save=1 in the URL.
o article.php blindly accepts the $user variable specified in the URL.
o There is no code in article.php that checks if cookiedecode() actually worked and the password specified in $user is not checked against the MySQL-database.
o article.php accepts the $cookieusrtime variable as specified in the URL.
o It is possible to modify the mysql_query-string in getusrinfo() by escaping the "`" in $user[1] or $user[2]. Like this (without the double-quotes): "` or uname=`USERNAME".
getusrinfo() will then return anything with uname=USERNAME, even if the password doesn`t match the one in $user. The full query-string sent to MySQL will end up looking like this:
select uid, name, uname, email, femail, url, user_avatar, user_icq, user_occ, user_from, user_intrest, user_sig, user_viewemail, user_theme, user_aim, user_yim, user_msnm, pass, storynum, umode, uorder, thold, noscore, bio, ublockon, ublock, theme, commentmax, timezone_offset from $prefix"._users." where uname=`USERNAME` and pass=`` or uname=`USERNAME`
To produce the query above the $user variable should contain a base64-encoded version of: USERID:USERNAME:` or uname `USERNAME` (base64_encoded)
o When the userinfo is received from getusrinfo() by article.php, it blindly sets a "user="-cookie containing the encrypted password.
Fix: Thanks to Sascha Endlicher and John Cox for comming up with the fix below.
In article.php, change the offending code to:
if (($save) && (is_user($user))) { cookiedecode($user); mysql_query("update $pntable[users] set umode=`$mode`, uorder=`$order`, thold=`$thold` where uid=`$cookie[0]`"); getusrinfo($user); $info = base64_encode("$userinfo[uid]:$userinfo[uname]:$userinfo[pass]:$userinfo[storynum]:$userinfo[umode]:$userinfo[uorder]:$userinfo[thold]:$userinfo[noscore]"); setcookie("user","$info",time()+$cookieusrtime); }
This may work in PHPNuke as well.
A new version of article.php for PostNuke is available from https://sourceforge.net/project/showfiles.php?group_id=27927 under Fixes.
Exploit: If an attacker requests a URL consisting of: article.php?save=1& sid=20& [any sid will do..] cookieusrtime=160000& [to get a decent expire-date on the cookie] user=USERID:USERNAME:` or uname=`USERNAME [base64_encoded]
And goes back to the main page, the requestor will be logged in as USERNAME.
Additional information: The information has been provided by Magnus Skjegstad.
|