2021-01-01 15:28:28 +05:30
package file
import (
2022-02-23 13:54:46 +01:00
"bufio"
2021-10-30 13:17:47 +03:00
"encoding/hex"
2022-02-23 13:54:46 +01:00
"io"
2021-01-01 15:28:28 +05:30
"os"
2022-02-10 15:59:05 +05:30
"strings"
2022-02-25 01:49:14 +01:00
"time"
2021-01-01 15:28:28 +05:30
2022-02-23 13:54:46 +01:00
"github.com/docker/go-units"
2021-01-01 15:28:28 +05:30
"github.com/pkg/errors"
2021-09-29 19:43:46 +03:00
"github.com/remeh/sizedwaitgroup"
2021-01-01 15:28:28 +05:30
"github.com/projectdiscovery/gologger"
2022-02-25 01:49:14 +01:00
"github.com/projectdiscovery/nuclei/v2/pkg/model"
2022-02-23 23:32:25 +01:00
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
2021-01-01 15:28:28 +05:30
"github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
2021-10-01 14:24:45 +03:00
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter"
2021-11-03 19:53:45 +05:30
templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
2022-02-25 01:49:14 +01:00
"github.com/projectdiscovery/nuclei/v2/pkg/types"
2021-01-01 15:28:28 +05:30
)
var _ protocols . Request = & Request { }
2021-11-03 19:53:45 +05:30
// Type returns the type of the protocol request
func ( request * Request ) Type ( ) templateTypes . ProtocolType {
return templateTypes . FileProtocol
}
2022-02-25 00:55:05 +01:00
type FileMatch struct {
Data string
Line int
ByteIndex int
Match bool
Extract bool
Expr string
2022-02-25 08:59:10 +01:00
Raw string
2022-02-25 00:55:05 +01:00
}
2021-01-01 15:28:28 +05:30
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
2022-02-23 13:54:46 +01:00
func ( request * Request ) ExecuteWithResults ( input string , metadata , previous output . InternalEvent , callback protocols . OutputEventCallback ) error {
2021-10-01 14:30:04 +03:00
wg := sizedwaitgroup . New ( request . options . Options . BulkSize )
2021-01-14 22:43:08 +05:30
2021-10-01 14:30:04 +03:00
err := request . getInputPaths ( input , func ( data string ) {
2022-02-10 15:59:05 +05:30
request . options . Progress . AddToTotal ( 1 )
2021-01-14 22:43:08 +05:30
wg . Add ( )
2021-09-29 19:43:46 +03:00
go func ( filePath string ) {
2021-01-14 22:43:08 +05:30
defer wg . Done ( )
2021-09-29 19:43:46 +03:00
file , err := os . Open ( filePath )
2021-01-14 22:43:08 +05:30
if err != nil {
2021-09-29 19:43:46 +03:00
gologger . Error ( ) . Msgf ( "Could not open file path %s: %s\n" , filePath , err )
2021-01-14 22:43:08 +05:30
return
}
defer file . Close ( )
2021-01-01 15:28:28 +05:30
2021-01-14 22:43:08 +05:30
stat , err := file . Stat ( )
if err != nil {
2021-09-29 19:43:46 +03:00
gologger . Error ( ) . Msgf ( "Could not stat file path %s: %s\n" , filePath , err )
2021-01-14 22:43:08 +05:30
return
}
2022-02-23 13:54:46 +01:00
if stat . Size ( ) >= request . maxSize {
gologger . Verbose ( ) . Msgf ( "Limiting %s processed data to %s bytes: exceeded max size\n" , filePath , units . HumanSize ( float64 ( request . maxSize ) ) )
2021-01-16 14:10:24 +05:30
}
2022-02-23 13:54:46 +01:00
totalBytes := units . BytesSize ( float64 ( stat . Size ( ) ) )
fileReader := io . LimitReader ( file , request . maxSize )
var bytesCount , linesCount , wordsCount int
2022-02-23 23:32:25 +01:00
isResponseDebug := request . options . Options . Debug || request . options . Options . DebugResponse
2022-02-23 13:54:46 +01:00
scanner := bufio . NewScanner ( fileReader )
buffer := [ ] byte { }
scanner . Buffer ( buffer , int ( chunkSize ) )
2022-02-23 23:32:25 +01:00
2022-02-25 00:55:05 +01:00
var fileMatches [ ] FileMatch
2022-02-25 01:49:14 +01:00
exprLines := make ( map [ string ] [ ] int )
exprBytes := make ( map [ string ] [ ] int )
2022-02-23 13:54:46 +01:00
for scanner . Scan ( ) {
2022-02-25 08:59:10 +01:00
lineContent := scanner . Text ( )
n := len ( lineContent )
2022-02-23 13:54:46 +01:00
// update counters
currentBytes := bytesCount + n
processedBytes := units . BytesSize ( float64 ( currentBytes ) )
gologger . Verbose ( ) . Msgf ( "[%s] Processing file %s chunk %s/%s" , request . options . TemplateID , filePath , processedBytes , totalBytes )
2022-02-25 00:55:05 +01:00
dslMap := request . responseToDSLMap ( & fileStatus {
2022-02-25 08:59:10 +01:00
raw : lineContent ,
2022-02-23 13:54:46 +01:00
inputFilePath : input ,
matchedFileName : filePath ,
lines : linesCount ,
words : wordsCount ,
bytes : bytesCount ,
} )
2021-01-01 15:28:28 +05:30
2022-02-25 00:55:05 +01:00
if parts , ok := request . CompiledOperators . Execute ( dslMap , request . Match , request . Extract , isResponseDebug ) ; parts != nil && ok {
if parts . Extracts != nil {
for expr , extracts := range parts . Extracts {
for _ , extract := range extracts {
fileMatches = append ( fileMatches , FileMatch {
Data : extract ,
Extract : true ,
Line : linesCount + 1 ,
ByteIndex : bytesCount ,
Expr : expr ,
2022-02-25 08:59:10 +01:00
Raw : lineContent ,
2022-02-25 00:55:05 +01:00
} )
}
}
}
if parts . Matches != nil {
for expr , matches := range parts . Matches {
for _ , match := range matches {
fileMatches = append ( fileMatches , FileMatch {
Data : match ,
Match : true ,
Line : linesCount + 1 ,
ByteIndex : bytesCount ,
Expr : expr ,
2022-02-25 08:59:10 +01:00
Raw : lineContent ,
2022-02-25 00:55:05 +01:00
} )
}
}
2022-02-23 23:32:25 +01:00
}
}
2021-10-01 14:24:45 +03:00
2022-02-25 08:59:10 +01:00
currentLinesCount := 1 + strings . Count ( lineContent , "\n" )
2022-02-23 13:54:46 +01:00
linesCount += currentLinesCount
2022-02-25 08:59:10 +01:00
wordsCount += strings . Count ( lineContent , " " )
2022-02-23 13:54:46 +01:00
bytesCount = currentBytes
2022-02-23 23:32:25 +01:00
2022-02-23 13:54:46 +01:00
}
2022-02-25 00:55:05 +01:00
2022-02-25 08:59:10 +01:00
// build event structure to interface with internal logic
2022-02-25 01:49:14 +01:00
internalEvent := request . responseToDSLMap ( & fileStatus {
inputFilePath : input ,
matchedFileName : filePath ,
} )
operatorResult := & operators . Result { }
for _ , fileMatch := range fileMatches {
operatorResult . Matched = operatorResult . Matched || fileMatch . Match
operatorResult . Extracted = operatorResult . Extracted || fileMatch . Extract
switch {
case fileMatch . Extract :
if operatorResult . Extracts == nil {
operatorResult . Extracts = make ( map [ string ] [ ] string )
}
if _ , ok := operatorResult . Extracts [ fileMatch . Expr ] ; ! ok {
operatorResult . Extracts [ fileMatch . Expr ] = [ ] string { fileMatch . Data }
} else {
operatorResult . Extracts [ fileMatch . Expr ] = append ( operatorResult . Extracts [ fileMatch . Expr ] , fileMatch . Data )
}
operatorResult . OutputExtracts = append ( operatorResult . OutputExtracts , fileMatch . Data )
2022-02-25 12:51:28 +01:00
if operatorResult . Extracts == nil {
operatorResult . OutputUnique = make ( map [ string ] struct { } )
}
operatorResult . OutputUnique [ fileMatch . Data ] = struct { } { }
2022-02-25 01:49:14 +01:00
case fileMatch . Match :
if operatorResult . Matches == nil {
operatorResult . Matches = make ( map [ string ] [ ] string )
}
if _ , ok := operatorResult . Matches [ fileMatch . Expr ] ; ! ok {
operatorResult . Matches [ fileMatch . Expr ] = [ ] string { fileMatch . Data }
} else {
operatorResult . Matches [ fileMatch . Expr ] = append ( operatorResult . Matches [ fileMatch . Expr ] , fileMatch . Data )
}
}
exprLines [ fileMatch . Expr ] = append ( exprLines [ fileMatch . Expr ] , fileMatch . Line )
exprBytes [ fileMatch . Expr ] = append ( exprBytes [ fileMatch . Expr ] , fileMatch . ByteIndex )
}
2022-02-25 00:55:05 +01:00
2022-02-25 01:49:14 +01:00
// build results
var results [ ] * output . ResultEvent
for expr , items := range operatorResult . Matches {
results = append ( results , & output . ResultEvent {
MatcherStatus : true ,
TemplateID : types . ToString ( internalEvent [ "template-id" ] ) ,
TemplatePath : types . ToString ( internalEvent [ "template-path" ] ) ,
Info : internalEvent [ "template-info" ] . ( model . Info ) ,
Type : types . ToString ( internalEvent [ "type" ] ) ,
Path : types . ToString ( internalEvent [ "path" ] ) ,
2022-02-25 02:12:54 +01:00
Matched : types . ToString ( internalEvent [ "path" ] ) ,
2022-02-25 01:49:14 +01:00
Host : types . ToString ( internalEvent [ "host" ] ) ,
ExtractedResults : items ,
// Response: types.ToString(wrapped.InternalEvent["raw"]),
2022-02-25 08:59:10 +01:00
Timestamp : time . Now ( ) ,
Lines : exprLines [ expr ] ,
MatcherName : expr ,
2022-02-25 01:49:14 +01:00
} )
}
for expr , items := range operatorResult . Extracts {
results = append ( results , & output . ResultEvent {
MatcherStatus : true ,
TemplateID : types . ToString ( internalEvent [ "template-id" ] ) ,
TemplatePath : types . ToString ( internalEvent [ "template-path" ] ) ,
Info : internalEvent [ "template-info" ] . ( model . Info ) ,
Type : types . ToString ( internalEvent [ "type" ] ) ,
Path : types . ToString ( internalEvent [ "path" ] ) ,
2022-02-25 02:12:54 +01:00
Matched : types . ToString ( internalEvent [ "matched" ] ) ,
2022-02-25 01:49:14 +01:00
Host : types . ToString ( internalEvent [ "host" ] ) ,
ExtractedResults : items ,
Lines : exprLines [ expr ] ,
2022-02-25 02:12:54 +01:00
ExtractorName : expr ,
2022-02-25 01:49:14 +01:00
// FileToIndexPosition: exprBytes,
Timestamp : time . Now ( ) ,
} )
}
event := & output . InternalWrappedEvent {
InternalEvent : internalEvent ,
Results : results ,
OperatorsResult : operatorResult ,
}
2022-02-25 08:59:10 +01:00
dumpResponse ( event , request . options , fileMatches , filePath )
2022-02-25 01:49:14 +01:00
callback ( event )
2022-02-23 23:32:25 +01:00
request . options . Progress . IncrementRequests ( )
2021-01-14 22:43:08 +05:30
} ( data )
2021-01-01 15:28:28 +05:30
} )
2021-01-14 22:43:08 +05:30
wg . Wait ( )
2021-01-01 15:28:28 +05:30
if err != nil {
2021-11-05 03:01:41 +05:30
request . options . Output . Request ( request . options . TemplatePath , input , request . Type ( ) . String ( ) , err )
2021-10-01 14:30:04 +03:00
request . options . Progress . IncrementFailedRequestsBy ( 1 )
2021-01-01 19:36:21 +05:30
return errors . Wrap ( err , "could not send file request" )
2021-01-01 15:28:28 +05:30
}
2021-01-01 19:36:21 +05:30
return nil
2021-01-01 15:28:28 +05:30
}
2021-10-30 13:17:47 +03:00
2022-02-25 08:59:10 +01:00
func dumpResponse ( event * output . InternalWrappedEvent , requestOptions * protocols . ExecuterOptions , filematches [ ] FileMatch , filePath string ) {
2021-11-01 20:45:54 +02:00
cliOptions := requestOptions . Options
if cliOptions . Debug || cliOptions . DebugResponse {
2022-02-25 00:55:05 +01:00
for _ , fileMatch := range filematches {
2022-02-25 08:59:10 +01:00
lineContent := fileMatch . Raw
2022-02-25 00:55:05 +01:00
hexDump := false
2022-02-25 08:59:10 +01:00
if responsehighlighter . HasBinaryContent ( lineContent ) {
2022-02-25 00:55:05 +01:00
hexDump = true
2022-02-25 08:59:10 +01:00
lineContent = hex . Dump ( [ ] byte ( lineContent ) )
2022-02-25 00:55:05 +01:00
}
2022-02-25 08:59:10 +01:00
highlightedResponse := responsehighlighter . Highlight ( event . OperatorsResult , lineContent , cliOptions . NoColor , hexDump )
2022-02-25 00:55:05 +01: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 13:17:47 +03:00
}
}
}