Maison > développement back-end > Golang > Attaque et défense SSRF en Go

Attaque et défense SSRF en Go

Libérer: 2023-07-24 14:05:16
avant
876 Les gens l'ont consulté
Qu'est-ce que SSRF

SSRF s'écrit en anglais par Server Side Request Forgery, qui se traduit par falsification de requête côté serveur. Lorsque l'attaquant ne parvient pas à obtenir les autorisations du serveur, il utilise la vulnérabilité du serveur pour envoyer une requête construite en tant que serveur à l'intranet où se trouve le serveur. Concernant le contrôle d’accès aux ressources intranet, chacun doit en être conscient.

Attaque et défense SSRF en Go

Si la déclaration ci-dessus n'est pas facile à comprendre, alors Lao Xu donnera simplement un exemple pratique. De nombreuses plates-formes d'écriture prennent désormais en charge le téléchargement d'images via des URL. Si le serveur ne vérifie pas strictement les URL, des attaquants malveillants peuvent accéder aux ressources intranet.

"Une digue de mille kilomètres s'effondrera dans un nid de fourmis". Nous, les programmeurs, ne devons ignorer aucune faille pouvant entraîner des risques, et de telles failles sont susceptibles de devenir un tremplin pour la performance des autres. Afin de ne pas devenir un tremplin, Lao Xu examinera les tours offensifs et défensifs de SSRF avec tous les lecteurs.

Round 1 : Adresses intranet en constante évolution

Pourquoi utiliser le mot « en constante évolution » ? Lao Xu ne répondra pas pour l'instant, mais lecteurs, veuillez continuer à lire patiemment. Ci-dessous, Lao Xu utilise 182.61.200.7(www.baidu.com的一个IP地址)这个IP和各位读者一起复习一下IPv4的不同表示方式。

Attaque et défense SSRF en Go

注意⚠️:点分混合制中,以点分割地每一部分均可以写作不同的进制(仅限于十、八和十六进制)。

上面仅是IPv4的不同表现方式,IPv6的地址也有三种不同表示方式。而这三种表现方式又可以有不同的写法。下面以IPv6中的回环地址0:0:0:0:0:0:0:1 comme exemple.

Attaque et défense SSRF en Go

Remarque⚠️ : Le premier 0 de chaque formulaire. De la même manière que la représentation compressée 0 bit et la représentation d'adresse IPv4 intégrée, une adresse IPv6 peut également être écrite dans différentes représentations.

Après avoir tant parlé, Lao Xu ne peut plus compter le nombre de façons différentes d'écrire une adresse IP. Veuillez faire quelques calculs si vous êtes bon en mathématiques.

IP interne, vous pensez que c'est par ici ? Bien sûr que non! Je ne sais pas si des lecteurs ont entendu parler de l'accès aux ressources xip.io这个域名。xip可以帮你做自定义的DNS解析,并且可以解析到任意IP地址(包括内网)。

Attaque et défense SSRF en Go

我们通过xip提供的域名解析,还可以将内网IP通过域名的方式进行访问。

关于内网IP的访问到这儿仍将继续!搞过Basic验证的应该都知道,可以通过http://user:passwd@hostname/. Si l’attaquant change sa façon d’écrire, il pourra peut-être contourner certaines des logiques les moins rigoureuses, comme indiqué ci-dessous.

Attaque et défense SSRF en Go

Concernant l'adresse intranet, Lao Xu a épuisé toutes ses réserves de connaissances pour résumer le contenu ci-dessus, alors Lao Xu a dit que l'adresse intranet en constante évolution n'est pas de trop !

Pour le moment, Lao Xu veut simplement demander, lorsque des attaquants malveillants utilisent ces différentes formes d'adresses intranet pour télécharger des images, comment les identifier et refuser l'accès ? Personne ne pourra vraiment utiliser des expressions régulières pour effectuer le filtrage ci-dessus. Si tel est le cas, laissez-moi un message et dites-le-moi afin que je puisse en tirer des leçons.

Nous avons essentiellement compris les différentes adresses intranet, la question est donc maintenant de savoir comment la convertir en une IP que nous pouvons juger. Pour résumer, les adresses intranet ci-dessus peuvent être divisées en trois catégories : 1. Il s'agit d'une adresse IP elle-même, mais son expression n'est pas uniforme ; 2. Un nom de domaine qui pointe vers l'adresse IP de l'intranet ; 3. Une adresse qui contient une vérification de base ; informations et l’adresse IP de l’intranet. En fonction de ces trois types de caractéristiques, vous pouvez identifier l'adresse intranet et refuser l'accès en suivant les étapes suivantes avant de lancer une demande.

  1. Analysez le nom d'hôte dans l'adresse.

  2. Initiez la résolution DNS et obtenez l'IP.

  3. Déterminez si l'IP est une adresse intranet.

Lorsque vous évaluez l'adresse intranet dans les étapes ci-dessus, veuillez ne pas ignorer l'adresse de bouclage IPv6 et l'adresse locale unique IPv6. Voici la logique utilisée par Lao Xu pour déterminer si l'adresse IP est une adresse IP intranet.

// IsLocalIP 判断是否是内网ip
func IsLocalIP(ip net.IP) bool {
if ip == nil {
return false
	}
// 判断是否是回环地址, ipv4时是127.0.0.1;ipv6时是::1
if ip.IsLoopback() {
return true
	}
// 判断ipv4是否是内网
if ip4 := ip.To4(); ip4 != nil {
return ip4[0] == 10 || // 10.0.0.0/8
			(ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31) || // 172.16.0.0/12
			(ip4[0] == 192 && ip4[1] == 168) // 192.168.0.0/16
	}
// 判断ipv6是否是内网
if ip16 := ip.To16(); ip16 != nil {
// 参考 https://tools.ietf.org/html/rfc4193#section-3
// 参考 https://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses
// 判断ipv6唯一本地地址
return 0xfd == ip16[0]
	}
// 不是ip直接返回false
return false
}
Copier après la connexion

L'image ci-dessous montre le résultat des étapes ci-dessus pour détecter si la demande est une demande intranet.

Attaque et défense SSRF en Go

Résumé : les URL se présentent sous différentes formes et vous pouvez utiliser la résolution DNS pour obtenir l'adresse IP standardisée afin de déterminer s'il s'agit d'une ressource intranet.

Round 2 : Saut d'URL

Si les attaquants malveillants attaquent uniquement via différentes manières d'écrire l'IP, alors nous pouvons naturellement nous asseoir et nous détendre, mais cette bataille entre la lance et le bouclier ne fait que commencer.

Passons en revue la stratégie de défense du premier tour. Détecter si la demande est une ressource intranet avant que la demande ne soit officiellement lancée. Si l'attaquant accède à la ressource intranet via un saut d'URL pendant le processus de demande, il peut complètement contourner le premier tour. stratégie. Le processus d’attaque spécifique est le suivant.

Attaque et défense SSRF en Go

Comme le montre la figure, un attaquant peut obtenir des ressources intranet via un saut d'URL. Avant de présenter comment se défendre contre les attaques par saut d'URL, Lao Xu et les lecteurs examineront d'abord le code d'état de redirection HTTP-3xx.

Selon Wikipédia, il existe 9 codes de redirection 3xx allant de 300 à 308. Lao Xu a jeté un regard particulier sur le code source de go et a découvert que le http.ClientLa requête émise ne prend en charge que les valeurs suivantes : Code d'orientation. http.Client发出的请求仅支持如下几个重定向码。

301

301  : La ressource demandée a été définitivement déplacée vers un nouvel emplacement ; la réponse peut être mise en cache ; la requête de redirection doit être une requête GET.

302:要求客户端执行临时重定向;只有在Cache-Control或Expires中进行指定的情况下,这个响应才是可缓存的;重定向请求一定是GET请求。

303:当POST(或PUT / DELETE)请求的响应在另一个URI能被找到时可用此code,这个code存在主要是为了允许由脚本激活的POST请求输出重定向到一个新的资源;303响应禁止被缓存;重定向请求一定是GET请求。

307:临时重定向;不可更改请求方法,如果原请求是POST,则重定向请求也是POST。

308 : Redirection permanente ; la méthode de requête ne peut pas être modifiée. Si la requête d'origine est POST, la requête redirigée est également POST.

C'est tout pour la révision du code de statut 3xx, poursuivons la discussion sur les tours offensifs et défensifs de SSRF. Étant donné que le saut d'URL côté serveur peut comporter des risques, nous pouvons complètement éviter ces risques tant que nous désactivons le saut d'URL. Cependant, nous ne pouvons pas faire cela. Même si cette approche évite les risques, elle est également très susceptible d'endommager accidentellement les requêtes normales. Alors comment prévenir de telles attaques ?

看过老许“Go中的HTTP请求之——HTTP1.1请求流程分析”这篇文章的读者应该知道,对于重定向有业务需求时,可以自定义http.Client的CheckRedirect。下面我们先看一下CheckRedirect的定义。

CheckRedirect func(req *Request, via []*Request) error
Copier après la connexion

这里特别说明一下,req是即将发出的请求且请求中包含前一次请求的响应,via是已经发出的请求。在知晓这些条件后,防御URL跳转攻击就变得十分容易了。

  1. 根据前一次请求的响应直接拒绝307308的跳转(此类跳转可以是POST请求,风险极高)。

  2. 解析出请求的IP,并判断是否是内网IP。

根据上述步骤,可如下定义http.Client

client := &http.Client{
	CheckRedirect: func(req *http.Request, via []*http.Request) error {
// 跳转超过10次,也拒绝继续跳转
if len(via) >= 10 {
return fmt.Errorf("redirect too much")
		}
		statusCode := req.Response.StatusCode
if statusCode == 307 || statusCode == 308 {
// 拒绝跳转访问
return fmt.Errorf("unsupport redirect method")
		}
// 判断ip
		ips, err := net.LookupIP(req.URL.Host)
if err != nil {
return err
		}
for _, ip := range ips {
if IsLocalIP(ip) {
return fmt.Errorf("have local ip")
			}
			fmt.Printf("%s -> %s is localip?: %v\n", req.URL, ip.String(), IsLocalIP(ip))
		}
return nil
	},
}
Copier après la connexion

如上自定义CheckRedirect可以防范URL跳转攻击,但此方式会进行多次DNS解析,效率不佳。后文会结合其他攻击方式介绍更加有效率的防御措施。

Résumé : les attaques par saut d'URL peuvent être évitées grâce à la personnalisationhttp.ClientCheckRedirect.

Round 3 : DNS Rebinding

Comme nous le savons tous, pour lancer une requête HTTP, vous devez d'abord solliciter le service DNS pour obtenir l'adresse IP correspondant au nom de domaine. Si l'attaquant dispose d'un service DNS contrôlable, il peut contourner la stratégie de défense précédente via la reconnexion DNS et l'attaque.

Le processus spécifique est tel qu'indiqué dans l'image ci-dessous.

Attaque et défense SSRF en Go

Lors de la vérification si la ressource est légale, le serveur a effectué la première résolution DNS et a obtenu une IP non intranet avec un TTL de 0. Jugez l’adresse IP résolue et constatez que l’adresse IP non intranet peut être utilisée pour les demandes ultérieures. Étant donné que le serveur DNS de l'attaquant définit le TTL sur 0, la résolution DNS doit être effectuée à nouveau lorsque la demande est formellement lancée. À ce stade, le serveur DNS renvoie l'adresse intranet. Puisqu'il est entré dans la phase de demande de ressources et qu'il n'existe aucune mesure défensive, l'attaquant peut obtenir les ressources intranet.

En guise de mention supplémentaire, Lao Xu a spécifiquement examiné une partie du code source de la résolution DNS dans Go et a constaté que Go ne met pas en cache les résultats DNS, donc même si le TTL n'est pas 0, il existe un risque de rebinding DNS. .

在发起请求的过程中有DNS解析才让攻击者有机可乘。如果我们能对该过程进行控制,就可以避免DNS重绑定的风险。对HTTP请求控制可以通过自定义http.Transport来实现,而自定义http.Transport也有两个方案。

方案一

dialer := &net.Dialer{}
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
	host, port, err := net.SplitHostPort(addr)
// 解析host和 端口
if err != nil {
return nil, err
	}
// dns解析域名
	ips, err := net.LookupIP(host)
if err != nil {
return nil, err
	}
// 对所有的ip串行发起请求
for _, ip := range ips {
		fmt.Printf("%v -> %v is localip?: %v\n", addr, ip.String(), IsLocalIP(ip))
if IsLocalIP(ip) {
continue
		}
// 非内网IP可继续访问
// 拼接地址
		addr := net.JoinHostPort(ip.String(), port)
// 此时的addr仅包含IP和端口信息
		con, err := dialer.DialContext(ctx, network, addr)
if err == nil {
return con, nil
		}
		fmt.Println(err)
	}

return nil, fmt.Errorf("connect failed")
}
// 使用此client请求,可避免DNS重绑定风险
client := &http.Client{
	Transport: transport,
}
Copier après la connexion

transport.DialContext的作用是创建未加密的TCP连接,我们通过自定义此函数可规避DNS重绑定风险。另外特别说明一下,如果传递给dialer.DialContext方法的地址是常规IP格式则可使用net包中的parseIPZone函数直接解析成功,否则会继续发起DNS解析请求。

方案二

dialer := &net.Dialer{}
dialer.Control = func(network, address string, c syscall.RawConn) error {
// address 已经是ip:port的格式
	host, _, err := net.SplitHostPort(address)
if err != nil {
return err
	}
	fmt.Printf("%v is localip?: %v\n", address, IsLocalIP(net.ParseIP(host)))
return nil
}
transport := http.DefaultTransport.(*http.Transport).Clone()
// 使用官方库的实现创建TCP连接
transport.DialContext = dialer.DialContext
// 使用此client请求,可避免DNS重绑定风险
client := &http.Client{
	Transport: transport,
}
Copier après la connexion

dialer.Control在创建网络连接之后实际拨号之前调用,且仅在go版本大于等于1.11时可用,其具体调用位置在sock_posix.go中的(*netFD).dial方法里。

Attaque et défense SSRF en Go

Les deux solutions de défense ci-dessus peuvent non seulement empêcher les attaques de rebinding DNS, mais également empêcher d'autres méthodes d'attaque. En fait, Lao Xu a même recommandé la deuxième option, qui est une solution unique !

Résumé :

  1. Un attaquant peut mener une attaque de rebinding DNS via son propre service DNS.

  2. Les attaques de rereliure DNS peuvent être évitées grâce à la personnalisationhttp.Transport.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:Go语言进阶学习
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal