@@ -2,6 +2,7 @@ package parser
22
33import (
44 "context"
5+ "net"
56 "net/http"
67 "net/url"
78 "strings"
@@ -21,19 +22,112 @@ type DefaultPageMeta struct {
2122 Title , Description , URL , Image string
2223}
2324
25+ // isPrivateIP はIPアドレスがプライベート、ループバック、リンクローカル、またはその他の内部アドレスかどうかを判定します
26+ func isPrivateIP (ip net.IP ) bool {
27+ if ip == nil {
28+ return true // 不明なIPはブロック
29+ }
30+ // ループバック (127.0.0.0/8, ::1)
31+ if ip .IsLoopback () {
32+ return true
33+ }
34+ // プライベートアドレス (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7)
35+ if ip .IsPrivate () {
36+ return true
37+ }
38+ // リンクローカル (169.254.0.0/16, fe80::/10)
39+ if ip .IsLinkLocalUnicast () || ip .IsLinkLocalMulticast () {
40+ return true
41+ }
42+ // 未指定アドレス (0.0.0.0, ::)
43+ if ip .IsUnspecified () {
44+ return true
45+ }
46+ // マルチキャスト
47+ if ip .IsMulticast () {
48+ return true
49+ }
50+ return false
51+ }
52+
53+ // validateURL はURLがSSRFに対して安全かどうかを検証し、検証済みのIPアドレスを返します
54+ func validateURL (u * url.URL ) ([]net.IP , error ) {
55+ // スキームの検証
56+ if u .Scheme != "http" && u .Scheme != "https" {
57+ return nil , ErrNotAllowed
58+ }
59+
60+ // ホスト名を取得
61+ host := u .Hostname ()
62+ if host == "" {
63+ return nil , ErrNotAllowed
64+ }
65+
66+ // IPアドレスの場合は直接検証
67+ if ip := net .ParseIP (host ); ip != nil {
68+ if isPrivateIP (ip ) {
69+ return nil , ErrNotAllowed
70+ }
71+ return []net.IP {ip }, nil
72+ }
73+
74+ // ホスト名の場合はDNS解決して検証
75+ ips , err := net .LookupIP (host )
76+ if err != nil {
77+ return nil , ErrNetwork
78+ }
79+
80+ for _ , ip := range ips {
81+ if isPrivateIP (ip ) {
82+ return nil , ErrNotAllowed
83+ }
84+ }
85+
86+ return ips , nil
87+ }
88+
2489// ParseMetaForURL 指定したURLのメタタグをパースした結果を返します。
2590func ParseMetaForURL (url * url.URL ) (* opengraph.OpenGraph , * DefaultPageMeta , error ) {
2691 _ = requestLimiter .Acquire (context .Background (), 1 )
2792 defer requestLimiter .Release (1 )
2893
94+ // SSRF対策: URLを検証し、検証済みIPアドレスを取得
95+ resolvedIPs , err := validateURL (url )
96+ if err != nil {
97+ return nil , nil , err
98+ }
99+
29100 og , meta , isSpecialDomain , err := FetchSpecialDomainInfo (url )
30101 if isSpecialDomain && (err == nil ) {
31102 return og , meta , nil
32103 }
33104
34- client := http.Client {
105+ // SSRF対策: 検証済みのIPアドレスを使用してリクエストを送信
106+ dialer := & net.Dialer {
35107 Timeout : 5 * time .Second ,
36108 }
109+ transport := & http.Transport {
110+ DialContext : func (ctx context.Context , network , addr string ) (net.Conn , error ) {
111+ _ , port , err := net .SplitHostPort (addr )
112+ if err != nil {
113+ return nil , err
114+ }
115+
116+ // 検証済みのIPアドレスに接続
117+ addr = net .JoinHostPort (resolvedIPs [0 ].String (), port )
118+ return dialer .DialContext (ctx , network , addr )
119+ },
120+ }
121+
122+ client := http.Client {
123+ Timeout : 5 * time .Second ,
124+ Transport : transport ,
125+ CheckRedirect : func (req * http.Request , _ []* http.Request ) error {
126+ // Validate redirect destination to prevent SSRF via redirects
127+ _ , err := validateURL (req .URL )
128+ return err
129+ },
130+ }
37131
38132 req , err := http .NewRequest ("GET" , url .String (), nil )
39133 if err != nil {
0 commit comments