-
-
Notifications
You must be signed in to change notification settings - Fork 5.8k
Include file extension checks in attachment API #32151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
35b44c2
d377e25
f9880a5
78ba2c0
864dc5a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,12 +28,13 @@ func IsErrFileTypeForbidden(err error) bool { | |
} | ||
|
||
func (err ErrFileTypeForbidden) Error() string { | ||
return "This file extension or type is not allowed to be uploaded." | ||
return "This file cannot be uploaded or modified due to a forbidden file extension or type." | ||
} | ||
|
||
var wildcardTypeRe = regexp.MustCompile(`^[a-z]+/\*$`) | ||
|
||
// Verify validates whether a file is allowed to be uploaded. | ||
// Verify validates whether a file is allowed to be uploaded. If buf is empty, it will just check if the file | ||
// has an allowed file extension. | ||
func Verify(buf []byte, fileName, allowedTypesStr string) error { | ||
allowedTypesStr = strings.ReplaceAll(allowedTypesStr, "|", ",") // compat for old config format | ||
|
||
|
@@ -56,21 +57,31 @@ func Verify(buf []byte, fileName, allowedTypesStr string) error { | |
return ErrFileTypeForbidden{Type: fullMimeType} | ||
} | ||
extension := strings.ToLower(path.Ext(fileName)) | ||
isBufEmpty := len(buf) <= 1 | ||
|
||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers | ||
for _, allowEntry := range allowedTypes { | ||
if allowEntry == "*/*" { | ||
return nil // everything allowed | ||
} else if strings.HasPrefix(allowEntry, ".") && allowEntry == extension { | ||
} | ||
if strings.HasPrefix(allowEntry, ".") && allowEntry == extension { | ||
return nil // extension is allowed | ||
} else if mimeType == allowEntry { | ||
} | ||
if isBufEmpty { | ||
continue // skip mime type checks if buffer is empty | ||
} | ||
Comment on lines
+70
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One problem with this implementation is that if we have the following conditions:
We would return an Then again, as I look at my example above, the logic in
... this would pass the verification checks. The current implementation does not follow this behavior and just checks if the filename is allowed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently I see a few approaches to this problem:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far I can think of the third approach may suits best in this situation
. thanks :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's acceptable to not allow uploading empty file in reality. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. then it above fn will have a simple addon
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, I believe it makes sense to disallow empty attachments.
My current problem is I am not sure if we should follow the way |
||
if mimeType == allowEntry { | ||
return nil // mime type is allowed | ||
} else if wildcardTypeRe.MatchString(allowEntry) && strings.HasPrefix(mimeType, allowEntry[:len(allowEntry)-1]) { | ||
} | ||
if wildcardTypeRe.MatchString(allowEntry) && strings.HasPrefix(mimeType, allowEntry[:len(allowEntry)-1]) { | ||
return nil // wildcard match, e.g. image/* | ||
} | ||
} | ||
|
||
log.Info("Attachment with type %s blocked from upload", fullMimeType) | ||
if !isBufEmpty { | ||
log.Info("Attachment with type %s blocked from upload", fullMimeType) | ||
} | ||
|
||
return ErrFileTypeForbidden{Type: fullMimeType} | ||
} | ||
|
||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// Copyright 2024 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package integration | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"testing" | ||
|
||
auth_model "code.gitea.io/gitea/models/auth" | ||
repo_model "code.gitea.io/gitea/models/repo" | ||
"code.gitea.io/gitea/models/unittest" | ||
user_model "code.gitea.io/gitea/models/user" | ||
"code.gitea.io/gitea/modules/setting" | ||
"code.gitea.io/gitea/modules/test" | ||
"code.gitea.io/gitea/tests" | ||
) | ||
|
||
func TestAPIEditReleaseAttachmentWithUnallowedFile(t *testing.T) { | ||
// Limit the allowed release types (since by default there is no restriction) | ||
defer test.MockVariableValue(&setting.Repository.Release.AllowedTypes, ".exe")() | ||
defer tests.PrepareTestEnv(t)() | ||
|
||
attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 9}) | ||
release := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{ID: attachment.ReleaseID}) | ||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID}) | ||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) | ||
|
||
session := loginUser(t, repoOwner.Name) | ||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) | ||
|
||
filename := "file.bad" | ||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets/%d", repoOwner.Name, repo.Name, release.ID, attachment.ID) | ||
req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ | ||
"name": filename, | ||
}).AddTokenAuth(token) | ||
|
||
session.MakeRequest(t, req, http.StatusUnprocessableEntity) | ||
} |
Uh oh!
There was an error while loading. Please reload this page.