2013-07-24 2 views
8

Как вы можете извлечь указатель имени сервера из сообщения Hello клиента TLS. Я в настоящее время изо всех сил пытаюсь понять это очень cryptic RFC 3546 на TLS-расширениях, в которых определяется SNI.Извлечение имени имени сервера (SNI) от клиента TLS привет

Вещи я понял до сих пор:

  • Хост utf8 кодируются и читаемым при utf8 enocde буфера.
  • Theres один байт перед хостом, который определяет его длину.

Если бы я мог узнать точное положение этого байта длины, извлечение SNI было бы довольно простым. Но как мне добраться до этого байта в первую очередь?

+3

Простой подход, который вы пытаетесь предпринять, неверен. Вам нужно разобрать запрос, включая его расширения, и затем получить данные из соответствующего расширения. –

+0

Да, я уверен в этом, но на самом деле я не знаю, как его разобрать. Вы понимаете, как работает рукопожатие TLS? – buschtoens

+0

Конечно, я делаю так, как мы предлагаем библиотеку безопасности как один из наших основных продуктов. Вам нужно открыть RFC (http://tools.ietf.org/html/rfc5246) и реализовать его. –

ответ

22

Я сделал это в sniproxy, рассматривая пакет приветствия клиента TLS в Wireshark, читая, что 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

Отлично, я приехал сюда, потому что я хотел иметь незашифрованный простой TLS-форвардер на основе SNI. Итак, со sniproxy, что уже сделано. – JanKanis

0

Я заметил, что домен всегда добавляется двумя нулевыми байтами и одним байтом длины. Возможно, это неподписанное 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; 
} 

Этот код ищет последовательность из двух нулевых байтов. Если он находит один, он предполагает, что следующий байт является параметром длины. Он проверяет, находится ли длина на границе буфера, и, если это так, читается последовательность байтов как 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' 

Всегда, независимо от того, какой субдомен я выбираю, домен нацелен дважды. Похоже, что поле SNI вложено внутри другого поля.

Я открыт для предложений и улучшений! :)

Я превратил это в модуль узла, для всех, кого волнует: sni.

+0

Причина для downvote? – buschtoens

+2

Я не думаю, что регулярные выражения - лучший способ извлечь данные из бинарного криптографического протокола. Сообщение Hello Hello содержит 32 байта случайных данных, которые могут соответствовать вашему регулярному выражению. – dlundquist

+0

Я не знаю, что он заслуживает нисходящего потока, я имею в виду, что он нашел решение. Я натолкнулся на то же, но как примечания dlundquist, я не буду полагаться на то, что он согласован или исключает возможность случайных байтов, загрязняющих соответствие регулярных выражений. Однако он работает. –

4

Используйте WireShark и захватить только TLS (SSL) пакетов путем добавления фильтра tcp port 443. Затем найдите сообщение «Клиент Hello». Ниже вы можете увидеть его необработанные данные.

Развернуть Secure Socket Layer->TLSv1.2 Record Layer: Handshake Protocol: Client Hello->...
и вы увидите Extension: server_name->Server Name Indication extension. Имя сервера в пакете Handshake не зашифровано.

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 
} 
Смежные вопросы