TLSクライアントHelloメッセージからサーバー名表示をどのように抽出しますか。私は現在、SNIが定義されているTLS拡張でこれverycryptic RFC 3546 を理解するのに苦労しています。
私がこれまでに理解したこと:
その長さのバイトの正確な位置を見つけることができれば、SNIの抽出は非常に簡単です。しかし、そもそもどうすればそのバイトに到達できますか?
私はこれを sniproxy で行い、RFCがかなり良い方法であることを読みながら、WiresharkでTLSクライアントのhelloパケットを調べました。それほど難しいことではありません。多くの可変長フィールドをスキップして、正しい要素タイプがあるかどうかを確認する必要があります。
私は現在テストに取り組んでおり、次のような注釈付きのサンプルパケットがあります。
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
};
WireSharkを使用し、フィルターtcp port 443
を追加してTLS(SSL)パッケージのみをキャプチャします。次に、「ClientHello」メッセージを見つけます。以下にその生データを見ることができます。
Secure Socket Layer
->
TLSv1.2 Record Layer: Handshake Protocol: Client Hello
->
...
そして、Extension: server_name
->
Server Name Indication extension
が表示されます。ハンドシェイクパッケージのサーバー名は暗号化されていません。
ドメインには常に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として読み取ります。後で、配列を正規表現してドメインを抽出することができます。
驚くほどうまく機能します!それでも、私は何か奇妙なことに気づきました。
'�\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フィールドが別のフィールド内にネストされているようです。
私は提案や改善を受け入れています! :)
私はこれをNodeモジュールに変えました、気にするすべての人のために: sni 。
興味のある人のために、これは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
}