Lucene search

K
securityvulnsSecurityvulnsSECURITYVULNS:DOC:18500
HistoryNov 26, 2007 - 12:00 a.m.

two bytehoard 2.1 bugs

2007-11-2600:00:00
vulners.com
37

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&#40;&#41;;
      return array&#40;&quot;username&quot;=&gt;$_SESSION[&#39;username&#39;]&#41;;
    
    }

@@ -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&#40;&#41;;
      return array&#40;&quot;username&quot;=&gt;$_SESSION[&#39;username&#39;]&#41;;
    
    }

@@ -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&#40;&#41;;
      return array&#40;&quot;username&quot;=&gt;$_SESSION[&#39;username&#39;]&#41;;
    
    }

@@ -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&#40;&#41;;
    
  •   return array&#40;&quot;username&quot;=&gt;&quot;&quot;&#41;;
    
    }

@@ -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&#40;str_replace&#40;&quot;/..&quot;, &quot;&quot;, $filepath&#41;&#41;;               # Get rid 
    

of any nasty directory ups and URL encodes.
+

  •   $filepath = urldecode&#40;str_replace&#40;&quot;..&quot;, &quot;&quot;, $filepath&#41;&#41;;                # Get rid of 
    

any nasty directory ups and URL encodes.

    if &#40;substr&#40;$filepath, -1&#41; == &quot;/&quot;&#41; {
              $filepath = substr&#40;$filepath, 0, -1&#41;;         # Get rid of any trailing 

slashes.
@@ -585,7 +585,7 @@

    $filepath = str_replace&#40;$badcharacters, &quot;&quot;, $filepath&#41;;         # 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[&#39;reset_username&#39;];
    
  •   $userrows = select_bhdb&#40;&quot;users&quot;, array&#40;&quot;username&quot;=&gt;$username&#41;, &quot;&quot;&#41;;
    
  •   $xgqd_username = $_POST[&#39;reset_username&#39;];
    
  •   $userrows = select_bhdb&#40;&quot;users&quot;, array&#40;&quot;username&quot;=&gt;$xgqd_username&#41;, &quot;&quot;&#41;;
      if &#40;empty&#40;$userrows&#41;&#41; {
              # Open layout object
              $layoutobj = new bhlayout&#40;&quot;generic&quot;&#41;;
    

@@ -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&#40;&quot;passwordresets&quot;, array&#40;&quot;username&quot;=&gt;$username, 
    

"resetid"=>$resetid, "time"=>time()));

  •           insert_bhdb&#40;&quot;passwordresets&quot;, array&#40;&quot;username&quot;=&gt;$xgqd_username, 
    

"resetid"=>$resetid, "time"=>time()));

            # Get their email address
  •           $userirows = select_bhdb&#40;&quot;userinfo&quot;, array&#40;&quot;username&quot;=&gt;$username, 
    

"itemname"=>"email"), "");

  •           $userirows = select_bhdb&#40;&quot;userinfo&quot;, 
    

array("username"=>$xgqd_username, "itemname"=>"email"), "");
$emailaddr = $userirows[0]['itemcontent'];

            # Email them about it with the validation link
            $emailobj = new bhemail&#40;$emailaddr&#41;;
            $emailobj-&gt;subject = str_replace&#40;&quot;#SITENAME#&quot;, 

$bhconfig['sitename'], $bhlang['emailsubject:passreset_request']);

  •           $emailobj-&gt;message = str_replace&#40;&quot;#LINK#&quot;, 
    

bh_get_weburi()."/index.php?page=passreset&doresetid=$resetid&username=$username",
$bhlang['email:passreset_request']);

  •           $emailobj-&gt;message = str_replace&#40;&quot;#LINK#&quot;, 
    

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][&#39;username&#39;];
    
  •           $xgqd_username = $userirows[0][&#39;username&#39;];
              $resetid = md5&#40;time&#40;&#41;.rand&#40;1, 99999&#41;.rand&#40;54, time&#40;&#41;&#41;&#41;;
    
  •           insert_bhdb&#40;&quot;passwordresets&quot;, array&#40;&quot;username&quot;=&gt;$username, 
    

"resetid"=>$resetid, "time"=>time()));

  •           insert_bhdb&#40;&quot;passwordresets&quot;, array&#40;&quot;username&quot;=&gt;$xgqd_username, 
    

"resetid"=>$resetid, "time"=>time()));

            # Get their email address
            $emailaddr = $userirows[0][&#39;itemcontent&#39;];

@@ -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-&gt;message = str_replace&#40;&quot;#LINK#&quot;, 
    

bh_get_weburi()."/index.php?page=passreset&doresetid=$resetid&username=$username",
str_replace("#USERNAME#", $username, $bhlang['email:passreset_u_request']));

  •           $emailobj-&gt;message = str_replace&#40;&quot;#LINK#&quot;, 
    

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&#39;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&#40;$signup[&#39;username&#39;]&#41;;
    
  •           $usernamerows = select_bhdb&#40;&quot;users&quot;, array&#40;&quot;username&quot;=&gt;$username&#41;, &quot;&quot;&#41;;
    
  •           $regusernamerows = select_bhdb&#40;&quot;registrations&quot;, 
    

array("username"=>$username), "");

  •           $vlhq_username = strtolower&#40;$signup[&#39;username&#39;]&#41;;
    
  •           $usernamerows = select_bhdb&#40;&quot;users&quot;, 
    

array("username"=>$vlhq_username), "");

  •           $regusernamerows = select_bhdb&#40;&quot;registrations&quot;, 
    

array("username"=>$vlhq_username), "");

  •           if &#40;&#40;!empty&#40;$usernamerows&#41;&#41; || &#40;!empty&#40;$regusernamerows&#41;&#41; || 
    

($username == "guest") || ($username == "admin") || ($username ==
"administrator") || ($username == "all")) {

  •           if &#40;&#40;!empty&#40;$usernamerows&#41;&#41; || &#40;!empty&#40;$regusernamerows&#41;&#41; || 
    

($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-&gt;display&#40;&#41;;
  •           } elseif &#40;strlen&#40;$username&#41; &gt; 255&#41; {
    
  •           } elseif &#40;strlen&#40;$vlhq_username&#41; &gt; 255&#41; {
                      bh_log&#40;$bhlang[&#39;error:username_too_long&#39;], BH_ERROR&#41;;
                      # Open layout object
                      $layoutobj = new bhlayout&#40;&quot;signup&quot;&#41;;
    

@@ -90,7 +90,7 @@
# Dispatch an email
$emailobj = new bhemail($signup['email']);
$emailobj->subject = str_replace("#SITENAME#",
$bhconfig['sitename'], $bhlang['emailsubject:registration_validation']);

  •                           $emailobj-&gt;message = str_replace&#40;&quot;#LINK#&quot;, 
    

bh_get_weburi()."/index.php?page=signup&confirmregid=$regid&username=$username",
$bhlang['email:registration_validation']);

  •                           $emailobj-&gt;message = str_replace&#40;&quot;#LINK#&quot;, 
    

bh_get_weburi()."/index.php?page=signup&confirmregid=$regid&username=$vlhq_username",
$bhlang['email:registration_validation']);
$emailaway = $emailobj->send();

                            if &#40;$emailaway == false&#41; {

@@ -101,8 +101,8 @@
$layoutobj->content1 = "<br><br>".$bhlang['error:email_error'];
$layoutobj->display();
} else {

  •                                   insert_bhdb&#40;&quot;registrations&quot;, array&#40;&quot;regid&quot;=&gt;$regid, 
    

"username"=>$username, "password"=>md5($signup['pass1']),
"fullname"=>$signup['fullname'], "email"=>$signup['email'],
"status"=>"0", "regtime"=>time()));

  •                                   bh_log&#40;$bhlang[&#39;log:user_signed_up_&#39;].$username, &quot;BH_SIGNUP&quot;&#41;;
    
  •                                   insert_bhdb&#40;&quot;registrations&quot;, array&#40;&quot;regid&quot;=&gt;$regid, 
    

"username"=>$vlhq_username, "password"=>md5($signup['pass1']),
"fullname"=>$signup['fullname'], "email"=>$signup['email'],
"status"=>"0", "regtime"=>time()));

  •                                   bh_log&#40;$bhlang[&#39;log:user_signed_up_&#39;].$vlhq_username, &quot;BH_SIGNUP&quot;&#41;;
                                      # Open layout object
                                      $layoutobj = new bhlayout&#40;&quot;generic&quot;&#41;;
                                      # 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&#40;$bhlang[&#39;log:user_validated_&#39;].$username, 
    

"BH_SIGNUP_VALIDATED");

  •                           bh_log&#40;$bhlang[&#39;log:user_validated_&#39;].$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&#40;$bhlang[&#39;log:user_validated_&#39;].$username, 
    

"BH_SIGNUP_VALIDATED");

  •                                   bh_log&#40;$bhlang[&#39;log:user_signup_m_pending_&#39;].$username, 
    

"BH_SIGNUP_M_PENDING");

  •                                   bh_log&#40;$bhlang[&#39;log:user_validated_&#39;].$vlhq_username, 
    

"BH_SIGNUP_VALIDATED");

  •                                   bh_log&#40;$bhlang[&#39;log:user_signup_m_pending_&#39;].$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-&gt;display&#40;&#41;;

-}
\ No newline at end of file
+}

-------------------------PATCH ENDS
HERE----------------------------------------------------------------------------