
Security can be examined at various levels:
- How we can protect the phpMyAdmin installation directory
- Which workstations can access phpMyAdmin
- The databases that a legitimate user can see
- How in-transit data protection and access protection can be enforced via a physical USB key on the workstation
- How we can use a logging system to record login attempts
Suppose an unauthorized person is trying to execute our copy of phpMyAdmin. If we use the simple config
authentication type, anyone knowing the URL of our phpMyAdmin will have the same effective rights to our data as we do. In this case, we should use the directory protection mechanism offered by our web server (for example, .htaccess
, a filename with a leading dot) to add a level of protection.
If we decide to use http
or cookie
authentication types, our data would be safe enough. However, we should take normal precautions with our password (including its periodic change).
The directory where phpMyAdmin is installed contains sensitive data. Not only the configuration file, but also all scripts stored there, must be protected from alteration. We should ensure that apart from us, only the web server's effective user has read access to the files contained in this directory, and that only we can write to them.
Note
phpMyAdmin's scripts never have to modify anything inside of this directory, except when we use the Save export file to server feature (explained in Chapter 6,Exporting Data and Structure (Backup)).
Another recommendation is to rename the default phpMyAdmin
directory to something less obvious; this discourages the probing of our server. This is called security by obscurity and can be very effective—but please avoid choosing other obvious names, such as admin
.
Another possible attack is from other developers who have an account on the same web server as we do. In this kind of attack, someone can try to open our config.inc.php
file. As this file is readable by the web server, someone could try to include
our file from their PHP scripts. This is why it's recommended to use PHP's open_basedir
feature, possibly applying it to all directories from which such attacks could originate.
Version 3.0 has introduced a new PHP error trapping behavior in phpMyAdmin, based on PHP's custom error handler mechanism. One of the benefits of this error handler is the avoidance of path disclosure, which is considered a security weakness. The default settings related to this are:
$cfg['Error_Handler'] = array(); $cfg['Error_Handler']['display'] = false;
You should leave the default value for display as false, unless you are developing a new phpMyAdmin feature and want to see all PHP errors and warnings.
An additional level of protection can be added, this time verifying the Internet Protocol (IP) address of the machine from which the request to use phpMyAdmin is received. To achieve this level of protection, we construct rules allowing or denying access, and specify the order in which these rules will be applied.
<'allow' | 'deny'> <username> [from] <source>
The from
keyword is optional. Here are some examples:
Rule Description
allow Bob from 1.2.3.4
>User Bob
is allowed access from IP address 1.2.3.4
.
allow Bob from 1.2.3/24
>User Bob
is allowed from any address matching the network 1.2.3
(this is CIDR IP matching).
deny Alice from 4.5/16
>User Alice
is denied access when located on network 4.5
.
allow Melanie from all
>User Melanie
can login from anywhere.
allow Julie from localhost
>Equivalent to 127.0.0.1
deny % from all
>all
can be used as an equivalent to 0.0.0.0/0, meaning any host. Here, the %
sign means any user.
Usually we will have several rules. Let's say we wish to have the two rules that follow:
allow Marc from 45.34.23.12 allow Melanie from all
We have to put them in config.inc.php
(in the related server-specific section) as follows:
$cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow Marc from 45.34.23.12', 'allow Melanie from all');
When defining a single rule or multiple rules, a PHP array is used. We must follow its syntax, enclosing each complete rule within single quotes and separating each rule from the next with a comma. Thus, if we have only one rule, we must still use an array to specify it like this:
$cfg['Servers'][$i]['AllowDeny']['rules'] = array('allow Marc from 45.34.23.12');
The next parameter explains the order in which rules are interpreted.
By default, this parameter is empty:
$cfg['Servers'][$i]['AllowDeny']['order'] = '';
This means that no IP-based verification is made.
Suppose we want to allow access by default, denying access only to some username/IP pairs. We should use:
$cfg['Servers'][$i]['AllowDeny']['order'] = 'deny,allow';
In this case, all deny
rules will be applied first, followed by allow
rules. If a case is not mentioned in the rules, access is granted. Being more restrictive, we'd want to deny by default. We can use:
$cfg['Servers'][$i]['AllowDeny']['order'] = 'allow,deny';
In this case, all allow
rules are applied first, followed by deny
rules. If a case is not mentioned in the rules, access is denied. The third (and most restrictive) way of specifying the order of rules is:
$cfg['Servers'][$i]['AllowDeny']['order'] = 'explicit';
Now, deny
rules are applied before allow
rules. But for them to be accepted, a username/IP address must be listed in the allow
rules, and not in the deny
rules.
As the root
user is present in almost all MySQL installations, it's often the target of attacks. A parameter permits us to easily block all phpMyAdmin logins of the MySQL's root
account, by using the following:
$cfg['Servers'][$i]['AllowRoot'] = FALSE;
Some system administrators prefer to disable the root
account at the MySQL server level, creating another less obvious account that possesses the same privileges. This has the advantage of blocking root access from all sources, and not just from phpMyAdmin.
HTTP is not inherently immune to network sniffing (grabbing sensitive data off the wire). So, if we want to protect not only our username and password but all of the data that travels between our web server and the browser, we have to use HTTPS.
To do so, assuming that our web server supports HTTPS, we just have to start phpMyAdmin by putting https
instead of http
in the URL: https://www.mydomain.com/phpMyAdmin/.
If we are using PmaAbsoluteUri
auto-detection:
$cfg['PmaAbsoluteUri'] = '';
phpMyAdmin will see that we are using HTTPS in the URL and react accordingly.
If not, we must put the https
part in this parameter as follows:
$cfg['PmaAbsoluteUri'] = 'https://www.mydomain.com/phpMyAdmin';
We can automatically switch users to an HTTPS connection with this setting:
$cfg['ForceSSL'] = TRUE;
Support for the Swekey hardware authentication USB key has been merged into cookie-based authentication in phpMyAdmin. Reference for this USB key is available at http://phpmyadmin.net/auth_key. A Swekey can be used with all compatible web applications to add a level of security, based on the possession of this physical device. In the case of phpMyAdmin, it does not replace the normal MySQL authentication.
The contrib/swekey.sample.conf
sample file should be used as a starting point to configure this feature. This file contains sample configuration commands and comments from the vendor. The principle is simple—each Swekey device contains a unique ID, and this ID must be associated with a MySQL username in the Swekey configuration file. These lines are part of the sample file:
0000000000000000000000000000763A:root 000000000000000000000000000089E4:steve 0000000000000000000000000000231E:scott
This means that the person responsible for the phpMyAdmin configuration tracks which Swekey is given to each user and puts this information in the file. The vendor does not need to be informed about this association. They only keep track of which Swekeys are sold and which are deactivated. Hence, a Swekey can be passed from one user to another, provided that the configuration is updated accordingly.
Other security-related directives are in the sample file, and it's recommended to refrain from changing them—SERVER_CHECK, SERVER_RNDTOKEN
, and SERVER_STATUS
in particular. This is because these directives access servers via https
, which is a guarantee of a secure channel between our web server and the vendor's.
Once modified, this file should be copied over to a directory outside of the web server's document root (suggested place is /etc/swekey-pma.conf)
. Then, a configuration parameter in config.inc.php
must be set as well:
$cfg['Servers'][$i]['auth_swekey_config'] = '/etc/swekey-pma.conf';
Let's see what happens now when phpMyAdmin is started and no Swekey is connected to your workstation:

We notice three changes:
- The username field cannot be typed in
- A small key icon is displayed
- An error message appears
It's normal that the username field is deactivated, as usernames are now taken from the Swekey configuration file. The icon can be clicked to reach the vendor's website. There is a testing mechanism on this site, to verify if a Swekey is connected. Drivers can also be downloaded in case the automatic installation did not work.
Now if you connect your Swekey, assuming that user marc
has been defined and associated to this device, a reassuring panel is displayed:

It would not make sense to rely on the protection offered by this device, if our users have access to a webspace on which they can install their own copy of phpMyAdmin. Obviously, phpMyAdmin has to be configured accordingly to make use of the Swekey. Hence, a user-installed copy of phpMyAdmin would circumvent the protection. This means that a host provider should permit access to his MySQL server only from a web server on which he or she installed a controlled copy of phpMyAdmin, and on which other users cannot install their own copy.
When PHP is running as an Apache module, extra PHP functions are made available to the application. One of these is apache_note()
, which is referenced at http://php.net/apache_note. Although the name of this function might not be evident, it can be used to set name-value pairs inside of the Apache system. This information is accessible to other Apache modules; we can take advantage of this to ask Apache to include some information of our choice in its access log.
Subsequently, log files analyzing tools can be used to gather information about our legitimate users or possible break-in attempts.
If phpMyAdmin detects the presence of the apache_note()
function, it automatically creates the following notes during the login process:
- userID: This contains the username for which a login attempt was made
- userStatus: This contains a message explaining the outcome of the login attempt
The possible status messages are:
Status message Description
"ok" >The user is logged in
"mysql-denied" >The MySQL server refused this login attempt
"allow-denied" >phpMyAdmin's allow/deny rules denied this login attempt
"root-denied" >phpMyAdmin's $cfg['Servers'][$i]['AllowRoot']
directive (set to false) blocked a root login attempt
"empty-denied" >phpMyAdmin's $cfg['Servers'][$i]['AllowNoPassword']
directive is set to false and a login attempt without a password was made
Up to now, the notes are only present inside the Apache system. Normally we want to see them, therefore we will modify Apache's logging configuration to achieve this goal. The exact recipe for this depends on the configuration file name chosen by the distributor of Apache, but we'll use the most common one: httpd.conf
.
This file usually contains a LogFormat
directive that controls which information goes into one of Apache's log file. phpMyAdmin's documentation suggests this modified directive:
LogFormat "%h %l %u %t \"%r\" %>s %b \ \"%{Referer}i\" \"%{User-Agent}i\" %{userID}n %{userStatus}n" \ pma_combined
We recognize the userID
and userStatus
notes coming from phpMyAdmin, coated with Apache's special characters. Hence we can apply this newly-configured pma_combined
format to Apache's access log with:
CustomLog "logs/access_log" pma_combined
After restarting Apache, all login attempts will be logged and can be analyzed for statistical or security purposes.