2021-01-01 09:58:28 +00:00
package file
import (
2022-02-23 12:54:46 +00:00
"bufio"
2021-10-30 10:17:47 +00:00
"encoding/hex"
2022-02-23 12:54:46 +00:00
"io"
2021-01-01 09:58:28 +00:00
"os"
2022-03-01 17:59:33 +00:00
"path/filepath"
2022-02-10 10:29:05 +00:00
"strings"
2021-01-01 09:58:28 +00:00
2022-02-23 12:54:46 +00:00
"github.com/docker/go-units"
2022-03-01 17:59:33 +00:00
"github.com/mholt/archiver"
2021-01-01 09:58:28 +00:00
"github.com/pkg/errors"
2021-09-29 16:43:46 +00:00
2021-01-01 09:58:28 +00:00
"github.com/projectdiscovery/gologger"
2023-10-17 12:14:13 +00:00
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter"
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
2022-11-06 20:24:23 +00:00
sliceutil "github.com/projectdiscovery/utils/slice"
2024-04-03 15:50:57 +00:00
syncutil "github.com/projectdiscovery/utils/sync"
2021-01-01 09:58:28 +00:00
)
var _ protocols . Request = & Request { }
2021-11-03 14:23:45 +00:00
// Type returns the type of the protocol request
func ( request * Request ) Type ( ) templateTypes . ProtocolType {
return templateTypes . FileProtocol
}
2022-02-24 23:55:05 +00:00
type FileMatch struct {
Data string
Line int
ByteIndex int
Match bool
Extract bool
Expr string
2022-02-25 07:59:10 +00:00
Raw string
2022-02-24 23:55:05 +00:00
}
2022-12-20 20:59:28 +00:00
var errEmptyResult = errors . New ( "Empty result" )
2022-03-01 17:59:33 +00:00
2021-01-01 09:58:28 +00:00
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
2022-10-03 10:12:20 +00:00
func ( request * Request ) ExecuteWithResults ( input * contextargs . Context , metadata , previous output . InternalEvent , callback protocols . OutputEventCallback ) error {
2024-04-03 15:50:57 +00:00
wg , err := syncutil . New ( syncutil . WithSize ( request . options . Options . BulkSize ) )
if err != nil {
return err
}
err = request . getInputPaths ( input . MetaInput . Input , func ( filePath string ) {
2021-01-14 17:13:08 +00:00
wg . Add ( )
2022-03-01 17:59:33 +00:00
func ( filePath string ) {
2021-01-14 17:13:08 +00:00
defer wg . Done ( )
2022-03-01 17:59:33 +00:00
archiveReader , _ := archiver . ByExtension ( filePath )
switch {
case archiveReader != nil :
switch archiveInstance := archiveReader . ( type ) {
case archiver . Walker :
err := archiveInstance . Walk ( filePath , func ( file archiver . File ) error {
2022-03-22 13:18:01 +00:00
if ! request . validatePath ( "/" , file . Name ( ) , true ) {
2022-03-01 17:59:33 +00:00
return nil
}
2022-03-22 09:52:57 +00:00
// every new file in the compressed multi-file archive counts 1
request . options . Progress . AddToTotal ( 1 )
2022-03-01 17:59:33 +00:00
archiveFileName := filepath . Join ( filePath , file . Name ( ) )
2023-08-31 12:33:01 +00:00
event , fileMatches , err := request . processReader ( file . ReadCloser , archiveFileName , input , file . Size ( ) , previous )
2022-03-01 17:59:33 +00:00
if err != nil {
2022-12-20 20:59:28 +00:00
if errors . Is ( err , errEmptyResult ) {
2022-03-22 09:52:57 +00:00
// no matches but one file elaborated
request . options . Progress . IncrementRequests ( )
2022-03-01 17:59:33 +00:00
return nil
}
2022-03-22 09:52:57 +00:00
gologger . Error ( ) . Msgf ( "%s\n" , err )
// error while elaborating the file
request . options . Progress . IncrementFailedRequestsBy ( 1 )
2022-03-01 17:59:33 +00:00
return err
}
defer file . Close ( )
dumpResponse ( event , request . options , fileMatches , filePath )
callback ( event )
2022-03-22 09:52:57 +00:00
// file elaborated and matched
2022-03-01 17:59:33 +00:00
request . options . Progress . IncrementRequests ( )
return nil
} )
if err != nil {
gologger . Error ( ) . Msgf ( "%s\n" , err )
return
}
case archiver . Decompressor :
2022-03-22 09:52:57 +00:00
// compressed archive - contains only one file => increments the counter by 1
request . options . Progress . AddToTotal ( 1 )
2022-03-01 17:59:33 +00:00
file , err := os . Open ( filePath )
if err != nil {
gologger . Error ( ) . Msgf ( "%s\n" , err )
2022-03-22 09:52:57 +00:00
// error while elaborating the file
request . options . Progress . IncrementFailedRequestsBy ( 1 )
2022-03-01 17:59:33 +00:00
return
}
defer file . Close ( )
fileStat , _ := file . Stat ( )
tmpFileOut , err := os . CreateTemp ( "" , "" )
if err != nil {
gologger . Error ( ) . Msgf ( "%s\n" , err )
2022-03-22 09:52:57 +00:00
// error while elaborating the file
request . options . Progress . IncrementFailedRequestsBy ( 1 )
2022-03-01 17:59:33 +00:00
return
}
defer tmpFileOut . Close ( )
defer os . RemoveAll ( tmpFileOut . Name ( ) )
if err := archiveInstance . Decompress ( file , tmpFileOut ) ; err != nil {
gologger . Error ( ) . Msgf ( "%s\n" , err )
2022-03-22 09:52:57 +00:00
// error while elaborating the file
request . options . Progress . IncrementFailedRequestsBy ( 1 )
2022-03-01 17:59:33 +00:00
return
}
_ = tmpFileOut . Sync ( )
// rewind the file
_ , _ = tmpFileOut . Seek ( 0 , 0 )
2023-08-31 12:33:01 +00:00
event , fileMatches , err := request . processReader ( tmpFileOut , filePath , input , fileStat . Size ( ) , previous )
2022-03-01 17:59:33 +00:00
if err != nil {
2022-12-20 20:59:28 +00:00
if errors . Is ( err , errEmptyResult ) {
2022-03-22 09:52:57 +00:00
// no matches but one file elaborated
request . options . Progress . IncrementRequests ( )
return
2022-03-01 17:59:33 +00:00
}
2022-03-22 09:52:57 +00:00
gologger . Error ( ) . Msgf ( "%s\n" , err )
// error while elaborating the file
request . options . Progress . IncrementFailedRequestsBy ( 1 )
2022-03-01 17:59:33 +00:00
return
}
dumpResponse ( event , request . options , fileMatches , filePath )
callback ( event )
2022-03-22 09:52:57 +00:00
// file elaborated and matched
2022-03-01 17:59:33 +00:00
request . options . Progress . IncrementRequests ( )
}
default :
2022-03-22 09:52:57 +00:00
// normal file - increments the counter by 1
request . options . Progress . AddToTotal ( 1 )
2023-08-31 12:33:01 +00:00
event , fileMatches , err := request . processFile ( filePath , input , previous )
2022-03-01 17:59:33 +00:00
if err != nil {
2022-12-20 20:59:28 +00:00
if errors . Is ( err , errEmptyResult ) {
2022-03-22 09:52:57 +00:00
// no matches but one file elaborated
request . options . Progress . IncrementRequests ( )
return
2022-03-01 17:59:33 +00:00
}
2022-03-22 09:52:57 +00:00
gologger . Error ( ) . Msgf ( "%s\n" , err )
// error while elaborating the file
request . options . Progress . IncrementFailedRequestsBy ( 1 )
2022-03-01 17:59:33 +00:00
return
}
dumpResponse ( event , request . options , fileMatches , filePath )
callback ( event )
2022-03-22 09:52:57 +00:00
// file elaborated and matched
2022-03-01 17:59:33 +00:00
request . options . Progress . IncrementRequests ( )
2021-01-14 17:13:08 +00:00
}
2022-03-01 17:59:33 +00:00
} ( filePath )
2021-01-01 09:58:28 +00:00
} )
2022-03-01 17:59:33 +00:00
2021-01-14 17:13:08 +00:00
wg . Wait ( )
2021-01-01 09:58:28 +00:00
if err != nil {
2022-11-09 13:18:56 +00:00
request . options . Output . Request ( request . options . TemplatePath , input . MetaInput . Input , request . Type ( ) . String ( ) , err )
2021-10-01 11:30:04 +00:00
request . options . Progress . IncrementFailedRequestsBy ( 1 )
2021-01-01 14:06:21 +00:00
return errors . Wrap ( err , "could not send file request" )
2021-01-01 09:58:28 +00:00
}
2021-01-01 14:06:21 +00:00
return nil
2021-01-01 09:58:28 +00:00
}
2021-10-30 10:17:47 +00:00
2023-08-31 12:33:01 +00:00
func ( request * Request ) processFile ( filePath string , input * contextargs . Context , previousInternalEvent output . InternalEvent ) ( * output . InternalWrappedEvent , [ ] FileMatch , error ) {
2022-03-01 17:59:33 +00:00
file , err := os . Open ( filePath )
if err != nil {
return nil , nil , errors . Errorf ( "Could not open file path %s: %s\n" , filePath , err )
}
defer file . Close ( )
stat , err := file . Stat ( )
if err != nil {
return nil , nil , errors . Errorf ( "Could not stat file path %s: %s\n" , filePath , err )
}
if stat . Size ( ) >= request . maxSize {
maxSizeString := units . HumanSize ( float64 ( request . maxSize ) )
gologger . Verbose ( ) . Msgf ( "Limiting %s processed data to %s bytes: exceeded max size\n" , filePath , maxSizeString )
}
2022-03-03 09:26:43 +00:00
return request . processReader ( file , filePath , input , stat . Size ( ) , previousInternalEvent )
2022-03-01 17:59:33 +00:00
}
2023-08-31 12:33:01 +00:00
func ( request * Request ) processReader ( reader io . Reader , filePath string , input * contextargs . Context , totalBytes int64 , previousInternalEvent output . InternalEvent ) ( * output . InternalWrappedEvent , [ ] FileMatch , error ) {
2022-03-01 17:59:33 +00:00
fileReader := io . LimitReader ( reader , request . maxSize )
2022-03-03 09:26:43 +00:00
fileMatches , opResult := request . findMatchesWithReader ( fileReader , input , filePath , totalBytes , previousInternalEvent )
2022-03-03 06:18:35 +00:00
if opResult == nil && len ( fileMatches ) == 0 {
2022-12-20 20:59:28 +00:00
return nil , nil , errEmptyResult
2022-03-01 17:59:33 +00:00
}
// build event structure to interface with internal logic
2023-08-31 12:33:01 +00:00
return request . buildEvent ( input . MetaInput . Input , filePath , fileMatches , opResult , previousInternalEvent ) , fileMatches , nil
2022-03-01 17:59:33 +00:00
}
2023-08-31 12:33:01 +00:00
func ( request * Request ) findMatchesWithReader ( reader io . Reader , input * contextargs . Context , filePath string , totalBytes int64 , previous output . InternalEvent ) ( [ ] FileMatch , * operators . Result ) {
2022-02-26 07:02:16 +00:00
var bytesCount , linesCount , wordsCount int
isResponseDebug := request . options . Options . Debug || request . options . Options . DebugResponse
2022-03-03 09:26:43 +00:00
totalBytesString := units . BytesSize ( float64 ( totalBytes ) )
2023-06-13 15:24:23 +00:00
// we are forced to check if the whole file needs to be elaborated
// - matchers-condition option set to AND
hasAndCondition := request . CompiledOperators . GetMatchersCondition ( ) == matchers . ANDCondition
// - any matcher has AND condition
for _ , matcher := range request . CompiledOperators . Matchers {
if hasAndCondition {
break
}
if matcher . GetCondition ( ) == matchers . ANDCondition {
hasAndCondition = true
}
}
2022-02-26 07:02:16 +00:00
scanner := bufio . NewScanner ( reader )
buffer := [ ] byte { }
2023-06-13 15:24:23 +00:00
if hasAndCondition {
2022-05-30 08:15:28 +00:00
scanner . Buffer ( buffer , int ( defaultMaxReadSize ) )
scanner . Split ( func ( data [ ] byte , atEOF bool ) ( advance int , token [ ] byte , err error ) {
defaultMaxReadSizeInt := int ( defaultMaxReadSize )
if len ( data ) > defaultMaxReadSizeInt {
return defaultMaxReadSizeInt , data [ 0 : defaultMaxReadSizeInt ] , nil
}
if ! atEOF {
return 0 , nil , nil
}
return len ( data ) , data , bufio . ErrFinalToken
} )
} else {
scanner . Buffer ( buffer , int ( chunkSize ) )
}
2022-02-26 07:02:16 +00:00
var fileMatches [ ] FileMatch
2022-02-26 08:06:43 +00:00
var opResult * operators . Result
2022-02-26 07:02:16 +00:00
for scanner . Scan ( ) {
lineContent := scanner . Text ( )
n := len ( lineContent )
// update counters
currentBytes := bytesCount + n
processedBytes := units . BytesSize ( float64 ( currentBytes ) )
2022-03-03 09:26:43 +00:00
gologger . Verbose ( ) . Msgf ( "[%s] Processing file %s chunk %s/%s" , request . options . TemplateID , filePath , processedBytes , totalBytesString )
2023-08-31 12:33:01 +00:00
dslMap := request . responseToDSLMap ( lineContent , input . MetaInput . Input , filePath )
2022-02-26 08:06:43 +00:00
for k , v := range previous {
dslMap [ k ] = v
}
2023-06-09 14:22:56 +00:00
// add template context variables to DSL map
2024-01-18 00:23:42 +00:00
if request . options . HasTemplateCtx ( input . MetaInput ) {
dslMap = generators . MergeMaps ( dslMap , request . options . GetTemplateCtx ( input . MetaInput ) . GetAll ( ) )
}
2022-02-26 08:06:43 +00:00
discardEvent := eventcreator . CreateEvent ( request , dslMap , isResponseDebug )
newOpResult := discardEvent . OperatorsResult
if newOpResult != nil {
if opResult == nil {
opResult = newOpResult
} else {
opResult . Merge ( newOpResult )
}
if newOpResult . Matched || newOpResult . Extracted {
if newOpResult . Extracts != nil {
for expr , extracts := range newOpResult . Extracts {
for _ , extract := range extracts {
fileMatches = append ( fileMatches , FileMatch {
Data : extract ,
Extract : true ,
Line : linesCount + 1 ,
ByteIndex : bytesCount ,
Expr : expr ,
Raw : lineContent ,
} )
}
2022-02-26 07:02:16 +00:00
}
}
2022-02-26 08:06:43 +00:00
if newOpResult . Matches != nil {
for expr , matches := range newOpResult . Matches {
for _ , match := range matches {
fileMatches = append ( fileMatches , FileMatch {
Data : match ,
Match : true ,
Line : linesCount + 1 ,
ByteIndex : bytesCount ,
Expr : expr ,
Raw : lineContent ,
} )
}
2022-02-26 07:02:16 +00:00
}
}
2022-03-04 08:28:58 +00:00
for _ , outputExtract := range newOpResult . OutputExtracts {
fileMatches = append ( fileMatches , FileMatch {
Data : outputExtract ,
Match : true ,
Line : linesCount + 1 ,
ByteIndex : bytesCount ,
Expr : outputExtract ,
Raw : lineContent ,
} )
}
2022-02-26 07:02:16 +00:00
}
}
currentLinesCount := 1 + strings . Count ( lineContent , "\n" )
linesCount += currentLinesCount
wordsCount += strings . Count ( lineContent , " " )
bytesCount = currentBytes
}
2022-02-26 08:06:43 +00:00
return fileMatches , opResult
2022-02-26 07:02:16 +00:00
}
2022-02-26 08:06:43 +00:00
func ( request * Request ) buildEvent ( input , filePath string , fileMatches [ ] FileMatch , operatorResult * operators . Result , previous output . InternalEvent ) * output . InternalWrappedEvent {
2022-02-26 07:02:16 +00:00
exprLines := make ( map [ string ] [ ] int )
exprBytes := make ( map [ string ] [ ] int )
internalEvent := request . responseToDSLMap ( "" , input , filePath )
2022-02-26 08:06:43 +00:00
for k , v := range previous {
internalEvent [ k ] = v
}
2022-02-26 07:02:16 +00:00
for _ , fileMatch := range fileMatches {
exprLines [ fileMatch . Expr ] = append ( exprLines [ fileMatch . Expr ] , fileMatch . Line )
exprBytes [ fileMatch . Expr ] = append ( exprBytes [ fileMatch . Expr ] , fileMatch . ByteIndex )
}
2022-02-26 08:06:43 +00:00
event := eventcreator . CreateEventWithOperatorResults ( request , internalEvent , operatorResult )
2022-06-02 11:36:50 +00:00
// Annotate with line numbers if asked by the user
if request . options . Options . ShowMatchLine {
for _ , result := range event . Results {
switch {
case result . MatcherName != "" :
result . Lines = exprLines [ result . MatcherName ]
case result . ExtractorName != "" :
result . Lines = exprLines [ result . ExtractorName ]
default :
for _ , extractedResult := range result . ExtractedResults {
result . Lines = append ( result . Lines , exprLines [ extractedResult ] ... )
}
2022-03-04 08:28:58 +00:00
}
2022-10-25 19:01:42 +00:00
result . Lines = sliceutil . Dedupe ( result . Lines )
2022-02-26 08:06:43 +00:00
}
2022-02-26 07:02:16 +00:00
}
2022-02-26 08:06:43 +00:00
return event
2022-02-26 07:02:16 +00:00
}
2023-05-31 20:58:10 +00:00
func dumpResponse ( event * output . InternalWrappedEvent , requestOptions * protocols . ExecutorOptions , filematches [ ] FileMatch , filePath string ) {
2021-11-01 18:45:54 +00:00
cliOptions := requestOptions . Options
if cliOptions . Debug || cliOptions . DebugResponse {
2022-02-24 23:55:05 +00:00
for _ , fileMatch := range filematches {
2022-02-25 07:59:10 +00:00
lineContent := fileMatch . Raw
2022-02-24 23:55:05 +00:00
hexDump := false
2022-02-25 07:59:10 +00:00
if responsehighlighter . HasBinaryContent ( lineContent ) {
2022-02-24 23:55:05 +00:00
hexDump = true
2022-02-25 07:59:10 +00:00
lineContent = hex . Dump ( [ ] byte ( lineContent ) )
2022-02-24 23:55:05 +00:00
}
2022-02-25 07:59:10 +00:00
highlightedResponse := responsehighlighter . Highlight ( event . OperatorsResult , lineContent , cliOptions . NoColor , hexDump )
2022-02-24 23:55:05 +00:00
gologger . Debug ( ) . Msgf ( "[%s] Dumped match/extract file snippet for %s at line %d\n\n%s" , requestOptions . TemplateID , filePath , fileMatch . Line , highlightedResponse )
2021-10-30 10:17:47 +00:00
}
}
}