目次
ラウンド 1: 常に変化するイントラネット アドレス
ラウンド 2: URL ジャンプ
ラウンド 3: DNS 再バインド
ホームページ バックエンド開発 Golang Go における SSRF の攻撃と防御

Go における SSRF の攻撃と防御

Jul 24, 2023 pm 02:05 PM
go ssrf

SSRF とは

SSRF は英語で Server Side Request Forgery と綴られ、サーバーサイドと翻訳されます。捏造を要求します。攻撃者はサーバー権限の取得に失敗すると、サーバーの脆弱性を利用して、サーバーとして構築されたリクエストをサーバーが配置されているイントラネットに送信します。イントラネット リソースのアクセス制御については、全員が認識する必要があります。

Go における SSRF の攻撃と防御

#上記の説明が理解しにくい場合は、老徐が実際的な例を挙げます。現在、多くの書き込みプラットフォームが URL を介した画像のアップロードをサポートしていますが、サーバーが URL を厳密に検証しない場合、悪意のある攻撃者がイントラネット リソースにアクセスできる可能性があります。

「アリの巣で千マイルの堤防が決壊する。」 私たちプログラマーはリスクを引き起こす可能性のある抜け穴を無視すべきではありませんし、そのような抜け穴は他の人のパフォーマンスの踏み台になる可能性があります。踏み台にならないよう、老徐はSSRFの攻防を読者の皆さんと一緒に見守っていきます。

ラウンド 1: 常に変化するイントラネット アドレス

なぜ「常に変化する」という言葉を使うのでしょうか?老徐は今のところ答えませんが、読者は辛抱強く読み続けてください。次に、Lao Xu は IP 182.61.200.7 (www.baidu.com の IP アドレス) を使用して、読者と一緒に IPv4 のさまざまな表現を確認します。

Go における SSRF の攻撃と防御

注意⚠️: ドット混合システムでは、ドットで区切られた各部分を異なる基数で記述することができます (10 進数、8 進数、および 16 進数に制限されます)。 。

上記は IPv4 の表現の違いだけであり、IPv6 アドレスにも 3 つの異なる表現方法があります。そして、これら 3 つの表現方法はさまざまな方法で書くことができます。以下では、例として IPv6 のループバック アドレス 0:0:0:0:0:0:0:1 を取り上げます。

Go における SSRF の攻撃と防御

注⚠️: 16 進表記の各 X の先頭の 0 は省略できるので、部分的に省略しても構いません。その部分は省略しないでください。 、それにより、IPv6 アドレスをさまざまな形式で書き込みます。 0 ビット圧縮表現および埋め込み IPv4 アドレス表現と同様に、IPv6 アドレスもさまざまな表現で記述することができます。

これだけ話した後、Lao Xu は IP を何通りに記述できるかを数えることができなくなりました。数学が得意な方は計算してみてください。

イントラネット IP ここで終わりだと思いますか?もちろん違います!読者の中で xip.io というドメイン名を聞いたことがある人がいるかどうかはわかりません。 xip は、カスタマイズされた DNS 解決を行うのに役立ち、任意の IP アドレス (イントラネットを含む) に解決できます。

Go における SSRF の攻撃と防御

xip が提供するドメイン名解決を使用しており、ドメイン名を通じてイントラネット IP にアクセスすることもできます。

イントラネット IP へのアクセスはここで継続されます。基本認証を行ったことがある人なら誰でも、http://user:passwd@hostname/ を通じてリソースにアクセスできることを知っているはずです。攻撃者が記述方法を変更すると、以下に示すように、厳密性の低いロジックの一部をバイパスできる可能性があります。

Go における SSRF の攻撃と防御

イントラネット アドレスに関しては、老徐は知識のすべてを使い果たして上記の内容を要約したため、刻々と変化するイントラネット アドレスについてはあまり多くを語ることはできません。

現時点では、Lao Xu が聞きたいのは、悪意のある攻撃者がこれらのさまざまな形式のイントラネット アドレスを使用して画像をアップロードした場合、どのようにそれらを識別してアクセスを拒否するのかということです。正規表現を使用して上記のフィルタリングを実行できる人は実際にはいないでしょう。その場合は、メッセージを残して私に教えてください。勉強させていただきます。

さまざまなイントラネット アドレスについては基本的に理解できたので、問題はそれを判断できる IP にどのように変換するかです。まとめると、上記のイントラネット アドレスは、1. IP アドレスそのものであるが表現が統一されていない、2. イントラネット IP を指すドメイン名、3. 基本検証を含むアドレスの 3 つのカテゴリに分類できます。情報とイントラネット IP。これら 3 種類の特性に基づいて、要求を開始する前に次の手順に従ってイントラネット アドレスを特定し、アクセスを拒否できます。

  1. アドレス内のホスト名を解析します。

  2. DNS 解決を開始し、IP を取得します。

  3. IP がイントラネット アドレスであるかどうかを確認します。

上記の手順では、イントラネット アドレスを判断する際に、IPv6 ループバック アドレスと IPv6 固有ローカル アドレスを無視しないでください。以下は、IP がイントラネット IP であるかどうかを判断するために Lao Xu が使用するロジックです。

// 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
}
ログイン後にコピー

下の図は、上記の手順に従ってリクエストがイントラネット リクエストであるかどうかを検出した結果を示しています。

Go における SSRF の攻撃と防御

概要: URL にはさまざまな形式があり、DNS 解決を使用して標準化された IP を取得し、それがイントラネット リソースであるかどうかを判断できます。

ラウンド 2: URL ジャンプ

悪意のある攻撃者が IP のさまざまな書き込み方法のみを介して攻撃する場合、私たちは自然に座ってリラックスすることができますが、この槍と盾の戦いはまだ始まったばかりです。

ラウンド 1 の防御戦略を確認しましょう。リクエストがイントラネット リソースであるかどうかの検出は、リクエストが正式に開始される前に行われます。攻撃者がリクエスト プロセス中に URL ジャンプを通じてイントラネット リソースにアクセスすると、ターン 1 での防御戦略。具体的な攻撃手順は以下の通りです。

Go における SSRF の攻撃と防御

図に示すように、攻撃者は URL ジャンプを通じてイントラネット リソースを取得できます。 URL ジャンプ攻撃に対する防御方法を紹介する前に、Lao Xu と読者はまず HTTP リダイレクト ステータス コード 3xx を確認します。

Wikipedia によると、300 から 308 までの 9 つの 3xx リダイレクト コードがあります。 Lao Xu は go のソース コードを特別に調べ、公式の http.Client リクエストが次のリダイレクト コードのみをサポートしていることを発見しました。

301: 要求されたリソースは新しい場所に永続的に移動されました。応答はキャッシュ可能です。リダイレクト要求は GET 要求である必要があります。

302: クライアントは一時的なリダイレクトを実行する必要があります。この応答は、Cache-Control または Expires で指定されている場合にのみキャッシュ可能です。リダイレクト要求は GET 要求である必要があります。

303: このコードは、POST (または PUT / DELETE) リクエストへの応答が別の URI で見つかる場合に使用できます。このコードは主に、スクリプトによってアクティブ化された POST リクエストを許可するために存在します。出力は新しいリソースにリダイレクトされます。303 応答のキャッシュは禁止されています。リダイレクトされるリクエストは GET リクエストである必要があります。

307: 一時的なリダイレクト。リクエスト メソッドは変更できません。元のリクエストが POST の場合、リダイレクトされたリクエストも POST になります。

308: 永続的なリダイレクト。リクエスト メソッドは変更できません。元のリクエストが POST の場合、リダイレクトされたリクエストも POST になります。

3xx ステータス コードのレビューはここで終了します。SSRF の攻撃ラウンドと防御ラウンドについての議論を続けましょう。サーバー側でのURLジャンプはリスクを伴う可能性があるため、URLジャンプを無効にすればリスクを完全に回避できます。このアプローチはリスクを回避しますが、通常のリクエストを誤って破損する可能性も非常に高くなります。では、そのような攻撃を防ぐにはどうすればよいでしょうか?

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

CheckRedirect func(req *Request, via []*Request) error
ログイン後にコピー

这里特别说明一下,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
	},
}
ログイン後にコピー

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

概要: http.ClientCheckRedirect をカスタマイズすることで、URL ジャンプ攻撃を防ぐことができます。

ラウンド 3: DNS 再バインド

ご存知のとおり、HTTP リクエストを開始するには、まず DNS サービスにリクエストして、ドメイン名に対応する IP アドレスを取得する必要があります。攻撃者が制御可能な DNS サービスを持っている場合、DNS の再バインドと攻撃を通じて以前の防御戦略を回避することができます。

具体的なプロセスを次の図に示します。

Go における SSRF の攻撃と防御

リソースが合法かどうかを確認する際、サーバーは最初の DNS 解決を実行し、TTL が 0 の非イントラネット IP を取得しました。解決された IP を判断し、イントラネット以外の IP が後続のリクエストに使用できることを確認します。攻撃者の DNS サーバーは TTL を 0 に設定するため、リクエストが正式に開始されたときに DNS 解決を再度実行する必要があります。このとき、DNS サーバーはイントラネット アドレスを返しますが、リソース要求段階に入っており、防御手段がないため、攻撃者はイントラネット リソースを取得できます。

補足として、Lao Xu は特に Go での DNS 解決のソース コードの一部を調べたところ、Go は DNS 結果をキャッシュしていないことがわかりました。そのため、TTL が 0 でなくても、依然として 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,
}
ログイン後にコピー

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,
}
ログイン後にコピー

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

Go における SSRF の攻撃と防御

上記の 2 つの防御ソリューションは、DNS リバインディング攻撃を防ぐだけでなく、他の攻撃方法も防ぐことができます。実際、老徐は 1 回で完了する解決策である 2 番目の選択肢を推奨しました。

概要:

  1. 攻撃者は、独自の DNS サービスを通じて DNS 再バインド攻撃を実行できます。

  2. DNS 再バインド攻撃は、http.Transport をカスタマイズすることで防止できます。

以上がGo における SSRF の攻撃と防御の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Go WebSocket メッセージを送信するにはどうすればよいですか? Go WebSocket メッセージを送信するにはどうすればよいですか? Jun 03, 2024 pm 04:53 PM

Go では、gorilla/websocket パッケージを使用して WebSocket メッセージを送信できます。具体的な手順: WebSocket 接続を確立します。テキスト メッセージを送信します。 WriteMessage(websocket.TextMessage,[]byte("message")) を呼び出します。バイナリ メッセージを送信します。WriteMessage(websocket.BinaryMessage,[]byte{1,2,3}) を呼び出します。

Golang 関数のライフサイクルと変数スコープの深い理解 Golang 関数のライフサイクルと変数スコープの深い理解 Apr 19, 2024 am 11:42 AM

Go では、関数のライフ サイクルには定義、ロード、リンク、初期化、呼び出し、戻り値が含まれます。変数のスコープは関数レベルとブロック レベルに分割されますが、ブロック内の変数はブロック内でのみ表示されます。 。

Go で正規表現を使用してタイムスタンプを照合するにはどうすればよいですか? Go で正規表現を使用してタイムスタンプを照合するにはどうすればよいですか? Jun 02, 2024 am 09:00 AM

Go では、正規表現を使用してタイムスタンプを照合できます。ISO8601 タイムスタンプの照合に使用されるような正規表現文字列をコンパイルします。 ^\d{4}-\d{2}-\d{2}T \d{ 2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$ 。 regexp.MatchString 関数を使用して、文字列が正規表現と一致するかどうかを確認します。

GolangとGo言語の違い GolangとGo言語の違い May 31, 2024 pm 08:10 PM

Go と Go 言語は、異なる特性を持つ別個の存在です。 Go (Golang とも呼ばれます) は、同時実行性、高速なコンパイル速度、メモリ管理、およびクロスプラットフォームの利点で知られています。 Go 言語の欠点としては、他の言語に比べてエコシステムが充実していないこと、構文が厳格であること、動的型付けが欠如していることが挙げられます。

Golang の技術的なパフォーマンスの最適化でメモリ リークを回避するにはどうすればよいですか? Golang の技術的なパフォーマンスの最適化でメモリ リークを回避するにはどうすればよいですか? Jun 04, 2024 pm 12:27 PM

メモリ リークは、ファイル、ネットワーク接続、データベース接続などの使用されなくなったリソースを閉じることによって、Go プログラムのメモリを継続的に増加させる可能性があります。弱参照を使用してメモリ リークを防ぎ、強参照されなくなったオブジェクトをガベージ コレクションの対象にします。 go coroutine を使用すると、メモリ リークを避けるために、終了時にコルーチンのスタック メモリが自動的に解放されます。

IDE で Golang 関数のドキュメントを表示するにはどうすればよいですか? IDE で Golang 関数のドキュメントを表示するにはどうすればよいですか? Apr 18, 2024 pm 03:06 PM

IDE を使用して Go 関数のドキュメントを表示する: 関数名の上にカーソルを置きます。ホットキーを押します (GoLand: Ctrl+Q; VSCode: GoExtensionPack をインストールした後、F1 キーを押して「Go:ShowDocumentation」を選択します)。

Go 同時関数の単体テストのガイド Go 同時関数の単体テストのガイド May 03, 2024 am 10:54 AM

並行関数の単体テストは、同時環境での正しい動作を確認するのに役立つため、非常に重要です。同時実行機能をテストするときは、相互排他、同期、分離などの基本原則を考慮する必要があります。並行機能は、シミュレーション、競合状態のテスト、および結果の検証によって単体テストできます。

Golang関数がマップパラメータを受け取る際の注意点 Golang関数がマップパラメータを受け取る際の注意点 Jun 04, 2024 am 10:31 AM

Go の関数にマップを渡すと、デフォルトでコピーが作成され、コピーへの変更は元のマップには影響しません。元のマップを変更する必要がある場合は、ポインタを介してそれを渡すことができます。空のマップは技術的には nil ポインターであり、空ではないマップを期待する関数に空のマップを渡すとエラーが発生するため、空のマップは慎重に扱う必要があります。

See all articles