Categories
IT Web

WordPress – Bilder auf der eigenen Homepage schützen

In WordPress nutze ich zum Darstellen meiner Bilder das Gallery-Plugin “NextGEN Gallery“.

Da ich nicht will das Hinz und Kunz meine Bilder betrachten können, habe ich die Seiten vor Fremdzugriff geschützt.
Zuerst habe ich es mit den WordPress eigenen Bordmitteln (Visibility entsprechend geändert) und dann mit einem Plugin “Page Restrict” probiert.

Das Problem was mir dabei aufgefallen ist:
Es wird zwar die jeweilige Seite geschützt, man muss sich also entsprechend WordPress gegenüber authentifizieren, aber die eingebetteten Inhalte bleiben davon unberührt.
D.h. angemeldete Benutzer können zum Beispiel die Links zu Bildern per E-Mail verrschicken und den Schutz somit für Fremde umgehen.

Nach ein bisschen recherchieren habe ich leider festgestellt, dass das auch bei großen sozialen Netzwerken wie facebook und lokalisten der Fall ist!!!
Wenn hier jemand die URL zu einem in facebook oder lokalisten eingefügten Bild kopiert, sich ausloggt und in die URL aufruft, wird das Bild trotzdem angezeigt!!!

 

Dieses Sicherheitsproblem lässt sich meiner Meinung nach nur lösen, indem die Bilder nicht direkt aufgerufen werden, sondern eine php-Abfrage dazwischen gelegt wird.
Der php Code muss prüfen, ob das Bild ausgeliefert werden darf oder nicht.

Kurze Erklärung zu NextGEN Gallery:
Ich gehe bei meiner Ausführung davon aus, dass die NextGEN Gallerien im Verzeichnis wp-content/gallery abgelegt sind. Das ist die Standardeinstellung.

 

Folgendes Schritte müssen dafür durchgeführt werden:

1) Das Original-Verzeichnis zu den Gallerien muss per .htaccess Datei vor dem Zugriff geschützt werden.

# Disable Directory Browsing
Options All -Indexes

SetEnvIf Request_URI ".*/wp-content/gallery/.*" protected

# Auth directives
AuthUserFile <path_to_your_httpdocs_directory>/.htpasswd

AuthName "Password Protected"
AuthType Basic

# Setup a deny/allow
Order Deny,Allow

# Deny from everyone
Deny from all

# except if either of these are satisfied
Satisfy any

# 1. a valid authenticated user
Require valid-user

# 2. the "require_auth" var is NOT set
Allow from env=!protected

Die .htpasswd Datei müsst ihr natürlich noch anlegen und füllen.

Damit ist das Verzeichnis der kompletten Gallerien schon einmal geschützt.

Das könnte jetzt eigentlich auch schon so bleiben, nur ist die htaccess-Abfrage nicht wirklich schön. Aus diesem Grund müssen noch ein paar Schritte durchgeführt werden.

 

2) Beim Ausliefern des HTML-Codes schreiben wir via Apache-Modul alle URLs welche auf die Gallerien verweisen um, aber nur, wenn wir uns nicht im WordPress Admin-Bereich befinden.
(Dazu wird das Apache-Modul mod_substitute benötigt)

<LocationMatch  ^((?!wp-admin).)*$ >
  AddOutputFilterByType SUBSTITUTE text/html
  Substitute "s|wp-content/gallery/|wp-content/gallery-images/|ni"
</LocationMatch>

Mit dieser Regel wird aus dieser URL:
http://<your-domain>/wp-content/gallery/dummy_gallery/abc123.jpg
dann diese:
http://<your-domain>/wp-content/gallery-images/dummy_gallery/abc123.jpg

Wird jetzt eine so modifizierte HTML-Seite aufgerufen, bekommt der Besucher kein einziges Bild zu sehen. Das gallery-images Verzeichnis existiert nicht.
Darum brauchen wir eine zusätzliche Erweiterung in der .htaccess Datei.

 

3) gallery-images via .htaccess auf php-Datei mappen

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteCond %{HTTP_REFERER} wp-admin
  RewriteRule ^wp-content/gallery-images\/(.*) wp-content/gallery/$1 [NC]
  RewriteCond %{HTTP_REFERER} !wp-admin
  RewriteRule ^wp-content/gallery-images\/(.*) renderImageFromFile.php?imgUrl=$1 [NC] 
</IfModule>

 

 4) Jetzt noch die php-Datei erzeugen

<?php
    // include wordpress functions needed for user validation
    require_once('./wp-config.php');

    $dummy_image = "dummy_image.jpg";
    $rootpath = 'wp-content/gallery/';
    $imgpath = $rootpath . $_GET['imgUrl'];

    //check if file exists
    if(!file_exists($imgpath))
    {
       exit;
    }

    // visitor needs to be logged in
    if(!is_user_logged_in()) //this is a wordpress function
    {
       header('Content-Type: image/jpeg');
       header("Content-Length: " . filesize($dummy_image));
       $fp = fopen($dummy_image, 'rb');
       fpassthru($fp);
       fclose($fp);
       exit;
    }

    // check referer url domain
    $refURL = parse_url($_SERVER['HTTP_REFERER'],PHP_URL_HOST);

    if($refURL != '<your_domain>')
    {
       header('Content-Type: image/jpeg');
       header("Content-Length: " . filesize($dummy_image));
       $fp = fopen($dummy_image, 'rb');
       fpassthru($fp);
       fclose($fp);
       exit;
    }

    // Get the mimetype for the file
    $finfo = finfo_open(FILEINFO_MIME_TYPE);  

    // return mime type ala mimetype extension
    $mime_type = finfo_file($finfo, $imgpath);
    finfo_close($finfo);
     
    switch ($mime_type){
        case "image/jpeg":
            // Set the content type header - in this case image/jpg
            header('Content-Type: image/jpeg');
            break;
        case "image/png":
            // Set the content type header - in this case image/png
            header('Content-Type: image/png');
            break;
        case "image/gif":
            // Set the content type header - in this case image/gif
            header('Content-Type: image/gif');
            break;
    }
   
    header("Content-Length: " . filesize($imgpath));
    $fp = fopen($imgpath, 'rb');
    
    fpassthru($fp);
    fclose($fp);
     
?>

 

Mit diesen kleinen Änderungen sind alle Gallerie Bilder der WordPress Seite vor fremdem Zugriff geschützt, auch wenn Links zu den einzelnen Bildern verteilt werden.

 

 

 

 

Categories
IT Web

DokuWiki – Verschlüsselung auf dem Dateisystem hinzufügen

Um von überall aus auf diverse persönliche Informationen, Notizen, Kochrezepte usw. zugreifen zu können, habe ich mir DokuWiki auf dem Server installiert.

DokuWiki ist ein Wiki-System, welches in PHP umgesetzt ist, und sämtliche Daten auf dem Filesystem ablegt.

Ich habe das Wiki soweit “abgeschottet”, dass kein einziger Beitrag öffentlich einsehbar ist. Besucher müssen sich also mit einem Benutzernamen und Passwort einloggen um Beiträge zu sehen.
Der Button zum Registrieren neuer Benutzer wird ebenfalls nicht angezeigt.

Und natürlich ist der komplette Zugriff auf https umgebogen.
Das kann man einfach mit einer entsprechenden .htaccess Datei erledigen:

RewriteEngine On
RewriteCond %{HTTPS} !on
RewriteRule ^(.*) https://<DOKUWIKI_URL>/$1 [R,L]

 Sicherheitstechnisch ist das schon einmal gar nicht so schlecht.

 

Allerdings:
Wenn sich ein Hacker Zugang zum Server verschafft hat, dann hält ihn die Login-Restriktion im Browser auch nicht mehr auf, sämtliche Daten einfach direkt im Filesystem zu lesen!

 

Lösung:
Um dieses Problem zu umgehen, habe ich DokuWiki ein wenig mit Code erweitert.
Per Knopfdruck kann man das komplette data-Verzeichnis (in dem sämtliche Wiki-Seiten abgelegt werden) auf dem Dateisystem verschlüsseln bzw. wieder entschlüsseln.
Dadurch ist der Zugriff auch auf Filesystem-Ebene abgesichert.

decrypt box
decrypt box

Für meine Lösung sind folgende Schritte notwendig:
(ich beschreibe das jetzt für die Standard-Installation, ich habe bei mir ein anderes Theme gewählt. Dann müssen die Anpassungen natürlich im jeweiligen Theme geschehen.)

1. Dojo Toolkit nachrüsten
Für einen schönen Dialog zur Passwort-Eingabe habe ich mich für das Dojo Toolkit entschieden.

cd lib/tpl/dokuwiki
wget http://download.dojotoolkit.org/release-1.9.2/dojo-release-1.9.2.tar.gz
tar xvfz dojo-release-1.9.2.tar.gz
ln -s dojo-release-1.9.2 dojo

2. Dojo über die main.php Datei auf jeder Seite laden. Dazu einfach vor dem </head> Tag folgenden Code einfügen:

  <link rel="StyleSheet" type="text/css" href="<?php echo DOKU_TPL?>dojo/dijit/themes/tundra/tundra.css">
    <script type="text/javascript">
      var djConfig = {
        parseOnLoad : true
      };
    </script>
    <script type="text/javascript" src="<?php echo DOKU_TPL?>dojo/dojo/dojo.js"></script>
    <script>
      dojo.require("dojo.parser");
      dojo.require("dijit.form.Button");
      dojo.require("dijit.Dialog");
      dojo.require("dijit.form.TextBox");
      //dojo.addOnLoad(hideDialog);

      function hideDialog()
      {
        dijit.byId('dialog1').hide();
      }

      function showDialog()
      {
        dijit.byId('dialog1').show();
      }
    </script>

3. Im <body> Tag noch die CSS Klasse einfügen

<body class="tundra">

 4. Den Code für den Button in die tpl_footer.php einbinden

<?php
/**
 * Template footer, included in the main and detail files
 */

// must be run from within DokuWiki
if (!defined('DOKU_INC')) die();
// check if encrypted file exists
 $msg="";
 $url="#";
 if(is_file("dokuwiki_data_backup.tar.gz.enc"))
 {
   $msg="Decrypt";
   $url="<cgi-path>/dec";
 }
 else
 {
   $msg="Encrypt";
   $url="<cgi-path>/enc";
 }
?>
<script language="javascript">
  function checkPw(dialogFields)
  {
     if (dialogFields.password.value != dialogFields.confpassword.value)
     {
        alert("Confirmation password is different.");
        return false;
     }
     dialogFields.type.value=dialogFields.password.value;
     return true;
  }
</script>

<!-- ********** FOOTER ********** -->
<div id="dokuwiki__footer"><div>
    <?php tpl_license(''); // license text ?>
<div>
  <div style="font-size:8pt;position:absolute;height:65px;"><a style="color:black" onclick="javascript:showDialog();" href="#"><?=$msg?></a>
</div>

    <div>
        <?php
            tpl_license('button', true, false, false); // license button, no wrapper
            $target = ($conf['target']['extern']) ? 'target="'.$conf['target']['extern'].'"' : '';
        ?>
        <a href="http://www.dokuwiki.org/donate" title="Donate" <?php echo $target?>><img
            src="<?php echo tpl_basedir(); ?>images/button-donate.gif" width="80" height="15" alt="Donate" /></a>
        <a href="http://www.php.net" title="Powered by PHP" <?php echo $target?>><img
            src="<?php echo tpl_basedir(); ?>images/button-php.gif" width="80" height="15" alt="Powered by PHP" /></a>
        <a href="http://validator.w3.org/check/referer" title="Valid HTML5" <?php echo $target?>><img
            src="<?php echo tpl_basedir(); ?>images/button-html5.png" width="80" height="15" alt="Valid HTML5" /></a>
        <a href="http://jigsaw.w3.org/css-validator/check/referer?profile=css3" title="Valid CSS" <?php echo $target?>><img
            src="<?php echo tpl_basedir(); ?>images/button-css.png" width="80" height="15" alt="Valid CSS" /></a>
        <a href="http://dokuwiki.org/" title="Driven by DokuWiki" <?php echo $target?>><img
            src="<?php echo tpl_basedir(); ?>images/button-dw.png" width="80" height="15" alt="Driven by DokuWiki" /></a>
    </div>
</div></div><!-- /footer -->
<div dojoType="dijit.Dialog" id="dialog1" title="<?=$msg?>">
      <form action="<?=$url?>" method="post" validate="true" id="encdec_form" onsubmit="return checkPw(this);">
        <input type="hidden" name="type" id="type" value="<?=$msg?>" />
        <table width="350">
          <tr>
            <td><label>Password:</label></td>
            <td><input type="password" trim="true" dojoType="dijit.form.TextBox" value="" name="password" id="password"/></td>
          </tr>
          <tr>
            <td><label>Confirm Password:</label></td>
            <td><input type="password" trim="true" dojoType="dijit.form.TextBox" value="" name="confpassword" id="confpassword"/></td>
          </tr>
          <tr><td colspan="2">&nbsp;</td></tr>
          <tr>
            <td colspan="2" align="center">
            <table border="0" cellspacing="0" cellpadding="0">
                <tr>
                  <td align="center" valign="top"><button dojoType="dijit.form.Button" type="submit" id="LoginButton">Ok</button></td>
                  &nbsp;
                  <td align="left" valign="top"><button dojoType="dijit.form.Button" type="button"
                  onclick="document.getElementById('encdec_form').reset();" id="Cancel">Cancel</button></td>
                </tr>
           </table>
           </td>
          </tr>
        </table>
      </form>
    </div>

<?php
tpl_includeFile('footer.html');

 5. Jetzt noch zwei Skripten im cgi-Verzeichnis

Einmal zum verschlüsseln: enc

#!/bin/sh

echo Content-type: text/html
echo ""

if [ -f "<PATH_TO_WIKI>/dokuwiki_data_backup.tar.gz.enc" ] ; then
  echo ""
else
  #get pass
  read passfromform
  pass=`echo "$passfromform" | cut -d"=" -f2 | cut -d"&" -f1`

  # check pass
  if [ "" != "${pass}" ] ; then
    # change dir
    cd <PATH_TO_WIKI>
 
    # create file
    tar cf dokuwiki_data_backup.tar data 2>/dev/null 1>/dev/null
    gzip -9 dokuwiki_data_backup.tar
    openssl des3 -k $pass -in dokuwiki_data_backup.tar.gz -out dokuwiki_data_backup.tar.gz.enc
    rm dokuwiki_data_backup.tar.gz
    rm -rf data

    # add directories
    mkdir data
    mkdir data/attic
    mkdir data/cache
    mkdir data/index
    mkdir data/locks
    mkdir data/media
    mkdir data/media_attic
    mkdir data/media_meta
    mkdir data/meta
    mkdir data/pages
    mkdir data/tmp
    mkdir data/cache/c
    mkdir data/cache/e
    mkdir data/cache/6
    chmod -R 777 data

    cd -
  fi
fi

echo "<html><body><script>history.back();</script></body></html>"

 Und einmal zum entschlüsseln: dec

#!/bin/sh

echo Content-type: text/html
echo ""

if [ -f "<PATH_TO_WIKI>/dokuwiki_data_backup.tar.gz.enc" ] ; then
  #get pass
  read passfromform
  pass=`echo "$passfromform" | cut -d"=" -f2 | cut -d"&" -f1`

  if [ "" != "${pass}" ] ; then
    # change directory
    cd <PATH_TO_WIKI> 

    # decrypt file
    openssl des3 -k $pass -d -in dokuwiki_data_backup.tar.gz.enc -out dokuwiki_data_backup.tar.gz

    # check for errors
    if [ $? -eq 0 ] ; then
      # remove directory
      rm -rf data

      # unzip
      gunzip dokuwiki_data_backup.tar.gz

      # untar
      tar xf dokuwiki_data_backup.tar
      chmod -R a+rw data
      rm dokuwiki_data_backup.tar
      rm dokuwiki_data_backup.tar.gz.enc
    else
      rm dokuwiki_data_backup.tar.gz
    fi
    cd -
    echo ""
  fi
else
  echo ""
fi

echo "<html><body><script>history.back();</script></body></html>"

 

Anmerkungen:

  1. Um die Optik aufzubessern, kann man natürlich den Text Entschlüsseln/Verschlüsseln durch eine schöne Grafik austauschen.
  2. Es macht auch Sinn, die verschlüsselte Datei außerhalb des Webservers, also nicht im httpdocs-Verzeichnis, abzulegen.
  3. Ein Backup der Wiki-Daten ist durch die einzelne, verschlüsselte Datei natürlich auch recht einfach.

 

 

Categories
IT Unix

Debian: IPv4 bei dselect erzwingen

Ich hatte auf unserem neuen Root Server das Problem, dass dselect beim Zugriff auf security.debian.org immer auf die IPv6 Adresse gegangen ist, und es hier zu Timeouts kam.

Eine host-Abfrage lieferte folgendes Ergebnis:

$ host security.debian.org
security.debian.org has address 212.211.132.250
security.debian.org has address 212.211.132.32
security.debian.org has address 195.20.242.89
security.debian.org has IPv6 address 2001:a78:5:1:216:35ff:fe7f:6ceb
security.debian.org has IPv6 address 2001:8d8:580:400:6564:a62:0:2
security.debian.org has IPv6 address 2001:a78:5:0:216:35ff:fe7f:be4f
security.debian.org mail is handled by 10 chopin.debian.org.

Es kommen also beide Adressen IPv4 und IPv6 zurück.

Um die Verwendung der IPv4 Adresse zu erzwingen, muss man in der /etc/gai.conf folgende Zeile einfügen:

precedence ::ffff:0:0/96 100

Anschließend wird immer die IPv4 Adresse verwendet.