2013-07-24 1 views
8

어떻게 TLS 클라이언트 Hello 메시지에서 서버 이름 표시를 추출 하시겠습니까? SNI가 정의 된 TLS Extensions에서이 부분을 이해하기 위해 고심하고 있습니다. 암호가 걸려있는 RFC 3546.TLS 클라이언트에서 SNI (서버 이름 표시) 추출 안녕하세요

것들 지금까지 이해했습니다

  • 호스트가 UTF8 인코딩하고 UTF8 버퍼를 enocde 때 읽을 수 있습니다.
  • 호스트의 1 바이트 전에 길이가 결정됩니다.

해당 길이 바이트의 정확한 위치를 알 수 있으면 SNI를 추출하는 것이 매우 간단합니다. 하지만 어떻게 처음부터 그 바이트를 얻습니까?

+3

직접 시도하는 방식이 잘못되었습니다. 해당 확장을 포함하여 요청을 구문 분석 한 다음 해당 확장에서 데이터를 가져와야합니다. –

+0

그래, 나는 그것에 대해 확신하지만, 실제로 그것을 파싱하는 방법을 모른다. TLS 핸드 셰이크가 어떻게 작동하는지 이해합니까? – buschtoens

+0

물론, 우리는 우리의 주요 제품 중 하나로 보안 라이브러리를 제공합니다. RFC (http://tools.ietf.org/html/rfc5246)를 열고 구현해야합니다. –

답변

22

Wireshark의 TLS 클라이언트 hello 패킷을 검사하면서 RFC를 읽는 것이 꽤 좋은 방법이라는 것을 알고있는 동안 sniproxy에서이 작업을 수행했습니다. 너무 어려운 것은 아니며, 가변 길이 필드를 많이 건너 뛰어 올바른 요소 유형이 있는지 확인해야합니다.

나는 지금 내 테스트 작업을하고, 도움이 될이 주석 샘플 패킷이있어 :

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 바이트의 0 바이트와 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; 
} 

이 코드는 두 개의 0 바이트의 순서를 찾습니다. 하나를 찾으면 다음 바이트가 길이 매개 변수라고 가정합니다. 길이가 여전히 버퍼의 경계에 있는지 검사하고, 그렇다면 바이트 시퀀스를 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' 

내가 선택한 하위 도메인에 관계없이 도메인은 두 번 타겟팅됩니다. SNI 필드가 다른 필드 안에 중첩되어있는 것처럼 보입니다.

제안 사항과 개선점이 있습니다. :)

누구나 신경 쓰이는 노드 모듈로 바꿨습니다 : sni.

+0

downvote 이유는 무엇입니까? – buschtoens

+2

정규 표현식이 바이너리 암호화 프로토콜에서 데이터를 추출하는 가장 좋은 방법이라고 생각하지 않습니다. 클라이언트 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++ 코드의 임시 버전입니다. 지금까지 효과가있었습니다. 이 함수는 클라이언트 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 
} 
관련 문제