Skip to content

Java Stream mit Puffer einlesen

Bin heute zufällig über folgende Methode gestolpert:
String readToString1(InputStream in)  throws IOException
{
    byte[]buf = new byte[256];
    StringBuilder sb = new StringBuilder();
    int n;
    do {
      n = in.read(buf, 0, 256);
      if (n > 0) {
        String s = new String(buf, 0, n, "UTF-8");
        sb.append(s);
      }
    } while(n > 0);
    return sb.toString();
}
Diese Funktion soll einen InputStream dessen Zeichen UTF-8 codiert sind in einen String lesen. Problem (mal abgesehen von der unnötigen Verwendung des if und den temporär angelegten String Objekten) bei dieser Funktion ist allerdings, dass der 256-byte Puffer in einen String umgewandelt wird, denn dabei werden ein oder mehrere Bytes durch den Zeichenkonverter gelesen. Falls der Zeichenkonverter dabei am ende des Puffers anlangt, so ist das Zeichen unvollständig. Das führt dann dazu dass ein Ersetzungszeichen (das Fragezeichen) am Ende des String steht. Man sollte solche starren bytepuffer also vermeiden, wenn man diese in Zeichen umwandeln will. Besser ist folgendes Vorgehen:
String readToString2(InputStream in) throws IOException
{
    char[] buf = new char[128];
    StringBuilder sb = new StringBuilder();
    Reader r = new InputStreamReader(in, "UTF-8");
    int n;
    while((n = r.read(buf, 0, 128)) != -1)
    {
       sb.append(buf, 0, n);
    }
    return sb.toString();
}
Um das unterschiedliche Verhalten zu testen, erzeuge ich einen InputStream der an ungeraden Byte Positionen Umlaute hat (also zuerst ein 1 byte Zeichen und dann lauter 2 byte Umlaute) und übergebe beiden Testmethoden diese Streams. Zusätzlich benutze ich noch Mockito, um zu ermitteln welche Methoden wie aufgerufen wurden:
import static org.mockito.Mockito.*;
public static void main(String[] args) throws IOException
{
    byte[] umlaut = "ö".getBytes("UTF-8");
    byte[] inbuf   =  new byte[513];
    inbuf[0] = 'X'; for(int i = 0;i<256;i++)
    {inbuf[i*2+1] = umlaut[0]; inbuf[i*2+2] = umlaut[1]; }
		
    InputStream in1 = spy(new ByteArrayInputStream(inbuf));
    System.out.println(" readToString1()=" + readToString1(in1));
	// make sure the inputstream was used with efficient block reads
	verify(in1, times(4)).read(any(byte[].class), eq(0), eq(256));
	verifyNoMoreInteractions(in1);

    InputStream in2 = spy(new ByteArrayInputStream(inbuf));
    System.out.println(" readToString2()=" + readToString2(in2));
	// make sure the inputstream was used with efficient block reads
	verify(in2, atMost(2)).read(any(byte[].class), eq(0), eq(8192));
	verify(in2, atMost(1)).available();
	verifyNoMoreInteractions(in2);
}
Und hier das Ergebnis (gekürtzt):
 readToString1()=Xööö...ööö??ööö...ööö??
 readToString2()=Xööö...öööööööö...ööööö
Am Ende jeden Puffers zerschneidet die erste Methode die 2 Bytes eines Umlauts, und deswegen erscheinen an diesen Stellen das Füllzeichen des Character Konverters. Bei UTF-8 streams ist es unwahrscheinlich dass ein multi-byte Zeichen ausgerechnet genau auf eine Blockgrenze fällt - umso unwahrscheinlicher ist es, dass ein Problem damit beim Testen auffällt. Übrigens ist es nicht notwendig hier einen BufferedInputStream oder BufferedReader zu verwenden. Der Reader wird ja bereits mit einem char array buffer (und nicht einzelnen Zeichen) gelesen. Zudem liest der InputSreamReader() aus dem darunterliegenden InputStream mit einem StreamDecoder der einen eigenen Lesepuffer (bei den Sun Klassen ist das ein 8kb Puffer) hat.

SSL/TLS BEAST Lücke

Im September 2011 haben die Sicherheitsforscher Duong und Rizzo nachgewiesen, dass eine - seit 2002 bekannte und in TLS 1.1 behobene - Schwachstelle in den SSL 3.0 und TLS 1.0 Verschlüsselungsprotokollen nicht nur theoretisch ist, sonder wirklich ausgenutzt werden können. Unter bestimmten Bedingungen erlaubt diese Schwachstelle einem Angreifer Teile aus einer SSL/TLS geschützten Übertragung zu ermitteln.

Gezeigt wurde dies am Beispiel eines abgefangenen paypal http Session Cookies, was erlaubte eine Browser Sitzung zu übernehmen. Das Problem ist unter dem Namen „BEAST“ (Browser Exploit Against SSL/TLS) bekannt, und wird unter der Common Vulnerability ID  CVE-2011-3389 geführt. Bei der Demonstration wurde ein Java Applet verwendet um die notwendigen Daten einzuschleusen, nach Duong/Rizzo es soll aber auch mit WebSockets oder JavaScript XHR Aufrufen möglich sein.

Dies zugrundeliegende kryptografische Schwäche ist ein generelles Problem vom SSL 3.0/TLS 1.0. Es wurde aber als 2002 nur als theoretischer Angriff gesehen, jetzt sind Angriffe mit Hilfe der erweiterten Funktionen des WebBrowsers bekannt geworden. Da es für einen erfolgreichen Angriff nicht nur notwendig ist, dass die verschlüsselte Verbindung abgehört werden kann, sondern auch, dass der Angreifer in den Klartext eigene Stück einfügen kann (Chosen Plaintext). Dies ist durch die Verwendung von JavaScript auf Webseiten relativ einfach möglich.

Wird bei SSL/TLS eine Blockchiffre im CBC (Cipher Block Chaining) Modus verwendet, so benutzt SSL 3.0 und TLS 1.0 einen vom Vorgängerblock abgeleiteten Initialisierungsvektor. Da sich dieser ermitteln lässt ist hier ein Problem gegeben, das durch geschicktes einfügen von Füllzeichen in den Klartext erlaubt Inhalte zeichenweise an den Blockgrenzen zu ermitteln.

Dies zu behaben bedarf es einer neuen Protokollversion: TLS  1.1. Aber in TLS 1.0 und SSL 3.0 kann es nicht so einfach verhindert werden. Somit hilft mittelfristig nur ein Update auf diese „neuen“ Protokolle, die zwar schon Jahrelang verfügbar sind, sich aber in der Praxis in den meisten WebServern und WebBrowsern noch nicht durchgesetzt haben (vor allem nicht per default aktiviert sind).

Mögliche Gegenmaßnahmen zu BEAST (und deren Probleme) sind:

  1. SSL/TLS Ciphers nicht im CBC Modus verwenden. Diese können in den gängigen Browsern und Servern abgeschalten werden. Die Gefahr dass dann Gegenstellen keine gemeinsame Verschlüsselung aushandeln können ist allerdings groß. Sollte also nur bei einer kleinen Anzahl von bekannten Kommunikationsgegenstellen benutzt werden.
  2. Statt die CBC Chiffren abzuschalten kann auf jedenfall die Stromchiffre RC4 (TLS_RSA_WITH_RC4_128_SHA) bevorzugt werden. Diese verwendet kein CBC und ist damit nicht betroffen. Dies macht Google z.B. schon seit einiger Zeit. RC4 ist nicht unumstritten, es gibt Angriffe gegen das Verfahren, die aber in SSL nicht auftreten.
  3. Oracle Java (JSSE) ab Version 1.6.0_29 und 7.0.0_1 implementiert einen CBC Schutz (der mit dem System Property jsse.enableCBCProtection aus kompatibilitätsgründen wieder abgeschalten werden kann) bei dem der erste Block in zwei mit der Länge 1 und (n-1) gesplittet wird. Erst mit Java 7 wird TLS 1.1 und 1.2 unterstützt.
  4. Der gleiche Fix wird gerade in Chrome Beta 15 getestet, es gab schon Kompatiblitätsprobleme.
  5. Für Opera 10.51 war der Fix geplant, ist aktuell noch nicht vorhanden (Opera benutzt keine WebSockets).
  6. Dieser Schutz wird auch in Mozilla Firefox (via NSS library) eingebaut, wartet dort aber noch auf eine Lösung der Kompatibilitätsprobleme und ist somit nicht Bestandteil von Firefox 7 (Mozilla sagt die WebSockets sind nicht verwundbar).
  7. Es ist zu erwarten dass Microsoft für den Internet Explorer nachzieht, bisher empfehlen sie nur die RC4 Chiffre zu bevorzugen (Windows XP scheint dies zu tun), Vorsicht bei dem Umgang mit http Seiten walten zu lassen und ggf. TLS 1.1 auf Client und Server zu aktivieren (immerhin unterstützen Microsoft Produkte dies schon, wird nur aus Kompatibilitätsgründen nicht aktiviert).
  8. Die Option TLS 1.1 (oder neuer) zu verwenden wird leider noch einige Zeit auf sich warten lassen. Besonders da SSL 3.0 und TLS 1.0 abgeschalten werden müssten, um zu verhindern das Angreifer diese erzwingen. Leider hängt openssl der TLS 1.1 Entwicklung nach, so dass auch Apache hier nur mit der RC4 Cipher gerettet werden kann (alternativ kann man mod_gnutls oder mod_nss verwenden, die sind aber beide weniger stark in der Nutzung).