I recently had the idea to create a login system to PhpMyAdmin with a token created from login credentials stored in a database. The url would look like this :
pma.example.com/index.php?token=WCxuFWj1QtGGapZhfSPS9Eo1Q9q666sR
The token contains 32 randomly generated alphanumeric characters.
So I have an API made in Python with Flask which communicates locally with PhpMyadmin and the different applications with which has two routes:
127.0.0.1:4040/api/v1/create_token
[POST] This request has as JSON argumentdatabase_host_id
for the MySQL host ID to connect defined in PhpMyAdmin configuration file,database_username
which is as written the database user to connect and finallydatabase_host
which is the database password. The response have antoken
argument for the client to receive the new generated token. The JSON sent and received should look like this :
JSON received by the API :
{
"database_host_id": 1,
"database_username": "pma",
"database_password: "4wAyS8",
}
JSON sent to the client:
{
"token": "WCxuFWj1QtGGapZhfSPS9Eo1Q9q666sR"
}
127.0.0.1:4040/api/v1/get_database
[POST] This request has a JSON argumenttoken
to know from which token to retrieve the connection information. So the response will have all this information along withdatabase_host_id
,database_username
anddatabase_password
. The JSON sent and received should look like this :
JSON received by the API :
{
"token": "WCxuFWj1QtGGapZhfSPS9Eo1Q9q666sR"
}
JSON sent to the client :
{
"database_host_id": 1,
"database_username": "pma",
"database_password: "4wAyS8",
}
It's nice to have an API but if you don't use it, it's useless, so I searched in the source code of PhpMyAdmin how the authentication system worked.
I could understand that :
/libraries/common.inc.php
This script would at runtime use what is called anAuthPlugin
with this piece of code to then be used as a login interface by the various classes:
$auth_plugin = Plugins::getAuthPlugin();
$auth_plugin->authenticate();
- I went to see what an
AuthPlugin
was by looking at the different functions associated with this class. I deduced that all the files inlibraries/classes/Plugins/Auth
were connection plugins and allowed to define the different connection methods for MySQL hosts that we define in the PhpMyAdmin configuration file with$cfg['Servers'][$i]['auth_type']
. Knowing that the default authentication type was Cookie, I went to see the contents of the filelibrairies/classes/Plugins/Auth/AuthenticationCookie.php
.
So now I had to modify the AuthenticationCookie.php
script so that when token
was contained in the URL, it could retrieve the information from the API and then use it to connect to the database associated with that token.
And that's where I get stuck because despite my PHP basics, I can't get what I'm doing to work. I could notice despite my attempts that when I retrieve the token from the URL, it was not at all what was defined, it was as if it was encrypted.
So here are my questions:
- Should I encrypt the password even though the API only communicates locally?
- Shouldn't I just use the SignOn connection method for my MySQL host?
- Is it safe to do what I am trying to do?
Information:
- PhpMyAdmin version: 5.1.3
I would appreciate any kind of help in my reasoning to do or for any answers to my questions
CodePudding user response:
I finally ended up making a SignOn script associated with my server that gets the token from the URL, asks the API for the token information and then connects with it. It works perfectly.
<?php
/**
* Single signon for phpMyAdmin
*
* This is just example how to use session based single signon with
* phpMyAdmin, it is not intended to be perfect code and look, only
* shows how you can integrate this functionality in your application.
*/
declare(strict_types=1);
/* Use cookies for session */
ini_set('session.use_cookies', 'true');
/* Change this to true if using phpMyAdmin over https */
$secure_cookie = false;
/* Need to have cookie visible from parent directory */
session_set_cookie_params(0, '/', '', $secure_cookie, true);
/* Create signon session */
$session_name = 'SignonSession';
session_name($session_name);
// Uncomment and change the following line to match your $cfg['SessionSavePath']
//session_save_path('/foobar');
@session_start();
/* Was data posted? */
if (isset($_REQUEST['token'])) {
$data = array(
'token'=> $_REQUEST['token']
);
$payload = json_encode($data);
$curl = curl_init('http://127.0.0.1:4040/api/v1/get_database');
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type:application/json'));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($curl);
curl_close($curl);
$response = json_decode($response, true);
/* Store there credentials */
$_SESSION['PMA_single_signon_user'] = $response->database_username;
$_SESSION['PMA_single_signon_password'] = $response->database_password;
$_SESSION['PMA_single_signon_host'] = $_POST['host'];
$_SESSION['PMA_single_signon_port'] = $_POST['port'];
/* Update another field of server configuration */
$_SESSION['PMA_single_signon_cfgupdate'] = ['verbose' => 'Signon test'];
$_SESSION['PMA_single_signon_HMAC_secret'] = hash('sha1', uniqid(strval(rand()), true));
$id = session_id();
/* Close that session */
@session_write_close();
/* Redirect to phpMyAdmin (should use absolute URL here!) */
header('Location: ../index.php');
} else {
/* Show simple form */
header('Content-Type: text/html; charset=utf-8');
echo '<?xml version="1.0" encoding="utf-8"?>' . "\n";
echo '<!DOCTYPE HTML>
<html lang="en" dir="ltr">
<head>
<link rel="icon" href="../favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="../favicon.ico" type="image/x-icon">
<meta charset="utf-8">
<title>Loading...</title>
</head>
<body>';
if (isset($_SESSION['PMA_single_signon_error_message'])) {
echo '<p >';
echo $_SESSION['PMA_single_signon_error_message'];
echo '</p>';
}
echo '
<h1>Loading...</h1>
</body>
</html>';
}
I know it's not finished but I plan to improve it. So this is the solution I found.