Skip to content

Commit d55e2f8

Browse files
c-fYour Name
authored andcommitted
smalle upload refactoring
1 parent e04edde commit d55e2f8

File tree

4 files changed

+101
-64
lines changed

4 files changed

+101
-64
lines changed

pkg/httpserver/authlayer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"net/http"
66
)
77

8-
func (t *HTTPServer) basicauthlayer(handler http.Handler) http.HandlerFunc {
8+
func (t *HTTPServer) basicauthlayer(handler http.Handler) http.Handler {
99
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1010
user, pass, ok := r.BasicAuth()
1111
if !ok || user != t.options.BasicAuthUsername || pass != t.options.BasicAuthPassword {

pkg/httpserver/httpserver.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ type HTTPServer struct {
3232
layers http.Handler
3333
}
3434

35+
// LayerHandler is the interface of all layer funcs
36+
type Middleware func(http.Handler) http.Handler
37+
3538
// New http server instance with options
3639
func New(options *Options) (*HTTPServer, error) {
3740
var h HTTPServer
@@ -50,10 +53,25 @@ func New(options *Options) (*HTTPServer, error) {
5053
if options.Sandbox {
5154
dir = SandboxFileSystem{fs: http.Dir(options.Folder), RootFolder: options.Folder}
5255
}
53-
h.layers = h.loglayer(http.FileServer(dir))
56+
57+
httpHandler := http.FileServer(dir)
58+
addHandler := func(newHandler Middleware) {
59+
httpHandler = newHandler(httpHandler)
60+
}
61+
62+
// middleware
63+
if options.EnableUpload {
64+
addHandler(h.uploadlayer)
65+
}
66+
5467
if options.BasicAuthUsername != "" || options.BasicAuthPassword != "" {
55-
h.layers = h.loglayer(h.basicauthlayer(http.FileServer(dir)))
68+
addHandler(h.basicauthlayer)
5669
}
70+
71+
httpHandler = h.loglayer(httpHandler)
72+
73+
// add handler
74+
h.layers = httpHandler
5775
h.options = options
5876

5977
return &h, nil

pkg/httpserver/loglayer.go

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@ package httpserver
22

33
import (
44
"bytes"
5-
"io/ioutil"
65
"net/http"
76
"net/http/httputil"
8-
"path"
9-
"path/filepath"
107

118
"github.com/projectdiscovery/gologger"
129
)
@@ -23,59 +20,6 @@ func (t *HTTPServer) loglayer(handler http.Handler) http.Handler {
2320
lrw := newLoggingResponseWriter(w)
2421
handler.ServeHTTP(lrw, r)
2522

26-
// Handles file write if enabled
27-
if EnableUpload && r.Method == http.MethodPut {
28-
// sandbox - calcolate absolute path
29-
if t.options.Sandbox {
30-
absPath, err := filepath.Abs(filepath.Join(t.options.Folder, r.URL.Path))
31-
if err != nil {
32-
gologger.Print().Msgf("%s\n", err)
33-
w.WriteHeader(http.StatusBadRequest)
34-
return
35-
}
36-
// check if the path is within the configured folder
37-
pattern := t.options.Folder + string(filepath.Separator) + "*"
38-
matched, err := filepath.Match(pattern, absPath)
39-
if err != nil {
40-
gologger.Print().Msgf("%s\n", err)
41-
w.WriteHeader(http.StatusBadRequest)
42-
return
43-
} else if !matched {
44-
gologger.Print().Msg("pointing to unauthorized directory")
45-
w.WriteHeader(http.StatusBadRequest)
46-
return
47-
}
48-
}
49-
50-
var (
51-
data []byte
52-
err error
53-
)
54-
if t.options.Sandbox {
55-
maxFileSize := toMb(t.options.MaxFileSize)
56-
// check header content length
57-
if r.ContentLength > maxFileSize {
58-
gologger.Print().Msg("request too large")
59-
return
60-
}
61-
// body max length
62-
r.Body = http.MaxBytesReader(w, r.Body, maxFileSize)
63-
}
64-
65-
data, err = ioutil.ReadAll(r.Body)
66-
if err != nil {
67-
gologger.Print().Msgf("%s\n", err)
68-
w.WriteHeader(http.StatusInternalServerError)
69-
return
70-
}
71-
err = handleUpload(t.options.Folder, path.Base(r.URL.Path), data)
72-
if err != nil {
73-
gologger.Print().Msgf("%s\n", err)
74-
w.WriteHeader(http.StatusInternalServerError)
75-
return
76-
}
77-
}
78-
7923
if EnableVerbose {
8024
headers := new(bytes.Buffer)
8125
lrw.Header().Write(headers) //nolint

pkg/httpserver/uploadlayer.go

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,96 @@ package httpserver
33
import (
44
"errors"
55
"io/ioutil"
6+
"net/http"
7+
"os"
8+
"path"
69
"path/filepath"
710
"strings"
11+
12+
"github.com/projectdiscovery/gologger"
813
)
914

15+
// uploadlayer handles PUT requests and save the file to disk
16+
func (t *HTTPServer) uploadlayer(handler http.Handler) http.Handler {
17+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
18+
// Handles file write if enabled
19+
if EnableUpload && r.Method == http.MethodPut {
20+
// sandbox - calcolate absolute path
21+
if t.options.Sandbox {
22+
absPath, err := filepath.Abs(filepath.Join(t.options.Folder, r.URL.Path))
23+
if err != nil {
24+
gologger.Print().Msgf("%s\n", err)
25+
w.WriteHeader(http.StatusBadRequest)
26+
return
27+
}
28+
// check if the path is within the configured folder
29+
pattern := t.options.Folder + string(filepath.Separator) + "*"
30+
matched, err := filepath.Match(pattern, absPath)
31+
if err != nil {
32+
gologger.Print().Msgf("%s\n", err)
33+
w.WriteHeader(http.StatusBadRequest)
34+
return
35+
} else if !matched {
36+
gologger.Print().Msg("pointing to unauthorized directory")
37+
w.WriteHeader(http.StatusBadRequest)
38+
return
39+
}
40+
}
41+
42+
var (
43+
data []byte
44+
err error
45+
)
46+
if t.options.Sandbox {
47+
maxFileSize := toMb(t.options.MaxFileSize)
48+
// check header content length
49+
if r.ContentLength > maxFileSize {
50+
gologger.Print().Msg("request too large")
51+
return
52+
}
53+
// body max length
54+
r.Body = http.MaxBytesReader(w, r.Body, maxFileSize)
55+
}
56+
57+
data, err = ioutil.ReadAll(r.Body)
58+
if err != nil {
59+
gologger.Print().Msgf("%s\n", err)
60+
w.WriteHeader(http.StatusInternalServerError)
61+
return
62+
}
63+
64+
sanitizedPath := filepath.FromSlash(path.Clean("/" + strings.Trim(r.URL.Path, "/")))
65+
66+
err = handleUpload(t.options.Folder, sanitizedPath, data)
67+
if err != nil {
68+
gologger.Print().Msgf("%s\n", err)
69+
w.WriteHeader(http.StatusInternalServerError)
70+
return
71+
} else {
72+
w.WriteHeader(http.StatusCreated)
73+
return
74+
}
75+
}
76+
77+
handler.ServeHTTP(w, r)
78+
})
79+
}
80+
1081
func handleUpload(base, file string, data []byte) error {
1182
// rejects all paths containing a non exhaustive list of invalid characters - This is only a best effort as the tool is meant for development
1283
if strings.ContainsAny(file, "\\`\"':") {
1384
return errors.New("invalid character")
1485
}
1586

16-
// allow upload only in subfolders
17-
rel, err := filepath.Rel(base, file)
18-
if rel == "" || err != nil {
19-
return err
87+
untrustedPath := filepath.Clean(filepath.Join(base, file))
88+
if !strings.HasPrefix(untrustedPath, filepath.Clean(base)) {
89+
return errors.New("invalid path")
90+
}
91+
trustedPath := untrustedPath
92+
93+
if _, err := os.Stat(path.Dir(trustedPath)); os.IsNotExist(err) {
94+
return errors.New("invalid path")
2095
}
2196

22-
return ioutil.WriteFile(file, data, 0655)
97+
return ioutil.WriteFile(trustedPath, data, 0655)
2398
}

0 commit comments

Comments
 (0)