Wordpress mit verschiedenen Datenbanknutzern absichern

28.02.2014 yahe administration code legacy security wordpress

WordPress ist nicht gerade ein Stück Software, das für sein herausragendes Sicherheitskonzept bekannt ist. Viele Dinge muss man erst mühsam im Alleingang nachbessern. So hat es mich schon immer gestört, dass Nutzer, die sich meinen Blog ansehen, im Hintergrund den gleichen Datenbanknutzer verwenden, wie ich als Administrator, der die Seite konfiguriert und neue Bloginhalte bereitstellt. Wenn es möglich wäre, wenigstens für diese zwei stark gegenteiligen Nutzungsszenarien verschiedene Datenbanknutzer zu verwenden, wäre schon viel erreicht. Aus diesem Grund habe ich mich heute einmal hingesetzt und genau das implementiert. Alle Änderungen, die ich vornehmen mussten, sind so konzipiert, dass sie ein Update der WordPress-Installation überleben. Das heißt, dass keine Core-Files angerührt werden müssen.

Das größte Problem, das zu lösen war, ist, dass der Datenbanknutzer schon relativ früh beim Einlesen der "wp-config.php" gewählt wird. Die Information, dass man eingeloggt ist, steht jedoch erst viel später zur Verfügung. Es musste also ein Weg gefunden werden, diese Information bereits früher bereitzustellen. Meine Lösung ist es, beim erfolgreichen Login einfach eine Session-Variable zu setzen und diese nach einem Logout wieder zu löschen. Das erledige ich mit einem Plugin:

<?php
  /*
    Plugin Name: LoginSession
    Plugin URI: http://yahe.sh/
    Description: Generates a reusable PHP session when logged in.
    Version: 0.1c3
    Author: Yahe
    Author URI: http://yahe.sh/

    this code is based on http://wordpress.org/support/topic/using-session-in-wordpress
  */

  /* STOP EDITING HERE IF YOU DO NOT KNOW WHAT YOU ARE DOING */

  define("LOGINSESSION_LOGGEDIN", "loginsession_loggedin");
  define("LOGINSESSION_STARTED",  "loginsession_started");

  function loginsession_start() {
    $started = false;

    // check if session already exists
// will work starting with PHP 5.4.0:
//    if (PHP_SESSION_NONE === session_status()) {
    if ("" === session_id()) {
      $started = session_start();
    }

    // session should exist now
// will work starting with PHP 5.4.0:
//    if (PHP_SESSION_ACTIVE === session_status()) {
    if ("" !== session_id()) {
      // prevent session fixation
      session_regenerate_id(true);

      $_SESSION[LOGINSESSION_LOGGEDIN] = true;
      $_SESSION[LOGINSESSION_STARTED]  = $started;
    }
  }

  function loginsession_stop() {
    // check if session still exists
// will work starting with PHP 5.4.0:
//    if (PHP_SESSION_ACTIVE === session_status()) {
    if ("" !== session_id()) {
      // prevent session fixation
      session_regenerate_id(true);

      // remove loggedin value from session
      if (isset($_SESSION[LOGINSESSION_LOGGEDIN])) {
        unset($_SESSION[LOGINSESSION_LOGGEDIN]);
      }

      // check if we started the session
      if (isset($_SESSION[LOGINSESSION_STARTED])) {
        if ($_SESSION[LOGINSESSION_STARTED]) {
          // destroy the session if we did
          session_unset();
          session_destroy();

          // unset the session cookie
          if (isset($_COOKIE[session_name()])) {
            $params = session_get_cookie_params();
            setcookie(session_name(), "", time()-3600, $params["path"], $params["domain"], $params["secure"], $params["httponly"]);
          }
        } else {
          // remove started value from session if we did not
          unset($_SESSION[LOGINSESSION_STARTED]);
        }
      }
    }
  }

  add_action("wp_login",  "loginsession_start");
  add_action("wp_logout", "loginsession_stop");
?>

Anschließend bedarf es noch einer Änderung in der "wp-config.php". Dort werden statt einem Datenbanknutzer (bestehend aus "DB_USER" und "DB_PASSWORD") nun zwei Datenbanknutzer definiert. Der mit den umfangreichen Rechten wird nur noch dann genutzt, wenn entweder der Wert "loginsession_loggedin" in der Session auf true gesetzt ist oder wenn die Konstante "DOING_CRON" definiert ist. Das eine ist der Fall, wenn der WordPress-Nutzer eingeloggt ist, das andere ist der Fall, wenn das Skript "wp-cron.php" ausgeführt wird.

$loginsession_evaluated = false;
// open the session if there is one
if ((isset($_COOKIE[session_name()])) || (isset($_GET[session_name()]))) {
  session_start();

  if ((isset($_SESSION["loginsession_loggedin"])) && ($_SESSION["loginsession_loggedin"])) {
    $loginsession_evaluated = true;
  }
}

// select user dependent on loginsession
if (($loginsession_evaluated) || (defined("DOING_CRON"))) {
  define("DB_USER",     "<ADMIN USER>");
  define("DB_PASSWORD", "<ADMIN PASSWORD>");
} else {
  define("DB_USER",     "<GUEST USER>");
  define("DB_PASSWORD", "<GUEST PASSWORD>");
}

Damit das ganze ordentlich funktioniert, müsst ihr natürlich noch den Datenbanknutzer für eure Gäste anlegen. Dazu verbindet ihr euch mit eurer Datenbank und führt dort ein paar Befehle aus. Ich gehe davon aus, dass eure Datenbank "wordpress" heißt und ihr den Präfix "wp_" verwendet habt.

CREATE USER '<GUEST USER>'@'%' IDENTIFIED BY '<GUEST PASSWORD>';
GRANT SELECT ON wordpress.* TO '<GUEST USER>'@'%';
GRANT INSERT ON wordpress.wp_commentmeta TO '<GUEST USER>'@'%';
GRANT INSERT ON wordpress.wp_comments TO '<GUEST USER>'@'%';
GRANT UPDATE ON wordpress.wp_commentmeta TO '<GUEST USER>'@'%';
GRANT UPDATE ON wordpress.wp_comments TO '<GUEST USER>'@'%';
GRANT DELETE ON wordpress.wp_commentmeta TO '<GUEST USER>'@'%';
GRANT DELETE ON wordpress.wp_comments TO '<GUEST USER>'@'%';

Mit dieser Grundausrüstung können eure Gäste weiterhin alles lesen, jedoch nur noch Kommentare erstellen. Sie können hingegen keine Nutzeraccounts mehr manipulieren, keine Blogbeiträge mehr manipulieren und auch keine Konfigurationseinstellungen mehr manipulieren. So schnell habt ihr euren WordPress-Blog vor jeder Menge Zero-Day-Exploits abgesichert.

Mit diesen restriktiven Einstellungen funktioniert jedoch die Verwendung des normalen WordPress-Cronjobs nicht mehr. Dieser wird typischerweise regelmäßig durch das Besuchen des Blogs gestartet. Da normale Besucher nun jedoch keine administrativen Rechte mehr haben, kann der Cronjob seine Arbeiten nicht mehr verrichten. Das Problem kann jedoch gelöst werden. Zuerst einmal muss in die "wp-config.php" noch eine weitere Zeile aufgenommen werden:

define("DISABLE_WP_CRON", true);

Anschließend richtet ihr in eurem System einen echten Cronjob ein, der in regelmäßigen Abständen die WordPress-Cronjobs aufruft. Achtet darauf, dass der Cronjob nicht zu häufig hintereinander aufgerufen wird, damit Locksituation vermieden werden:

wget -q -O - http://<BLOGURL>/wp-cron.php?doing_wp_cron >/dev/null 2>&1

Je nachdem, was ihr sonst noch so für Plugins betreibt, kann es notwendig sein, dass ihr dem Account "<GUEST USER>" weitere Rechte in eurer Datenbank einräumt, zum Beispiel, wenn ihr ein Nutzertracking verwenden solltet.


Search

Categories

administration (40)
arduino (12)
calcpw (2)
code (33)
hardware (16)
java (2)
legacy (113)
linux (27)
publicity (6)
review (2)
security (58)
thoughts (21)
windows (17)
wordpress (19)