Plaintext database user and password in config.php

Hey guys, I’m absolutely new here and not yet very experienced with Nextcloud. I just installed Nextcloud 15.0.5 on the webspace of our external provider. What I recognized is, that user and password of the Nextcloud database is stored in plaintext in the config.php file.

Well, I’m not a IT security expert - but this seems to be strange to me…

Even if access to this file is restricted, I’m pretty sure, the admin of the provider will habe access to read this file.

I really used the search function in this board but did not get useful results for me. If this topic was discussed before, I’m sorry to open a new thread…

My questions:

  1. Is this a security issue?
  2. Is this a bad configuration issue from my side?
  3. What is your suggestion to deal with this point?

Thanks a lot!
Tonio

This is how most webapps handles database connections. Wordpress for example does the same. The configuration has the username and password in plaintext.

This is not optimal from a security point. But it is pretty much the only way if keeping things simple enough.

Wow, thank you very much for this quick reponse! So… I guess Owncloud will handle this issue the same way?

Yes they do.

1 Like

In php, it is possible to use the getenv statement to get the value of an environment variable.
With this method, you can use safe software (Hashicorp Vault, Secret Server, etc.) and use dynamic authentication.

Thus, “most webapps” are not secure and do not respect good practices. TLS 1.2/1.3 do not completely protect an application. Securing authentication elements is an important principle.

The findings are as follows. Nextcloud wants to help having private clouds to prevent attacks. The problem is that the attack can come from the inside. And having the passwords in clear text in a configuration file is a major security flaw. A loophole that could be easily solved if Nextcloud wanted to make it happen. Which doesn’t seem to be the case.

Here a sample of code to use env var in config.php

config.php

<?php
$dbname = getenv('MYSQL_DATABASE') ;
$dbuser = getenv('MYSQL_USER');
$dbpassword = getenv('MYSQL_PASSWORD');
$dbhost = getenv('MYSQL_HOST');

$CONFIG = array {
...
...
),
'dbname' => $dbname,
'dbuser' => $dbuser,
'dbhost' => $dbhost,
'dbpassword' => $dbpassword,
....
}

And if you use docker, and vault, you can try this :slight_smile:

copy entrypoint.sh, and modify. then use a volume to map the file inside the container

#!/bin/sh
set -eu

apt update -y && apt install -y jq

VLT_TOKEN=$(curl -s -X POST -d '{"role_id":"'$ROLE_ID'","secret_id":"'$SECRET_ID'"}' $VAULT_ADDR:$VAULT_PORT/v1/auth/approle/login | jq -r '.auth.client_token')
DB_KEYS=$(curl -s -H "X-Vault-Token:$VLT_TOKEN" ${VAULT_ADDR}:${VAULT_PORT}/v1/${VAULT_NEXTCLOUD_DB_PATH})
export MYSQL_DATABASE=$(echo $DB_KEYS | jq -r '.data.data.MYSQL_DATABASE')
export MYSQL_PASSWORD=$(echo $DB_KEYS | jq -r '.data.data.MYSQL_PASSWORD')
export MYSQL_USER=$(echo $DB_KEYS | jq -r '.data.data.MYSQL_USER')

ROLE_ID and SECRET_ID can be generated by another scripts (in case of dynamic credential of the database) and injected to the container. Or using a secret (Swarm, Kubernested, Consol, etc…)

Edit to use config.php, and other config file (php) to use Vault as credentials authenticator with renewable token .

Config.php
$filename = '/tmp/token'; // Temporary filename for token store $vlt_url = getenv('VAULT_ADDR'); // Set Vault server information in environment variables $vlt_port = getenv('VAULT_PORT');

if (file_exists($filename)) {
    $myfile = fopen($filename, "r+");
    $token = fread($myfile, filesize($filename));
    fclose($myfile);
    echo $contents ;
    # Renew Token if not max TTL
    $ch = curl_init();
    $headers = array();
    $headers[] = 'X-Vault-Token:' . $token;
    curl_setopt($ch, CURLOPT_URL, $vlt_url.":".$vlt_port."/v1/auth/token/renew-self");
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $json_secrets = curl_exec($ch);
    $GLOBALS['token'] = json_decode($json_secrets)->{'auth'}->{'client_token'};
    curl_close($ch);
}

if ($GLOBALS['token'] == "") {
    # GET New token
    # Vault Login
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $vlt_url.":".$vlt_port."/v1/auth/nextcloud/login");
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, '{"role_id":"'.getenv('role_id').'","secret_id":"'.getenv('secret_id').'"}');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $json_return = curl_exec($ch);
    curl_close($ch);

    # Parse Token
    $json_token = json_decode($json_return);
    $GLOBALS['token'] = $json_token->{'auth'}->{'client_token'};
    $_ENV['token'] = $GLOBALS['token'];
    $myfile = fopen("/tmp/token", "w+");
    fwrite($myfile, $GLOBALS['token']);
    fclose($myfile);
}

Exemple for database config file :slight_smile:
$vlt_path = getenv('VAULT_NEXTCLOUD_DB_PATH'); // Env var for path in vault

# Set Headers with Token
$headers = array();
$headers[] = 'X-Vault-Token:' . $GLOBALS['token'];

# Get credentials
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $vlt_url.":".$vlt_port."/v1/".$vlt_path);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$json_secrets = curl_exec($ch);
curl_close($ch);

#echo "[DEBUG] : " . $json_secrets;
# Set variables
$user = json_decode($json_secrets)->{'data'}->{'data'}->{'MYSQL_USER'};
$pass = json_decode($json_secrets)->{'data'}->{'data'}->{'MYSQL_PASSWORD'};
$database = json_decode($json_secrets)->{'data'}->{'data'}->{'MYSQL_DATABASE'};
#$host = json_decode($json_secrets)->{'data'}->{'data'}->{'MYSQL_HOST'};
#$port = json_decode($json_secrets)->{'data'}->{'data'}->{'MYSQL_PORT'};
# Configure database
$CONFIG = array (
  'dbtype' => 'mysql',
  'dbhost' => getenv('MYSQL_HOST'),
  'dbport' => '',
  'dbtableprefix' => 'oc_',
  'mysql.utf8mb4' => true,
  'dbname' => $database,
  'dbuser' => $user,
  'dbpassword' => $pass,
); 

exemple for smtp.config.php

> $vlt_path = getenv('VAULT_GMAIL_PATH');
#Set headers with token
$headers = array();
$headers[] = 'X-Vault-Token:' . $GLOBALS['token'];
# Get credentials
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $vlt_url.":".$vlt_port."/v1/".$vlt_path);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$json_secrets = curl_exec($ch);
curl_close($ch);

# Set variables
$user = json_decode($json_secrets)->{'data'}->{'data'}->{'name'};
$pass = json_decode($json_secrets)->{'data'}->{'data'}->{'password'};
$host = json_decode($json_secrets)->{'data'}->{'data'}->{'host'};
$port = json_decode($json_secrets)->{'data'}->{'data'}->{'port'};
$mail_from = json_decode($json_secrets)->{'data'}->{'data'}->{'mail'};
$domain = json_decode($json_secrets)->{'data'}->{'data'}->{'domain'};

#if ($host && $mail_from && $domain) {
  $CONFIG = array (
    'mail_smtpmode' => 'smtp',
    'mail_smtphost' => $host ?: '',
    'mail_smtpport' => $port ?: '25',
    'mail_smtpsecure' => getenv('SMTP_SECURE') ?: '',
    'mail_smtpauth' => $user && $pass,
    'mail_smtpauthtype' => getenv('SMTP_AUTHTYPE') ?: 'LOGIN',
    'mail_smtpname' => $user ?: '',
    'mail_smtppassword' => $pass ?: '',
    'mail_from_address' => $mail_from,
    'mail_domain' => $domain,
  );
#}

It’s not perfect but it works. I hope this will help

Sylvain