mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-15 20:40:52 +09:00
f69e15afe7
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>
160 lines
4.6 KiB
Go
160 lines
4.6 KiB
Go
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package setting
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
auth_model "gitea.dev/models/auth"
|
|
"gitea.dev/models/db"
|
|
"gitea.dev/modules/setting"
|
|
"gitea.dev/modules/templates"
|
|
"gitea.dev/modules/util"
|
|
"gitea.dev/modules/web"
|
|
"gitea.dev/services/context"
|
|
"gitea.dev/services/forms"
|
|
)
|
|
|
|
const (
|
|
tplSettingsApplications templates.TplName = "user/settings/applications"
|
|
)
|
|
|
|
// Applications render manage access token page
|
|
func Applications(ctx *context.Context) {
|
|
ctx.Data["Title"] = ctx.Tr("settings.applications")
|
|
ctx.Data["PageIsSettingsApplications"] = true
|
|
|
|
loadApplicationsData(ctx)
|
|
|
|
ctx.HTML(http.StatusOK, tplSettingsApplications)
|
|
}
|
|
|
|
// ApplicationsPost response for add user's access token
|
|
func ApplicationsPost(ctx *context.Context) {
|
|
form := web.GetForm(ctx).(*forms.NewAccessTokenForm)
|
|
ctx.Data["Title"] = ctx.Tr("settings_title")
|
|
ctx.Data["PageIsSettingsApplications"] = true
|
|
|
|
_ = ctx.Req.ParseForm()
|
|
var scopeNames []string
|
|
const accessTokenScopePrefix = "scope-"
|
|
for k, v := range ctx.Req.Form {
|
|
if strings.HasPrefix(k, accessTokenScopePrefix) {
|
|
scopeNames = append(scopeNames, v...)
|
|
}
|
|
}
|
|
|
|
scope, err := auth_model.AccessTokenScope(strings.Join(scopeNames, ",")).Normalize()
|
|
if err != nil {
|
|
ctx.ServerError("GetScope", err)
|
|
return
|
|
}
|
|
if !scope.HasPermissionScope() {
|
|
ctx.Flash.Error(ctx.Tr("settings.at_least_one_permission"), true)
|
|
}
|
|
|
|
if ctx.HasError() {
|
|
loadApplicationsData(ctx)
|
|
ctx.HTML(http.StatusOK, tplSettingsApplications)
|
|
return
|
|
}
|
|
|
|
t := &auth_model.AccessToken{
|
|
UID: ctx.Doer.ID,
|
|
Name: form.Name,
|
|
Scope: scope,
|
|
}
|
|
|
|
exist, err := auth_model.AccessTokenByNameExists(ctx, t)
|
|
if err != nil {
|
|
ctx.ServerError("AccessTokenByNameExists", err)
|
|
return
|
|
}
|
|
if exist {
|
|
ctx.Flash.Error(ctx.Tr("settings.generate_token_name_duplicate", t.Name))
|
|
ctx.Redirect(setting.AppSubURL + "/user/settings/applications")
|
|
return
|
|
}
|
|
|
|
// a token-authenticated request must not mint a token with a broader scope than its own, nor
|
|
// drop the public-only restriction. Web routes accept basic-auth PATs/OAuth tokens too, so this
|
|
// must mirror the REST API guard in routers/api/v1/user/app.go.
|
|
if ctx.Data["IsApiToken"] == true {
|
|
apiTokenScope, ok := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
|
|
if !ok {
|
|
ctx.HTTPError(http.StatusForbidden, "the authenticating token has no scope")
|
|
return
|
|
}
|
|
hasScope, err := apiTokenScope.CanCreateChildScope(t.Scope)
|
|
if err != nil {
|
|
ctx.ServerError("CanCreateChildScope", err)
|
|
return
|
|
}
|
|
if !hasScope {
|
|
ctx.HTTPError(http.StatusForbidden, "cannot create an access token with a broader scope than the authenticating token")
|
|
return
|
|
}
|
|
if t.Scope, err = t.Scope.EnforcePublicOnlyFrom(apiTokenScope); err != nil {
|
|
ctx.ServerError("EnforcePublicOnlyFrom", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
if err := auth_model.NewAccessToken(ctx, t); err != nil {
|
|
ctx.ServerError("NewAccessToken", err)
|
|
return
|
|
}
|
|
|
|
ctx.Flash.Success(ctx.Tr("settings.generate_token_success"))
|
|
ctx.Flash.Info(t.Token)
|
|
|
|
ctx.Redirect(setting.AppSubURL + "/user/settings/applications")
|
|
}
|
|
|
|
// DeleteApplication response for delete user access token
|
|
func DeleteApplication(ctx *context.Context) {
|
|
if err := auth_model.DeleteAccessTokenByID(ctx, ctx.FormInt64("id"), ctx.Doer.ID); err != nil {
|
|
ctx.Flash.Error("DeleteAccessTokenByID: " + err.Error())
|
|
} else {
|
|
ctx.Flash.Success(ctx.Tr("settings.delete_token_success"))
|
|
}
|
|
|
|
ctx.JSONRedirect(setting.AppSubURL + "/user/settings/applications")
|
|
}
|
|
|
|
func loadApplicationsData(ctx *context.Context) {
|
|
ctx.Data["AccessTokenScopePublicOnly"] = auth_model.AccessTokenScopePublicOnly
|
|
tokens, err := db.Find[auth_model.AccessToken](ctx, auth_model.ListAccessTokensOptions{UserID: ctx.Doer.ID})
|
|
if err != nil {
|
|
ctx.ServerError("ListAccessTokens", err)
|
|
return
|
|
}
|
|
ctx.Data["Tokens"] = tokens
|
|
ctx.Data["EnableOAuth2"] = setting.OAuth2.Enabled
|
|
|
|
// Handle specific ordered token categories for admin or non-admin users
|
|
tokenCategoryNames := auth_model.GetAccessTokenCategories()
|
|
if !ctx.Doer.IsAdmin {
|
|
tokenCategoryNames = util.SliceRemoveAll(tokenCategoryNames, "admin")
|
|
}
|
|
ctx.Data["TokenCategories"] = tokenCategoryNames
|
|
|
|
if setting.OAuth2.Enabled {
|
|
ctx.Data["Applications"], err = db.Find[auth_model.OAuth2Application](ctx, auth_model.FindOAuth2ApplicationsOptions{
|
|
OwnerID: ctx.Doer.ID,
|
|
})
|
|
if err != nil {
|
|
ctx.ServerError("GetOAuth2ApplicationsByUserID", err)
|
|
return
|
|
}
|
|
ctx.Data["Grants"], err = auth_model.GetOAuth2GrantsByUserID(ctx, ctx.Doer.ID)
|
|
if err != nil {
|
|
ctx.ServerError("GetOAuth2GrantsByUserID", err)
|
|
return
|
|
}
|
|
}
|
|
}
|