Upgrade to Pro — share decks privately, control downloads, hide ads and more …

FQDN(ドメイン名)のバリデーションが意外と面倒だった #phpcon2022

akase244
September 25, 2022

FQDN(ドメイン名)のバリデーションが意外と面倒だった #phpcon2022

PHPカンファレンス2022の「大LT大会」のセッションで発表したスライドです。

akase244

September 25, 2022
Tweet

More Decks by akase244

Other Decks in Programming

Transcript

  1. こんな入力を防ぎたい 5 • www.example,com 「カンマ」を含んでいる • www.example com 「空白」を含んでいる •

    www.examp!e.com 「エクスクラメーション」を含んでいる • www.example.com 「ドメイン名」が含まれていない
  2. 7 • FILTER_VALIDATE_BOOL (= FILTER_VALIDATE_BOOLEAN) • FILTER_VALIDATE_DOMAIN • FILTER_VALIDATE_EMAIL •

    FILTER_VALIDATE_FLOAT • FILTER_VALIDATE_INT • FILTER_VALIDATE_IP • FILTER_VALIDATE_MAC • FILTER_VALIDATE_REGEXP • FILTER_VALIDATE_URL 検証フィルタ
  3. 「カンマ」が含まれている 10 $ docker container run \ --rm -it php:8.1.10-cli-alpine

    \ php -r 'var_dump( filter_var( "www.example,com", FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME ) );' bool(false)
  4. 「空白」が含まれている 11 $ docker container run \ --rm -it php:8.1.10-cli-alpine

    \ php -r 'var_dump( filter_var( "www.example com", FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME ) );' bool(false)
  5. 「エクスクラメーション」が含まれ ている 12 $ docker container run \ --rm -it

    php:8.1.10-cli-alpine \ php -r 'var_dump( filter_var( "www.examp!e.com", FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME ) );' bool(false)
  6. 「ドメイン名」が含まれていない 14 $ docker container run \ --rm -it php:8.1.10-cli-alpine

    \ php -r 'var_dump( filter_var( "www", FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME ) );' string(3) "www"
  7. FILTER_FLAG_HOSTNAME を指定して実行 19 $ docker container run \ --rm -it

    php:8.1.10-cli-alpine \ php -r 'var_dump( filter_var( "localhost", FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME ) );' string(9) "localhost"
  8. ここにヒントがありそう 22 ドメイン名が RFC 1034, RFC 1035, RFC 952, RFC

    1123, RFC 2732, RFC 2181, RFC 1123 に照らして正しいかを 検証します。
  9. ドメイン名のバリデーションに関係しそう なRFCを読んでみた 23 • RFC 952 (DOD INTERNET HOST TABLE

    SPECIFICATION) • RFC 1034 (DOMAIN NAMES - CONCEPTS AND FACILITIES) • RFC 1035 (DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION) • RFC 1123 (Requirements for Internet Hosts -- Application and Support) • RFC 2181 (Clarifications to the DNS Specification)
  10. RFC 952 (DOD INTERNET HOST TABLE SPECIFICATION) 24 • ASSUMPTIONS

    ◦ A "name" (Net, Host, Gateway, or Domain name) is a text string up to 24 characters drawn from the alphabet (A-Z), digits (0-9), minus sign (-), and period (.). Note that periods are only allowed when they serve to delimit components of "domain style names". (See RFC-921, "Domain Name System Implementation Schedule", for background). ◦ No blank or space characters are permitted as part of a name. ◦ No distinction is made between upper and lower case. ◦ The first character must be an alpha character. ◦ The last character must not be a minus sign or period.
  11. 25 • 3.1. Name space specifications and terminology ◦ The

    most common interpretation uses the root "." as either the single origin or as one of the members of the search list, so a multi-label relative name is often one where the trailing dot has been omitted to save typing. RFC 1034 (DOMAIN NAMES - CONCEPTS AND FACILITIES)
  12. 26 • 2.3.1. Preferred name syntax ◦ The labels must

    follow the rules for ARPANET host names. They must start with a letter, end with a letter or digit, and have as interior characters only letters, digits, and hyphen. There are also some restrictions on the length. Labels must be 63 characters or less. • 2.3.4. Size limits ◦ Various objects and parameters in the DNS have size limits. They are listed below. Some could be easily changed, others are more fundamental. ▪ labels 63 octets or less ▪ names 255 octets or less • 3.1. Name space definitions ◦ Since every domain name ends with the null label of the root, a domain name is terminated by a length byte of zero. ◦ To simplify implementations, the total length of a domain name (i.e., label octets and label length octets) is restricted to 255 octets or less. RFC 1035 (DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION)
  13. 27 • 2.1 Host Names and Numbers ◦ The syntax

    of a legal Internet host name was specified in RFC-952 [DNS:4]. One aspect of host name syntax is hereby changed: the restriction on the first character is relaxed to allow either a letter or a digit. Host software MUST support this more liberal syntax. ◦ Host software MUST handle host names of up to 63 characters and SHOULD handle host names of up to 255 characters. RFC 1123 (Requirements for Internet Hosts -- Application and Support)
  14. 28 • 11. Name syntax ◦ The length of any

    one label is limited to between 1 and 63 octets. ◦ A full domain name is limited to 255 octets (including the separators). ◦ The zero length full name is defined as representing the root of the DNS tree, and is typically written and displayed as ".". RFC 2181 (Clarifications to the DNS Specification)
  15. 利用不可 37 $ dig www.www.www.www.www.www.www.www.www.www.www.www.www.www.www.www.www.w ww.www.www.www.www.www.www.www.www.www.www.www.www.www.www.www.www.ww w.www.www.www.www.www.www.www.www.www.www.www.www.www.www.www.www.www .www.www.www.www.www.www.www.www.www.tsunagi.me +shortdig: 'www.www.www.www.www.www.www.www.www.www.www.www.www.www.www.www.www.

    www.www.www.www.www.www.www.www.www.www.www.www.www.www.www.www.www.w ww.www.www.www.www.www.www.www.www.www.www.www.www.www.www.www.www.ww w.www.www.www.www.www.www.www.www.www.tsunagi.me' is not a legal IDNA2008 name (domain name longer than 255 characters), use +noidnin
  16. 44 • ドメイン名は253文字以下でなければならない(最大長の 255オクテット - 予約分の2オクテット) ◦ ドメイン名の表記は末尾のドットが省略されていることが多い(予約分の 1オクテット) ◦

    すべてのドメイン名は rootのnullラベルで終了する(予約分の 1オクテット) • ラベルに利用可能な文字 ◦ 英字(A-Z(大文字、小文字の区別はなし)) ◦ 数字(0-9) ◦ マイナス記号(ハイフン) • ラベルは63文字以下でなければならない ◦ ラベルの最初及び最後の文字は英数字でなければならない ◦ ラベルの最初及び最後の文字としてマイナス記号は利用できない ◦ ドットは区切り文字なのでラベルとしては利用できない しかし、このバリデーション の実装は面倒
  17. 47 507 static int _php_filter_validate_domain(char * domain, size_t len, zend_long

    flags) /* {{{ */ 508 { ・ ・ 525 /* The total length cannot exceed 253 characters (final dot not included) */ 526 if (l > 253) { 527 return 0; 528 } ドメイン名は253文字以下 254文字以上はfalse
  18. 48 507 static int _php_filter_validate_domain(char * domain, size_t len, zend_long

    flags) /* {{{ */ 508 { ・ ・ 519 /* Ignore trailing dot */ 520 if (l > 0 && *t == '.') { 521 e = t; 522 l--; 523 } 末尾のドットを省略 末尾のドットは無視する (RFCの仕様通り)
  19. 49 507 static int _php_filter_validate_domain(char * domain, size_t len, zend_long

    flags) /* {{{ */ 508 { ・ ・ ・ 530 /* First char must be alphanumeric */ 531 if(*s == '.' || (hostname && !isalnum((int)*(unsigned char *)s))) { 532 return 0; 533 } ラベルの最初及び最後の文字 は英数字 最初の文字が「.」(ドット)または英数 字以外の場合はfalse
  20. 535 while (s < e) { 536 if (*s ==

    '.') { 537 /* The first and the last character of a label must be alphanumeric */ 538 if (*(s + 1) == '.' || (hostname && (!isalnum((int)*(unsigned char *)(s - 1)) || !isalnum((int)*(unsigned char *)(s + 1))))) { 539 return 0; 540 } ・ ・ 50 ラベルの最初及び最後の文字 は英数字 最初及び最後の文字が「.」(ドット)または 英数字以外の場合はfalse
  21. 51 535 while (s < e) {                ・                ・ 544

    } else { 545 if (i > 63 || (hostname && *s != '-' && !isalnum((int)*(unsigned char *)s))) { 546 return 0; 547 }                ・                ・ ラベルは英数字、ハイフン 63文字以下 ラベルが64文字以上、または、 英数字、ハイフン以外はfalse
  22. 52 • ドメイン名は253文字以下でなければならない(最大長の 255オクテット - 予約分の2オクテット) ◦ ドメイン名の表記は末尾のドットが省略されていることが多い(予約分の 1オクテット) ◦

    すべてのドメイン名は rootのnullラベルで終了する(予約分の 1オクテット) • ラベルに利用可能な文字 ◦ 英字(A-Z(大文字、小文字の区別はなし)) ◦ 数字(0-9) ◦ マイナス記号(ハイフン) • ラベルは63文字以下でなければならない ◦ ラベルの最初及び最後の文字は英数字でなければならない ◦ ラベルの最初及び最後の文字としてマイナス記号は利用できない ◦ ドットは区切り文字なのでラベルとしては利用できない あれ?全部網羅されている
  23. 55 バリデーションの例 <?php function validate(string $domain): bool { return (

    (count(explode('.', $domain)) >= 2) && filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) ); } var_dump(validate('www')); // false var_dump(validate('www.example.com')); // true var_dump(validate('www.example,com')); // false var_dump(validate('www.example com')); // false var_dump(validate('www.examp!e.com')); // false 「.」(ドット)で区切られた数が 2以上の場合という条件を追加
  24. checkdnsrr 59 $ docker container run \ --rm -it php:8.1.10-cli-alpine

    \ php -r 'var_dump( checkdnsrr("twitter.com", "A") );' bool(true)
  25. filter_var / FILTER_VALIDATE_IP gethostbyname 60 $ docker container run \

    --rm -it php:8.1.10-cli-alpine \ php -r 'var_dump( filter_var( gethostbyname("twitter.com"), FILTER_VALIDATE_IP ) );' string(12) "104.244.42.1"