Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/file/find.go
2070 views
1
package file
2
3
import (
4
"io"
5
"io/fs"
6
"os"
7
"path/filepath"
8
"strings"
9
10
"github.com/pkg/errors"
11
"github.com/projectdiscovery/gologger"
12
fileutil "github.com/projectdiscovery/utils/file"
13
folderutil "github.com/projectdiscovery/utils/folder"
14
)
15
16
// getInputPaths parses the specified input paths and returns a compiled
17
// list of finished absolute paths to the files evaluating any allowlist, denylist,
18
// glob, file or folders, etc.
19
func (request *Request) getInputPaths(target string, callback func(string)) error {
20
processed := make(map[string]struct{})
21
22
// Template input includes a wildcard
23
if strings.Contains(target, "*") && !request.NoRecursive {
24
if err := request.findGlobPathMatches(target, processed, callback); err != nil {
25
return errors.Wrap(err, "could not find glob matches")
26
}
27
return nil
28
}
29
30
// Template input is either a file or a directory
31
file, err := request.findFileMatches(target, processed, callback)
32
if err != nil {
33
return errors.Wrap(err, "could not find file")
34
}
35
if file {
36
return nil
37
}
38
if request.NoRecursive {
39
return nil // we don't process dirs in no-recursive mode
40
}
41
// Recursively walk down the Templates directory and run all
42
// the template file checks
43
if err := request.findDirectoryMatches(target, processed, callback); err != nil {
44
return errors.Wrap(err, "could not find directory matches")
45
}
46
return nil
47
}
48
49
// findGlobPathMatches returns the matched files from a glob path
50
func (request *Request) findGlobPathMatches(absPath string, processed map[string]struct{}, callback func(string)) error {
51
matches, err := filepath.Glob(absPath)
52
if err != nil {
53
return errors.Errorf("wildcard found, but unable to glob: %s\n", err)
54
}
55
for _, match := range matches {
56
if !request.validatePath(absPath, match, false) {
57
continue
58
}
59
if _, ok := processed[match]; !ok {
60
processed[match] = struct{}{}
61
callback(match)
62
}
63
}
64
return nil
65
}
66
67
// findFileMatches finds if a path is an absolute file. If the path
68
// is a file, it returns true otherwise false with no errors.
69
func (request *Request) findFileMatches(absPath string, processed map[string]struct{}, callback func(string)) (bool, error) {
70
info, err := os.Stat(absPath)
71
if err != nil {
72
return false, err
73
}
74
if !info.Mode().IsRegular() {
75
return false, nil
76
}
77
if _, ok := processed[absPath]; !ok {
78
if !request.validatePath(absPath, absPath, false) {
79
return false, nil
80
}
81
processed[absPath] = struct{}{}
82
callback(absPath)
83
}
84
return true, nil
85
}
86
87
// findDirectoryMatches finds matches for templates from a directory
88
func (request *Request) findDirectoryMatches(absPath string, processed map[string]struct{}, callback func(string)) error {
89
err := filepath.WalkDir(
90
absPath,
91
func(path string, d fs.DirEntry, err error) error {
92
// continue on errors
93
if err != nil {
94
return nil
95
}
96
if d.IsDir() {
97
return nil
98
}
99
if !request.validatePath(absPath, path, false) {
100
return nil
101
}
102
if _, ok := processed[path]; !ok {
103
callback(path)
104
processed[path] = struct{}{}
105
}
106
return nil
107
},
108
)
109
return err
110
}
111
112
// validatePath validates a file path for blacklist and whitelist options
113
func (request *Request) validatePath(absPath, item string, inArchive bool) bool {
114
extension := filepath.Ext(item)
115
// extension check
116
if len(request.extensions) > 0 {
117
if _, ok := request.extensions[extension]; ok {
118
return true
119
} else if !request.allExtensions {
120
return false
121
}
122
}
123
124
var (
125
fileExists bool
126
dataChunk []byte
127
)
128
if !inArchive && request.MimeType {
129
// mime type check
130
// read first bytes to infer runtime type
131
fileExists = fileutil.FileExists(item)
132
if fileExists {
133
dataChunk, _ = readChunk(item)
134
if len(request.mimeTypesChecks) > 0 && matchAnyMimeTypes(dataChunk, request.mimeTypesChecks) {
135
return true
136
}
137
}
138
}
139
140
if matchingRule, ok := request.isInDenyList(absPath, item); ok {
141
gologger.Verbose().Msgf("Ignoring path %s due to denylist item %s\n", item, matchingRule)
142
return false
143
}
144
145
// denied mime type checks
146
if !inArchive && request.MimeType && fileExists {
147
if len(request.denyMimeTypesChecks) > 0 && matchAnyMimeTypes(dataChunk, request.denyMimeTypesChecks) {
148
return false
149
}
150
}
151
152
return true
153
}
154
155
func (request *Request) isInDenyList(absPath, item string) (string, bool) {
156
extension := filepath.Ext(item)
157
// check for possible deny rules
158
// - extension is in deny list
159
if _, ok := request.denyList[extension]; ok {
160
return extension, true
161
}
162
163
// - full path is in deny list
164
if _, ok := request.denyList[item]; ok {
165
return item, true
166
}
167
168
// file is in a forbidden subdirectory
169
filename := filepath.Base(item)
170
fullPathWithoutFilename := strings.TrimSuffix(item, filename)
171
relativePathWithFilename := strings.TrimPrefix(item, absPath)
172
relativePath := strings.TrimSuffix(relativePathWithFilename, filename)
173
174
// - filename is in deny list
175
if _, ok := request.denyList[filename]; ok {
176
return filename, true
177
}
178
179
// - relative path is in deny list
180
if _, ok := request.denyList[relativePath]; ok {
181
return relativePath, true
182
}
183
184
// relative path + filename are in the forbidden list
185
if _, ok := request.denyList[relativePathWithFilename]; ok {
186
return relativePathWithFilename, true
187
}
188
189
// root path + relative path are in the forbidden list
190
if _, ok := request.denyList[fullPathWithoutFilename]; ok {
191
return fullPathWithoutFilename, true
192
}
193
194
// check any progressive combined part of the relative and absolute path with filename for matches within rules prefixes
195
if pathTreeItem, ok := request.isAnyChunkInDenyList(relativePath, false); ok {
196
return pathTreeItem, true
197
}
198
if pathTreeItem, ok := request.isAnyChunkInDenyList(item, true); ok {
199
return pathTreeItem, true
200
}
201
202
return "", false
203
}
204
205
func readChunk(fileName string) ([]byte, error) {
206
r, err := os.Open(fileName)
207
if err != nil {
208
return nil, err
209
}
210
211
defer func() {
212
_ = r.Close()
213
}()
214
215
var buff [1024]byte
216
if _, err = io.ReadFull(r, buff[:]); err != nil {
217
return nil, err
218
}
219
return buff[:], nil
220
}
221
222
func (request *Request) isAnyChunkInDenyList(path string, splitWithUtils bool) (string, bool) {
223
var paths []string
224
225
if splitWithUtils {
226
pathInfo, _ := folderutil.NewPathInfo(path)
227
paths, _ = pathInfo.Paths()
228
} else {
229
pathTree := strings.Split(path, string(os.PathSeparator))
230
for i := range pathTree {
231
paths = append(paths, filepath.Join(pathTree[:i]...))
232
}
233
}
234
for _, pathTreeItem := range paths {
235
if _, ok := request.denyList[pathTreeItem]; ok {
236
return pathTreeItem, true
237
}
238
}
239
240
return "", false
241
}
242
243