スマートフォン解析 top

TOP > タイガーチームセキュリティレポート > Behind Behind the Masq : CVE-2017-14493

タイガーチームセキュリティレポート

Behind Behind the Masq : CVE-2017-14493

Behind the Maskといえば1979年に発表されたYMOの楽曲であり、アルバム「ソリッド・ステイト・サヴァイヴァー」に収録された、長年愛されている名曲です。このレポートはそのMaskではなくdnsmasqの脆弱性の裏側を覗き見るものです。

CVE-2017-14493はGoogle社によって発見され、2017年10月2日に公開された7件のdnsmasqの脆弱性のうちの一つです。これらの脆弱性は2.77以前のバージョンのdnsmasqに存在し、対策は2.78以降へのバージョンアップです。

このレポートでは、公開されたPoCやdnsmasqのcommit diffなどの情報をもとに、攻撃の中身を解析していきます。

関連 :
Behind Behind the Masq : CVE-2017-14491
Behind Behind the Masq : CVE-2017-14492
Behind Behind the Masq : CVE-2017-14494
Behind Behind the Masq : CVE-2017-14495
Behind Behind the Masq : CVE-2017-13704
Behind Behind the Masq : CVE-2017-14496

攻撃の流れ

この脆弱性は、dnsmasqでDHCPv6機能を有効にしている場合、細工したリレー転送メッセージ(RELAY-FORW)を受け取ることでバッファオーバフローを起こすものです。

Attackerdnsmasq細工したDHCPv6 RELAY-FORWメッセージStack-based Buffer Overflow

PoC実行結果

Google社が公開したPoCを実行してみた様子です。上のウインドウがdnsmasq、下がAttackerです。コマンド一発でAddress Sanitizerがオーバフローを検知しました。

攻撃後にdnsmasq側で表示されているメッセージはAddress Sanitizerによるものです。詳しくはこちらをご覧ください。

パケット解析

検証に使用したPoCはこちらです。

このPoCは、DHCPv6 RELAY-FORWメッセージのClient Link-Layer Address Optionに大きなデータをセットすることで、バッファオーバフローを引き起こすことを狙っています。生成されるパケットを見ていきましょう。

ヘッダ部

まずは、ヘッダ部からです。コードはこちらです。

    pkg = b"".join([
        u8(12),                         # DHCP6RELAYFORW
        u16(0x0313), u8(0x37),          # transaction ID
        b"_" * (34 - 4),

msg-typeの値は、IANAのサイトにまとめられています。このPoCのtypeは12なので、RELAY-FORWです。PoCコードはClient/Server Message Formatsに則って記述されていますが、RELAY-FORWのフォーマットはそれとは異なります。

RELAY-FORWメッセージのフォーマットはrfc3315 Relay Agent/Server Message Formatsで定義されています。

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|    msg-type   |   hop-count   |                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
|                                                               |
|                         link-address                          |
|                                                               |
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
|                               |                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
|                                                               |
|                         peer-address                          |
|                                                               |
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
|                               |                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
.                                                               .
.            options (variable number and length)   ....        .
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

hop-countには、u16(0x0313)の先頭1バイトがはまりますので、セットされる値は0x03です。 link-addressには、u16(0x0313)の下位1バイトと、u8(0x37)、および、b"_" * (34 - 4)の先頭14バイトで、合計16バイト分が割り当てられます。peer-addressは、b"_" * (34 - 4)の下位16バイトです。

options

続いて、options領域です。当てはまるコードはこちら

        # Option 79 = OPTION6_CLIENT_MAC
        # Moves argument into char[DHCP_CHADDR_MAX], DHCP_CHADDR_MAX = 16
        gen_option(79, "A" * 74 + pack("<Q", 0x1337DEADBEEF)),

gen_optionは下記のように定義されています。

def gen_option(option, data, length=None):
    if length is None:
        length = len(data)

    return b"".join([
        u16(option),
        u16(length),
        data
    ])

option-codeが79なので、こちらによるとOPTION_CLIENT_LINKLAYER_ADDRが適用されます。フォーマットはrfc6939で定義されています。

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| OPTION_CLIENT_LINKLAYER_ADDR  |           option-length       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   link-layer type (16 bits)   |                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
|               link-layer address (variable length)            |
|                                                               |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

option-lengthは、link-layer typelink-layer addressの長さが入ります。link-layer typeは、"A" * 74 + pack("<Q", 0x1337DEADBEEF)の先頭2バイトの"AA"が、link-layer addressには3バイト目以降が入ります。pack("<Q", 0x1337DEADBEEF)は8バイトのunsigned long long変換されるので、option-lengthには、74 + 8 = 82が入ります。

送信パケットまとめ

まとめると、以下のようなパケットになります。

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -
| msg-type=12   | hop-count=0x03|                               | ^
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               | |
|                                                               | |
|                   link-address = 0x1337 + '_' * 14            | |
|                                                               | |
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| | Relay Agent/Server Message
|                               |                               | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               | |
|                                                               | |
|                   peer-address = '_' * 16                     | |
|                                                               | |
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| | -
|                               |        option-code = 79       | | ^
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
|     option-length = 82        | link-layer type='AA'=0x4141   | | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
|                                                               | | | Client Link-Layer Address Option
|               link-layer address (variable length)            | | |
|                 = 'A' * 72 + 0xEFBEADDE37130000               | | |
|                                                               | v v
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -

このペイロードのポイントは、link-layer addressに長大なデータを格納していることにあります。

罹患箇所

PoCが生成するパケットがdnsmasqの内部でどのように処理されるのか、見ていきましょう。参照するコードは全てバージョン2.78test2のものです。

罹患箇所は、src/rfc3315.c#211dhcp6_maybe_relay関数でmemcpyをコールしているところです。

  /* RFC-6939 */
  if ((opt = opt6_find(opts, end, OPTION6_CLIENT_MAC, 3)))
    {
      state->mac_type = opt6_uint(opt, 0, 2);
      state->mac_len = opt6_len(opt) - 2;
      memcpy(&state->mac[0], opt6_ptr(opt, 2), state->mac_len);
    }

例によって、格納先state->macのサイズを調べましょう。

state->macのサイズ

state->macsrc/rfc3315.c#32で定義されています。

struct state {
  unsigned char *clid;
  int clid_len, iaid, ia_type, interface, hostname_auth, lease_allocate;
  char *client_hostname, *hostname, *domain, *send_domain;
  struct dhcp_context *context;
  struct in6_addr *link_address, *fallback, *ll_addr, *ula_addr;
  unsigned int xid, fqdn_flags;
  char *iface_name;
  void *packet_options, *end;
  struct dhcp_netid *tags, *context_tags;
  unsigned char mac[DHCP_CHADDR_MAX];
  unsigned int mac_len, mac_type;
#ifdef OPTION6_PREFIX_CLASS
  struct prefix_class *send_prefix_class;
#endif
};

DHCP_CHADDR_MAX定数は、src/dhcp-protocol.h#91で、次のように定義されています。

#define DHCP_CHADDR_MAX 16

したがって、state->macは、16バイト分がスタック領域に確保されるということがわかりました。

罹患箇所の動き

引き続き、処理内容を追跡していきます。

まず、opt6_find関数で、type = 79のオプション領域を取得しています。

  if ((opt = opt6_find(opts, end, OPTION6_CLIENT_MAC, 3)))

opt6_findは対象の領域の先頭アドレスを返却するので、この場合は、Client Link-Layer Address Optionの先頭アドレスがoptに入ります。

続いて、次の2行でoption-typeoption-lengthを取り出しています。

      state->mac_type = opt6_uint(opt, 0, 2);
      state->mac_len = opt6_len(opt) - 2;

opt6_uintopt6_len関数は、データの取り出しと同時に、ポインタを読み進めるため、この処理の後、optは、link-layer address領域の先頭アドレスを指しています。

最後にバッファオーバフローが発生する箇所です。

      memcpy(&state->mac[0], opt6_ptr(opt, 2), state->mac_len);

opt6_ptrは、オプションのデータ領域を先頭に、指定したサイズ分オフセットしたアドレスのポインタを返すマクロです。ここでは2バイトオフセットしているので、link-layer typeを読み飛ばしてlink-layer addressを返しています。state->mac_lenは、option-lengthから2減算した値が入っているので、80がmemcpyするサイズとして指定されています。

格納先のstate->macは16バイトしか確保されていないので、80バイト分のコピーを実行することでバッファオーバフローが発生します。

修正内容

commit diffを検討します。

--- a/src/rfc3315.c
+++ b/src/rfc3315.c
@@ -206,6 +206,9 @@ static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz,
   /* RFC-6939 */
   if ((opt = opt6_find(opts, end, OPTION6_CLIENT_MAC, 3)))
     {
+      if (opt6_len(opt) - 2 > DHCP_CHADDR_MAX) {
+        return 0;
+      }
       state->mac_type = opt6_uint(opt, 0, 2);
       state->mac_len = opt6_len(opt) - 2;
       memcpy(&state->mac[0], opt6_ptr(opt, 2), state->mac_len);

データの取り出しとコピーを行う前にoption-lengthを調べ、コピー先state->macのサイズDHCP_CHADDR_MAXと比較しています。option-lengthが領域のサイズを超えた場合はreturnすることで、今回のバッファオーバフローを回避しています。

まとめ

CVE-2017-14493を検証しました。今回も単純なバッファオーバフローでした。ペイロードは、大きくは壊れていませんでしたが、PoCコードにプロトコルへの誤解を感じ取れました。攻撃が成立すれば内容は問わないというスタンスなのでしょう。PoCの内容と実装にズレがあると、攻撃の意図を読み取るのに苦労します。

参考情報

[1] Behind the Masq: Yet more DNS, and DHCP, vulnerabilities (Google Security Blog)
https://security.googleblog.com/2017/10/behind-masq-yet-more-dns-and-dhcp.html

[2] google / security-research-pocs (github)
https://github.com/google/security-research-pocs

[3] dnsmasq (thekelleys)
http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=summary

[4] google / sanitizers (github)
https://github.com/google/sanitizers

[5] rfc3315 Dynamic Host Configuration Protocol for IPv6 (DHCPv6) (IETF Tools)
https://tools.ietf.org/html/rfc3315

[6] rfc6221 Lightweight DHCPv6 Relay Agent (IETF Tools)
https://tools.ietf.org/html/rfc6221

[7] rfc6939 Client Link-Layer Address Option in DHCPv6 (IETF Tools)
https://tools.ietf.org/html/rfc6939

[8] Dynamic Host Configuration Protocol for IPv6 (DHCPv6)
https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml

[9] 7.3. struct — Interpret strings as packed binary data (Python Documentation)
https://docs.python.org/2.7/library/struct.html#format-characters

[10] ビハインド・ザ・マスク_(曲) (Wikipedia)
https://ja.wikipedia.org/wiki/ビハインド・ザ・マスク_(曲)

修正履歴

  • 2017/12/13 : 表現の修正と、link-layer addressに記載したDEADBEEFのバイトオーダの修正を行いました。
  • 2017/12/18 : シーケンス図を修正しました。

R&D部準備室 柏崎央士