CVE-2015-6830: Exposure of Sensitive Information to an Unauthorized Actor (CWE-200)
value: libraries/plugins/auth/AuthenticationCookie.class.php in phpMyAdmin 4.3.x before 4.3.13.2 and 4.4.x before 4.4.14.1 allows remote attackers to bypass a multiple-reCaptcha protection mechanism against brute-force credential guessing by providing a correct response to a single reCaptcha.
CWE description: The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
Published: 2015-09-14T01:59Z
Repository: https://github.com/phpmyadmin/phpmyadmin
Language: PHP
Files changed: ChangeLog, libraries/plugins/auth/AuthenticationCookie.class.php, test/classes/plugin/auth/PMA_AuthenticationCookie_test.php
Commit date: 2015-09-08 07:13:24 +1000
Commit message: Fix reCaptcha bypass

Signed-off-by: Madhura Jayaratne <madhura.cj@gmail.com>

Diff: diff --git a/ChangeLog b/ChangeLog
index 69d8cfd695ca2..39124c9107e04 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,8 +1,11 @@
 phpMyAdmin - ChangeLog
 ======================
 
+4.4.14.1 (Not yet released)
+- issue        [security] reCaptcha bypass
+
 4.4.14.0 (2015-08-20)
 - issue #11367 Export after search, missing WHERE clause
 - issue #11380 Incomplete message after import
 - issue        Incorrect scalar type declaration (reported under PHP 7)
 - issue #11389 ReCaptcha produces deprecated messages under PHP 7
diff --git a/libraries/plugins/auth/AuthenticationCookie.class.php b/libraries/plugins/auth/AuthenticationCookie.class.php
index c85f74a41fd8d..2b75262e7e8fa 100644
--- a/libraries/plugins/auth/AuthenticationCookie.class.php
+++ b/libraries/plugins/auth/AuthenticationCookie.class.php
@@ -221,22 +221,13 @@ public function auth()
         } else {
             echo '    <input type="hidden" name="server" value="'
                 . $GLOBALS['server'] . '" />';
         } // end if (server choice)
 
-        // We already have one correct captcha.
-        $skip = false;
-        if (  isset($_SESSION['last_valid_captcha'])
-            && $_SESSION['last_valid_captcha']
-        ) {
-            $skip = true;
-        }
-
         // Add captcha input field if reCaptcha is enabled
         if (  !empty($GLOBALS['cfg']['CaptchaLoginPrivateKey'])
             && !empty($GLOBALS['cfg']['CaptchaLoginPublicKey'])
-            && !$skip
         ) {
             // If enabled show captcha to the user on the login screen.
             echo '<script src="https://www.google.com/recaptcha/api.js?hl='
                 . $GLOBALS['lang'] . '" async defer></script>';
             echo '<div class="g-recaptcha" data-sitekey="'
@@ -334,12 +325,10 @@ public function authCheck()
             // according to the PHP manual we should do this before the destroy:
             //$_SESSION = array();
 
             if (! defined('TESTSUITE')) {
                 session_destroy();
-                // $_SESSION array is not immediately emptied
-                $_SESSION['last_valid_captcha'] = false;
             }
             // -> delete password cookie(s)
             if ($GLOBALS['cfg']['LoginCookieDeleteAll']) {
                 foreach ($GLOBALS['cfg']['Servers'] as $key => $val) {
                     $GLOBALS['PMA_Config']->removeCookie('pmaPass-' . $key);
@@ -357,22 +346,13 @@ public function authCheck()
             }
         }
 
         if (! empty($_REQUEST['pma_username'])) {
 
-            // We already have one correct captcha.
-            $skip = false;
-            if (isset($_SESSION['last_valid_captcha'])
-                && $_SESSION['last_valid_captcha']
-            ) {
-                $skip = true;
-            }
-
             // Verify Captcha if it is required.
             if (! empty($GLOBALS['cfg']['CaptchaLoginPrivateKey'])
                 && ! empty($GLOBALS['cfg']['CaptchaLoginPublicKey'])
-                && ! $skip
             ) {
                 if (! empty($_POST["g-recaptcha-response"])) {
 
                     include_once 'libraries/plugins/auth/recaptcha/autoload.php';
                     $reCaptcha = new \ReCaptcha\ReCaptcha(
@@ -386,22 +366,15 @@ public function authCheck()
                     );
 
                     // Check if the captcha entered is valid, if not stop the login.
                     if ($resp == null || ! $resp->isSuccess()) {
                         $conn_error = __('Entered captcha is wrong, try again!');
-                        $_SESSION['last_valid_captcha'] = false;
                         return false;
-                    } else {
-                        $_SESSION['last_valid_captcha'] = true;
                     }
                 } else {
-                    if (! isset($_SESSION['last_valid_captcha'])
-                        || ! $_SESSION['last_valid_captcha']
-                    ) {
-                        $conn_error = __('Please enter correct captcha!');
-                        return false;
-                    }
+                    $conn_error = __('Please enter correct captcha!');
+                    return false;
                 }
             }
 
             // The user just logged in
             $GLOBALS['PHP_AUTH_USER'] = $_REQUEST['pma_username'];
diff --git a/test/classes/plugin/auth/PMA_AuthenticationCookie_test.php b/test/classes/plugin/auth/PMA_AuthenticationCookie_test.php
index e42e232409a1b..421b59c57db84 100644
--- a/test/classes/plugin/auth/PMA_AuthenticationCookie_test.php
+++ b/test/classes/plugin/auth/PMA_AuthenticationCookie_test.php
@@ -184,11 +184,12 @@ public function testAuth()
         $GLOBALS['pmaThemeImage'] = 'test';
         $GLOBALS['conn_error'] = true;
         $GLOBALS['cfg']['Lang'] = 'en';
         $GLOBALS['cfg']['AllowArbitraryServer'] = true;
         $GLOBALS['cfg']['Servers'] = array(1, 2);
-        $_SESSION['last_valid_captcha'] = true;
+        $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
+        $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
         $GLOBALS['target'] = 'testTarget';
         $GLOBALS['db'] = 'testDb';
         $GLOBALS['table'] = 'testTable';
 
         file_put_contents('testlogo_right.png', '');
@@ -306,11 +307,10 @@ public function testAuth()
 
         $GLOBALS['pmaThemeImage'] = 'test';
         $GLOBALS['cfg']['Lang'] = '';
         $GLOBALS['cfg']['AllowArbitraryServer'] = false;
         $GLOBALS['cfg']['Servers'] = array(1);
-        $_SESSION['last_valid_captcha'] = false;
         $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = 'testprivkey';
         $GLOBALS['cfg']['CaptchaLoginPublicKey'] = 'testpubkey';
         $GLOBALS['server'] = 0;
 
         $GLOBALS['error_handler'] = new PMA_Error_Handler;
@@ -429,11 +429,10 @@ public function testAuthCheck()
         );
         @unlink('testConfigSwekey');
 
         // case 2
 
-        $_SESSION['last_valid_captcha'] = false;
         $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = 'testprivkey';
         $GLOBALS['cfg']['CaptchaLoginPublicKey'] = 'testpubkey';
         $_POST["g-recaptcha-response"] = '';
         $_REQUEST['pma_username'] = 'testPMAUser';
 
@@ -479,11 +478,12 @@ public function testAuthCheck()
             isset($_COOKIE['pmaPass-1'])
         );
 
         // case 6
 
-        $_SESSION['last_valid_captcha'] = true;
+        $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
+        $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
         $_REQUEST['old_usr'] = '';
         $_REQUEST['pma_username'] = 'testPMAUser';
         $_REQUEST['pma_servername'] = 'testPMAServer';
         $_REQUEST['pma_password'] = 'testPMAPSWD';
         $GLOBALS['cfg']['AllowArbitraryServer'] = true;
@@ -609,11 +609,12 @@ public function testAuthCheckDecryptUser()
         $_COOKIE['pmaServer-1'] = 'pmaServ1';
         $_COOKIE['pmaUser-1'] = 'pmaUser1';
         $_COOKIE['pma_iv-1'] = base64_encode('testiv09testiv09');
         $GLOBALS['cfg']['blowfish_secret'] = 'secret';
         $_SESSION['last_access_time'] = '';
-        $_SESSION['last_valid_captcha'] = true;
+        $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
+        $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
 
         // mock for blowfish function
         $this->object = $this->getMockBuilder('AuthenticationCookie')
             ->disableOriginalConstructor()
             ->setMethods(array('cookieDecrypt'))
@@ -647,11 +648,12 @@ public function testAuthCheckDecryptPassword()
         $_COOKIE['pmaServer-1'] = 'pmaServ1';
         $_COOKIE['pmaUser-1'] = 'pmaUser1';
         $_COOKIE['pmaPass-1'] = 'pmaPass1';
         $_COOKIE['pma_iv-1'] = base64_encode('testiv09testiv09');
         $GLOBALS['cfg']['blowfish_secret'] = 'secret';
-        $_SESSION['last_valid_captcha'] = true;
+        $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
+        $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
         $_SESSION['last_access_time'] = time() - 1000;
         $GLOBALS['cfg']['LoginCookieValidity'] = 1440;
 
         // mock for blowfish function
         $this->object = $this->getMockBuilder('AuthenticationCookie')
@@ -692,11 +694,12 @@ public function testAuthCheckAuthFails()
         $_COOKIE['pmaServer-1'] = 'pmaServ1';
         $_COOKIE['pmaUser-1'] = 'pmaUser1';
         $_COOKIE['pma_iv-1'] = base64_encode('testiv09testiv09');
         $GLOBALS['cfg']['blowfish_secret'] = 'secret';
         $_SESSION['last_access_time'] = 1;
-        $_SESSION['last_valid_captcha'] = true;
+        $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
+        $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
         $GLOBALS['cfg']['LoginCookieValidity'] = 0;
         $_SESSION['last_access_time'] = -1;
         // mock for blowfish function
         $this->object = $this->getMockBuilder('AuthenticationCookie')
             ->disableOriginalConstructor()

Vulnerable code:         // We already have one correct captcha.
        $skip = false;
        if (  isset($_SESSION['last_valid_captcha'])
            && $_SESSION['last_valid_captcha']
        ) {
            $skip = true;
        }

            && !$skip
                // $_SESSION array is not immediately emptied
                $_SESSION['last_valid_captcha'] = false;
            // We already have one correct captcha.
            $skip = false;
            if (isset($_SESSION['last_valid_captcha'])
                && $_SESSION['last_valid_captcha']
            ) {
                $skip = true;
            }

                && ! $skip
                        $_SESSION['last_valid_captcha'] = false;
                    } else {
                        $_SESSION['last_valid_captcha'] = true;
                    if (! isset($_SESSION['last_valid_captcha'])
                        || ! $_SESSION['last_valid_captcha']
                    ) {
                        $conn_error = __('Please enter correct captcha!');
                        return false;
                    }
        $_SESSION['last_valid_captcha'] = true;
        $_SESSION['last_valid_captcha'] = false;
        $_SESSION['last_valid_captcha'] = false;
        $_SESSION['last_valid_captcha'] = true;
        $_SESSION['last_valid_captcha'] = true;
        $_SESSION['last_valid_captcha'] = true;
        $_SESSION['last_valid_captcha'] = true;
Fixed code: 4.4.14.1 (Not yet released)
- issue        [security] reCaptcha bypass

                    $conn_error = __('Please enter correct captcha!');
                    return false;
        $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
        $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
        $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
        $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
        $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
        $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
        $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
        $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
        $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
        $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';