mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-16 22:12:07 +02:00
Addresses a batch of privately reported security issues, grouped by area: - **SSRF** - migration PR-patch/asset fetches, OAuth2 avatar & OpenID discovery, pull-mirror URL re-validation, and the outbound proxy path. - **Access-token scope** - prevent scope escalation on token creation; keep public-only tokens confined (feeds, packages, Actions listings, star/watch lists, limited/private owners). - **Access control / disclosure** - go-get default-branch leak, webhook authorization-header leak, watch clearing on private transitions, label/attachment scoping. - **Denial of service** - input bounds for npm dist-tags, Debian control files, Arch file lists, and SSH keys. ### 📌 Attention for site admins Not breaking - existing configs keep working - but two changes are worth a look: - **New SSRF protection** Outbound requests (migrations, OAuth2 avatars, OpenID discovery, pull mirrors, proxy path) are now validated against the allow/block host lists. If your instance legitimately reaches internal hosts, you may need to add them to `[security].ALLOWED_HOST_LIST` (and the relevant `ALLOW_LOCALNETWORKS` settings). - **Deprecation** `[webhook].ALLOWED_HOST_LIST` is deprecated and will be removed in a future release. Use `[security].ALLOWED_HOST_LIST` instead; the old key still works for now. --------- Co-authored-by: TheFox0x7 <thefox0x7@gmail.com> Co-authored-by: techknowlogick <techknowlogick@gitea.io> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Zettat123 <zettat123@gmail.com>
115 lines
3.3 KiB
Go
115 lines
3.3 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package auth
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"crypto/subtle"
|
|
"encoding/hex"
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
auth_model "gitea.dev/models/auth"
|
|
"gitea.dev/modules/setting"
|
|
"gitea.dev/modules/timeutil"
|
|
"gitea.dev/modules/util"
|
|
)
|
|
|
|
// Based on https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence#secure-remember-me-cookies
|
|
|
|
// The auth token consists of two parts: ID and token hash
|
|
// Every device login creates a new auth token with an individual id and hash.
|
|
// If a device uses the token to login into the instance, a fresh token gets generated which has the same id but a new hash.
|
|
|
|
var (
|
|
ErrAuthTokenInvalidFormat = util.NewInvalidArgumentErrorf("auth token has an invalid format")
|
|
ErrAuthTokenExpired = util.NewInvalidArgumentErrorf("auth token has expired")
|
|
ErrAuthTokenInvalidHash = util.NewInvalidArgumentErrorf("auth token is invalid")
|
|
)
|
|
|
|
func CheckAuthToken(ctx context.Context, value string) (*auth_model.AuthToken, error) {
|
|
if len(value) == 0 {
|
|
return nil, nil //nolint:nilnil // the auth method is not applicable
|
|
}
|
|
|
|
parts := strings.SplitN(value, ":", 2)
|
|
if len(parts) != 2 {
|
|
return nil, ErrAuthTokenInvalidFormat
|
|
}
|
|
|
|
t, err := auth_model.GetAuthTokenByID(ctx, parts[0])
|
|
if err != nil {
|
|
if errors.Is(err, util.ErrNotExist) {
|
|
return nil, ErrAuthTokenExpired
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
if t.ExpiresUnix < timeutil.TimeStampNow() {
|
|
return nil, ErrAuthTokenExpired
|
|
}
|
|
|
|
hashedToken := sha256.Sum256([]byte(parts[1]))
|
|
|
|
if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(hex.EncodeToString(hashedToken[:]))) == 0 {
|
|
// If an attacker steals a token and uses the token to create a new session the hash gets updated.
|
|
// When the victim uses the old token the hashes don't match anymore and the victim should be notified about the compromised token.
|
|
// Revoke the token so the attacker's rotated token (which shares this ID) can no longer be used.
|
|
if err := auth_model.DeleteAuthTokenByID(ctx, t.ID); err != nil {
|
|
return nil, err
|
|
}
|
|
return nil, ErrAuthTokenInvalidHash
|
|
}
|
|
|
|
return t, nil
|
|
}
|
|
|
|
func RegenerateAuthToken(ctx context.Context, t *auth_model.AuthToken) (*auth_model.AuthToken, string, error) {
|
|
token, hash := generateTokenAndHash()
|
|
|
|
newToken := &auth_model.AuthToken{
|
|
ID: t.ID,
|
|
TokenHash: hash,
|
|
UserID: t.UserID,
|
|
ExpiresUnix: timeutil.TimeStampNow().AddDuration(time.Duration(setting.LogInRememberDays*24) * time.Hour),
|
|
}
|
|
|
|
if err := auth_model.UpdateAuthTokenByID(ctx, newToken); err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
return newToken, token, nil
|
|
}
|
|
|
|
func CreateAuthTokenForUserID(ctx context.Context, userID int64) (*auth_model.AuthToken, string, error) {
|
|
t := &auth_model.AuthToken{
|
|
UserID: userID,
|
|
ExpiresUnix: timeutil.TimeStampNow().AddDuration(time.Duration(setting.LogInRememberDays*24) * time.Hour),
|
|
}
|
|
|
|
t.ID = util.CryptoRandomString(10)
|
|
|
|
token, hash := generateTokenAndHash()
|
|
|
|
t.TokenHash = hash
|
|
|
|
if err := auth_model.InsertAuthToken(ctx, t); err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
return t, token, nil
|
|
}
|
|
|
|
func generateTokenAndHash() (string, string) {
|
|
buf := util.CryptoRandomBytes(32)
|
|
|
|
token := hex.EncodeToString(buf)
|
|
|
|
hashedToken := sha256.Sum256([]byte(token))
|
|
|
|
return token, hex.EncodeToString(hashedToken[:])
|
|
}
|