GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

漏洞修复 --- CVE-1999-0524

漏洞原理

  1. 暴露的服务信息
    当主机响应 ICMP 时间戳请求(Timestamp Request, Type 13) 时,会在回复包(Timestamp Reply, Type 14)中包含当前系统的精确时间戳(以毫秒为单位的 UTC 时间)。

     
  2. 协议设计缺陷
    ICMP 时间戳协议(RFC 792)设计于早期网络环境,未考虑安全风险。许多系统默认开启此功能,且未对请求来源做过滤。现在已经将这个功能视为漏洞不在支持


风险影响

    1. 系统指纹识别
      通过时间戳可推断:

      • 操作系统的默认时间格式(如 Unix 时间戳 vs Windows 时间)

      • 系统时区配置(通过 UTC 时间偏移计算)

      • 设备类型(服务器/工控设备等持续运行的系统时间戳数值更大)

    2. 网络侦察辅助

      • 探测主机存活状态(绕过禁 ping 策略)

      • 绘制网络拓扑(结合 TTL 值判断跃点数)

    3. 高级攻击铺垫

      • 时间同步攻击:为伪造 Kerberos 票据等时间敏感攻击提供参考基准

      • 服务漏洞利用:识别老旧系统(如 Windows NT/95)以针对性投递漏洞利用载荷

 

poc:

没找到合适的ip测试,只有测试脚本

#!/usr/bin/env python3                                    # 指定使用Python3解释器运行此脚本
# -*- coding: utf-8 -*-                                   # 设置文件编码为UTF-8,支持中文字符
"""
CVE-1999-0524 ICMP Timestamp and Address Mask Request Exploit
ICMP 时间戳和地址掩码请求信息泄露漏洞利用代码

漏洞描述:
- 某些系统会响应ICMP时间戳请求,泄露系统时间信息
- 某些系统会响应ICMP地址掩码请求,泄露网络配置信息
- 攻击者可以利用这些信息进行网络侦察

注意:此代码仅用于教育和安全测试目的
"""

import socket                                            # 导入socket模块,用于网络通信
import struct                                            # 导入struct模块,用于二进制数据的打包和解包
import time                                              # 导入time模块,用于时间相关操作
import sys                                               # 导入sys模块,用于系统相关操作(如退出程序)
import argparse                                          # 导入argparse模块,用于解析命令行参数
from datetime import datetime                            # 从datetime模块导入datetime类,用于时间格式化

# ICMP 类型定义 - 这些是网络协议中规定的标准数值
ICMP_ECHO_REQUEST = 8          # ICMP回显请求(ping命令使用的类型)
ICMP_ECHO_REPLY = 0            # ICMP回显应答(ping响应的类型)
ICMP_TIMESTAMP_REQUEST = 13    # ICMP时间戳请求(询问目标系统时间)
ICMP_TIMESTAMP_REPLY = 14      # ICMP时间戳应答(目标系统回复时间)
ICMP_ADDRESS_MASK_REQUEST = 17 # ICMP地址掩码请求(询问目标网络配置)
ICMP_ADDRESS_MASK_REPLY = 18   # ICMP地址掩码应答(目标系统回复网络配置)

def calculate_checksum(data):                            # 定义计算ICMP校验和的函数
    """
    计算ICMP校验和 - 用于验证数据包完整性
    
    参数:
        data: 需要计算校验和的二进制数据
    
    返回:
        校验和值(16位整数)
    """
    if len(data) % 2:                                    # 如果数据长度为奇数
        data += b'\x00'                                  # 在末尾补一个0字节,确保长度为偶数
    
    checksum = 0                                         # 初始化校验和为0
    for i in range(0, len(data), 2):                     # 每次处理2个字节(16位)
        word = (data[i] << 8) + data[i + 1]              # 将两个字节组合成一个16位整数
        checksum += word                                 # 累加到校验和中
    
    while checksum >> 16:                                # 处理进位(如果校验和超过16位)
        checksum = (checksum & 0xFFFF) + (checksum >> 16) # 将高位进位加到低位
    
    return ~checksum & 0xFFFF                            # 取反并保留低16位作为最终校验和

def create_icmp_packet(icmp_type, icmp_code=0, icmp_id=0, icmp_seq=0, data=b''): # 定义创建ICMP数据包的函数
    """
    创建完整的ICMP数据包
    
    参数:
        icmp_type: ICMP类型(如13表示时间戳请求)
        icmp_code: ICMP代码(通常为0)
        icmp_id: ICMP标识符(用于匹配请求和响应)
        icmp_seq: ICMP序列号(用于区分不同的请求)
        data: 附加数据(如时间戳数据)
    
    返回:
        完整的ICMP数据包(字节串)
    """
    icmp_header = struct.pack('!BBHHH', icmp_type, icmp_code, 0, icmp_id, icmp_seq) # 创建ICMP头部,校验和暂时为0
    # struct.pack格式说明:
    # '!' = 网络字节序(大端序)
    # 'B' = 无符号字符(1字节)- icmp_type
    # 'B' = 无符号字符(1字节)- icmp_code  
    # 'H' = 无符号短整型(2字节)- checksum(暂时为0)
    # 'H' = 无符号短整型(2字节)- icmp_id
    # 'H' = 无符号短整型(2字节)- icmp_seq
    
    icmp_packet = icmp_header + data                     # 将头部和数据组合成完整数据包
    
    checksum = calculate_checksum(icmp_packet)           # 计算整个数据包的校验和
    
    icmp_header = struct.pack('!BBHHH', icmp_type, icmp_code, checksum, icmp_id, icmp_seq) # 重新创建头部,包含正确的校验和
    
    return icmp_header + data                            # 返回包含正确校验和的完整ICMP数据包

def send_timestamp_request(target_ip):                   # 定义发送时间戳请求的函数
    """
    发送ICMP时间戳请求到目标主机
    
    参数:
        target_ip: 目标IP地址(字符串格式)
    
    返回:
        True: 成功获取时间戳信息
        False: 请求失败或超时
    """
    print(f"[+] 向 {target_ip} 发送ICMP时间戳请求...")  # 显示正在发送请求的信息
    
    try:                                                 # 开始异常处理块
        sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) # 创建原始套接字
        # socket.AF_INET = IPv4地址族
        # socket.SOCK_RAW = 原始套接字类型(可以发送自定义协议数据包)
        # socket.IPPROTO_ICMP = ICMP协议
        
        sock.settimeout(5)                               # 设置套接字超时时间为5秒
        
        current_time = int(time.time() * 1000) % (2**32) # 获取当前时间戳(毫秒),并限制在32位范围内
        
        timestamp_data = struct.pack('!III', current_time, 0, 0) # 创建时间戳数据
        # 格式:原始时间戳、接收时间戳(0)、传输时间戳(0)
        # '!III' = 网络字节序,3个无符号32位整数
        
        packet = create_icmp_packet(                     # 创建ICMP时间戳请求包
            ICMP_TIMESTAMP_REQUEST,                      # ICMP类型:时间戳请求(13)
            icmp_id=12345,                               # 标识符:12345(任意选择的数值)
            icmp_seq=1,                                  # 序列号:1
            data=timestamp_data                          # 附加数据:时间戳数据
        )
        
        sock.sendto(packet, (target_ip, 0))              # 发送数据包到目标IP
        # sendto用于无连接套接字,第二个参数是目标地址
        # 端口号为0,因为ICMP不使用端口
        
        print(f"[+] 时间戳请求已发送到 {target_ip}")      # 显示发送成功信息
        
        try:                                             # 开始接收响应的异常处理
            response, addr = sock.recvfrom(1024)         # 接收响应数据包(最大1024字节)
            print(f"[+] 收到来自 {addr[0]} 的响应")        # 显示收到响应的信息
            
            icmp_response = response[20:]                # 跳过IP头部(前20字节),获取ICMP部分
            
            icmp_type, icmp_code, checksum, icmp_id, icmp_seq = struct.unpack('!BBHHH', icmp_response[:8]) # 解析ICMP头部
            
            if icmp_type == ICMP_TIMESTAMP_REPLY:        # 如果收到的是时间戳回复(类型14)
                print("[+] 成功!目标响应了时间戳请求")    # 显示成功信息
                
                if len(icmp_response) >= 20:             # 如果响应数据足够长(包含时间戳数据)
                    orig_timestamp, recv_timestamp, trans_timestamp = struct.unpack('!III', icmp_response[8:20]) # 解析时间戳数据
                    
                    print(f"[+] 原始时间戳: {orig_timestamp}")     # 显示原始时间戳
                    print(f"[+] 接收时间戳: {recv_timestamp}")     # 显示接收时间戳
                    print(f"[+] 传输时间戳: {trans_timestamp}")    # 显示传输时间戳
                    
                    if recv_timestamp > 0:               # 如果接收时间戳有效
                        readable_time = datetime.fromtimestamp(recv_timestamp / 1000.0) # 转换为可读时间格式
                        print(f"[+] 目标系统时间: {readable_time}") # 显示目标系统的时间
                
                return True                              # 返回成功标志
            else:                                        # 如果收到的不是时间戳回复
                print(f"[-] 收到非时间戳响应,类型: {icmp_type}") # 显示错误信息
                return False                             # 返回失败标志
                
        except socket.timeout:                           # 如果接收超时
            print("[-] 超时:目标未响应时间戳请求")        # 显示超时信息
            return False                                 # 返回失败标志
            
    except PermissionError:                              # 如果权限不足
        print("[-] 错误:需要管理员权限来创建原始套接字") # 显示权限错误信息
        return False                                     # 返回失败标志
    except Exception as e:                               # 如果发生其他异常
        print(f"[-] 发送时间戳请求时出错: {e}")          # 显示具体错误信息
        return False                                     # 返回失败标志
    finally:                                             # 无论如何都会执行的清理代码
        sock.close()                                     # 关闭套接字,释放资源

def send_address_mask_request(target_ip):                # 定义发送地址掩码请求的函数
    """
    发送ICMP地址掩码请求到目标主机

    参数:
        target_ip: 目标IP地址(字符串格式)

    返回:
        True: 成功获取地址掩码信息
        False: 请求失败或超时
    """
    print(f"\n[+] 向 {target_ip} 发送ICMP地址掩码请求...") # 显示正在发送请求的信息(\n表示换行)

    try:                                                 # 开始异常处理块
        sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) # 创建原始套接字
        sock.settimeout(5)                               # 设置5秒超时

        mask_data = struct.pack('!I', 0)                 # 创建地址掩码请求数据(4字节,初始值为0)
        # '!I' = 网络字节序,1个无符号32位整数

        packet = create_icmp_packet(                     # 创建ICMP地址掩码请求包
            ICMP_ADDRESS_MASK_REQUEST,                   # ICMP类型:地址掩码请求(17)
            icmp_id=12346,                               # 标识符:12346(与时间戳请求区分)
            icmp_seq=1,                                  # 序列号:1
            data=mask_data                               # 附加数据:地址掩码数据
        )

        sock.sendto(packet, (target_ip, 0))              # 发送数据包到目标IP
        print(f"[+] 地址掩码请求已发送到 {target_ip}")    # 显示发送成功信息

        try:                                             # 开始接收响应的异常处理
            response, addr = sock.recvfrom(1024)         # 接收响应数据包
            print(f"[+] 收到来自 {addr[0]} 的响应")        # 显示收到响应的信息

            icmp_response = response[20:]                # 跳过IP头部,获取ICMP部分

            icmp_type, icmp_code, checksum, icmp_id, icmp_seq = struct.unpack('!BBHHH', icmp_response[:8]) # 解析ICMP头部

            if icmp_type == ICMP_ADDRESS_MASK_REPLY:     # 如果收到的是地址掩码回复(类型18)
                print("[+] 成功!目标响应了地址掩码请求")  # 显示成功信息

                if len(icmp_response) >= 12:             # 如果响应数据足够长(包含地址掩码)
                    address_mask = struct.unpack('!I', icmp_response[8:12])[0] # 解析地址掩码(32位整数)

                    mask_str = socket.inet_ntoa(struct.pack('!I', address_mask)) # 转换为点分十进制格式
                    # inet_ntoa将32位整数转换为IP地址字符串格式
                    print(f"[+] 地址掩码: {mask_str}")     # 显示地址掩码

                    mask_bits = bin(address_mask).count('1') # 计算掩码中1的个数(网络位数)
                    # bin()将整数转换为二进制字符串,count('1')统计1的个数
                    print(f"[+] 子网掩码位数: /{mask_bits}") # 显示CIDR格式的子网掩码

                return True                              # 返回成功标志
            else:                                        # 如果收到的不是地址掩码回复
                print(f"[-] 收到非地址掩码响应,类型: {icmp_type}") # 显示错误信息
                return False                             # 返回失败标志

        except socket.timeout:                           # 如果接收超时
            print("[-] 超时:目标未响应地址掩码请求")      # 显示超时信息
            return False                                 # 返回失败标志

    except PermissionError:                              # 如果权限不足
        print("[-] 错误:需要管理员权限来创建原始套接字") # 显示权限错误信息
        return False                                     # 返回失败标志
    except Exception as e:                               # 如果发生其他异常
        print(f"[-] 发送地址掩码请求时出错: {e}")        # 显示具体错误信息
        return False                                     # 返回失败标志
    finally:                                             # 无论如何都会执行的清理代码
        sock.close()                                     # 关闭套接字,释放资源

def main():                                              # 定义主函数,程序的入口点
    """
    主函数 - 程序的控制中心
    """
    parser = argparse.ArgumentParser(description='CVE-1999-0524 ICMP信息泄露漏洞测试工具') # 创建命令行参数解析器
    parser.add_argument('target', help='目标IP地址')      # 添加必需参数:目标IP地址
    parser.add_argument('-t', '--timestamp', action='store_true', help='发送时间戳请求') # 添加可选参数:时间戳测试
    parser.add_argument('-m', '--mask', action='store_true', help='发送地址掩码请求') # 添加可选参数:地址掩码测试
    parser.add_argument('-a', '--all', action='store_true', help='发送所有类型的请求') # 添加可选参数:全部测试

    args = parser.parse_args()                           # 解析命令行参数

    try:                                                 # 验证IP地址格式
        socket.inet_aton(args.target)                    # 尝试将字符串转换为网络地址
        # inet_aton如果IP地址格式无效会抛出异常
    except socket.error:                                 # 如果IP地址格式无效
        print(f"[-] 无效的IP地址: {args.target}")        # 显示错误信息
        sys.exit(1)                                      # 退出程序,返回错误代码1

    print("=" * 60)                                      # 打印分隔线(60个等号)
    print("CVE-1999-0524 ICMP信息泄露漏洞测试工具")       # 打印程序标题
    print("=" * 60)                                      # 打印分隔线
    print(f"目标: {args.target}")                        # 显示目标IP地址
    print(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") # 显示当前时间
    print("=" * 60)                                      # 打印分隔线

    try:                                                 # 检查是否有管理员权限
        test_sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) # 尝试创建原始套接字
        test_sock.close()                                # 立即关闭测试套接字
    except PermissionError:                              # 如果权限不足
        print("[-] 错误:此工具需要管理员权限运行")        # 显示权限错误信息
        print("[-] 请以管理员身份运行此脚本")              # 显示解决方案
        sys.exit(1)                                      # 退出程序

    success_count = 0                                    # 初始化成功计数器

    if args.all or args.timestamp:                       # 如果用户选择了全部测试或时间戳测试
        if send_timestamp_request(args.target):          # 发送时间戳请求
            success_count += 1                           # 如果成功,计数器加1

    if args.all or args.mask:                            # 如果用户选择了全部测试或地址掩码测试
        if send_address_mask_request(args.target):       # 发送地址掩码请求
            success_count += 1                           # 如果成功,计数器加1

    if not (args.timestamp or args.mask or args.all):   # 如果用户没有指定任何测试类型
        print("[-] 请指定要执行的测试类型 (-t, -m, 或 -a)") # 显示使用提示
        parser.print_help()                              # 打印帮助信息
        sys.exit(1)                                      # 退出程序

    print("\n" + "=" * 60)                              # 打印结果分隔线
    print("测试完成")                                    # 显示测试完成信息
    print(f"成功的请求: {success_count}")                # 显示成功的请求数量

    if success_count > 0:                                # 如果有成功的请求
        print("\n[!] 警告:目标系统存在信息泄露漏洞")      # 显示安全警告
        print("[!] 建议:")                              # 显示修复建议标题
        print("    1. 配置防火墙阻止不必要的ICMP请求")    # 建议1
        print("    2. 禁用ICMP时间戳和地址掩码响应")      # 建议2
        print("    3. 实施网络分段和访问控制")            # 建议3
    else:                                                # 如果没有成功的请求
        print("\n[+] 目标系统未响应测试请求,可能已经修复或配置了防护措施") # 显示安全信息

    print("=" * 60)                                      # 打印结束分隔线

if __name__ == "__main__":                               # 如果脚本被直接运行(而不是被导入)
    main()                                               # 调用主函数

 

 

 

修复:

ICMP Timestamp 请求响应漏洞修复方案 (CVE-1999-0524)

Windows 系统修复方法:

  1. 通过防火墙禁用 ICMP Timestamp

    # 创建入站规则 (阻止 Timestamp 请求)
    New-NetFirewallRule -DisplayName "Block ICMP Timestamp Request" `
      -Direction Inbound `
      -Protocol ICMPv4 `
      -IcmpType 13 `
      -Action Block
    
    # 创建出站规则 (阻止 Timestamp 响应)
    New-NetFirewallRule -DisplayName "Block ICMP Timestamp Reply" `
      -Direction Outbound `
      -Protocol ICMPv4 `
      -IcmpType 14 `
      -Action Block
  2. 组策略配置 (域环境)

    • 打开 gpedit.msc

    • 导航:计算机配置 > Windows 设置 > 安全设置 > 高级安全 Windows 防火墙

    • 分别创建入站/出站规则,阻止 ICMP 类型 13 和 14

Linux 系统修复方法:

  1. 使用 iptables 禁用

    # 阻止入站 Timestamp 请求 (类型13)
    sudo iptables -A INPUT -p icmp --icmp-type timestamp-request -j DROP
    
    # 阻止出站 Timestamp 响应 (类型14)
    sudo iptables -A OUTPUT -p icmp --icmp-type timestamp-reply -j DROP
    
    # 永久保存规则 (根据发行版选择)
    sudo iptables-save | sudo tee /etc/iptables/rules.v4
  2. 使用 firewalld (RHEL/CentOS/Fedora)

    # 添加富规则阻止 ICMP 类型
    sudo firewall-cmd --permanent \
      --add-rich-rule='rule icmp-type name="timestamp-request" drop'
    sudo firewall-cmd --permanent \
      --add-rich-rule='rule icmp-type name="timestamp-reply" drop'
    sudo firewall-cmd --reload
  3. 内核参数调整 (可选)

    # 禁用 ICMP 时间戳响应
    echo 1 | sudo tee /proc/sys/net/ipv4/icmp_echo_ignore_all
    # 永久生效
    echo "net.ipv4.icmp_echo_ignore_all = 1" | sudo tee -a /etc/sysctl.conf
    sudo sysctl -p

网络设备层修复:

  1. 在边界防火墙/路由器添加规则:

    # Cisco 示例
    access-list 101 deny icmp any any timestamp-request
    access-list 101 deny icmp any any timestamp-reply
  2. 云平台安全组配置:

    • AWS/Azure/GCP 安全组中拒绝 ICMP 类型 13 和 14

验证方法:

# Windows 验证
使用上面的py脚本

# Linux 验证 (需安装 nmap)
nmap -PP -sP TARGET_IP
# 应看不到时间戳响应

注意:该漏洞威胁值较低(2.1分),修复时应评估业务影响。建议优先在网络边界拦截,避免影响内部合法使用。现代操作系统已默认加强防护,此漏洞主要影响遗留系统。

 

posted on 2025-06-25 21:58  GKLBB  阅读(960)  评论(0)    收藏  举报