检查IP地址是否在私有网络空间中

15

我有一个Go程序,它接收来自客户端的URL并使用net/http包获取它们。在进行进一步处理之前,我想检查URL是否映射到私有(不可路由/RFC1918网络)地址空间。

最直接的方法是执行显式DNS请求并检查已知的私有范围的地址。之后,为URL执行HTTP GET请求。

有更好的方法来完成这个任务吗?最好与http.Client集成,以便作为GET请求的一部分执行。


“私有网络空间”是什么?RFC1918网络? - Martin Tournoij
1
我遇到了完全相同的问题,并发现:没有更好的替代方案。请参见 https://github.com/mhausenblas/clump/blob/master/main.go#L83 - Michael Hausenblas
抱歉,是的,RFC1918网络。我更新了问题以说明这一点。 - StefanOS
3
你想做什么?你只是想记录主机是否在私有网络中,还是在发出请求之前还需要做其他事情?@MichaelHausenblas:FYI net.IPNet拥有Contains方法 - JimB
对于一个使用案例,我想要中止针对RFC1918网络的GET请求。对于另一个案例,我只想记录IP地址以及它是私有的还是公共的。 - StefanOS
4个回答

30

你可能还想包含对环回地址(IPv4或IPv6)和/或IPv6链路本地地址或唯一本地地址的检查。这是一个例子,其中包含RFC1918地址列表以及针对它们的简单检查,使用isPrivateIP(ip net.IP)函数:

var privateIPBlocks []*net.IPNet

func init() {
    for _, cidr := range []string{
        "127.0.0.0/8",    // IPv4 loopback
        "10.0.0.0/8",     // RFC1918
        "172.16.0.0/12",  // RFC1918
        "192.168.0.0/16", // RFC1918
        "169.254.0.0/16", // RFC3927 link-local
        "::1/128",        // IPv6 loopback
        "fe80::/10",      // IPv6 link-local
        "fc00::/7",       // IPv6 unique local addr
    } {
        _, block, err := net.ParseCIDR(cidr)
        if err != nil {
            panic(fmt.Errorf("parse error on %q: %v", cidr, err))
        }
        privateIPBlocks = append(privateIPBlocks, block)
    }
}

func isPrivateIP(ip net.IP) bool {
    if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
        return true
    }

    for _, block := range privateIPBlocks {
        if block.Contains(ip) {
            return true
        }
    }
    return false
  }

2
还有 fc00::/7 - dtoux
缺失的IPv4 linkLocal: "169.254.0.0/16" // RFC3927 - https://en.wikipedia.org/wiki/Link-local_address或者额外加上:ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() - Dougnukem
更完整的列表:https://github.com/letsencrypt/boulder/blob/master/bdns/dns.go 并以不同的方式进行了改编:https://github.com/wader/filtertransport/blob/master/filter.go - xuiqzy

10
这将更容易在Go 1.17(2021年Q4,5年后)中实现,正如Go 101报道所述:
请参见commit c73fcccCL 272668

net: 添加IP.IsPrivate()

添加IsPrivate()辅助函数以根据RFC 1918和RFC 4193检查IP是否为私有

这解决了golang问题29146,由Aaran McGuire提出:

net包似乎有很多帮助程序来报告IP是什么。例如:

  • IsLoopback()
  • IsMulticast()
  • IsInterfaceLocalMulticast()

但是没有帮助程序报告IP地址是否在私有范围内(RFC 1918RFC 4193)。


@StefanOS 感谢您的编辑。非常感激。 - VonC
2
顺便说一句,我预计在Go 1.17发布后将接受的答案更改为这个。 - StefanOS

4
package main

import (
    "fmt"
    "net"
)

func main() {
    fmt.Println(privateIPCheck("1.1.1.1"))  // False since this is not a private IP
    fmt.Println(privateIPCheck("10.8.0.1")) // True: Since this is a private ip.
}

// Check if a ip is private.
func privateIPCheck(ip string) bool {
    ipAddress := net.ParseIP(ip)
    return ipAddress.IsPrivate()
}

这需要使用 Go 1.17 版本。


2

看起来没有比我描述的更好的方法了。结合@MichaelHausenblas的代码和@JimB的建议,我的代码最终变成了这样。

func privateIP(ip string) (bool, error) {
    var err error
    private := false
    IP := net.ParseIP(ip)
    if IP == nil {
        err = errors.New("Invalid IP")
    } else {
        _, private24BitBlock, _ := net.ParseCIDR("10.0.0.0/8")
        _, private20BitBlock, _ := net.ParseCIDR("172.16.0.0/12")
        _, private16BitBlock, _ := net.ParseCIDR("192.168.0.0/16")
        private = private24BitBlock.Contains(IP) || private20BitBlock.Contains(IP) || private16BitBlock.Contains(IP)
    }
    return private, err
}

这里似乎缺少了些东西。你检查了IP,但我可以创建一个DNS,在第一次调用时响应公共IP,然后在第二次调用时响应127.0.0.1。这就是所谓的DNS重新绑定 https://en.wikipedia.org/wiki/DNS_rebinding你如何防范这种情况呢? - lyrixx
我同意这可能会成为一个问题,具体取决于您的使用情况,据我所知,使用http.Client无法防范这种情况。 - StefanOS
2
我找到了一个方法 :) 这篇文章讲得非常清楚:https://linkai.io/blog/securely-issuing-http-requests-from-the-cloud/ 我尝试了(并应用了)它,没有任何问题 :+1: - lyrixx
1
@StefanOS 总结来自 linkai.io 文章的观点,lyrixx 提到:在构建 http.Client 时,你可以限制它底层 TCP 和 TLS 机制本身,以便不向某些 IP 发出请求。这样,实际的请求甚至重定向是在执行时检查的,没有时间问题。 至少在禁止请求时,我猜你也可以更改已解析的 IP 地址的域,并向该已检查的 IP 发出请求,但我不确定这是否足够。用 http.Client 的另一种方法对我来说更加清晰和易于理解。 - xuiqzy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接