Skip to content

Commit

Permalink
Add argument to specify illegal extensions (#51)
Browse files Browse the repository at this point in the history
File uploads with these file extensions will be rejected. This to limit abuse.
  • Loading branch information
espebra authored Oct 20, 2023
1 parent f79b889 commit e353f01
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 22 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ The path to an mmdb formatted geoip database like GeoLite2-City.mmdb. This is op

If this argument is set, then the client IP will be read from the proxy headers provided in the incoming HTTP requests. This argument should only be set if there is an HTTP proxy running in front of Filebin, that is using the proxy headers to tell Filebin the original client IP address.

#### `--reject-file-extensions string` (default: not set)

A whitespace separated list of file extensions that will be rejected. Example: "exe bat dll".

#### `--s3-access-key string` (default: not set)

The access key to use when connecting to the S3 bucket where files will be stored.
Expand Down
1 change: 1 addition & 0 deletions ds/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ type Config struct {
RequireApproval bool
AllowRobots bool
BaseUrl url.URL
RejectFileExtensions []string
}
15 changes: 14 additions & 1 deletion http_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"net/http"
"net/http/httputil"
"os"
"path"
"strconv"
//"strings"
"time"
Expand Down Expand Up @@ -142,6 +143,18 @@ func (h *HTTP) uploadFile(w http.ResponseWriter, r *http.Request) {
}
// TODO: Input validation on content-length. Between min:max.

// Reject file names with certain extensions
// Remove the . from the extension
thisExtension := path.Ext(inputFilename)
if len(thisExtension) > 0 {
for _, extension := range h.config.RejectFileExtensions {
if "."+extension == thisExtension {
h.Error(w, r, fmt.Sprintf("Rejecting file name %s with illegal extension: %s", inputFilename, extension), "Illegal file extension", 992, http.StatusForbidden)
return
}
}
}

// Check if bin exists
bin, found, err := h.dao.Bin().GetByID(inputBin)
if err != nil {
Expand Down Expand Up @@ -206,7 +219,7 @@ func (h *HTTP) uploadFile(w http.ResponseWriter, r *http.Request) {
if h.config.LimitStorageBytes > 0 {
totalBytesConsumed := h.dao.Info().StorageBytesAllocated()
if totalBytesConsumed >= h.config.LimitStorageBytes {
h.Error(w, r, fmt.Sprintf("Storage limit reached (currently consuming %s) when trying to upload file %q to bin %q", humanize.Bytes(totalBytesConsumed), inputFilename, inputBin), "Insufficient storage\n", 633, http.StatusInsufficientStorage)
h.Error(w, r, fmt.Sprintf("Storage limit reached (currently consuming %s) when trying to upload file %q to bin %q", humanize.Bytes(totalBytesConsumed), inputFilename, inputBin), "Insufficient storage, please retry later\n", 633, http.StatusInsufficientStorage)
return
}
}
Expand Down
16 changes: 16 additions & 0 deletions http_file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,22 @@ func TestBinInputValidation(t *testing.T) {
UploadContent: "content a",
StatusCode: 400,
},
{
Description: "File with illegal extension)",
Method: "POST",
Bin: "1234567890",
Filename: "a.illegal1",
UploadContent: "content a",
StatusCode: 403,
},
{
Description: "File with illegal extension)",
Method: "POST",
Bin: "1234567890",
Filename: "b.illegal2",
UploadContent: "content b",
StatusCode: 403,
},
}
runTests(tcs, t)
}
11 changes: 6 additions & 5 deletions http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,12 @@ func TestMain(m *testing.M) {
log.Fatal(err)
}
c := ds.Config{
LimitFileDownloads: testLimitFileDownloads,
LimitStorageBytes: testLimitStorage,
Expiration: testExpiredAt,
HttpHost: testHTTPHost,
HttpPort: testHTTPPort,
LimitFileDownloads: testLimitFileDownloads,
LimitStorageBytes: testLimitStorage,
Expiration: testExpiredAt,
HttpHost: testHTTPHost,
HttpPort: testHTTPPort,
RejectFileExtensions: []string{"illegal1", "illegal2"},
}
h := &HTTP{
staticBox: &staticBox,
Expand Down
43 changes: 28 additions & 15 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"fmt"
_ "net/http/pprof"
"os"
"regexp"
"strconv"
"strings"
//"github.com/espebra/filebin2/ds"
"github.com/dustin/go-humanize"
"github.com/espebra/filebin2/dbl"
Expand All @@ -30,6 +32,7 @@ var (
// Limits
limitFileDownloadsFlag = flag.Uint64("limit-file-downloads", 0, "Limit the number of downloads per file. 0 disables this limit.")
limitStorageFlag = flag.String("limit-storage", "0", "Limit the storage capacity to use (examples: 100MB, 20GB, 2TB). 0 disables this limit.")
rejectFileExtensions = flag.String("reject-file-extensions", "", "A whitespace separated list of file extensions that will be rejected")

// HTTP
listenHostFlag = flag.String("listen-host", "127.0.0.1", "Listen host")
Expand Down Expand Up @@ -130,6 +133,15 @@ func main() {
}
fmt.Printf("TTL for presigned S3 URLs: %s\n", s3UrlTtl.String())

filter := regexp.MustCompile(`^[a-zA-Z0-9]+$`)
for _, v := range strings.Fields(*rejectFileExtensions) {
if !filter.Match([]byte(v)) {
fmt.Printf("Extension specified by --reject-file-extensions contains illegal characters: %v\n", v)
os.Exit(2)
}
fmt.Printf("Rejecting file extension: %s\n", v)
}

s3conn, err := s3.Init(*s3EndpointFlag, *s3BucketFlag, *s3RegionFlag, *s3AccessKeyFlag, *s3SecretKeyFlag, *s3SecureFlag, s3UrlTtl)
if err != nil {
fmt.Printf("Unable to initialize S3 connection: %s\n", err.Error())
Expand All @@ -156,21 +168,22 @@ func main() {
}

config := &ds.Config{
AdminPassword: *adminPasswordFlag,
AdminUsername: *adminUsernameFlag,
AllowRobots: *allowRobotsFlag,
BaseUrl: *u,
Expiration: *expirationFlag,
HttpHost: *listenHostFlag,
HttpAccessLog: *accessLogFlag,
HttpPort: *listenPortFlag,
HttpProxyHeaders: *proxyHeadersFlag,
LimitFileDownloads: *limitFileDownloadsFlag,
RequireApproval: *requireApprovalFlag,
SlackSecret: *slackSecretFlag,
SlackDomain: *slackDomainFlag,
SlackChannel: *slackChannelFlag,
Tmpdir: *tmpdirFlag,
AdminPassword: *adminPasswordFlag,
AdminUsername: *adminUsernameFlag,
AllowRobots: *allowRobotsFlag,
BaseUrl: *u,
Expiration: *expirationFlag,
HttpHost: *listenHostFlag,
HttpAccessLog: *accessLogFlag,
HttpPort: *listenPortFlag,
HttpProxyHeaders: *proxyHeadersFlag,
LimitFileDownloads: *limitFileDownloadsFlag,
RequireApproval: *requireApprovalFlag,
RejectFileExtensions: strings.Fields(*rejectFileExtensions),
SlackSecret: *slackSecretFlag,
SlackDomain: *slackDomainFlag,
SlackChannel: *slackChannelFlag,
Tmpdir: *tmpdirFlag,
}

config.LimitStorageBytes, err = humanize.ParseBytes(*limitStorageFlag)
Expand Down
2 changes: 1 addition & 1 deletion static/js/filebin2.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function FileAPI (c, t, d, f, bin, binURL) {
}
text = text + " uploaded";
if (counter_failed > 0) {
text = text + ". " + counter_failed + " failed, please retry later.";
text = text + ". " + counter_failed + " failed.";
box.className = "alert alert-danger";
} else if (counter_completed == counter_queue) {
text = text + ", all done!";
Expand Down

0 comments on commit e353f01

Please sign in to comment.