Glengamoi (Forum) · AspHeute · .NET Heute (RSS-Suche) · AspxFiles (Wiki) · .NET Blogs
ASP German Homepage Homepage
 

Liste

.NET 2.0 (1)
.NET Allgemein (16)
.NET Fu (5)
ADO.NET (11)
Aprilscherz (3)
ASP Grundlagen (44)
ASP Tricks (83)
ASP.NET (44)
ASPIntranet.de (5)
C# (28)
Datenbank (44)
Dokumentation (4)
IIS 6.0 (1)
Komponenten (29)
Optimierung (10)
Server (21)
Sicherheit (34)
Tee Off (6)
VB.NET (6)
WAP (8)
Web Services (11)
XML (9)

RSS 2.0 - Die neuesten fünf Artikel auf AspHeute.com


 

Suchen





 

English Articles
Chinese Articles
Unsere Autoren
 
Link zu AspHeute
Impressum
Werben
Anfragen

Vorsicht Falle: Dateien, die keine sind

Geschrieben von: Christoph Wille
Kategorie: Sicherheit

Eigentlich dürfte jedem das FileSystemObject ein Begriff sein - mit diesem kann man Dateien auslesen und schreiben, sowie einige wichtige Operationen (löschen zB) auf Dateien und Verzeichnissen ausführen. Allerdings ist kaum bekannt, was Windows (NT/2000) so alles unter Dateien versteht - nämlich keineswegs nur Dateien die auf der Festplatte liegen.

Unter Windows werden neben Dateien die auf der Festplatte liegen auch Devices (COM oder LPT Ports), Konsolen (CON oder AUX) und auch Named Pipes (zB verwendet SQL Server solche) verstanden. Die Frage stellt sich nun, wieso soll mich das ASP Programmierer interessieren? Nehmen wir folgendes harmlos aussehendes Beispiel (readfile.asp):

<%
   Const ForReading = 1, ForWriting = 2, ForAppending = 8
   Set fso = Server.CreateObject("Scripting.FileSystemObject")
   Set f = fso.OpenTextFile("c:\temp\COM1", ForReading, True)
   strRetVal = f.ReadLine
   f.Close
   Response.Write strRetVal
%>

Das mit c:\temp\COM1 ist kein schlechter Scherz von mir, das ist so perfekt gültig unter Windows, da man auf diese Art Devices von der Konsole aus ansprechen kann (das Verzeichnis oder Laufwerk ist übrigens frei wählbar, um es mal so zu formulieren...). Nur was passiert? Sobald ich mich auf diese Device hinverbinde, bleibt der Thread auf dem die ASP Seite läuft hängen, bis ein Timeout vom Port zurückkommt. Da ASP aus einem Threadpool bedient wird, kann ich damit elegant eine DoS (Denial of Service) Attacke gegen einen Server fahren - wenn er verwundbar ist.

Und verwundbar ist man schnell - sicher fällt dem einen oder anderen aus dem Stand eine Website ein, bei der im QueryString Dateinamen mitgegeben werden, um ausgelesen in den Content eingebaut zu werden.

Hinweis Das mit der Datei im QueryString ist gefährlicherweise nicht unüblich, mir ist das schon des öfteren untergekommen. Nur gibt es weitere Angriffspunkte: Sites die beim Upload den User den Dateinamen wählen lassen, Programme die aus Userinput Dateien (und deren Namen) generieren, und so einiges mehr. Die Gefahr ist nicht aus Luft gegriffen, schauen Sie sich einmal Ihren Code genauer an!

Wer jetzt argumentiert daß er doch überprüft ob die Datei existiert, soll sich folgendes Script (fileexists.asp) anschauen:

<%
   filespec = "c:\temp\COM1"
   Set fso = Server.CreateObject("Scripting.FileSystemObject")
   If (fso.FileExists(filespec)) Then
      msg = filespec & " exists."
   Else
      msg = filespec & " doesn't exist."
   End If
   Response.Write msg
%>

Nur leider - da COM1 eine gültige Datei ist - wird hier für FileExists True zurückgeliefert. Und dann greift man im guten Glauben darauf zu, und schon steht der Webserver!

Reservierte Wörter für Devices

Nach diesem ersten Aufwärmen worum es hier geht, möchte ich eine Liste von reservierten Wörtern für Devices nachschicken, mit denen man diesen Effekt hervorrufen kann:

  • COM1-COM9
  • LPT1-LPT9
  • CON, PRN, AUX, CLOCK$, NUL

In jedem beliebigen Verzeichnis sind diese reservierten Wörter für "echte" Dateinamen ungültig, allerdings per File API's von Windows können sie angesprochen werden. Und um der Sache das Sahnehäubchen aufzusetzen, das folgende geht auch noch:

Response.Write fso.FileExists("c:\temp\NUL.txt") ' liefert True

Wobei die Dateierweiterung völlig beliebig sein darf...

Wie verhindern wir den Crash?

Es gibt einige Möglichkeiten, den übergebenen oder einfach nur zu verwendenden Dateinamen auf Gültigkeit zu überprüfen:

  • Enthält der Dateiname zwischen einem / und einem Punkt (oder Ende des Strings) eines der angeführten reservierten Wörter, wird die "Datei" nicht aufgemacht. Diesen Check kann man per normalen Stringoperationen oder per Regular Expressions durchführen.
  • Wir checken den Typ der Datei, bevor wir sie angreifen.

Letztere Version hat einen großen Vorteil - sie schützt uns vor Attacken gegen Named Pipes als auch neu hinzukommende reservierte Wörter (so dies passieren sollte). Aber den grundsätzlichen Check des Dateinamens sollte man auf alle Fälle durchführen. Je genauer wir schauen, desto eher fällt uns nichts durch die Ritzen.

Checken des Dateityps

Daß FileExists vom FileSystemObject uns nicht hilft, wissen wir bereits. Und leider bietet uns das FileSystemObject keine Funktion an, den Dateityp herauszubekommen. Deshalb habe ich mich entschlossen, in C++ mittels ATL eine kleine Komponente zu schreiben (Sourcecode im Download des Artikels mit dabei), die den Dateityp herausbekommen kann.

Bevor ich den Code der Komponente SecurityEnhance.FileUtilities zeige, denke ich mir, daß mehr Leser daran interessiert sind zu sehen, wie man mit dieser Komponente arbeitet. Deshalb habe ich ein nettes Testscript gezimmert, das viele verschiedene Dateien durchtestet (filetypecheck.asp):

<%
Option Explicit
Const RET_FILE_TYPE_UNKNOWN	= 1
Const RET_FILE_TYPE_DISK	= 2
Const RET_FILE_TYPE_CHAR	= 3
Const RET_FILE_TYPE_PIPE	= 4

Sub FileCheck(ByVal strFile)
  Dim objSecurityCheck, nFileType, nErrorCode, strError
  Set objSecurityCheck = Server.CreateObject("SecurityEnhance.FileUtilities")

  On Error Resume Next
  nFileType = objSecurityCheck.GetFileType(strFile)
  nErrorCode = Err.Number
  strError = Err.Description
  On Error GoTo 0  ' re-enabling error handling clear the Err object

  If (0 = nErrorCode) Then
    Response.Write strFile & ": is of type " & nFileType
  Else
    Response.Write strError
  End If
  Response.Write "<br>" & vbCrLf
End Sub

FileCheck "c:\temp\thisfiledoesnotexist.txt"
' above will return error 2, "The system cannot find the file specified. "
FileCheck "c:\temp\COM1"
' above will return type 3 (character file) typically an LPT device or a console.
FileCheck "c:\boot.ini"
' above returns error 5, "Access is denied." (hopefully)
FileCheck "c:\view.txt"
' above returns type 2, disk file (if file exists of course)
FileCheck "c:\COM1.txt"
' also returns type 3 - attention!
FileCheck "c:\COM1somemoretext"
' this does not work (2: file not found)
FileCheck "c:\somefile.COM1"
' as well as this (2: file not found)
%>

Hinweis Obwohl ich die wichtigeren Win32 Fehlernummern im Sourcecode kommentiert habe, könnte es sein, daß andere auftreten. Um von der Fehlernummer zum Beschreibungstext zu kommen, geben Sie auf der Kommandozeile einfach net helpmsg nnnn ein, wobei nnnn die Fehlernummer ist.

Der Output - obwohl durch Kommentare im Script bereits vorweggenommen, sieht so aus:

Im Prinzip gilt für die Methode GetFileType folgendes: alles an Typ anders als 2 (RET_FILE_TYPE_DISK) wird nicht einmal mit spitzen Fingern angegriffen - solche "Dateien" sind aus Sicherheitsgründen als "off limits" zu betrachten. Wenn man das beherzigt, kann einem niemand ein "Datei" unterschieben, die den Server crasht.

Bevor wir nun zum Code der Komponente kommen, und wie diese an den Dateityp kommt - die Komponente ist an sich nur auf Lesezugriff für Dateien ausgelegt (die Datei muß existieren). Allerdings ist dies nicht weiter schlimm, weil Typen anders als 2 (RET_FILE_TYPE_DISK) sich für Lesen und Schreiben gleich verhalten: kommt der Typ ohne Fehler retour und ist ungleich 2, dann wird die Datei nicht geschrieben.

Um es also noch einmal deutlich zu machen: egal ob wir in eine Datei schreiben oder von ihr lesen wollen, wenn der Typ von GetFileType ungleich 2 ist, passiert die Operation aus Sicherheitsgründen nicht.

Die Komponente

Als Abschluß für sehr Interessierte (die anderen dürfen zur Schlußbemerkung verzweigen), nun der relevante Code der Komponente. Obwohl für viele C++ ungewohnt sein mag, so kann ich versichern, daß es keineswegs schwierig ist (aus FileUtilities.cpp):

#define E_FILEERROR    MAKE_HRESULT(1,FACILITY_ITF,1)
#define RET_FILE_TYPE_UNKNOWN 1
#define RET_FILE_TYPE_DISK 2
#define RET_FILE_TYPE_CHAR 3
#define RET_FILE_TYPE_PIPE 4

STDMETHODIMP CFileUtilities::GetFileType(BSTR FileName, long *FileType)
{
  USES_CONVERSION;
  *FileType = -1;

  // open the file for generic reading
  HANDLE hFile = ::CreateFile(W2A(FileName), 
            GENERIC_READ,
            FILE_SHARE_READ | FILE_SHARE_WRITE, 
            NULL, 
            OPEN_EXISTING, 
            0, 
            NULL );

  if (INVALID_HANDLE_VALUE == hFile)
  {
    TCHAR achErrFormatting[256];
    wsprintf(achErrFormatting,
          "The file %s could not be accessed. The Win32 error code is %ld.",
          W2A(FileName), ::GetLastError());
    Error(achErrFormatting, 0, NULL, IID_IFileUtilities, E_FILEERROR);
    return E_FILEERROR;
  }
  else
  {
    DWORD dwFileType = ::GetFileType(hFile);
    switch (dwFileType)
    {
    case FILE_TYPE_UNKNOWN: *FileType = RET_FILE_TYPE_UNKNOWN; break;
    case FILE_TYPE_DISK: *FileType = RET_FILE_TYPE_DISK; break;
    case FILE_TYPE_CHAR: *FileType = RET_FILE_TYPE_CHAR; break;
    case FILE_TYPE_PIPE: *FileType = RET_FILE_TYPE_PIPE; break;
    default: *FileType = -1;
    }
    ::CloseHandle(hFile);
  }
  return S_OK;
}

Die zwei wichtigen Funktionen CreateFile und GetFileType habe ich herausgehoben. Diese beiden erledigen die Arbeit, der Rest ist nur Beiwerk daß die Fehlerfälle korrekt behandelt werden, und alles fein säuberlich aufgeräumt wird. Es wäre nett gewesen, hätte Microsoft an eine solche Funktion beim FileSystemObject gedacht.

Schlußbemerkung

Der heutige Artikel beweist wieder einmal auf sehr eindrückliche Weise, daß aller Input den man in seinen Applikationen übernimmt sehr gefährlich sein kann ("All input is evil until proven otherwise", wie schon in der Artikelserie über SQL Injection bemerkt). Auf alle Fälle sollte man die einfache Überprüfung in seine Scripts einbauen, besser und sicherer ist allerdings die Verwendung der Komponente.

Download des Codes

Klicken Sie hier, um den Download zu starten.

Verwandte Artikel

Dateityp-Ermittlung in Managed C++
Gegengifte für SQL Injection
Komponentenverwendung einschränken
SQL Injection
Verhinderung von SQL Injection Marke .NET

Wenn Sie jetzt Fragen haben...

Wenn Sie Fragen rund um die in diesem Artikel vorgestellte Technologie haben, dann schauen Sie einfach bei uns in den Community Foren der deutschen .NET Community vorbei. Die Teilnehmer helfen Ihnen gerne, wenn Sie sich zur im Artikel vorgestellten Technologie weiterbilden möchten.

Haben Sie Fragen die sich direkt auf den Inhalt des Artikels beziehen, dann schreiben Sie dem Autor! Unsere Autoren freuen sich über Feedback zu ihren Artikeln. Ein einfacher Klick auf die Autor kontaktieren Schaltfläche (weiter unten) und schon haben Sie ein für diesen Artikel personalisiertes Anfrageformular.

 

Und zu guter Letzt möchten wir Sie bitten, den Artikel zu bewerten. Damit helfen Sie uns, die Qualität der Artikel zu verbessern - und anderen Lesern bei der Auswahl der Artikel, die sie lesen sollten.

Bewerten Sie diesen Artikel
 Sehr gut   Nicht genügend  
   1  2  3  4  5  
 

  
   Für Ausdruck optimierte Seite

©2000-2006 AspHeute.com
Alle Rechte vorbehalten. Der Inhalt dieser Seiten ist urheberrechtlich geschützt.
Eine Übernahme von Texten (auch nur auszugsweise) oder Graphiken bedarf unserer schriftlichen Zustimmung.