2013-07-24 12 views
8

TLSクライアントのHelloメッセージからサーバー名の表示をどのように抽出しますか? SNIが定義されているTLS Extensionsの非常に cryptic RFC 3546を理解しようとしています。私はこれまで理解しましたTLSクライアントからのSNI(Server Name Indication)の抽出hello

物事:あなたがバッファをenocde UTF8とき

  • ホストは、UTF8エンコードされた、読み取り可能です。
  • ホストの1バイト前に、長さが決まります。

もし私がその長さバイトの正確な位置を知ることができれば、SNIの抽出は非常に簡単です。しかし、どのように私はそのバイトに最初に行くのですか?

+3

あなたが試みる簡単なアプローチは間違っています。拡張機能を含む要求を解析し、対応する拡張機能からデータを取得する必要があります。 –

+0

ええ、私はそれについて確信していますが、実際にそれを解析する方法は実際にはわかりません。 TLSハンドシェイクの仕組みを理解していますか? – buschtoens

+0

確かに、私たちは主な製品の1つとしてセキュリティライブラリを提供しています。 RFC(http://tools.ietf.org/html/rfc5246)を開いて実装する必要があります。 –

答えて

22

これをsniproxyで行い、WiresharkのTLSクライアントのhelloパケットを調べて、そのRFCを読むのはかなり良い方法です。それはあまりにも難しいことではありません。あなたが正しい要素タイプを持っているかどうかをチェックし、過去にスキップしなければならない多くの可変長フィールドです。

私は今、私のテストに取り組んで、そして役立つかもしれない。この注釈付きのサンプルパケットを持っている:

const unsigned char good_data_2[] = { 
    // TLS record 
    0x16, // Content Type: Handshake 
    0x03, 0x01, // Version: TLS 1.0 
    0x00, 0x6c, // Length (use for bounds checking) 
     // Handshake 
     0x01, // Handshake Type: Client Hello 
     0x00, 0x00, 0x68, // Length (use for bounds checking) 
     0x03, 0x03, // Version: TLS 1.2 
     // Random (32 bytes fixed length) 
     0xb6, 0xb2, 0x6a, 0xfb, 0x55, 0x5e, 0x03, 0xd5, 
     0x65, 0xa3, 0x6a, 0xf0, 0x5e, 0xa5, 0x43, 0x02, 
     0x93, 0xb9, 0x59, 0xa7, 0x54, 0xc3, 0xdd, 0x78, 
     0x57, 0x58, 0x34, 0xc5, 0x82, 0xfd, 0x53, 0xd1, 
     0x00, // Session ID Length (skip past this much) 
     0x00, 0x04, // Cipher Suites Length (skip past this much) 
      0x00, 0x01, // NULL-MD5 
      0x00, 0xff, // RENEGOTIATION INFO SCSV 
     0x01, // Compression Methods Length (skip past this much) 
      0x00, // NULL 
     0x00, 0x3b, // Extensions Length (use for bounds checking) 
      // Extension 
      0x00, 0x00, // Extension Type: Server Name (check extension type) 
      0x00, 0x0e, // Length (use for bounds checking) 
      0x00, 0x0c, // Server Name Indication Length 
       0x00, // Server Name Type: host_name (check server name type) 
       0x00, 0x09, // Length (length of your data) 
       // "localhost" (data your after) 
       0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, 
      // Extension 
      0x00, 0x0d, // Extension Type: Signature Algorithms (check extension type) 
      0x00, 0x20, // Length (skip past since this is the wrong extension) 
      // Data 
      0x00, 0x1e, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 
      0x05, 0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 
      0x04, 0x02, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 
      0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03, 
      // Extension 
      0x00, 0x0f, // Extension Type: Heart Beat (check extension type) 
      0x00, 0x01, // Length (skip past since this is the wrong extension) 
      0x01 // Mode: Peer allows to send requests 
}; 
+0

これを共有していただきありがとうございます。 +1 –

+0

これは私の元々の半分の答えよりも明らかに精巧です。チェックしてください。 :D – buschtoens

+0

偉大な、私はSNIに基づいて非解読単純なTLSフォワーダを持っていたかったのでここに来た。したがって、既に行われているスニプロキシで。 – JanKanis

0

ドメインが常に2つのゼロバイトと1つの長さバイトの前にあることに気付きました。 24ビット整数の符号なしかもしれませんが、私のDNSサーバは77文字以上のドメイン名を許可しないため、テストできません。

この知識に基づいて、私はこの(Node.js)コードを考え出しました。

function getSNI(buf) { 
    var sni = null 
    , regex = /^(?:[a-z0-9-]+\.)+[a-z]+$/i; 
    for(var b = 0, prev, start, end, str; b < buf.length; b++) { 
    if(prev === 0 && buf[b] === 0) { 
     start = b + 2; 
     end = start + buf[b + 1]; 
     if(start < end && end < buf.length) { 
     str = buf.toString("utf8", start, end); 
     if(regex.test(str)) { 
      sni = str; 
      continue; 
     } 
     } 
    } 
    prev = buf[b]; 
    } 
    return sni; 
} 

このコードは、2つのゼロバイトのシーケンスを探します。見つかった場合は、次のバイトが長さパラメータであるとみなされます。長さがまだバッファの境界にあるかどうかチェックし、バッファの境界がUTF-8であるかどうかをチェックします。後で、RegEx配列を使用してドメインを抽出することができます。

驚くほどうまく機能します!それでも、私は何か奇妙なことに気づいた。

'�\n�\u0014\u0000�\u0000�\u00009\u00008�\u000f�\u0005\u0000�\u00005�\u0007�\t�\u0011�\u0013\u0000E\u0000D\u0000f\u00003\u00002�\f�\u000e�\u0002�\u0004\u0000�\u0000A\u0000\u0005\u0000\u0004\u0000/�\b�\u0012\u0000\u0016\u0000\u0013�\r�\u0003��\u0000\n' 
'\u0000\u0015\u0000\u0000\u0012test.cubixcraft.de' 
'test.cubixcraft.de' 
'\u0000\b\u0000\u0006\u0000\u0017\u0000\u0018\u0000\u0019' 
'\u0000\u0005\u0001\u0000\u0000' 

私が選択したサブドメインにかかわらず、ドメインは2度ターゲティングされます。 SNIフィールドが別のフィールドの中にネストされているようです。

私は、提案と改善にオープンしています! :)

私はこれをノードモジュールに変えました。だれもが気にするのはsniです。

+0

downvoteの理由は? – buschtoens

+2

私は正規表現がバイナリ暗号プロトコルからデータを抽出する最良の方法であるとは思わない。 Client Helloメッセージには、正規表現に一致する32バイトのランダムデータが含まれています。 – dlundquist

+0

私はそれがdownvoteに値するのか分からない、私は彼が解決策を見つけたことを意味する。私は同じだが、dlundquistのノートに遭遇した、私はそれに頼るつもりはないまたは正規表現のマッチを汚染するランダムなバイトの可能性を排除するつもりはない。しかし、それは動作します。 –

4

使用WireSharkのフィル​​タtcp port 443を追加することによってのみ、TLS(SSL)パッケージを取り込みます。次に、「クライアントのハロー」メッセージを探します。以下の生データを見ることができます。

Secure Socket Layer->TLSv1.2 Record Layer: Handshake Protocol: Client Hello->...
を展開して、あなたはExtension: server_name->Server Name Indication extensionが表示されます。ハンドシェイクパッケージのサーバー名は暗号化されていません。興味がある人のため

http://i.stack.imgur.com/qt0gu.png

+1

私たちは、SNIを決定するプログラム的な方法を探しています。それにもかかわらず、これは面白いかもしれませんので、削除しないでください。 – buschtoens

0

、これは、C/C++コードの仮バージョンです。それはこれまで働いていた。この関数は、Client Helloを含むバイト配列内のサーバー名の位置と、lenパラメーター内の名前の長さを返します。

char *get_TLS_SNI(unsigned char *bytes, int* len) 
{ 
    unsigned char *curr; 
    unsigned char sidlen = bytes[43]; 
    curr = bytes + 1 + 43 + sidlen; 
    unsigned short cslen = ntohs(*(unsigned short*)curr); 
    curr += 2 + cslen; 
    unsigned char cmplen = *curr; 
    curr += 1 + cmplen; 
    unsigned char *maxchar = curr + 2 + ntohs(*(unsigned short*)curr); 
    curr += 2; 
    unsigned short ext_type = 1; 
    unsigned short ext_len; 
    while(curr < maxchar && ext_type != 0) 
    { 
     ext_type = ntohs(*(unsigned short*)curr); 
     curr += 2; 
     ext_len = ntohs(*(unsigned short*)curr); 
     curr += 2; 
     if(ext_type == 0) 
     { 
      curr += 3; 
      unsigned short namelen = ntohs(*(unsigned short*)curr); 
      curr += 2; 
      *len = namelen; 
      return (char*)curr; 
     } 
     else curr += ext_len; 
    } 
    if (curr != maxchar) throw std::exception("incomplete SSL Client Hello"); 
    return NULL; //SNI was not present 
}