bytehoard-multi.txt

2007-11-27T00:00:00
ID PACKETSTORM:61245
Type packetstorm
Reporter Ernesto Alvarez
Modified 2007-11-27T00:00:00

Description

                                        
                                            `Application: Bytehoard  
Versions: 2.1 (alpha to epsilon)  
Release Date: 2007-11-26  
Author: Ernesto Alvarez / Activesec SA  
Kudos to: Rodrigo Seguel / Activesec SA for suggesting the session   
destruction approach  
Contact info: ealvarez at activesec biz  
Developer response: None. No response to mail, forum inactive and   
bugtracker operating intermitently.  
  
  
Privilege escalation in bytehoard 2.1  
  
Background  
  
Bytehoard is a web application written in PHP that serves as a file   
storage and sharing system.  
It has two levels of security, a user level and an admin level. Login is   
required but it can be configured to allow anyone to obtain a user level   
account if desired.  
  
  
Summary  
  
It is possible for a non admin user to gain admin privileges on   
bytehoard 2.1, by overwriting a session variable if the php option   
"register_globals" is enabled. This variable can be overwritten by   
abusing the "register user" or the "password reset" module.  
  
Impact  
  
A non-admin user can gain admin privileges, access another accounts and   
do operations under nonexistent accounts.  
  
  
Preconditions  
  
PHP setting "register_globals" must be enabled.  
  
  
Exploit (1)  
  
Log into bytehoard using a non privileged user.  
Perform any desired actions, then log out.  
Click on the "Lost Details" link.  
Input the desired username you want to have access to ("admin" to get   
administrator access) and submit the data.  
The system will either return an error message or a "mail sent" message.  
Ignore the last message and go directly to the index.php page (easily   
obtained by erasing the "?page=passreset" part)  
You should have access to the desired account.  
  
  
  
Exploit (2)  
  
Log into bytehoard using a non privileged user.  
Perform any desired actions, then log out.  
Click on the "Sign Up" link.  
Input the desired username you want to have access to (ignore the other   
parameters) and submit the data.  
The system will either return an error message (or a success message if   
you complete everything).  
Ignore the last message and go directly to the index.php page (easily   
obtained by erasing the "?page=signup" part)  
You should have access to the desired account.  
  
  
  
Details  
  
This privilege escalation is a direct consequence of using the same name   
on a local variable ("username" on "modules/passreset.inc.php" and   
"modules/signup.inc.php") and a global variable   
("$_SESSION['username']"). When the "register_globals" setting is   
enabled and the session variable "username" is set (to any value,   
including empty string), any changes made to the local variables will   
also be written on the global one.  
  
Since both modules set the variable to a user input string, and the   
authentication module uses that global variable to both determine if the   
user is logged in and which username to use, following the instructions   
given in the exploit section give immediate access.  
  
This bug does not manifest itself if nobody successfully logins first   
within the session where the exploit is attempted since the session   
variable "username" would not be set, and therefore would not be   
overwritten by the php interpreter.  
  
  
  
Recommended actions  
  
Change the variable name "username" first referenced in line 22 of   
"modules/passreset.inc.php" to something else.  
Change the variable name "username" first referenced in line 24 of   
"modules/signup.inc.php" to something else.  
Ensure proper session destruction during logout.  
Disable the "register_globals" setting for bytehoard.  
  
  
Notes  
  
Depending on the situation, this can be seen as more than a privilege   
escalation, since a malicious attacker can trick a legitimate user into   
logging using an attacker controlled computer or using session fixation.  
Were a method of setting the "$_SESSION['username']" found without   
having to log in, this exploit would become a remote root (for the   
application, not the host).  
These methods can also be used to escalate privileges to a nonexistent   
account. In that case, a home directory is created for that "phantom"   
user, and the system behaves normally, but no account is created. The   
phantom user's data can be retrieved by repeating the exploit.  
  
  
============================================================================================================================  
  
Application: Bytehoard  
Versions: 2.1 (alpha to epsilon)  
Release Date: 2007-11-26  
Author: Ernesto Alvarez / Activesec SA  
Contact info: ealvarez at activesec biz  
Developer response: None. No response to mail, forum inactive and   
bugtracker operating intermitently.  
  
Directory traversal in bytehoard 2.1  
  
  
Background  
  
Bytehoard is a web application written in PHP that serves as a file   
storage and sharing system.  
It has two levels of security, a user level and an admin level. Login is   
required but it can be configured to allow anyone to obtain a user level   
account if desired.  
  
  
Summary  
  
It is possible for an admin user to upload a file to the filestorage's   
parent directory. Under default conditions, this directory is   
bytehoard's document root, and world writable.  
  
  
Impact  
  
None. It was thought to be an arbitrary execution risk, but as noted by   
the Secunia Research team, an administrator can change the virtual root   
and can upload files to any directory in the web server. This reference   
is kept because it is a bug worth noticing and the patch included with   
in this document patches both bugs.  
  
  
Preconditions  
  
The attacker must have access to a bytehoard administrative account.  
(See previous privilege escalation to meet this precondition)  
The web server must execute php (or similar) files in the filestorage's   
parent directory  
  
  
Exploit  
  
Log in as a bytehoard administrator  
Click the "Upload files" link  
Change upload directory to an arbitrary path (by pushing the change   
button and selecting another directory)  
Edit the "infolder" GET parameter to ".." and go to the resulting url  
The resulting page should read "Uploading to: .." to the left of the   
change button  
Select a php file with a shell, exploit or action to be run in one of   
the upload slots, upload the file  
There should be an error trying to stat the uploaded file but bytehoard   
should continue the upload process  
The file has been deposited in the filestorage's parent directory and   
will be executed if called  
  
  
Details  
  
Before moving a file to its final location, its path name is sent   
through the function "bh_fpclean()" in   
"includes/filesystem/filesystem/filesystem.inc.php" in order to canonize   
its name and filter possible traversal attacks. This filter removes all   
"/.." substrings but fails to remove two dots without a preceding slash.  
  
By entering ".." (or ".." followed by a path) as the directory name, the   
filter takes no action. Bytehoard then uses this tainted path and places   
the file in the filestorage's parent directory.  
  
If bytehoard is installed in its default configuration, this directory   
would be the document root, and the file deposited there would be read   
or executed by the web server. Also according to the installation guide,   
this directory should be made world readable, writable and executable,   
allowing the webserver to deposit that file unimpeded.  
  
For normal users this attack is stopped by the access control system,   
because it determines that the user has not enough privileges to write   
the file. Instant access is granted to administrators, though. See   
function "bh_checkrights()" in   
"includes/filesystem/filesystem/filesystem.inc.php".  
  
  
Recommended actions  
  
Modify the filter "bh_fpclean()" in   
"includes/filesystem/filesystem/filesystem.inc.php" to prevent this type   
of traversal attack  
Make the bytehoard document root and its files not writable by the web   
server. Apart from the filestorage, it is only necessary to make the   
file "log" and the directory "cache" writable for bytehoard to be   
usable. This still leaves the possibility that an attacker will try to   
deposit a file on "log" or "cache".  
Move the filestorage out of the path served by the webserver.  
If the filestorage must remain in the path served by the webserver,   
consider putting it under a second, read only directory.  
  
  
Notes  
  
This attack can be used to read (among other things) the credentials   
stored in "config.inc.php", gaining access to the database used by   
bytehoard.  
  
  
=========================================================================================================================  
  
Patch  
  
A stopgap patch was made that tries to neutralize these two bugs. The   
patch included applies the first two recommended actions for the   
escalation bug. It also destroys session data, but does not completely   
destroy the session itself. It also modifies the filter to block the   
second attack. However, it will also modify any legitimate file path   
with two consecutive dots in it.  
  
This patch can be applied to any installed bytehoard 2.1/epsilon. It   
should be installed by running "patch -p1 < PATCH-NAME" in the document   
root (where index.php lies).  
  
  
  
-------------------------PATCH BEGINS   
HERE--------------------------------------------------------------------------  
  
diff -u -r bytehoard-2.1-epsilon/includes/auth/bytehoard.inc.php   
bytehoard-2.1-zeta/includes/auth/bytehoard.inc.php  
--- bytehoard-2.1-epsilon/includes/auth/bytehoard.inc.php 2005-10-20   
13:38:24.000000000 -0300  
+++ bytehoard-2.1-zeta/includes/auth/bytehoard.inc.php 2007-11-20   
11:24:46.553418600 -0300  
@@ -38,6 +38,7 @@  
# Returns the bhsession array as above.  
function bh_session_destroy() {  
$_SESSION['username'] = "";  
+ session_destroy();  
return array("username"=>$_SESSION['username']);  
}  
  
@@ -62,4 +63,4 @@  
$result = update_bhdb("users", array("password"=>md5($password)),   
array("username"=>$username));  
# The _bhdb functions return false for success.  
return true;  
-}  
\ No newline at end of file  
+}  
diff -u -r bytehoard-2.1-epsilon/includes/auth/ldap.inc.php   
bytehoard-2.1-zeta/includes/auth/ldap.inc.php  
--- bytehoard-2.1-epsilon/includes/auth/ldap.inc.php 2006-02-22   
16:11:14.000000000 -0300  
+++ bytehoard-2.1-zeta/includes/auth/ldap.inc.php 2007-11-20   
11:25:26.246384352 -0300  
@@ -42,6 +42,7 @@  
# Returns the bhsession array as above.  
function bh_session_destroy() {  
$_SESSION['username'] = "";  
+ session_destroy();  
return array("username"=>$_SESSION['username']);  
}  
  
@@ -99,4 +100,4 @@  
function bh_auth_set_password($username, $password) {  
# NOT SUPPORTED  
return false;  
-}  
\ No newline at end of file  
+}  
diff -u -r bytehoard-2.1-epsilon/includes/auth/ldap_base.inc.php   
bytehoard-2.1-zeta/includes/auth/ldap_base.inc.php  
--- bytehoard-2.1-epsilon/includes/auth/ldap_base.inc.php 2006-02-22   
16:11:42.000000000 -0300  
+++ bytehoard-2.1-zeta/includes/auth/ldap_base.inc.php 2007-11-20   
11:25:52.365413656 -0300  
@@ -42,6 +42,7 @@  
# Returns the bhsession array as above.  
function bh_session_destroy() {  
$_SESSION['username'] = "";  
+ session_destroy();  
return array("username"=>$_SESSION['username']);  
}  
  
@@ -108,4 +109,4 @@  
function bh_auth_set_password($username, $password) {  
# NOT SUPPORTED  
return false;  
-}  
\ No newline at end of file  
+}  
diff -u -r bytehoard-2.1-epsilon/includes/auth/phpbb2.inc.php   
bytehoard-2.1-zeta/includes/auth/phpbb2.inc.php  
--- bytehoard-2.1-epsilon/includes/auth/phpbb2.inc.php 2005-10-20   
13:38:24.000000000 -0300  
+++ bytehoard-2.1-zeta/includes/auth/phpbb2.inc.php 2007-11-20   
11:27:47.858855984 -0300  
@@ -126,6 +126,8 @@  
$dbconfig['prefix'] = $oldprefix;  
$dbconfig['db'] = $olddb;  
  
+ session_destroy();  
+  
return array("username"=>"");  
}  
  
@@ -149,4 +151,4 @@  
if (empty($authrows)) { return 0; }  
else { return 1; }  
  
-}  
\ No newline at end of file  
+}  
diff -u -r   
bytehoard-2.1-epsilon/includes/filesystem/filesystem/filesystem.inc.php   
bytehoard-2.1-zeta/includes/filesystem/filesystem/filesystem.inc.php  
---   
bytehoard-2.1-epsilon/includes/filesystem/filesystem/filesystem.inc.php   
2007-11-20 13:00:07.152755312 -0300  
+++   
bytehoard-2.1-zeta/includes/filesystem/filesystem/filesystem.inc.php   
2007-11-20 12:09:13.751942792 -0300  
@@ -570,8 +570,8 @@  
  
# This function cleans any string passed to it so it's a valid   
filepath - in case something sends a bad one.  
function bh_fpclean($filepath) {  
-   
- $filepath = urldecode(str_replace("/..", "", $filepath)); # Get rid   
of any nasty directory ups and URL encodes.  
+  
+ $filepath = urldecode(str_replace("..", "", $filepath)); # Get rid of   
any nasty directory ups and URL encodes.  
  
if (substr($filepath, -1) == "/") {  
$filepath = substr($filepath, 0, -1); # Get rid of any trailing   
slashes.  
@@ -585,7 +585,7 @@  
  
$filepath = str_replace($badcharacters, "", $filepath); # Get rid of   
any bad characters  
$filepath = str_replace("//", "/", $filepath); # Get rid of any   
double slashes  
-  
+   
return $filepath;  
}  
  
diff -u -r bytehoard-2.1-epsilon/modules/passreset.inc.php   
bytehoard-2.1-zeta/modules/passreset.inc.php  
--- bytehoard-2.1-epsilon/modules/passreset.inc.php 2005-10-20   
13:38:12.000000000 -0300  
+++ bytehoard-2.1-zeta/modules/passreset.inc.php 2007-11-20   
11:13:39.990751528 -0300  
@@ -19,8 +19,8 @@  
# See if there is a reset request  
if (!empty($_POST['reset_username'])) {  
# See if the username exists  
- $username = $_POST['reset_username'];  
- $userrows = select_bhdb("users", array("username"=>$username), "");  
+ $xgqd_username = $_POST['reset_username'];  
+ $userrows = select_bhdb("users", array("username"=>$xgqd_username), "");  
if (empty($userrows)) {  
# Open layout object  
$layoutobj = new bhlayout("generic");  
@@ -31,16 +31,16 @@  
} else {  
# Insert a password reset request row for that username  
$resetid = md5(time().rand(1, 99999).rand(54, time()));  
- insert_bhdb("passwordresets", array("username"=>$username,   
"resetid"=>$resetid, "time"=>time()));  
+ insert_bhdb("passwordresets", array("username"=>$xgqd_username,   
"resetid"=>$resetid, "time"=>time()));  
  
# Get their email address  
- $userirows = select_bhdb("userinfo", array("username"=>$username,   
"itemname"=>"email"), "");  
+ $userirows = select_bhdb("userinfo",   
array("username"=>$xgqd_username, "itemname"=>"email"), "");  
$emailaddr = $userirows[0]['itemcontent'];  
  
# Email them about it with the validation link  
$emailobj = new bhemail($emailaddr);  
$emailobj->subject = str_replace("#SITENAME#",   
$bhconfig['sitename'], $bhlang['emailsubject:passreset_request']);  
- $emailobj->message = str_replace("#LINK#",   
bh_get_weburi()."/index.php?page=passreset&doresetid=$resetid&username=$username",   
$bhlang['email:passreset_request']);  
+ $emailobj->message = str_replace("#LINK#",   
bh_get_weburi()."/index.php?page=passreset&doresetid=$resetid&username=$xgqd_username",   
$bhlang['email:passreset_request']);  
$emailaway = $emailobj->send();  
if ($emailaway == false) {  
# Open layout object  
@@ -73,9 +73,9 @@  
$layoutobj->display();  
} else {  
# Insert a password reset request row for that username  
- $username = $userirows[0]['username'];  
+ $xgqd_username = $userirows[0]['username'];  
$resetid = md5(time().rand(1, 99999).rand(54, time()));  
- insert_bhdb("passwordresets", array("username"=>$username,   
"resetid"=>$resetid, "time"=>time()));  
+ insert_bhdb("passwordresets", array("username"=>$xgqd_username,   
"resetid"=>$resetid, "time"=>time()));  
  
# Get their email address  
$emailaddr = $userirows[0]['itemcontent'];  
@@ -83,7 +83,7 @@  
# Email them about it with the validation link  
$emailobj = new bhemail($emailaddr);  
$emailobj->subject = str_replace("#SITENAME#",   
$bhconfig['sitename'], $bhlang['emailsubject:passreset_u_request']);  
- $emailobj->message = str_replace("#LINK#",   
bh_get_weburi()."/index.php?page=passreset&doresetid=$resetid&username=$username",   
str_replace("#USERNAME#", $username, $bhlang['email:passreset_u_request']));  
+ $emailobj->message = str_replace("#LINK#",   
bh_get_weburi()."/index.php?page=passreset&doresetid=$resetid&username=$xgqd_username",   
str_replace("#USERNAME#", $xgqd_username,   
$bhlang['email:passreset_u_request']));  
$emailaway = $emailobj->send();  
if ($emailaway == false) {  
# Open layout object  
@@ -180,4 +180,4 @@  
  
}  
  
-?>  
\ No newline at end of file  
+?>  
diff -u -r bytehoard-2.1-epsilon/modules/signup.inc.php   
bytehoard-2.1-zeta/modules/signup.inc.php  
--- bytehoard-2.1-epsilon/modules/signup.inc.php 2005-10-20   
13:38:12.000000000 -0300  
+++ bytehoard-2.1-zeta/modules/signup.inc.php 2007-11-20   
11:10:52.031285248 -0300  
@@ -22,11 +22,11 @@  
  
# Check username isn't reserved or in use. We test for admin,   
administrator, guest, and all because they may all get used.  
# Even though a few are in the users table anyway.  
- $username = strtolower($signup['username']);  
- $usernamerows = select_bhdb("users", array("username"=>$username), "");  
- $regusernamerows = select_bhdb("registrations",   
array("username"=>$username), "");  
+ $vlhq_username = strtolower($signup['username']);  
+ $usernamerows = select_bhdb("users",   
array("username"=>$vlhq_username), "");  
+ $regusernamerows = select_bhdb("registrations",   
array("username"=>$vlhq_username), "");  
  
- if ((!empty($usernamerows)) || (!empty($regusernamerows)) ||   
($username == "guest") || ($username == "admin") || ($username ==   
"administrator") || ($username == "all")) {  
+ if ((!empty($usernamerows)) || (!empty($regusernamerows)) ||   
($username == "guest") || ($vlhq_username == "admin") || ($vlhq_username   
== "administrator") || ($vlhq_username == "all")) {  
bh_log($bhlang['error:username_in_use'], BH_ERROR);  
# Open layout object  
$layoutobj = new bhlayout("signup");  
@@ -36,7 +36,7 @@  
$layoutobj->content1 = $_POST['signup'];  
  
$layoutobj->display();  
- } elseif (strlen($username) > 255) {  
+ } elseif (strlen($vlhq_username) > 255) {  
bh_log($bhlang['error:username_too_long'], BH_ERROR);  
# Open layout object  
$layoutobj = new bhlayout("signup");  
@@ -90,7 +90,7 @@  
# Dispatch an email  
$emailobj = new bhemail($signup['email']);  
$emailobj->subject = str_replace("#SITENAME#",   
$bhconfig['sitename'], $bhlang['emailsubject:registration_validation']);  
- $emailobj->message = str_replace("#LINK#",   
bh_get_weburi()."/index.php?page=signup&confirmregid=$regid&username=$username",   
$bhlang['email:registration_validation']);  
+ $emailobj->message = str_replace("#LINK#",   
bh_get_weburi()."/index.php?page=signup&confirmregid=$regid&username=$vlhq_username",   
$bhlang['email:registration_validation']);  
$emailaway = $emailobj->send();  
  
if ($emailaway == false) {  
@@ -101,8 +101,8 @@  
$layoutobj->content1 = "<br><br>".$bhlang['error:email_error'];  
$layoutobj->display();  
} else {  
- insert_bhdb("registrations", array("regid"=>$regid,   
"username"=>$username, "password"=>md5($signup['pass1']),   
"fullname"=>$signup['fullname'], "email"=>$signup['email'],   
"status"=>"0", "regtime"=>time()));  
- bh_log($bhlang['log:user_signed_up_'].$username, "BH_SIGNUP");  
+ insert_bhdb("registrations", array("regid"=>$regid,   
"username"=>$vlhq_username, "password"=>md5($signup['pass1']),   
"fullname"=>$signup['fullname'], "email"=>$signup['email'],   
"status"=>"0", "regtime"=>time()));  
+ bh_log($bhlang['log:user_signed_up_'].$vlhq_username, "BH_SIGNUP");  
# Open layout object  
$layoutobj = new bhlayout("generic");  
# Send the file listing to the layout, along with directory name  
@@ -135,7 +135,7 @@  
delete_bhdb("registrations", array("regid"=>$_GET['confirmregid'],   
"username"=>$_GET['username']));  
  
# All done. Say so.  
- bh_log($bhlang['log:user_validated_'].$username,   
"BH_SIGNUP_VALIDATED");  
+ bh_log($bhlang['log:user_validated_'].$vlhq_username,   
"BH_SIGNUP_VALIDATED");  
bh_log($bhlang['notice:signup_successful_can_login'], "BH_NOTICE");  
require "modules/login.inc.php";  
}  
@@ -165,8 +165,8 @@  
update_bhdb("registrations", array("status"=>"1"),   
array("regid"=>$_GET['confirmregid'], "username"=>$_GET['username']));  
  
# All done. Say so.  
- bh_log($bhlang['log:user_validated_'].$username,   
"BH_SIGNUP_VALIDATED");  
- bh_log($bhlang['log:user_signup_m_pending_'].$username,   
"BH_SIGNUP_M_PENDING");  
+ bh_log($bhlang['log:user_validated_'].$vlhq_username,   
"BH_SIGNUP_VALIDATED");  
+ bh_log($bhlang['log:user_signup_m_pending_'].$vlhq_username,   
"BH_SIGNUP_M_PENDING");  
# Open layout object  
$layoutobj = new bhlayout("generic");  
# Send the file listing to the layout, along with directory name  
@@ -196,4 +196,4 @@  
$layoutobj->content1 = "<br><br>".$bhlang['error:signup_disabled'];  
  
$layoutobj->display();  
-}  
\ No newline at end of file  
+}  
  
  
-------------------------PATCH ENDS   
HERE----------------------------------------------------------------------------  
`