[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: phpmyadmin / CVE-2016-9861 / PMASA-2016-66



Brian May <bam@debian.org> writes:

> I have patched a number of vulnerabilities in phpmyadmin in wheezy;
> there is a version available for testing at
> https://people.debian.org/~bam/debian/pool/main/p/phpmyadmin/

> The included isAllowedDomain does not include some checks that are in
> later versions:

I have now added these checks, and updated the package available at the
above URL. The updated debdiff is below.

diff -Nru phpmyadmin-3.4.11.1/debian/changelog phpmyadmin-3.4.11.1/debian/changelog
--- phpmyadmin-3.4.11.1/debian/changelog	2016-09-15 07:17:16.000000000 +1000
+++ phpmyadmin-3.4.11.1/debian/changelog	2016-12-06 08:18:14.000000000 +1100
@@ -1,3 +1,25 @@
+phpmyadmin (4:3.4.11.1-2+deb7u7) wheezy-security; urgency=high
+
+  * Non-maintainer upload by the LTS Team.
+  * CVE-2016-4412 / PMASA-2016-57: A user can be tricked in to following a
+    link leading to phpMyAdmin, which after authentication redirects to
+    another malicious site.
+  * CVE-2016-6626 / PMASA-2016-49: In the fix for PMASA-2016-57, we didn't
+    have sufficient checking and was possible to bypass whitelist.
+  * CVE-2016-9849 / PMASA-2016-60: Username deny rules bypass (AllowRoot &
+    Others) by using Null Byte.
+  * CVE-2016-9850 / PMASA-2016-61: Username matching for the allow/deny rules
+    may result in wrong matches and detection of the username in the rule due
+    to non-constant execution time.
+  * CVE-2016-9861 / PMASA-2016-66: In the fix for PMASA-2016-49, we has buggy
+    checks and was possible to bypass whitelist.
+  * CVE-2016-9864 / PMASA-2016-69: Multiple SQL injection vulnerabilities.
+  * CVE-2016-9865 / PMASA-2016-70: Due to a bug in serialized string parsing,
+    it was possible to bypass the protection offered by PMA_safeUnserialize()
+    function.
+
+ -- Brian May <bam@debian.org>  Tue, 06 Dec 2016 08:18:14 +1100
+
 phpmyadmin (4:3.4.11.1-2+deb7u6) wheezy-security; urgency=high
 
   * Non-maintainer upload by the Long Term Security Team.
diff -Nru phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-49.patch phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-49.patch
--- phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-49.patch	1970-01-01 10:00:00.000000000 +1000
+++ phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-49.patch	2016-12-06 08:18:14.000000000 +1100
@@ -0,0 +1,13 @@
+--- a/libraries/core.lib.php
++++ b/libraries/core.lib.php
+@@ -864,6 +864,10 @@
+ function PMA_isAllowedDomain($url)
+ {
+     $arr = parse_url($url);
++    // Avoid URLs without hostname or with credentials
++    if (empty($arr['host']) || ! empty($arr['user']) || ! empty($arr['pass'])) {
++        return false;
++    }
+     $domain = $arr["host"];
+     $domainWhiteList = array(
+         /* Include current domain */
diff -Nru phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-57.patch phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-57.patch
--- phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-57.patch	1970-01-01 10:00:00.000000000 +1000
+++ phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-57.patch	2016-12-06 08:18:14.000000000 +1100
@@ -0,0 +1,68 @@
+--- a/libraries/core.lib.php
++++ b/libraries/core.lib.php
+@@ -852,4 +852,38 @@
+ 
+     return unserialize($data);
+ }
++
++/**
++ * Checks whether domain of URL is whitelisted domain or not.
++ * Use only for URLs of external sites.
++ *
++ * @param string $url URL of external site.
++ *
++ * @return boolean.True:if domain of $url is allowed domain, False:otherwise.
++ */
++function PMA_isAllowedDomain($url)
++{
++    $arr = parse_url($url);
++    $domain = $arr["host"];
++    $domainWhiteList = array(
++        /* Include current domain */
++        $_SERVER['SERVER_NAME'],
++        /* phpMyAdmin domains */
++        'wiki.phpmyadmin.net', 'www.phpmyadmin.net', 'phpmyadmin.net',
++        'docs.phpmyadmin.net',
++        /* mysql.com domains */
++        'dev.mysql.com','bugs.mysql.com',
++        /* php.net domains */
++        'php.net',
++        /* Github domains*/
++        'github.com','www.github.com',
++        /* Following are doubtful ones. */
++        'www.primebase.com','pbxt.blogspot.com'
++    );
++    if (in_array(strtolower($domain), $domainWhiteList)) {
++        return true;
++    }
++
++    return false;
++}
+ ?>
+--- a/url.php
++++ b/url.php
+@@ -8,9 +8,22 @@
+  */
+ require_once './libraries/common.inc.php';
+ 
+-if (! PMA_isValid($_GET['url']) || ! preg_match('/^https?:\/\/[^\n\r]*$/', $_GET['url'])) {
++if (! PMA_isValid($_GET['url'])
++    || ! preg_match('/^https?:\/\/[^\n\r]*$/', $_GET['url'])
++    || ! PMA_isAllowedDomain($_GET['url'])
++) {
+     header('Location: ' . $cfg['PmaAbsoluteUri']);
+ } else {
+-    header('Location: ' . $_GET['url']);
++    // JavaScript redirection is necessary. Because if header() is used
++    //  then web browser sometimes does not change the HTTP_REFERER
++    //  field and so with old URL as Referer, token also goes to
++    //  external site.
++    echo "<script type='text/javascript'>
++            window.onload=function(){
++                window.location='" . PMA_escapeJsString($_GET['url']) . "';
++            }
++        </script>";
++    // Display redirecting msg on screen.
++    printf(__('Taking you to %s.'), htmlspecialchars($_GET['url']));
+ }
+ ?>
diff -Nru phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-60.patch phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-60.patch
--- phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-60.patch	1970-01-01 10:00:00.000000000 +1000
+++ phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-60.patch	2016-12-06 08:18:14.000000000 +1100
@@ -0,0 +1,50 @@
+--- a/libraries/auth/cookie.auth.lib.php
++++ b/libraries/auth/cookie.auth.lib.php
+@@ -432,7 +432,7 @@
+ 
+     if (! empty($_REQUEST['pma_username'])) {
+         // The user just logged in
+-        $GLOBALS['PHP_AUTH_USER'] = $_REQUEST['pma_username'];
++        $GLOBALS['PHP_AUTH_USER'] = PMA_sanitizeMySQLUser($_REQUEST['pma_username']);
+         $GLOBALS['PHP_AUTH_PW']   = empty($_REQUEST['pma_password']) ? '' : $_REQUEST['pma_password'];
+         if ($GLOBALS['cfg']['AllowArbitraryServer'] && isset($_REQUEST['pma_servername'])) {
+             $GLOBALS['pma_auth_server'] = PMA_sanitizeMySQLHost($_REQUEST['pma_servername']);
+--- a/libraries/core.lib.php
++++ b/libraries/core.lib.php
+@@ -766,6 +766,24 @@
+ }
+ 
+ /**
++ * Sanitizes MySQL username
++ *
++ * * strips part behind null byte
++ *
++ * @param string $name User given username
++ *
++ * @return string
++ */
++function PMA_sanitizeMySQLUser($name)
++{
++    $position = strpos($name, chr(0));
++    if ($position !== false) {
++        return substr($name, 0, $position);
++    }
++    return $name;
++}
++
++/**
+  * Safe unserializer wrapper
+  *
+  * It does not unserialize data containing objects
+--- a/libraries/auth/http.auth.lib.php
++++ b/libraries/auth/http.auth.lib.php
+@@ -155,6 +155,9 @@
+         unset($usr_pass);
+     }
+ 
++    // sanitize username
++    $PHP_AUTH_USER = PMA_sanitizeMySQLUser($PHP_AUTH_USER);
++
+     // User logged out -> ensure the new username is not the same
+     if (!empty($old_usr)
+         && (isset($PHP_AUTH_USER) && $old_usr == $PHP_AUTH_USER)) {
diff -Nru phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-61-1.patch phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-61-1.patch
--- phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-61-1.patch	1970-01-01 10:00:00.000000000 +1000
+++ phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-61-1.patch	2016-12-06 08:18:14.000000000 +1100
@@ -0,0 +1,11 @@
+--- a/libraries/ip_allow_deny.lib.php
++++ b/libraries/ip_allow_deny.lib.php
+@@ -169,7 +169,7 @@
+ 
+         // check for username
+         if (($rule_data[1] != '%') //wildcarded first
+-            && ($rule_data[1] != $username)) {
++            && (! hash_equals($rule_data[1], $username))) {
+             continue;
+         }
+ 
diff -Nru phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-61-2.patch phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-61-2.patch
--- phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-61-2.patch	1970-01-01 10:00:00.000000000 +1000
+++ phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-61-2.patch	2016-12-06 08:18:14.000000000 +1100
@@ -0,0 +1,45 @@
+--- a/libraries/auth/cookie.auth.lib.php
++++ b/libraries/auth/cookie.auth.lib.php
+@@ -524,14 +524,14 @@
+ 
+     // Ensures valid authentication mode, 'only_db', bookmark database and
+     // table names and relation table name are used
+-    if ($cfg['Server']['user'] != $GLOBALS['PHP_AUTH_USER']) {
++    if (! hash_equals($cfg['Server']['user'], $GLOBALS['PHP_AUTH_USER'])) {
+         foreach ($cfg['Servers'] as $idx => $current) {
+             if ($current['host'] == $cfg['Server']['host']
+              && $current['port'] == $cfg['Server']['port']
+              && $current['socket'] == $cfg['Server']['socket']
+              && $current['ssl'] == $cfg['Server']['ssl']
+              && $current['connect_type'] == $cfg['Server']['connect_type']
+-             && $current['user'] == $GLOBALS['PHP_AUTH_USER']) {
++             && hash_equals($current['user'], $GLOBALS['PHP_AUTH_USER'])) {
+                 $GLOBALS['server'] = $idx;
+                 $cfg['Server']     = $current;
+                 break;
+--- a/libraries/auth/http.auth.lib.php
++++ b/libraries/auth/http.auth.lib.php
+@@ -160,7 +160,7 @@
+ 
+     // User logged out -> ensure the new username is not the same
+     if (!empty($old_usr)
+-        && (isset($PHP_AUTH_USER) && $old_usr == $PHP_AUTH_USER)) {
++        && (isset($PHP_AUTH_USER) && hash_equals($old_usr, $PHP_AUTH_USER))) {
+         $PHP_AUTH_USER = '';
+         // -> delete user's choices that were stored in session
+         session_destroy();
+@@ -195,11 +195,12 @@
+ 
+     // Ensures valid authentication mode, 'only_db', bookmark database and
+     // table names and relation table name are used
+-    if ($cfg['Server']['user'] != $PHP_AUTH_USER) {
++    if (! hash_equals($cfg['Server']['user'], $PHP_AUTH_USER)) {
+         $servers_cnt = count($cfg['Servers']);
+         for ($i = 1; $i <= $servers_cnt; $i++) {
+             if (isset($cfg['Servers'][$i])
+-                && ($cfg['Servers'][$i]['host'] == $cfg['Server']['host'] && $cfg['Servers'][$i]['user'] == $PHP_AUTH_USER)) {
++                && $cfg['Servers'][$i]['host'] == $cfg['Server']['host']
++                && hash_equals($cfg['Servers'][$i]['user'], $PHP_AUTH_USER)) {
+                 $server        = $i;
+                 $cfg['Server'] = $cfg['Servers'][$i];
+                 break;
diff -Nru phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-66.patch phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-66.patch
--- phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-66.patch	1970-01-01 10:00:00.000000000 +1000
+++ phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-66.patch	2016-12-06 08:18:14.000000000 +1100
@@ -0,0 +1,31 @@
+--- a/libraries/core.lib.php
++++ b/libraries/core.lib.php
+@@ -882,10 +882,17 @@
+ function PMA_isAllowedDomain($url)
+ {
+     $arr = parse_url($url);
+-    // Avoid URLs without hostname or with credentials
+-    if (empty($arr['host']) || ! empty($arr['user']) || ! empty($arr['pass'])) {
++    // We need host to be set
++    if (! isset($arr['host']) || strlen($arr['host']) == 0) {
+         return false;
+     }
++    // We do not want these to be present
++    $blocked = array('user', 'pass', 'port');
++    foreach ($blocked as $part) {
++        if (isset($arr[$part]) && strlen($arr[$part]) != 0) {
++            return false;
++        }
++    }
+     $domain = $arr["host"];
+     $domainWhiteList = array(
+         /* Include current domain */
+@@ -902,7 +909,7 @@
+         /* Following are doubtful ones. */
+         'www.primebase.com','pbxt.blogspot.com'
+     );
+-    if (in_array(strtolower($domain), $domainWhiteList)) {
++    if (in_array($domain, $domainWhiteList)) {
+         return true;
+     }
+ 
diff -Nru phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-69-1.patch phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-69-1.patch
--- phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-69-1.patch	1970-01-01 10:00:00.000000000 +1000
+++ phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-69-1.patch	2016-12-06 08:18:14.000000000 +1100
@@ -0,0 +1,13 @@
+--- a/libraries/Tracker.class.php
++++ b/libraries/Tracker.class.php
+@@ -240,8 +240,9 @@
+     static public function getLogComment()
+     {
+         $date = date('Y-m-d H:i:s');
++        $user = preg_replace('/\s+/', ' ', $GLOBALS['cfg']['Server']['user']);
+ 
+-        return "# log " . $date . " " . $GLOBALS['cfg']['Server']['user'] . "\n";
++        return "# log " . $date . " " . $user . "\n";
+     }
+ 
+     /**
diff -Nru phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-69-2.patch phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-69-2.patch
--- phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-69-2.patch	1970-01-01 10:00:00.000000000 +1000
+++ phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-69-2.patch	2016-12-06 08:18:14.000000000 +1100
@@ -0,0 +1,20 @@
+--- a/tbl_tracking.php
++++ b/tbl_tracking.php
+@@ -106,13 +106,15 @@
+ if (isset($_REQUEST['report_export']) && $_REQUEST['export_type'] == 'sqldumpfile') {
+     @ini_set('url_rewriter.tags','');
+ 
+-    $dump = "# " . sprintf(__('Tracking report for table `%s`'), htmlspecialchars($_REQUEST['table'])) . "\n" .
++    // Replace all multiple whitespaces by a single space
++    $table = htmlspecialchars(preg_replace('/\s+/', ' ', $_REQUEST['table']));
++    $dump = "# " . sprintf(__('Tracking report for table `%s`'), $table) . "\n" .
+             "# " . date('Y-m-d H:i:s') . "\n";
+     foreach($entries as $entry) {
+         $dump .= $entry['statement'];
+     }
+     //$filename = 'log_' . str_replace(';', '', htmlspecialchars($_REQUEST['table'])) . '.sql';
+-    $filename = PMA_sanitize_filename('log_' . $_REQUEST['table'] . '.sql');
++    $filename = PMA_sanitize_filename('log_' . $table . '.sql');
+     header('Content-Type: text/x-sql');
+     header('Expires: ' . gmdate('D, d M Y H:i:s') . ' GMT');
+     header('Content-Disposition: attachment; filename="' . $filename . '"');
diff -Nru phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-70.patch phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-70.patch
--- phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-70.patch	1970-01-01 10:00:00.000000000 +1000
+++ phpmyadmin-3.4.11.1/debian/patches/pmasa-2016-70.patch	2016-12-06 08:18:14.000000000 +1100
@@ -0,0 +1,25 @@
+From 5e108a340f3eac6b6c488439343b6c1a7454787c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= <michal@cihar.com>
+Date: Tue, 4 Oct 2016 13:17:07 +0200
+Subject: [PATCH] Correctly parse string length when checking serialized data
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Michal Čihař <michal@cihar.com>
+---
+ libraries/core.lib.php                           | 2 +-
+ test/libraries/core/PMA_safeUnserialize_test.php | 1 +
+ 2 files changed, 2 insertions(+), 1 deletion(-)
+
+--- a/libraries/core.lib.php
++++ b/libraries/core.lib.php
+@@ -816,7 +816,7 @@
+             case 's':
+                 /* string */
+                 // parse sting length
+-                $strlen = intval($data[$i + 2]);
++                $strlen = intval(substr($data, $i + 2));
+                 // string start
+                 $i = strpos($data, ':', $i + 2);
+                 if ($i === false) {
diff -Nru phpmyadmin-3.4.11.1/debian/patches/series phpmyadmin-3.4.11.1/debian/patches/series
--- phpmyadmin-3.4.11.1/debian/patches/series	2016-09-15 07:03:37.000000000 +1000
+++ phpmyadmin-3.4.11.1/debian/patches/series	2016-12-06 08:18:14.000000000 +1100
@@ -32,3 +32,12 @@
 CVE-2016-6623.patch
 CVE-2016-6624.patch
 CVE-2016-6622.patch
+pmasa-2016-57.patch
+pmasa-2016-49.patch
+pmasa-2016-60.patch
+pmasa-2016-61-1.patch
+pmasa-2016-61-2.patch
+pmasa-2016-66.patch
+pmasa-2016-69-1.patch
+pmasa-2016-69-2.patch
+pmasa-2016-70.patch

-- 
Brian May <bam@debian.org>


Reply to: