IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    DeepSeek数据库暴露?扫描一下,应该不止此一家吧!

    smallnest发表于 2025-01-31 04:04:53
    love 0

    DeepSeek出街老火了,整个AI界都在热火朝天的讨论它。

    同时,安全界也没闲着,来自美国的攻击使它不得不通知中国大陆以外的手机号的注册,同时大家也对它的网站和服务安全性进行了审视,这不Wiz Research就发现它们的数据库面向公网暴露并且无需任何身份即可访问。这两个域名oauth2callback.deepseek.com:9000和dev.deepseek.com:9000。

    AI的核心技术既需要这些清北的天才去研究,产品也需要专业的人才去打磨。像DeepSeek这么专业的公司都可能出现这样的漏洞,相信互联网上这么数据库无密码暴露的实例也应该不在少数(实际只找到了2个)。

    基于上一篇《扫描全国的公网IP要多久》,我们改造一下代码,让它使用 tcp_syn 的方式探测clickhopuse的9000端口。

    首先声明,所有的技术都是为了给大家介绍使用Go语言开发底层的网络程序所做的演示,不是为了介绍安全和攻击方面的内容,所以也不会使用已经成熟的端口和IP扫描工具如zmap、rustscan、nmap、masscan、Advanced IP Scanner、Angry IP Scanner、unicornscan等工具。

    同时,也不会追求快速,我仅仅在家中的100M的网络中,使用一台10多年前的4核Linux机器进行测试,尽可能让它能出结果。我一般晚上启动它,早上吃过早餐后来查看结果。

    我想把这个实验分成两部分:

    1. 寻找中国大陆暴露9000端口的公网IP
    2. 检查这些IP是否是部署clickhouse并可以无密码的访问

    接下来先介绍第一部分。

    寻找暴露端口9000的IP

    我们需要将上一篇的代码改造,让它使用TCP进行扫描,而不是ICMP扫描,而且我们只扫描9000端口。

    为了更有效的扫描,我做了以下的优化:

    1. 使用ICMP扫描出来的可用IP, 一共五千多万
    2. 使用tcp sync模拟TCP建联是的握手,这样目的服务器会回一个sync+ack的包
    3. 同时探测机自动回复一个RST, 我们也别老挂着目的服务器,怪不好意思的,及时告诉人家别等着咱了

    同样的,我们也定义一个TCPScanner结构体,用来使用TCP握手来进行探测。如果你已经阅读了前一篇文章,应该对我们实现的套路有所了解。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    package fishfinding
    import (
    "net"
    "os"
    "time"
    "github.com/kataras/golog"
    "golang.org/x/net/bpf"
    "golang.org/x/net/ipv4"
    )
    type TCPScanner struct {
    src net.IP
    srcPort int
    dstPort int
    input chan string
    output chan string
    }
    func NewTCPScanner(srcPort, dstPort int, input chan string, output chan string) *TCPScanner {
    localIP := GetLocalIP()
    s := &TCPScanner{
    input: input,
    output: output,
    src: net.ParseIP(localIP).To4(),
    srcPort: srcPort,
    dstPort: dstPort,
    }
    return s
    }
    func (s *TCPScanner) Scan() {
    go s.recv()
    go s.send(s.input)
    }

    这里定义了一个TCPScanner结构体,它有一个Scan方法,用来启动接收和发送两个goroutine。接收goroutine用来接收目标服务器的回复(sync+ack 包),发送goroutine用来发送TCP sync包。

    发送逻辑

    发送goroutine首先通过net.ListenPacket创建一个原始套接字,这里使用的是ip4:tcp,然后发送TCP的包就可以了。

    我并没有使用gopacket这个库来构造TCP包,而是自己构造了TCP包,因为我觉得gopacket这个库太重了,而且我只需要构造TCP包,所以自己构造一个TCP包也不是很难。

    seq数我们使用了当前进程的PID,这样在接收到回包的时候,还可以使用这个seq数来判断是不是我们发送的回包。

    注意这里我们要计算tcp包的checksum, 并没有利用网卡的TCP/IP Checksum Offload功能,而是自己计算checksum,原因在于我的机的网卡很古老了,没有这个功能。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    func (s *TCPScanner) send(input chan string) error {
    defer func() {
    time.Sleep(5 * time.Second)
    close(s.output)
    golog.Infof("send goroutine exit")
    }()
    // 创建原始套接字
    conn, err := net.ListenPacket("ip4:tcp", s.src.To4().String())
    if err != nil {
    golog.Fatal(err)
    }
    defer conn.Close()
    pconn := ipv4.NewPacketConn(conn)
    // 不接收数据
    filter := createEmptyFilter()
    if assembled, err := bpf.Assemble(filter); err == nil {
    pconn.SetBPF(assembled)
    }
    seq := uint32(os.Getpid())
    for ip := range input {
    dstIP := net.ParseIP(ip)
    if dstIP == nil {
    golog.Errorf("failed to resolve IP address %s", ip)
    continue
    }
    // 构造 TCP SYN 包
    tcpHeader := &TCPHeader{
    Source: uint16(s.srcPort), // 源端口
    Destination: uint16(s.dstPort), // 目标端口(这里探测80端口)
    SeqNum: seq,
    AckNum: 0,
    Flags: 0x002, // SYN
    Window: 65535,
    Checksum: 0,
    Urgent: 0,
    }
    // 计算校验和
    tcpHeader.Checksum = tcpChecksum(tcpHeader, s.src, dstIP)
    // 序列化 TCP 头
    packet := tcpHeader.Marshal()
    // 发送 TCP SYN 包
    _, err = conn.WriteTo(packet, &net.IPAddr{IP: dstIP})
    if err != nil {
    golog.Errorf("failed to send TCP packet: %v", err)
    }
    }
    return nil
    }

    接收逻辑

    接收goroutine首先创建一个原始套接字,使用net.ListenIP,然后使用ipv4.NewPacketConn来创建一个ipv4.PacketConn,并设置ipv4.FlagSrc|ipv4.FlagDst|ipv4.FlagInterface,这样可以获取到源IP、目标IP和接口信息。
    这里必须设置ipv4.FlagSrc|ipv4.FlagDst|ipv4.FlagInterface, 否则不能获取到目标服务器的IP。pv4.FlagDst到是不需要的。

    接收到数据后,我们解析TCP头,然后判断是否是我们发送的包,如果是我们发送的包,我们就将目标IP发送到output通道。

    如果是我们发送的回包,我们就判断是否是SYN+ACK包,同时判断ACK是否和我们发送的seq对应,如果是,我们就将目标IP发送到output通道。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    func (s *TCPScanner) recv() error {
    defer recover()
    // 创建原始套接字
    conn, err := net.ListenIP("ip4:tcp", &net.IPAddr{IP: s.src})
    if err != nil {
    golog.Fatal(err)
    }
    defer conn.Close()
    pconn := ipv4.NewPacketConn(conn)
    if err := pconn.SetControlMessage(ipv4.FlagSrc|ipv4.FlagDst|ipv4.FlagInterface, true); err != nil {
    golog.Fatalf("set control message error: %v\n", err)
    }
    seq := uint32(os.Getpid()) + 1
    buf := make([]byte, 1024)
    for {
    n, peer, err := conn.ReadFrom(buf)
    if err != nil {
    golog.Errorf("failed to read: %v", err)
    continue
    }
    if n < tcpHeaderLength {
    continue
    }
    // 解析 TCP 头
    tcpHeader := ParseTCPHeader(buf[:n])
    if tcpHeader.Destination != uint16(s.srcPort) || tcpHeader.Source != uint16(s.dstPort) {
    continue
    }
    // golog.Info("peer: %s, flags: %d", peer.String(), tcpHeader.Flags)
    // 检查是否是 SYN+ACK, 同时检查ACK是否和发送的seq对应
    if tcpHeader.Flags == 0x012 && tcpHeader.AckNum == seq { // SYN + ACK
    s.output <- peer.String()
    }
    }
    }

    完整的代码在这里。

    最终我把可以连接端口9000的IP保存到了一个文件中,一共有970+个IP。

    检查没有身份验证clickhouse

    接下来我们要检查这些IP是否是clickhouse的服务,而且没有身份验证。

    使用类似的方法,我们定义一个ClickHouseChecker结构体,用来检查这些IP是否是clickhouse的服务。

    它会尝试使用这些IP和9000建立和clickhouse的连接,如果连接成功,并且调用Ping()方法成功,我们就认为这个IP是clickhouse的服务。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    package fishfinding
    import (
    "context"
    "fmt"
    "runtime"
    "sync"
    "time"
    "github.com/ClickHouse/clickhouse-go/v2"
    _ "github.com/ClickHouse/clickhouse-go/v2"
    "github.com/kataras/golog"
    )
    type ClickHouseChecker struct {
    wg *sync.WaitGroup
    port int
    input chan string
    output chan string
    }
    func NewClickHouseChecker(port int, input chan string, output chan string, wg *sync.WaitGroup) *ClickHouseChecker {
    s := &ClickHouseChecker{
    port: port,
    input: input,
    output: output,
    wg: wg,
    }
    return s
    }
    func (s *ClickHouseChecker) Check() {
    parallel := runtime.NumCPU()
    for i := 0; i < parallel; i++ {
    s.wg.Add(1)
    go s.check()
    }
    }
    func (s *ClickHouseChecker) check() {
    defer s.wg.Done()
    for ip := range s.input {
    if ip == "splitting" || ip == "failed" {
    continue
    }
    if isClickHouse(ip, s.port) {
    s.output <- ip
    }
    }
    }
    func isClickHouse(ip string, port int) bool {
    conn, err := clickhouse.Open(&clickhouse.Options{
    Addr: []string{fmt.Sprintf("%s:%d", ip, port)},
    // Auth: clickhouse.Auth{
    // Database: "default",
    // Username: "default",
    // Password: "",
    // },
    Settings: clickhouse.Settings{
    "max_execution_time": 1,
    },
    DialTimeout: time.Second,
    MaxOpenConns: 1,
    MaxIdleConns: 1,
    ConnMaxLifetime: time.Duration(1) * time.Minute,
    ConnOpenStrategy: clickhouse.ConnOpenInOrder,
    BlockBufferSize: 10,
    MaxCompressionBuffer: 1024,
    })
    if err != nil {
    golog.Errorf("open %s:%d failed: %v", ip, port, err)
    return false
    }
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    err = conn.Ping(ctx)
    if err != nil {
    golog.Warnf("ping %s:%d failed: %v", ip, port, err)
    return false
    }
    return true
    }

    实际扫描下来,几乎所有的IP的9000端口都连接超时或者不是clickhouse服务,只有4个IP是clickhouse服务,但是需要身份验证。,报错default: Authentication failed: password is incorrect, or there is no user with such name.

    挺好的一件事情,至少公网暴露的clickhouse服务都是需要身份验证的。

    当然也有可能是clickhouse的服务端配置了IP白名单,只允许内网访问,这样的话我们就无法访问了。也可能是clickhouse的端口改成了其他端口,我们无法访问。

    有必要扫描一下全网的IP和它们的9000端口了

    使用既有的程序即可。我们先拉取全网的网段信息。

    1
    wget -c -O- http://ftp.apnic.net/stats/apnic/delegated-apnic-latest | awk -F '|' '/ipv4/ {print $4 "/" 32-log($5)/log(2)}' | cat > ipv4.txt

    先用icmp_scan扫描一下公网课访问的IP地址:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ......
    [INFO] 2025/01/31 03:56 223.255.250.221 is alive
    [INFO] 2025/01/31 03:56 223.255.233.1 is alive
    [INFO] 2025/01/31 03:56 223.255.240.91 is alive
    [INFO] 2025/01/31 03:56 223.255.233.10 is alive
    [INFO] 2025/01/31 03:56 223.255.233.15 is alive
    [INFO] 2025/01/31 03:56 223.255.233.11 is alive
    [INFO] 2025/01/31 03:56 223.255.233.115 is alive
    [INFO] 2025/01/31 03:56 223.255.233.100 is alive
    [INFO] 2025/01/31 03:56 send goroutine exit
    [INFO] 2025/01/31 03:56 total: 884686592, alive: 15500888, time: 2h35m28.930123788s

    一共8亿多个IP,可以ping的通的有1500多万个,耗时2小时扫描完。

    根据网友在上一篇的留言反馈,光美国就有8亿多个IP。
    我问deepseek,全球有37亿个IP,美国有9亿个,这个数量才合理,我自己扫描的8亿要远远少于这个数量。而且活跃的IP我感觉应该远远大于1500多万。
    但是这些不重要了,我要做的就是能扫描到可以免密登录的clickhouse服务,看看这些IP里面有没有。

    接下来我们使用tcp_scan扫描这些IP的9000端口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ......
    [INFO] 2025/01/31 08:47 223.197.222.126 is alive
    [INFO] 2025/01/31 08:47 223.197.219.60 is alive
    [INFO] 2025/01/31 08:47 223.220.171.218 is alive
    [INFO] 2025/01/31 08:47 223.221.238.176 is alive
    [INFO] 2025/01/31 08:47 223.197.235.26 is alive
    [INFO] 2025/01/31 08:47 223.197.225.240 is alive
    [INFO] 2025/01/31 08:47 223.197.225.208 is alive
    [INFO] 2025/01/31 08:47 223.197.219.139 is alive
    [INFO] 2025/01/31 08:47 send goroutine exit
    [INFO] 2025/01/31 08:47 total: 15500890, alive: 3953, time: 2m41.23585658s

    在这1500多万个IP中,有3953个IP的9000端口是可以访问的,但是都需要验证能不能进行clickhouse的操作,我们需要进一步检查。

    接下来我们使用clickhouse_check检查这些IP是否是clickhouse服务:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ......
    [WARN] 2025/01/31 11:47 ping 223.197.222.126:9000 failed: read: read tcp 192.168.1.5:53494->223.197.222.126:9000: i/o timeout
    [WARN] 2025/01/31 11:47 ping 223.197.219.60:9000 failed: read: read tcp 192.168.1.5:49718->223.197.219.60:9000: i/o timeout
    [WARN] 2025/01/31 11:47 ping 223.221.238.176:9000 failed: read: read tcp 192.168.1.5:56662->223.221.238.176:9000: i/o timeout
    [WARN] 2025/01/31 11:47 ping 223.197.235.26:9000 failed: read: read tcp 192.168.1.5:47676->223.197.235.26:9000: i/o timeout
    [WARN] 2025/01/31 11:47 ping send:9000 failed: dial tcp: lookup send on 127.0.0.53:53: server misbehaving
    [WARN] 2025/01/31 11:47 ping total::9000 failed: dial tcp: address total::9000: too many colons in address
    [WARN] 2025/01/31 11:47 ping 223.197.225.240:9000 failed: read: read tcp 192.168.1.5:55342->223.197.225.240:9000: i/o timeout
    [WARN] 2025/01/31 11:47 ping 223.197.225.208:9000 failed: read: read tcp 192.168.1.5:43300->223.197.225.208:9000: i/o timeout
    [WARN] 2025/01/31 11:47 ping 223.197.219.139:9000 failed: read: read tcp 192.168.1.5:57552->223.197.219.139:9000: i/o timeout
    [INFO] 2025/01/31 11:47 total: 2, time: 4m20.744235925s

    4分钟完成。最终还是真的发现有两个IP的9000端口是clickhouse服务,而且不需要密码验证。

    类似的我们还可以验证Redis、Mysql等服务的安全性。



沪ICP备19023445号-2号
友情链接