Home page logo
/

fulldisclosure logo Full Disclosure mailing list archives

two bytehoard bugs
From: Ernesto Alvarez <ealvarez () activesec biz>
Date: Mon, 26 Nov 2007 11:29:21 -0300

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

_______________________________________________
Full-Disclosure - We believe in it.
Charter: http://lists.grok.org.uk/full-disclosure-charter.html
Hosted and sponsored by Secunia - http://secunia.com/


  By Date           By Thread  

Current thread:
  • two bytehoard bugs Ernesto Alvarez (Nov 26)
[ Nmap | Sec Tools | Mailing Lists | Site News | About/Contact | Advertising | Privacy ]