CVE-2022-1285 (Go, published 2022-06-01T06:15Z)
Description: value: Server-Side Request Forgery (SSRF) in GitHub repository gogs/gogs prior to 0.12.8.
Severity: MEDIUM (CVSS v3 base score: 6.5)
Weakness: CWE-918 \u2014 Server-Side Request Forgery (SSRF)
The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
Repository: https://github.com/gogs/gogs
Files changed:
CHANGELOG.md
conf/locale/locale_en-US.ini
internal/db/error.go
internal/db/webhook.go
internal/form/repo.go
internal/netutil/netutil.go
internal/netutil/netutil_test.go
internal/route/api/v1/repo/repo.go
internal/route/repo/repo.go
internal/route/repo/webhook.go
internal/route/repo/webhook_test.go
Commit message: webhook: revalidate local hostname before each delivery (#6988)



Vulnerable code:
settings.webhook.err_cannot_use_local_addresses = Non admins are not allowed to use local addresses.
	IsURLError         bool
	IsInvalidPath      bool
	IsPermissionDenied bool
	return fmt.Sprintf("invalid clone address [is_url_error: %v, is_invalid_path: %v, is_permission_denied: %v]",
		err.IsURLError, err.IsInvalidPath, err.IsPermissionDenied)
		if netutil.IsLocalHostname(u.Hostname(), conf.Security.LocalNetworkAllowlist) {
			return "", db.ErrInvalidCloneAddr{IsURLError: true}
// IsLocalHostname returns true if given hostname is resolved to local network
// address, except exempted from the allowlist.
func IsLocalHostname(hostname string, allowlist []string) bool {
			assert.Equal(t, test.want, IsLocalHostname(test.hostname, test.allowlist))
				c.RenderWithErr(c.Tr("form.url_error"), MIGRATE, &f)
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,
		// see https://github.com/gogs/gogs/issues/5366 for details.
		payloadURL, err := url.Parse(w.URL)
		if err != nil {
			return "PayloadURL", l.Tr("repo.settings.webhook.err_cannot_parse_payload_url", err), false
		}

		if netutil.IsLocalHostname(payloadURL.Hostname(), conf.Security.LocalNetworkAllowlist) {
			return "PayloadURL", l.Tr("repo.settings.webhook.err_cannot_use_local_addresses"), false
		}
	field, msg, ok := validateWebhook(c.User, c.Locale, w)
	field, msg, ok := validateWebhook(c.User, c.Locale, w)
			actor:   &db.User{IsAdmin: true},
			webhook: &db.Webhook{URL: "http://localhost:3306"},
			actor:    &db.User{},
			expMsg:   "repo.settings.webhook.err_cannot_use_local_addresses",
			field, msg, ok := validateWebhook(test.actor, l, test.webhook)

Fixed code:
- _Security:_ SSRF in webhook. [#6901](https://github.com/gogs/gogs/issues/6901)
migrate.clone_address_resolved_to_blocked_local_address = Clone address resolved to a local network address that is implicitly blocked.
settings.webhook.url_resolved_to_blocked_local_address = Payload URL resolved to a local network address that is implicitly blocked.
	IsURLError            bool
	IsInvalidPath         bool
	IsPermissionDenied    bool
	IsBlockedLocalAddress bool
	return fmt.Sprintf("invalid clone address [is_url_error: %v, is_invalid_path: %v, is_permission_denied: %v, is_blocked_local_address: %v]",
		err.IsURLError, err.IsInvalidPath, err.IsPermissionDenied, err.IsBlockedLocalAddress)
	"gogs.io/gogs/internal/netutil"
	if netutil.IsBlockedLocalHostname(t.URL, conf.Security.LocalNetworkAllowlist) {
		t.ResponseContent = "Payload URL resolved to a local network address that is implicitly blocked."
		return
	}

		if netutil.IsBlockedLocalHostname(u.Hostname(), conf.Security.LocalNetworkAllowlist) {
			return "", db.ErrInvalidCloneAddr{IsBlockedLocalAddress: true}
// IsBlockedLocalHostname returns true if given hostname is resolved to a local
// network address that is implicitly blocked (i.e. not exempted from the
// allowlist).
func IsBlockedLocalHostname(hostname string, allowlist []string) bool {
			assert.Equal(t, test.want, IsBlockedLocalHostname(test.hostname, test.allowlist))
			case addrErr.IsBlockedLocalAddress:
				c.ErrorStatus(http.StatusUnprocessableEntity, errors.New("Clone address resolved to a local network address that is implicitly blocked."))
				c.RenderWithErr(c.Tr("repo.migrate.clone_address")+c.Tr("form.url_error"), MIGRATE, &f)
			case addrErr.IsBlockedLocalAddress:
				c.RenderWithErr(c.Tr("repo.migrate.clone_address_resolved_to_blocked_local_address"), MIGRATE, &f)
func validateWebhook(l macaron.Locale, w *db.Webhook) (field, msg string, ok bool) {
	// 🚨 SECURITY: Local addresses must not be allowed by non-admins to prevent SSRF,
	// see https://github.com/gogs/gogs/issues/5366 for details.
	payloadURL, err := url.Parse(w.URL)
	if err != nil {
		return "PayloadURL", l.Tr("repo.settings.webhook.err_cannot_parse_payload_url", err), false
	if netutil.IsBlockedLocalHostname(payloadURL.Hostname(), conf.Security.LocalNetworkAllowlist) {
		return "PayloadURL", l.Tr("repo.settings.webhook.url_resolved_to_blocked_local_address"), false
	}
	field, msg, ok := validateWebhook(c.Locale, w)
	field, msg, ok := validateWebhook(c.Locale, w)
			webhook: &db.Webhook{URL: "https://www.google.com"},
			expMsg:   "repo.settings.webhook.url_resolved_to_blocked_local_address",
			field, msg, ok := validateWebhook(l, test.webhook)