CVE-2016-9125 (published 2017-03-28T02:59Z)

Revive Adserver before 3.2.3 suffers from session fixation, by allowing arbitrary session identifiers to be forced and, at the same time, by not invalidating the existing session upon a successful authentication. Under some circumstances, that could have been an opportunity for an attacker to steal an authenticated session.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-384
cwe_name: Session Fixation
description: Authenticating a user, or otherwise establishing a new user session, without invalidating any existing session identifier gives an attacker the opportunity to steal authenticated sessions.
extended_description: 
            Such a scenario is commonly observed when:
            
               A web application authenticates a user without first invalidating the existing session, thereby continuing to use the session already associated with the user.
               An attacker is able to force a known session identifier on a user so that, once the user authenticates, the attacker has access to the authenticated session.
               The application or container uses predictable session identifiers.
	    
	    In the generic exploit of session fixation vulnerabilities, an attacker creates a new session on a web application and records the associated session identifier. The attacker then causes the victim to associate, and possibly authenticate, against the server using that session identifier, giving the attacker access to the user's account through the active session.
         
         

Fix information:
repo_url: https://github.com/revive-adserver/revive-adserver
commit_details: repo_url: https://github.com/revive-adserver/revive-adserver
author: Matteo Beccati
author_date: 2015-11-20 07:23:43+01:00
msg: Fix h1 reports 93809 and 93813

Session Fixation
----------------

The HackerOne users kaviya and Kamini Singh have independendltly reported
that Revive Adserver was vulnerable to session fixation, by allowing arbitrary
session identifiers to be forced and at the same time by not invalidating the
existing session upon a successful authentication. Under some circumstances,
that could have been an opportunity for an attacker to steal an authenticated
sessions.

A CVE-ID has been requested, but not assigned yet.

CWE: CWE-384
CVSSv2: 7.8 (AV:N/AC:M/Au:N/C:C/I:P/A:N)
repository: repo_url: https://github.com/revive-adserver/revive-adserver
repo_name: revive-adserver/revive-adserver
description: The world's most popular free, open source ad serving system. You can download the latest release at:
repo_language: PHP
owner: revive-adserver
file_changes: filename: Auth.php
change_type: ModificationType.MODIFY
diff: @@ -86,6 +86,9 @@ class OA_Auth
                 OA_Auth::restart($GLOBALS['strUsernameOrPasswordWrong']);
             }
 
+            // Regenerate session ID now
+            phpAds_SessionRegenerateId();
+
             return OA_Auth::getSessionData($doUser);
         }
 
@@ -190,7 +193,7 @@ class OA_Auth
      */
     function restart($sMessage = '')
     {
-        $_COOKIE['sessionID'] = phpAds_SessionStart();
+        $_COOKIE['sessionID'] = phpAds_SessionRegenerateId();
         OA_Auth::displayLogin($sMessage, $_COOKIE['sessionID']);
     }
 

programming_language: PHP
filename: Login.php
change_type: ModificationType.MODIFY
diff: @@ -25,7 +25,7 @@ class OA_Upgrade_Login
     function checkLogin()
     {
         // Clean up session
-        $GLOBALS['session'] = array();
+        phpAds_clearSession();
 
         // Detection needs to happen every time to make sure that database parameters are
         $oUpgrader = new OA_Upgrade();
@@ -93,6 +93,7 @@ class OA_Upgrade_Login
             $doUser->joinAdd($doAUA);
             $doUser->find();
             if ($doUser->fetch()) {
+                phpAds_SessionRegenerateId();
                 phpAds_SessionDataRegister(OA_Auth::getSessionData($doUser));
                 phpAds_SessionDataStore();
             }
@@ -106,6 +107,8 @@ class OA_Upgrade_Login
         $aCredentials = $oPlugin->_getCredentials(false);
 
         if (!PEAR::isError($aCredentials)) {
+            phpAds_SessionRegenerateId();
+
             $doUser = $oPlugin->checkPassword($aCredentials['username'], $aCredentials['password']);
 
             if ($doUser) {
@@ -140,6 +143,8 @@ class OA_Upgrade_Login
                 $aCredentials = $oPlugin->_getCredentials(false);
 
                 if (!PEAR::isError($aCredentials)) {
+                    phpAds_SessionRegenerateId();
+
                     if (strtolower($aPref['admin']) == strtolower($aCredentials['username']) &&
                         $aPref['admin_pw'] == md5($aCredentials['password']))
                     {

programming_language: PHP
filename: Session.php
change_type: ModificationType.MODIFY
diff: @@ -76,10 +76,10 @@ class MAX_Dal_Admin_Session extends MAX_Dal_Common
         if ($doSession) {
             $doSession->sessiondata = $serialized_session_data;
             $doSession->update();
-        }
-        else {
+        } else {
             $doSession = OA_Dal::factoryDO('session');
-            $doSession->sessionid = $session_id;
+            // It's an md5, so 32 chars max
+            $doSession->sessionid = substr($session_id, 0, 32);
             $doSession->sessiondata = $serialized_session_data;
             $doSession->insert();
         }

programming_language: PHP
filename: lib-sessions.inc.php
change_type: ModificationType.MODIFY
diff: @@ -30,28 +30,29 @@ require_once MAX_PATH . '/lib/max/Dal/Admin/Session.php';
 function phpAds_SessionDataFetch()
 {
     global $session;
-    $dal = new MAX_Dal_Admin_Session();
 
     // Guard clause: Can't fetch a session without an ID
-	if (empty($_COOKIE['sessionID'])) {
+    if (empty($_COOKIE['sessionID']) || !preg_match('#^[0-9a-f]{32}$#D', $_COOKIE['sessionID'])) {
         return;
     }
 
+    $dal = new MAX_Dal_Admin_Session();
     $serialized_session = $dal->getSerializedSession($_COOKIE['sessionID']);
 
-    // This is required because 'sessionID' cookie is set to new during logout.
-    // According to comments in the file it is because some servers do not
-    // support setting cookies during redirect.
-    if (empty($serialized_session)) {
+    // Return if the session was not found (expired or forged)
+    if (!$serialized_session) {
         return;
     }
 
     $loaded_session = unserialize($serialized_session);
-	if (!$loaded_session) {
-        // XXX: Consider raising an error
+
+    // Or if it can't be unserialized and/or is not a session we started
+    if (empty($loaded_session['__authentic__'])) {
         return;
     }
-	$session = $loaded_session;
+
+    $session = $loaded_session;
+
     $dal->refreshSession($_COOKIE['sessionID']);
 }
 
@@ -75,21 +76,75 @@ function phpAds_SessionSetAdminCookie($name, $value)
 }
 
 /*-------------------------------------------------------*/
-/* Create a new sessionid                                */
+/* Start a new session                                   */
 /*-------------------------------------------------------*/
 
 function phpAds_SessionStart()
 {
 	global $session;
+
 	if (empty($_COOKIE['sessionID'])) {
-		$session = array();
-		$_COOKIE['sessionID'] = md5(uniqid('phpads', 1));
+		phpAds_clearSession();
+
+        $sessionId = phpAds_SessionGenerateId();
 
-        phpAds_SessionSetAdminCookie('sessionID', $_COOKIE['sessionID']);
+        $dal = new MAX_Dal_Admin_Session();
+        $dal->storeSerializedSession(serialize($session), $sessionId);
 	}
+
 	return $_COOKIE['sessionID'];
 }
 
+/*-------------------------------------------------------*/
+/* Generate a sessionid                                  */
+/*-------------------------------------------------------*/
+
+function phpAds_SessionGenerateId()
+{
+    $_COOKIE['sessionID'] = md5(uniqid('phpads', 1));
+
+    phpAds_SessionSetAdminCookie('sessionID', $_COOKIE['sessionID']);
+
+    return $_COOKIE['sessionID'];
+}
+
+/*-------------------------------------------------------*/
+/* Re-generate the sessionid                             */
+/*-------------------------------------------------------*/
+
+function phpAds_SessionRegenerateId()
+{
+    global $session;
+
+    $dal = new MAX_Dal_Admin_Session();
+
+    if (!empty($_COOKIE['sessionID'])) {
+        $dal->deleteSession($_COOKIE['sessionID']);
+    }
+
+    if (!empty($session['__authentic__'])) {
+        $sessionId = phpAds_SessionGenerateId();
+        $dal->storeSerializedSession(serialize($session), $sessionId);
+
+        return $sessionId;
+    }
+
+    unset($_COOKIE['sessionID']);
+
+    return phpAds_SessionStart();
+}
+
+/*-------------------------------------------------------*/
+/* Clear the session and mark it as authentic            */
+/*-------------------------------------------------------*/
+
+function phpAds_clearSession()
+{
+    $GLOBALS['session'] = array(
+        '__authentic__' => true,
+    );
+}
+
 /*-------------------------------------------------------*/
 /* Register the data in the session array                */
 /*-------------------------------------------------------*/

programming_language: PHP
filename: LogonServiceImpl.php
change_type: ModificationType.MODIFY
diff: @@ -93,7 +93,8 @@ class LogonServiceImpl extends BaseServiceImpl
 
         $_POST['login'] = 'Login';
 
-        $_COOKIE['sessionID'] = uniqid('phpads', 1);
+        unset($_COOKIE['sessionID']);
+        phpAds_SessionStart();
         $_POST['phpAds_cookiecheck'] = $_COOKIE['sessionID'];
 
         $this->preInitSession();

programming_language: PHP
filename: LogonServiceImpl.php
change_type: ModificationType.MODIFY
diff: @@ -93,7 +93,8 @@ class LogonServiceImpl extends BaseServiceImpl
 
         $_POST['login'] = 'Login';
 
-        $_COOKIE['sessionID'] = uniqid('phpads', 1);
+        unset($_COOKIE['sessionID']);
+        phpAds_SessionStart();
         $_POST['phpAds_cookiecheck'] = $_COOKIE['sessionID'];
 
         $this->preInitSession();

programming_language: PHP