Alps, Munich

SQL-Injections

Dieser Artikel geht über SQL-Injections, und wie man sie verhindern kann.

Was sind SQL-Injections?

Bei SQL-Injections macht ein Angreifer sich zu Nutze, dass Parameter an das Script ungeprüft an das SQL-Statement weitergereicht werden. Durch geschickte Manipulation des Parameters erreicht er so, dass das SQL-Statement eine völlig andere Bedeutung erhält.

Ein Beispiel: Wir haben ein Login-Script, welches einen Usernamen und ein Passwort übergeben bekommt. Das Script sucht nun in der users-Tabelle nach einem passenden User mit dem gleichen Passwort, und liefert im Erfolgsfall die User-ID zurück. Der Einfachheit halber gehen wir davon aus, dass der übergebene Username in $user und das Passwort in $pwd abgelegt ist.

Folgendes SELECT würde sich für die Aufgabe anbieten:

mysql_query("SELECT id FROM users WHERE name=$name AND pwd=$pwd");

Der Angreifer könnte nun jedoch als Passwort $pwd = "foo OR 1=1" an das Script übergeben. Dadurch sähe der fertige SELECT-Statement so aus:

SELECT id FROM users
  WHERE name=meier
    AND pwd=foo OR 1=1

Der Angreifer wäre damit trotz falschem Passwort erfolgreich als meier eingeloggt worden.

Abwehrmaßname 1: Hochkommata

Um dies abzuwehren, sollten grundsätzlich alle Variablen im SQL-Statement in einfache Hochkommata gesetzt werden. 1

mysql_query("SELECT id FROM users WHERE name='$name' AND pwd='$pwd'");

Der ursprüngliche Angriff mit $pwd = "foo OR 1=1" würde dann folgendes SELECT-Statement ergeben:

SELECT id FROM users
  WHERE name='meier'
    AND pwd='foo OR 1=1'

Die Datenbank nimmt in dem Fall an, dass das Passwort "foo OR 1=1" lautet. Der Angriff wäre damit wirkungslos. Allerdings kann der Angreifer selbst Hochkommata einsetzen. Mit folgendem Passwort wäre er wieder erfolgreich: $pwd = "foo' OR ‘1’='1". Dies würde das folgende SELECT-Statement ergeben:

SELECT id FROM users
  WHERE name='meier'
    AND pwd='foo' OR '1'='1'

Und schon würde der User meier erneut trotz falschem Passwort eingeloggt werden.

Abwehrmaßnahme 2: addslashes()

Um auch diesen Angriff abzuwehren, muss man sicher stellen, dass Hochkommata im Parameter ordentlich mit einem Backslash escaped werden. Dafür sorgt PHP mit den Magic Quotes automatisch, sofern sie aktiviert sind. Man sollte sich aber nicht darauf verlassen, sondern den Magic Quotes-Modus abschalten und sich selbst mit der Funktion addslashes() um ordentliches
Escapen kümmern.

Der oben genannte Angriff lässt sich abwehren, indem man die Parameter $user und $pwd mit addslashes() sichert:

$user = addslashes($user);
$pwd  = addslashes($pwd);
mysql_query("SELECT id FROM users WHERE name='$name' AND pwd='$pwd'");

addslashes() würde im Angriffs-Passwort ein Backslash vor die Hochkommata stellen, was dann folgendes SELECT-Statement ergibt:

SELECT id FROM users
  WHERE name='meier'
    AND pwd='foo\' OR \'1\'=\'1'

Damit wäre die Abfrage wieder sicher. Der Angreifer hat nun keine Möglichkeit mehr, die Abfrage zu manipulieren. SQL-Injections sind nun nicht mehr möglich.

Weitere Sicherungsmaßnahmen

Zunächst sollten alle Parameter an das Script auf Plausibilität geprüft werden. Wenn man zum Beispiel eine Zahl erwartet, sollte man den Parameter mit intval() in eine Zahl wandeln und anschließend eine Bereichsprüfung durchführen.

Weiterhin gilt grundsätzlich, wirklich niemals ganze SQL-Kommandos per Parameter an das nächste Script weiter zu reichen. Dem Angreifer wird es damit ermöglicht, direkt SQL-Kommandos an die Datenbank zu schicken. Die Palette reicht von kleinen Spionage-SELECTs bis hin zu einem katastrophalen DROP DATABASE.

Um ein SQL-Kommando von einem Script zum nächsten zu transportieren, sollte man Sessions verwenden. In dem Fall bleibt das Kommando auf dem Server und kann vom Benutzer weder eingesehen noch verändert werden.

Zusammenfassung

Um SQL-Injections erfolgreich zu vermeiden, sollte man also:

  • alle übergebenen Parameter auf Plausibilität prüfen.
  • sämtliche Variablen in SQL-Statements mit Hochkommata einschließen.
  • Magic Quotes abschalten, und stattdessen
  • alle Parameter vor der Übergabe an das SQL-Statement mit addslashes() sauber escapen.
  • zum Weiterreichen eines SQL-Statements an das nächste Script niemals Parameter, sondern nur Sessions verwenden.

1 Dies erlaubt MySQL auch bei numerischen Spalten. Andere Datenbanken können darauf allerdings mit einer Fehlermeldung reagieren.

Accept-Language

Das folgende Snippet parst den Accept-Language HTTP-Header und liefert ein Array mit den Sprachen.

Das zurückgelieferte Array enthält den Vorzugsgrad der Sprache als Key, der Value ist das Kürzel der jeweiligen Sprache. Das Array ist absteigend von der bevorzugten zu der am wenigsten bevorzugten Sprache sortiert.

/**
 * Parse the Accept-Language HTTP header sent by the browser. It
 * will return an array with the languages the user accepts, sorted
 * from most preferred to least preferred.
 *
 * @return  Array: key is the importance, value is the language code.
 */
function parseAcceptLanguage() {
  $ayLang = array();
  $aySeen = array();
  if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
    foreach(explode(',',$_SERVER['HTTP_ACCEPT_LANGUAGE']) as $llang) {
      preg_match("#^(.*?)([-_].*?)?(\;q\=(.*))?$#i", $llang, $ayM);
      $q = isset($ayM[4]) ? $ayM[4] : '1.0';
      $lang = strtolower(trim($ayM[1]));
      if(!in_array($lang, $aySeen)) {
        $ayLang[$q] = $lang;
        $aySeen[] = $lang;
      }
    }
    uksort($ayLang, create_function('$a,$b','return ($a>$b) ? -1 : 1;'));
  }
  return $ayLang;
}

Im Prinzip braucht man jetzt nichts weiter mehr zu machen, als das Array in der Reihenfolge zu durchlaufen, bis man eine Sprachkennung findet, die man auf der Seite unterstützt.

RFC-2047-Wandler

Dieser Codeschnippsel wandelt einen String nach RFC-2047 um.

Die RFC-2047 wird verwendet, um Umlaute in Mail- und News-Headern darzustellen. Die folgenden beiden Funktionen wandeln einen String entsprechend um.

/**
 * Converts the passed string to an RFC-2047 encoded word.
 * 
 * The string will always be converted. This function also takes care of
 * proper wrapping.
 *
 * @param   $str      String to be encoded
 * @param   $encode   The character set to be used (e.g. "ISO-8859-1")
 * @return  Converted string
 */
function rfc2047conv($str, $encode) {
  if(!strlen($str)) return "";
  $result  = "=?${encode}?Q?";
  $intro   = strlen($result);
  $charcnt = $intro;
  for($ix=0; $ix < strlen($str); $ix++) {
    if($charcnt > 70) {
      $result .= "?=\r\n =?${encode}Q?";
      $charcnt = $intro;
    }
    $chr = $str{$ix};
    if($chr=='?' || $chr=='_' || $chr=='=' || $chr<' ' || $chr>'~' ) {
      $result .= sprintf("=%02X", ord($chr));
      $charcnt+=3;
    }else {
      $result .= ($chr==' ' ? "_" : $chr);
      $charcnt++;
    }
  }
  $result .= "?=";
  return $result;
}

/**
 * This function will only encode the shortest required section of
 * the string. If there are no characters in the string that need to
 * be encoded, the string will be returned unaltered.
 * 
 * WARNING: Even though most mail clients can handle the result, it
 * is not RFC compliant!
 *
 * @param   $str      String to be encoded
 * @param   $encode   The character set to be used
 * @return  Converted string
 */
function rfc2047short($str, $encode) {
  return preg_replace(
    '/^([ -~]*)(.*?)([ -~]*)$/e',
    '"$1".rfc2047conv("$2",$encode)."$3"',
    $str
  );
}
Teneriffa 2002

Auch dieses Jahr ging es wieder auf unsere Lieblingsinsel Teneriffa. Diesmal verschlug es uns nach Costa Adeje. Dort ist es schön ruhig, aber der Touristenort Playa de las Americas ist zu Fuß zu erreichen – wer den Rummel sucht.

Teneriffa ist eine wunderschöne Wanderinsel mit abwechslungsreichen Landschaften. Eigentlich ist es zu schade, nur am Strand zu liegen oder mit einem Touristenbus durch die Insel gekarrt zu werden. Dieses Jahr stand für uns erstmalig auch die eine oder andere Wanderung auf dem Plan.

(Update: Diese Galerie wurde für das Redesign 2010 komplett neu gescannt und digital nachbearbeitet.)

Read this article...
Raytrace

In dieser Galerie zeige ich ein paar selbst gemachte Raytrace-Bilder, die ich auf meinem Amiga 4000/060 mit Maxon Cinema erstellt habe. Leider endete das Hobby mit meinem Umstieg auf Linux. Vielleicht fange ich aber irgendwann wieder damit an...

Read this article...