Skip to content

Commit b5363d2

Browse files
authored
Merge pull request #2909 from traPtitech/ogp-connection-dialer-control
dailer.Control によりDNS解決後のIPを検証するように修正
2 parents f5efeb4 + 24cedab commit b5363d2

1 file changed

Lines changed: 15 additions & 59 deletions

File tree

service/ogp/parser/parser.go

Lines changed: 15 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package parser
22

33
import (
44
"context"
5+
"errors"
56
"net"
67
"net/http"
78
"net/url"
89
"strings"
10+
"syscall"
911
"time"
1012

1113
"github.com/dyatlov/go-opengraph/opengraph"
@@ -60,82 +62,36 @@ func isPrivateIP(ip net.IP) bool {
6062
return false
6163
}
6264

63-
// validateURL はURLがSSRFに対して安全かどうかを検証し、検証済みのIPアドレスを返します
64-
func validateURL(u *url.URL) ([]net.IP, error) {
65-
// スキームの検証
66-
if u.Scheme != "http" && u.Scheme != "https" {
67-
return nil, ErrNotAllowed
68-
}
69-
70-
// ホスト名を取得
71-
host := u.Hostname()
72-
if host == "" {
73-
return nil, ErrNotAllowed
74-
}
75-
76-
// IPアドレスの場合は直接検証
77-
if ip := net.ParseIP(host); ip != nil {
78-
if isPrivateIP(ip) {
79-
return nil, ErrNotAllowed
80-
}
81-
return []net.IP{ip}, nil
82-
}
83-
84-
// ホスト名の場合はDNS解決して検証
85-
ips, err := net.LookupIP(host)
86-
if err != nil {
87-
return nil, ErrNetwork
88-
}
89-
90-
for _, ip := range ips {
91-
if isPrivateIP(ip) {
92-
return nil, ErrNotAllowed
93-
}
94-
}
95-
96-
return ips, nil
97-
}
98-
9965
// ParseMetaForURL 指定したURLのメタタグをパースした結果を返します。
10066
func ParseMetaForURL(url *url.URL) (*opengraph.OpenGraph, *DefaultPageMeta, error) {
10167
_ = requestLimiter.Acquire(context.Background(), 1)
10268
defer requestLimiter.Release(1)
10369

104-
// SSRF対策: URLを検証し、検証済みIPアドレスを取得
105-
resolvedIPs, err := validateURL(url)
106-
if err != nil {
107-
return nil, nil, err
108-
}
109-
11070
og, meta, isSpecialDomain, err := FetchSpecialDomainInfo(url)
11171
if isSpecialDomain && (err == nil) {
11272
return og, meta, nil
11373
}
11474

115-
// SSRF対策: 検証済みのIPアドレスを使用してリクエストを送信
75+
// SSRF対策: DNS解決後のIPアドレスを検証してプライベートIPへのアクセスをブロック
11676
dialer := &net.Dialer{
11777
Timeout: 5 * time.Second,
118-
}
119-
transport := &http.Transport{
120-
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
121-
_, port, err := net.SplitHostPort(addr)
78+
Control: func(_, address string, _ syscall.RawConn) error {
79+
host, _, err := net.SplitHostPort(address)
12280
if err != nil {
123-
return nil, err
81+
return err
12482
}
125-
126-
// 検証済みのIPアドレスに接続
127-
addr = net.JoinHostPort(resolvedIPs[0].String(), port)
128-
return dialer.DialContext(ctx, network, addr)
83+
ip := net.ParseIP(host)
84+
if isPrivateIP(ip) {
85+
logger.Info("blocked request to private IP", zap.String("url", url.String()), zap.String("ip", host))
86+
return errors.New("private IP address is not allowed")
87+
}
88+
return nil
12989
},
13090
}
131-
13291
client := http.Client{
133-
Timeout: 5 * time.Second,
134-
Transport: transport,
135-
CheckRedirect: func(req *http.Request, _ []*http.Request) error {
136-
// Validate redirect destination to prevent SSRF via redirects
137-
_, err := validateURL(req.URL)
138-
return err
92+
Timeout: 5 * time.Second,
93+
Transport: &http.Transport{
94+
DialContext: dialer.DialContext,
13995
},
14096
}
14197

0 commit comments

Comments
 (0)