Add unit tests

Signed-off-by: Maurício Meneghini Fauth <mauricio@fauth.dev>
This commit is contained in:
Maurício Meneghini Fauth 2021-10-17 00:29:39 -03:00
parent 3095181bb3
commit d057b68aa1
No known key found for this signature in database
GPG Key ID: 6A16FD38AFC89CC8
9 changed files with 301 additions and 16 deletions

View File

@ -1317,4 +1317,27 @@ class Core
$hmac = hash_hmac('sha256', $sqlQuery, $_SESSION[' HMAC_secret '] . $cfg['blowfish_secret']);
return hash_equals($hmac, $signature);
}
/**
* @return void
*/
public static function populateRequestWithEncryptedQueryParams()
{
if (! isset($_GET['eq']) || ! is_string($_GET['eq'])) {
unset($_GET['eq'], $_REQUEST['eq']);
return;
}
$decryptedQuery = Url::decryptQuery($_GET['eq']);
unset($_GET['eq'], $_REQUEST['eq']);
if ($decryptedQuery === null) {
return;
}
$urlQueryParams = (array) json_decode($decryptedQuery);
foreach ($urlQueryParams as $urlQueryParamKey => $urlQueryParamValue) {
$_GET[$urlQueryParamKey] = $urlQueryParamValue;
$_REQUEST[$urlQueryParamKey] = $urlQueryParamValue;
}
}
}

View File

@ -13,10 +13,14 @@ final class Crypto
/** @var bool */
private $hasSodiumSupport;
public function __construct()
/**
* @param bool $forceFallback Force the usage of the fallback functions.
*/
public function __construct($forceFallback = false)
{
$this->hasRandomBytesSupport = is_callable('random_bytes');
$this->hasSodiumSupport = $this->hasRandomBytesSupport
$this->hasRandomBytesSupport = ! $forceFallback && is_callable('random_bytes');
$this->hasSodiumSupport = ! $forceFallback
&& $this->hasRandomBytesSupport
&& is_callable('sodium_crypto_secretbox')
&& is_callable('sodium_crypto_secretbox_open')
&& defined('SODIUM_CRYPTO_SECRETBOX_NONCEBYTES')

View File

@ -1574,6 +1574,6 @@ class NavigationTree
$url = parse_url($link);
parse_str(htmlspecialchars_decode($url['query']), $query);
return $url['path'] . '?' . Url::buildHttpQuery($query);
return $url['path'] . '?' . htmlspecialchars(Url::buildHttpQuery($query));
}
}

View File

@ -270,7 +270,9 @@ class Url
unset($data[$paramKey]);
}
$data['eq'] = self::encryptQuery(json_encode($paramsToEncrypt));
if ($paramsToEncrypt !== []) {
$data['eq'] = self::encryptQuery(json_encode($paramsToEncrypt));
}
return http_build_query($data, null, $separator);
}

View File

@ -43,7 +43,6 @@ use PhpMyAdmin\Response;
use PhpMyAdmin\Session;
use PhpMyAdmin\ThemeManager;
use PhpMyAdmin\Tracker;
use PhpMyAdmin\Url;
use PhpMyAdmin\Util;
/**
@ -133,16 +132,7 @@ if (! defined('PMA_NO_SESSION')) {
Session::setUp($GLOBALS['PMA_Config'], $GLOBALS['error_handler']);
}
if (isset($_GET['eq']) && is_string($_GET['eq'])) {
$decryptedQuery = Url::decryptQuery($_GET['eq']);
if ($decryptedQuery !== null) {
$urlQueryParams = json_decode($decryptedQuery);
foreach ($urlQueryParams as $urlQueryParamKey => $urlQueryParamValue) {
$_GET[$urlQueryParamKey] = $urlQueryParamValue;
$_REQUEST[$urlQueryParamKey] = $urlQueryParamValue;
}
}
}
Core::populateRequestWithEncryptedQueryParams();
/**
* init some variables LABEL_variables_init

View File

@ -11,6 +11,7 @@ use PhpMyAdmin\Config;
use PhpMyAdmin\Core;
use PhpMyAdmin\Sanitize;
use PhpMyAdmin\Tests\PmaTestCase;
use PhpMyAdmin\Url;
use stdClass;
/**
@ -50,6 +51,7 @@ class CoreTest extends PmaTestCase
$GLOBALS['db'] = '';
$GLOBALS['table'] = '';
$GLOBALS['PMA_PHP_SELF'] = 'http://example.net/';
$GLOBALS['PMA_Config']->set('URLQueryEncryption', false);
}
/**
@ -1226,4 +1228,61 @@ class CoreTest extends PmaTestCase
// Must work now, (good secret and blowfish_secret)
$this->assertTrue(Core::checkSqlQuerySignature($sqlQuery, $hmac));
}
/**
* @return void
*/
public function testPopulateRequestWithEncryptedQueryParams()
{
global $PMA_Config;
$_SESSION = [];
$PMA_Config->set('URLQueryEncryption', true);
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
$_GET = ['pos' => '0', 'eq' => Url::encryptQuery('{"db":"test_db","table":"test_table"}')];
$_REQUEST = $_GET;
Core::populateRequestWithEncryptedQueryParams();
$expected = ['pos' => '0', 'db' => 'test_db', 'table' => 'test_table'];
$this->assertEquals($expected, $_GET);
$this->assertEquals($expected, $_REQUEST);
}
/**
* @return void
*
* @dataProvider providerForTestPopulateRequestWithEncryptedQueryParamsWithInvalidParam
*/
public function testPopulateRequestWithEncryptedQueryParamsWithInvalidParam($encrypted, $decrypted)
{
global $PMA_Config;
$_SESSION = [];
$PMA_Config->set('URLQueryEncryption', true);
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
$_GET = $encrypted;
$_REQUEST = $encrypted;
Core::populateRequestWithEncryptedQueryParams();
$this->assertEquals($decrypted, $_GET);
$this->assertEquals($decrypted, $_REQUEST);
}
/**
* @return string[][][]
*/
public function providerForTestPopulateRequestWithEncryptedQueryParamsWithInvalidParam()
{
return [
[[], []],
[['eq' => []], []],
[['eq' => ''], []],
[['eq' => 'invalid'], []],
];
}
}

View File

@ -0,0 +1,114 @@
<?php
namespace PhpMyAdmin\Tests\Crypto;
use PhpMyAdmin\Crypto\Crypto;
use PhpMyAdmin\Tests\PmaTestCase;
/**
* @covers \PhpMyAdmin\Crypto\Crypto
*/
class CryptoTest extends PmaTestCase
{
/**
* @return void
*/
public function testWithValidKeyFromConfig()
{
global $PMA_Config;
$_SESSION = [];
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
$cryptoWithSodium = new Crypto();
$encrypted = $cryptoWithSodium->encrypt('test');
$this->assertNotSame('test', $encrypted);
$this->assertSame('test', $cryptoWithSodium->decrypt($encrypted));
$this->assertArrayNotHasKey('URLQueryEncryptionSecretKey', $_SESSION);
$cryptoWithPhpseclib = new Crypto(true);
$encrypted = $cryptoWithPhpseclib->encrypt('test');
$this->assertNotSame('test', $encrypted);
$this->assertSame('test', $cryptoWithPhpseclib->decrypt($encrypted));
$this->assertArrayNotHasKey('URLQueryEncryptionSecretKey', $_SESSION);
}
/**
* @return void
*/
public function testWithValidKeyFromSession()
{
global $PMA_Config;
$_SESSION = ['URLQueryEncryptionSecretKey' => str_repeat('a', 32)];
$PMA_Config->set('URLQueryEncryptionSecretKey', '');
$cryptoWithSodium = new Crypto();
$encrypted = $cryptoWithSodium->encrypt('test');
$this->assertNotSame('test', $encrypted);
$this->assertSame('test', $cryptoWithSodium->decrypt($encrypted));
$this->assertArrayHasKey('URLQueryEncryptionSecretKey', $_SESSION);
$cryptoWithPhpseclib = new Crypto(true);
$encrypted = $cryptoWithPhpseclib->encrypt('test');
$this->assertNotSame('test', $encrypted);
$this->assertSame('test', $cryptoWithPhpseclib->decrypt($encrypted));
$this->assertArrayHasKey('URLQueryEncryptionSecretKey', $_SESSION);
}
/**
* @return void
*/
public function testWithNewSessionKey()
{
global $PMA_Config;
$_SESSION = [];
$PMA_Config->set('URLQueryEncryptionSecretKey', '');
$cryptoWithSodium = new Crypto();
$encrypted = $cryptoWithSodium->encrypt('test');
$this->assertNotSame('test', $encrypted);
$this->assertSame('test', $cryptoWithSodium->decrypt($encrypted));
$this->assertArrayHasKey('URLQueryEncryptionSecretKey', $_SESSION);
$this->assertEquals(32, mb_strlen($_SESSION['URLQueryEncryptionSecretKey'], '8bit'));
$cryptoWithPhpseclib = new Crypto(true);
$encrypted = $cryptoWithPhpseclib->encrypt('test');
$this->assertNotSame('test', $encrypted);
$this->assertSame('test', $cryptoWithPhpseclib->decrypt($encrypted));
$this->assertArrayHasKey('URLQueryEncryptionSecretKey', $_SESSION);
$this->assertEquals(32, mb_strlen($_SESSION['URLQueryEncryptionSecretKey'], '8bit'));
}
/**
* @return void
*/
public function testDecryptWithInvalidKey()
{
global $PMA_Config;
$_SESSION = [];
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
$cryptoWithSodium = new Crypto();
$encrypted = $cryptoWithSodium->encrypt('test');
$this->assertNotSame('test', $encrypted);
$this->assertSame('test', $cryptoWithSodium->decrypt($encrypted));
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('b', 32));
$cryptoWithSodium = new Crypto();
$this->assertNull($cryptoWithSodium->decrypt($encrypted));
$cryptoWithPhpseclib = new Crypto(true);
$encrypted = $cryptoWithPhpseclib->encrypt('test');
$this->assertNotSame('test', $encrypted);
$this->assertSame('test', $cryptoWithPhpseclib->decrypt($encrypted));
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
$cryptoWithPhpseclib = new Crypto(true);
$this->assertNull($cryptoWithPhpseclib->decrypt($encrypted));
}
}

View File

@ -11,6 +11,8 @@ use PhpMyAdmin\Config;
use PhpMyAdmin\Navigation\NavigationTree;
use PhpMyAdmin\Tests\PmaTestCase;
use PhpMyAdmin\Theme;
use PhpMyAdmin\Url;
use ReflectionMethod;
/*
* we must set $GLOBALS['server'] here
@ -101,4 +103,37 @@ class NavigationTreeTest extends PmaTestCase
$result = $this->object->renderDbSelect();
$this->assertContains('pma_navigation_select_database', $result);
}
/**
* @return void
*/
public function testEncryptQueryParams()
{
global $PMA_Config;
$_SESSION = [];
$PMA_Config->set('URLQueryEncryption', false);
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
$method = new ReflectionMethod($this->object, 'encryptQueryParams');
$method->setAccessible(true);
$link = 'tbl_structure.php?server=1&amp;db=test_db&amp;table=test_table&amp;pos=0';
$actual = $method->invoke($this->object, $link);
$this->assertEquals($link, $actual);
$PMA_Config->set('URLQueryEncryption', true);
$actual = $method->invoke($this->object, $link);
$this->assertStringStartsWith('tbl_structure.php?server=1&amp;pos=0&amp;eq=', $actual);
$url = parse_url($actual);
parse_str(htmlspecialchars_decode($url['query']), $query);
$this->assertRegExp('/^[a-zA-Z0-9-_=]+$/', $query['eq']);
$decrypted = Url::decryptQuery($query['eq']);
$this->assertJson($decrypted);
$this->assertSame('{"db":"test_db","table":"test_table"}', $decrypted);
}
}

View File

@ -27,6 +27,7 @@ class UrlTest extends TestCase
public function setUp()
{
unset($_COOKIE['pma_lang']);
$GLOBALS['PMA_Config']->set('URLQueryEncryption', false);
}
/**
@ -105,4 +106,61 @@ class UrlTest extends TestCase
$expected = '?server=x' . htmlentities($separator) . 'lang=en' ;
$this->assertEquals($expected, Url::getCommon());
}
/**
* @return void
*/
public function testBuildHttpQueryWithUrlQueryEncryptionDisabled()
{
global $PMA_Config;
$PMA_Config->set('URLQueryEncryption', false);
$params = ['db' => 'test_db', 'table' => 'test_table', 'pos' => 0];
$this->assertEquals('db=test_db&table=test_table&pos=0', Url::buildHttpQuery($params));
}
/**
* @return void
*/
public function testBuildHttpQueryWithUrlQueryEncryptionEnabled()
{
global $PMA_Config;
$_SESSION = [];
$PMA_Config->set('URLQueryEncryption', true);
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
$params = ['db' => 'test_db', 'table' => 'test_table', 'pos' => 0];
$query = Url::buildHttpQuery($params);
$this->assertStringStartsWith('pos=0&eq=', $query);
parse_str($query, $queryParams);
$this->assertCount(2, $queryParams);
$this->assertSame('0', $queryParams['pos']);
$this->assertTrue(is_string($queryParams['eq']));
$this->assertNotSame('', $queryParams['eq']);
$this->assertRegExp('/^[a-zA-Z0-9-_=]+$/', $queryParams['eq']);
$decrypted = Url::decryptQuery($queryParams['eq']);
$this->assertJson($decrypted);
$this->assertSame('{"db":"test_db","table":"test_table"}', $decrypted);
}
/**
* @return void
*/
public function testQueryEncryption()
{
global $PMA_Config;
$_SESSION = [];
$PMA_Config->set('URLQueryEncryption', true);
$PMA_Config->set('URLQueryEncryptionSecretKey', str_repeat('a', 32));
$query = '{"db":"test_db","table":"test_table"}';
$encrypted = Url::encryptQuery($query);
$this->assertNotSame($query, $encrypted);
$this->assertNotSame('', $encrypted);
$this->assertRegExp('/^[a-zA-Z0-9-_=]+$/', $encrypted);
$decrypted = Url::decryptQuery($encrypted);
$this->assertSame($query, $decrypted);
}
}