github.com/projectdiscovery/nuclei/v2@v2.9.15/pkg/protocols/file/find.go (about) 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 r.Close() 212 213 var buff [1024]byte 214 if _, err = io.ReadFull(r, buff[:]); err != nil { 215 return nil, err 216 } 217 return buff[:], nil 218 } 219 220 func (request *Request) isAnyChunkInDenyList(path string, splitWithUtils bool) (string, bool) { 221 var paths []string 222 223 if splitWithUtils { 224 pathInfo, _ := folderutil.NewPathInfo(path) 225 paths, _ = pathInfo.Paths() 226 } else { 227 pathTree := strings.Split(path, string(os.PathSeparator)) 228 for i := range pathTree { 229 paths = append(paths, filepath.Join(pathTree[:i]...)) 230 } 231 } 232 for _, pathTreeItem := range paths { 233 if _, ok := request.denyList[pathTreeItem]; ok { 234 return pathTreeItem, true 235 } 236 } 237 238 return "", false 239 }