Извлечь имя сервера (SNI) из приветствия клиента TLS

Как вы извлекаете указатель имени сервера из сообщения Hello клиента TLS. Я изо всех сил пытаюсь понять этот очень загадочный RFC 3546 в TLS Extensions, в котором определяется SNI.

Вещи, которые я понял до сих пор:

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

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

+8
источник поделиться
4 ответа

Я сделал это в 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
};
+22
источник

Используйте 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

+4
источник

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

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

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

0
источник

Для всех, кого это интересует, это предварительная версия кода 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
}
0
источник

Посмотрите другие вопросы по меткам или Задайте вопрос