ICMP13(CVE-1999-0524)の脆弱性対策とPythonによる検証方法

掲載日

結論

  • firewall-cmdで以下を実行します。
firewall-cmd --zone=public --add-icmp-block={timestamp-reply,timestamp-request} --permanent
firewall-cmd --reload

ブロックできたか確認する時は(13の場合)

  • ICMP送信テスト用pythonを作成します。
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import socket
import struct
#=============================================#
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)

#=============================================#
IPaddr = "X.X.X.X" # ICMP送り付けたいサーバーのアドレス

ICM_Type = (0x0D)
ICM_Code = (0x00)
ICM_ID = (0x01)
ICM_Seq = (0x01)
#=============================================#
ICM_LIST = [0]*6

ICM_LIST[0] = ICM_Type
ICM_LIST[1] = ICM_Code
ICM_LIST[2] = 0#ICM_ChekeSum1
ICM_LIST[3] = 0#ICM_ChekeSum2
ICM_LIST[4] = ICM_ID
ICM_LIST[5] = ICM_Seq
#=============================================#
# ICM_DATA = b"abcdefghijklmnopqrstuvwxyz"

# タイプ13の時は本当は送信時刻(Originate)を送るらしいがややこしいのでとりあえず0を送ってしまう。(UTC 0時からのミリ秒を送るらしい)
# 受信時刻(Receive)と送信時刻(Transmit)の書き込み用にデータを足しておく必要がある
ICM_DATA = b'\x00' * 4 * 3

ICM_LIST.extend(ICM_DATA)
#=============================================#
csum = 0
for i in range(int(len(ICM_LIST)/2)):
        csum += (ICM_LIST[i*2]<<8) | (ICM_LIST[i*2+1])
csum = (csum&0xffff) + (csum>>16)
csum = 0xffff-(csum)

print("ChekeSum:"+hex(csum))
#=============================================#
ICM_LIST[2] = (csum&0xFF00)>>8        #ICM_ChekeSum2
ICM_LIST[3] = csum&0x00FF     #ICM_ChekeSum1
#=============================================#
print(bytes(ICM_LIST))

sock.sendto(bytes(ICM_LIST), (IPaddr, 0))
#=============================================#
#data = sock.recv(255)
#
#for i in range(20,len(data)):
#    print ("%r" % hex(data[i])),
#=============================================#

try:
    data, addr = sock.recvfrom(1024)
    # IPヘッダ(20byte)を除いた部分を取得
    icmp_payload = data[20:]

    res_type = icmp_payload[0]

    if res_type == 14:
        print(f"\n === Response ===")

        # 8-11バイト目: Originate Timestamp
        # 12-16バイト目: Receive Timestamp
        # 17-20バイト目: Transmit Timestamp
        origin_ms = struct.unpack("!I", icmp_payload[8:12])[0]
        rec_ms = struct.unpack("!I", icmp_payload[12:16])[0]
        trans_ms = struct.unpack("!I", icmp_payload[16:20])[0]

        print(f"Originate Timestamp: {origin_ms}")
        print(f"Receive Timestamp: {rec_ms}")
        print(f"Transmit Timestamp: {trans_ms}")

    else:
        print(f"\n == Received ICMP type: {res_type}")

except socket.timeout:
    print("\n タイムアウトエラー")

上記スクリプトを実行して結果が以下のように帰ってきてしまうとブロック出来てません。

$ python send_icmp.py
ChekeSum:0xf2fd
b'\r\x00\xf2\xfd\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

 === Response ===
Originate Timestamp: 0
Receive Timestamp: 4764
Transmit Timestamp: 4764

 

以下のように表示されて時刻が表示されなければOK。


ChekeSum:0xc1fe
b'\r\x00\xc1\xfe\x01\x010\x00\x00\x00\x00\x00\x00\x00\x00'

 == Received ICMP type: 3

詳細

仕事でとあるサイトの脆弱性診断をしたところ、「ICMPタイプ13が通っちゃうよ!」との報告がありました。

ICMP自体初耳だったので、調べてみると以下の通り。

ICMP(Internet Control Message Protocol)とは、インターネット層(OSI参照モデルではネットワーク層)で使用される制御用の通信プロトコルです。

インターネット制御メッセージプロトコル(ICMP)とは?より引用

これを読んでも正直なんのこっちゃですが、Pingとかで使われる通信プロトコルのことみたいです。
ICMPヘッダというものがあり、そのタイプの値が「8」だとおなじみのPingになり、13を使うとサーバーの内部時刻を知ることが出来ます

 

そして、時計の時刻を乱数に使用している場合、内部時刻がバレると暗号の解読できちゃうよ!、ということみたいです。

時計の時刻を乱数等に使用している、かつその乱数がパスワード等の生成に使ってたりすると確かに良くなさそうです。結論にある通りfirewallでブロックしてあげます。

firewall-cmd --zone=public --add-icmp-block={timestamp-reply,timestamp-request} --permanent
# タイプ14(timestamp-reply)はタイプ13(timestamp-request)に対する応答なので多分なくてもいい。
firewall-cmd --reload

ブロック自体は簡単だったのですが、問題は確認方法で、ビルトインコマンドでは確認できないようです。(※AlmaLinux10の場合。)

npinghpingというコマンドを入れればいいみたいなのですが、こういった対応のためにコマンド入れていくと一つ一つは小さくても最終的にひっ迫しそうなのと、何が入ってるか把握できなくなりそうなので避けたい。

 

そこで、こちらのサイト様を参考に「ブロックできたか確認する時は(13の場合)」のpythonスクリプトで確認します。(参考というか前半はICMPタイプ変えたり送るデータをタイプ13に合わせて変更したくらいでリンク先サイト様のコピペです。try以降しか実装してません。)

また厄介なのが返却されるデータの形式で、「UTC深夜からの経過時間をミリ秒単位で示したもの」が返却されてきます。時差とかもあるので返却された時刻が本当に合ってるのか、計算がとても面倒です。

シンプルに考えられるように、今回はテスト用サーバーで以下を実行して、サーバー側をUTC深夜にします。

date -s "2026-01-01 00:00:00 UTC"
# 日付は何でもOK

この状態でpythonスクリプトを間髪入れずに実行すると、Receive TimestampTransmit Timestampが数秒程度になります。

 === Response ===
Originate Timestamp: 0
Receive Timestamp: 6177
Transmit Timestamp: 6177

UTCの部分をJSTにして実行すると、時差9時間で計算してくれます。ここが日付を跨ぐ場合がややこしくてちょっと詰まったのですが、恐らく以下のようになってます。

date -s "2026-01-01 09:00:00 JST"
# 日本の方が9時間進んでるので、この時UTCは00時00分なので、間髪入れずにスクリプト実行すると返り値は数秒程度になる。
date -s "2026-01-01 00:00:00 JST"
# 上記同様だが、この時UTCは前日の15:00:00になる。「UTC深夜からの経過時間をミリ秒単位で示したもの」が返却されるので「前日の00:00:00から計算する」ため返り値は15時間くらいのミリ秒になる。
#(最初この指定で時差9時間だからマイナス9時間のミリ秒が出てくるのかな?とか思ってたので15時間が返って来て混乱しました。)

参考

記事の作成者のA.W.のアイコン

この記事を書いた人

A.W.
茨城県在住Webエンジニアです。 PHP、PostgreSQL、Linuxなどを業務で使用しています。 趣味ではGoやNuxt、Flutterをやってます。

Comment

check コピーしました!