mirror of https://github.com/gogs/gogs.git
security: fix SSRF in repository migration (#6812)
Co-authored-by: Joe Chen <jc@unknwon.io>pull/6819/merge
parent
bb19f52c05
commit
242deca524
|
@ -21,6 +21,7 @@ All notable changes to Gogs are documented in this file.
|
|||
|
||||
### Fixed
|
||||
|
||||
- _Security:_ Potential SSRF in repository migration. [#6754](https://github.com/gogs/gogs/issues/6754)
|
||||
- Unable to use LDAP authentication on ARM machines. [#6761](https://github.com/gogs/gogs/issues/6761)
|
||||
|
||||
### Removed
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"gopkg.in/macaron.v1"
|
||||
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/netutil"
|
||||
)
|
||||
|
||||
// _______________________________________ _________.______________________ _______________.___.
|
||||
|
@ -69,6 +70,11 @@ func (f MigrateRepo) ParseRemoteAddr(user *db.User) (string, error) {
|
|||
if err != nil {
|
||||
return "", db.ErrInvalidCloneAddr{IsURLError: true}
|
||||
}
|
||||
|
||||
if netutil.IsLocalHostname(u.Hostname()) {
|
||||
return "", db.ErrInvalidCloneAddr{IsURLError: true}
|
||||
}
|
||||
|
||||
if len(f.AuthUsername)+len(f.AuthPassword) > 0 {
|
||||
u.User = url.UserPassword(f.AuthUsername, f.AuthPassword)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright 2022 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
var localCIDRs []*net.IPNet
|
||||
|
||||
func init() {
|
||||
// Parsing hardcoded CIDR strings should never fail, if in case it does, let's
|
||||
// fail it at start.
|
||||
rawCIDRs := []string{
|
||||
// https://datatracker.ietf.org/doc/html/rfc5735:
|
||||
"127.0.0.0/8", // Loopback
|
||||
"0.0.0.0/8", // "This" network
|
||||
"100.64.0.0/10", // Shared address space
|
||||
"169.254.0.0/16", // Link local
|
||||
"172.16.0.0/12", // Private-use networks
|
||||
"192.0.0.0/24", // IETF Protocol assignments
|
||||
"192.0.2.0/24", // TEST-NET-1
|
||||
"192.88.99.0/24", // 6to4 Relay anycast
|
||||
"192.168.0.0/16", // Private-use networks
|
||||
"198.18.0.0/15", // Network interconnect
|
||||
"198.51.100.0/24", // TEST-NET-2
|
||||
"203.0.113.0/24", // TEST-NET-3
|
||||
"255.255.255.255/32", // Limited broadcast
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc1918:
|
||||
"10.0.0.0/8", // Private-use networks
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc6890:
|
||||
"::1/128", // Loopback
|
||||
"FC00::/7", // Unique local address
|
||||
"FE80::/10", // Multicast address
|
||||
}
|
||||
for _, raw := range rawCIDRs {
|
||||
_, cidr, err := net.ParseCIDR(raw)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("parse CIDR %q: %v", raw, err))
|
||||
}
|
||||
localCIDRs = append(localCIDRs, cidr)
|
||||
}
|
||||
}
|
||||
|
||||
// IsLocalHostname returns true if given hostname is a known local address.
|
||||
func IsLocalHostname(hostname string) bool {
|
||||
ips, err := net.LookupIP(hostname)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
for _, ip := range ips {
|
||||
for _, cidr := range localCIDRs {
|
||||
if cidr.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2022 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsLocalHostname(t *testing.T) {
|
||||
tests := []struct {
|
||||
hostname string
|
||||
want bool
|
||||
}{
|
||||
{hostname: "localhost", want: true},
|
||||
{hostname: "127.0.0.1", want: true},
|
||||
{hostname: "::1", want: true},
|
||||
{hostname: "0:0:0:0:0:0:0:1", want: true},
|
||||
{hostname: "fuf.me", want: true},
|
||||
{hostname: "127.0.0.95", want: true},
|
||||
{hostname: "0.0.0.0", want: true},
|
||||
{hostname: "192.168.123.45", want: true},
|
||||
|
||||
{hostname: "gogs.io", want: false},
|
||||
{hostname: "google.com", want: false},
|
||||
{hostname: "165.232.140.255", want: false},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
assert.Equal(t, test.want, IsLocalHostname(test.hostname))
|
||||
})
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import (
|
|||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/form"
|
||||
"gogs.io/gogs/internal/netutil"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -118,23 +119,6 @@ func WebhooksNew(c *context.Context, orCtx *orgRepoContext) {
|
|||
c.Success(orCtx.TmplNew)
|
||||
}
|
||||
|
||||
var localHostnames = []string{
|
||||
"localhost",
|
||||
"127.0.0.1",
|
||||
"::1",
|
||||
"0:0:0:0:0:0:0:1",
|
||||
}
|
||||
|
||||
// isLocalHostname returns true if given hostname is a known local address.
|
||||
func isLocalHostname(hostname string) bool {
|
||||
for _, local := range localHostnames {
|
||||
if hostname == local {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func validateWebhook(actor *db.User, l macaron.Locale, w *db.Webhook) (field, msg string, ok bool) {
|
||||
if !actor.IsAdmin {
|
||||
// 🚨 SECURITY: Local addresses must not be allowed by non-admins to prevent SSRF,
|
||||
|
@ -144,7 +128,7 @@ func validateWebhook(actor *db.User, l macaron.Locale, w *db.Webhook) (field, ms
|
|||
return "PayloadURL", l.Tr("repo.settings.webhook.err_cannot_parse_payload_url", err), false
|
||||
}
|
||||
|
||||
if isLocalHostname(payloadURL.Hostname()) {
|
||||
if netutil.IsLocalHostname(payloadURL.Hostname()) {
|
||||
return "PayloadURL", l.Tr("repo.settings.webhook.err_cannot_use_local_addresses"), false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,25 +13,6 @@ import (
|
|||
"gogs.io/gogs/internal/mocks"
|
||||
)
|
||||
|
||||
func Test_isLocalHostname(t *testing.T) {
|
||||
tests := []struct {
|
||||
hostname string
|
||||
want bool
|
||||
}{
|
||||
{hostname: "localhost", want: true},
|
||||
{hostname: "127.0.0.1", want: true},
|
||||
{hostname: "::1", want: true},
|
||||
{hostname: "0:0:0:0:0:0:0:1", want: true},
|
||||
|
||||
{hostname: "gogs.io", want: false},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
assert.Equal(t, test.want, isLocalHostname(test.hostname))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validateWebhook(t *testing.T) {
|
||||
l := &mocks.Locale{
|
||||
MockLang: "en",
|
||||
|
|
Loading…
Reference in New Issue