PSR-1: Basic Coding Standard

Nein, in diesem Beitrag geht es nicht um die PSR-1 aus PIXEL GUN 3D, hier geht es um Coding Standards und zwar den der PHP Framework Interop Group, wie man standardkonformen PHP-Code schreibt. Von dieser Gruppe wurden mehrere Spezifikationen zu PHP verfasst. Die PSR-1, um die es hier maßgeblich geht, beschäftigt sich mit allgemeinen Vorgaben zu Inhalt/Struktur einer PHP-Datei und Namensgebungen.

Die PSR-1 (PHP Standard Recommendation 1) befasst sich, wie der Name „Basic Coding Standard“ schon sagt, mit dem allgemeinen, grundlegendem Programmierstil. Also zum Beispiel den Tags, der Namensgebung und dem zu verwendenden Zeichensatz.

Allgemeines


PHP-Tags

Damit der Code standardkonform ist, muss der PHP-Tag ausgeschrieben werden <?php ?>. Alternativ kann man die Kurzform des echo-Tags benutzen <?= ?>, andere Varianten sollen nicht genutzt werden.

Zeichensatz

Es soll nur UTF-8 ohne BOM verwendet werden.

Inhalt

Eine PHP-Datei darf nicht gleichzeitig Deklarationen und sogenannte „side-effects“ enthalten.

Zu den Side-Effects gehören unter anderem:

  • Ausgaben
  • Auswerfen von Ausnahmen oder Fehlermeldungen
  • Externe Verbindungen herstellen
  • Explizites Aufrufen von „require“ und „include“
  • Änderungen von statischen oder globalen Variablen
  • Ein- oder Auslesen einer Datei



Namespaces und Klassen

Namespaces müssen einer der beiden „autoloading“ Richtlinien (PSR-0 oder PSR-4) entsprechen. Diese enthalten unter anderem Spezifikationen zu der Vergabe von vollqualifizierten Klassennamen. Ziel ist, dass der Code bei Bedarf einheitlich und automatisch nachgeladen werden kann.

Namensgebung

  • Klassennamen müssen nach der StudlyCaps Schreibweise vergeben werden
  • Konstantennamen müssen komplett groß geschrieben werden. Einzelne Wörter werden dabei mit Unterstrichen voneinander getrennt
  • Methodennamen müssen nach der camelCase() Schreibweise vergeben werden
  • Attributnamen müssen nach keiner bestimmten Konvention geschrieben werden. Es sollte allerdings darauf geachtet werden, dass innerhalb eines Bereichs eine einheitliche Namensgebung verwendet wird.



Weitere Informationen gibt es auf der Seite der PHP Framework
Interop Group: www.php-fig.org

PHP De-Obfuscation

In den letzten Tagen hatte ich viel mit obfusciertem PHP-Quellcode (Frameworks & Schadcode) zu tun, der zumeist über einen langen String definiert war und dann mittels einfacher eval()-Funktion erst zur Laufzeit das ein oder andere dunkle Geheimnis preisgab.

Das erste Hindernis besteht darin, diesen String in lesbaren Quellcode zu formatieren. Nach kurzer Recherche im Web und ein paar Experimenten hatte ich folgendes kleine PHP-Script:

ob_start();
eval("?" . ">" . base64_decode( $code) . "<" . "?php");
$output = ob_get_contents();
ob_end_clean();
echo $output;

1. Die Funktion ob_start() aktiviert die Pufferung von Ausgaben ein. Während die Ausgabe-Pufferung aktiv ist, erfolgt keine Ausgabe an den Client. Stattdessen werden alle Ausgaben in einem internen Puffer gespeichert.

2. Die Funktion eval() wertet einen String als PHP-Code aus. Sie ist nützlich, wenn man bspw. PHP-Code aus einer Datenbank lädt. Für dieses Experiment jedoch wäre sie ungeeignet, da der PHP-Quellcode direkt ausgeführt würde. Wenn der String mit „?>“ (PHP endet) beginnt, wird der Code nicht ausgeführt und man hat eine saubere Ausgabe. Die Funktion base64_decode() war nötig, da der Code base64-kodiert war.

3. Mit der Funktion ob_get_contents() wird der Inhalt des internen Puffers in eine String-Variable kopiert. Die String-Variable enthält sämtliche Ausgaben seit dem Befehl ob_start().

4. Die Funktion ob_end_clean() löscht daraufhin den internen Ausgabepuffer und beendet das Outputbuffering.

5. Der Quellcode steht u.U immer noch in einer Zeile ist jedoch lesbar. Er kann nun angesehen und mit einer leistungsstarken IDE formatiert werden.

Im Quellcode der gegebenen PHP-Datei sind die Texte ASCII-codiert und die Strings deshalb nicht lesbar. Alle Zeichen wurden mit dem entsprechenden ASCII-Code abgebildet (z.B.: „\x73“). Das berechnen des Quellcodes mit eval() hilft hier nur soweit man während der Laufzeit noch auf diese Strings lesbar zugreifen kann. Möchte man jedoch schon den Quellcode lesen können, hilft die Funktion str_replace() weiter:

$output = str_replace("\\x73", "s", $input);

Der doppelte Backslash „\\“ ist nötig, um den Backslash selber zu escapen.

Um das an dieser Stelle zu beschleunigen:

$ascii_hex = array(
"\\x20", "\\x21", "\\x22", "\\x23", "\\x24", "\\x25", "\\x26", "\\x27", "\\x28",
"\\x29", "\\x2a", "\\x2b", "\\x2c", "\\x2d", "\\x2e", "\\x2f", "\\x30", "\\x31",
"\\x32", "\\x33", "\\x34", "\\x35", "\\x36", "\\x37", "\\x38", "\\x39", "\\x3a",
"\\x3b", "\\x3c", "\\x3d", "\\x3e", "\\x3f", "\\x40", "\\x41", "\\x42", "\\x43",
"\\x44", "\\x45", "\\x46", "\\x47", "\\x48", "\\x49", "\\x4a", "\\x4b", "\\x4c",
"\\x4d", "\\x4e", "\\x4f", "\\x50", "\\x51", "\\x52", "\\x53", "\\x54", "\\x55",
"\\x56", "\\x57", "\\x58", "\\x59", "\\x5a", "\\x5b", "\\x5c", "\\x5d", "\\x5e",
"\\x5f", "\\x60", "\\x61", "\\x62", "\\x63", "\\x64", "\\x65", "\\x66", "\\x67",
"\\x68", "\\x69", "\\x6a", "\\x6b", "\\x6c", "\\x6d", "\\x6e", "\\x6f", "\\x70",
"\\x71", "\\x72", "\\x73", "\\x74", "\\x75", "\\x76", "\\x77", "\\x78", "\\x79",
"\\x7a", "\\x7b", "\\x7c", "\\x7d", "\\x7e", "\\x7f"
);

$ascii_char = array(
" ", "!", '"', "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?",
"@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\", "]", "^", "_", "`", "a",
"b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r",
"s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~"
);

$output = str_replace($ascii_hex, $ascii_char, $input);

So hat man schnell den lesbaren Text /PHP-Quellcode.

Sicherlich ist das nicht die eleganteste Lösung und ich lasse mich gerne von schöneren überzeugen!

Ein weiterer Teil der Strings ist wieder base64-kodiert, wie bspw.:

$output = base64_decode("aHR0cDovL3d3dy5kaXJlY3Qtd2Vic29sdXRpb25zLmRlL2Jsb2cvd2ViZGVzaWduL3BocC1kZS1vYmZ1c2NhdGlvbi10ZWlsLTMuaHRtbA==");

Um dieses Problem zu lösen, hilft die PHP-Funktion preg_replace_callback():

function decode($erg) {
    return '"' . base64_decode($erg[1]) . '"';
}
$output = preg_replace_callback("/base64_decode\('([a-z0-9+=]*)'\)/i", 'decode', $input);