temporary line calculation with multiple file read

todo: replace with one pass scan via io.reader
This commit is contained in:
mzack 2022-02-23 23:32:25 +01:00
parent 6746071979
commit 1551feda5a
8 changed files with 120 additions and 62 deletions

View File

@ -145,10 +145,10 @@ func (r *Result) Merge(result *Result) {
} }
for k, v := range result.Matches { for k, v := range result.Matches {
r.Matches[k] = v r.Matches[k] = append(r.Matches[k], v...)
} }
for k, v := range result.Extracts { for k, v := range result.Extracts {
r.Extracts[k] = v r.Extracts[k] = append(r.Extracts[k], v...)
} }
r.outputUnique = make(map[string]struct{}) r.outputUnique = make(map[string]struct{})
@ -201,7 +201,6 @@ func (operators *Operators) Execute(data map[string]interface{}, match MatchFunc
// Start with the extractors first and evaluate them. // Start with the extractors first and evaluate them.
for _, extractor := range operators.Extractors { for _, extractor := range operators.Extractors {
var extractorResults []string var extractorResults []string
for match := range extract(data, extractor) { for match := range extract(data, extractor) {
extractorResults = append(extractorResults, match) extractorResults = append(extractorResults, match)

View File

@ -65,13 +65,13 @@ func (w *StandardWriter) formatScreen(output *ResultEvent) []byte {
builder.WriteString("]") builder.WriteString("]")
} }
if len(output.LineCount) > 0 { if len(output.Lines) > 0 {
builder.WriteString(" [LN: ") builder.WriteString(" [LN: ")
for i, line := range output.LineCount { for i, line := range output.Lines {
builder.WriteString(strconv.Itoa(line)) builder.WriteString(strconv.Itoa(line))
if i != len(output.LineCount)-1 { if i != len(output.Lines)-1 {
builder.WriteString(",") builder.WriteString(",")
} }
} }

View File

@ -105,8 +105,8 @@ type ResultEvent struct {
CURLCommand string `json:"curl-command,omitempty"` CURLCommand string `json:"curl-command,omitempty"`
// MatcherStatus is the status of the match // MatcherStatus is the status of the match
MatcherStatus bool `json:"matcher-status"` MatcherStatus bool `json:"matcher-status"`
// LineCount is the line count for the specified match // Lines is the line count for the specified match
LineCount []int `json:"matched-line"` Lines []int `json:"matched-line"`
FileToIndexPosition map[string]int `json:"-"` FileToIndexPosition map[string]int `json:"-"`
} }

View File

@ -1,6 +1,7 @@
package eventcreator package eventcreator
import ( import (
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
"github.com/projectdiscovery/nuclei/v2/pkg/output" "github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols" "github.com/projectdiscovery/nuclei/v2/pkg/protocols"
) )
@ -29,3 +30,25 @@ func CreateEventWithAdditionalOptions(request protocols.Request, outputEvent out
} }
return event return event
} }
func CreateEventWithResults(request protocols.Request, outputEvent output.InternalEvent, isResponseDebug bool, results ...*operators.Result) *output.InternalWrappedEvent {
event := &output.InternalWrappedEvent{InternalEvent: outputEvent}
for _, result := range results {
event.OperatorsResult = result
event.Results = append(event.Results, request.MakeResultEvent(event)...)
}
return event
}
func GetEventResults(request protocols.Request, outputEvent output.InternalEvent, isResponseDebug bool) []*operators.Result {
var results []*operators.Result
for _, compiledOperator := range request.GetCompiledOperators() {
if compiledOperator != nil {
result, ok := compiledOperator.Execute(outputEvent, request.Match, request.Extract, isResponseDebug)
if ok && results != nil {
results = append(results, result)
}
}
}
return nil
}

View File

@ -2,6 +2,7 @@ package file
import ( import (
"bufio" "bufio"
"os"
"strings" "strings"
"time" "time"
@ -76,8 +77,8 @@ type fileStatus struct {
bytes int bytes int
} }
// toDSLMap converts a file chunk elaboration to a map for use in DSL matching // responseToDSLMap converts a file chunk elaboration to a map for use in DSL matching
func (request *Request) toDSLMap(state *fileStatus) output.InternalEvent { func (request *Request) responseToDSLMap(state *fileStatus) output.InternalEvent {
return output.InternalEvent{ return output.InternalEvent{
"path": state.inputFilePath, "path": state.inputFilePath,
"matched": state.matchedFileName, "matched": state.matchedFileName,
@ -94,20 +95,8 @@ func (request *Request) toDSLMap(state *fileStatus) output.InternalEvent {
// MakeResultEvent creates a result event from internal wrapped event // MakeResultEvent creates a result event from internal wrapped event
func (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent { func (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {
filePath := wrapped.InternalEvent["path"].(string)
results := protocols.MakeDefaultResultEvent(request, wrapped) results := protocols.MakeDefaultResultEvent(request, wrapped)
raw, ok := wrapped.InternalEvent["raw"]
if !ok {
return results
}
linesOffset := wrapped.InternalEvent["lines"].(int)
rawStr, ok := raw.(string)
if !ok {
return results
}
for _, result := range results { for _, result := range results {
lineWords := make(map[string]struct{}) lineWords := make(map[string]struct{})
@ -123,13 +112,13 @@ func (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []
lineWords[v] = struct{}{} lineWords[v] = struct{}{}
} }
} }
result.LineCount = calculateLineFunc(rawStr, linesOffset, lineWords) result.Lines = calculateLineFunc(filePath, lineWords)
} }
// Identify the position of match in file using a dirty hack. // Identify the position of match in file using a dirty hack.
for _, result := range results { for _, result := range results {
for _, extraction := range result.ExtractedResults { for _, extraction := range result.ExtractedResults {
scanner := bufio.NewScanner(strings.NewReader(rawStr)) file, _ := os.Open(filePath)
scanner := bufio.NewScanner(file)
line := 1 line := 1
for scanner.Scan() { for scanner.Scan() {
@ -137,11 +126,12 @@ func (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []
if result.FileToIndexPosition == nil { if result.FileToIndexPosition == nil {
result.FileToIndexPosition = make(map[string]int) result.FileToIndexPosition = make(map[string]int)
} }
result.FileToIndexPosition[result.Matched] = line + linesOffset result.FileToIndexPosition[result.Matched] = line
continue continue
} }
line++ line++
} }
file.Close()
} }
} }
return results return results

View File

@ -34,7 +34,7 @@ func TestResponseToDSLMap(t *testing.T) {
require.Nil(t, err, "could not compile file request") require.Nil(t, err, "could not compile file request")
resp := "test-data\r\n" resp := "test-data\r\n"
event := request.toDSLMap(&fileStatus{raw: resp, inputFilePath: "one.one.one.one", matchedFileName: "one.one.one.one"}) event := request.responseToDSLMap(&fileStatus{raw: resp, inputFilePath: "one.one.one.one", matchedFileName: "one.one.one.one"})
require.Len(t, event, 10, "could not get correct number of items in dsl map") require.Len(t, event, 10, "could not get correct number of items in dsl map")
require.Equal(t, resp, event["raw"], "could not get correct resp") require.Equal(t, resp, event["raw"], "could not get correct resp")
} }
@ -59,7 +59,7 @@ func TestFileOperatorMatch(t *testing.T) {
require.Nil(t, err, "could not compile file request") require.Nil(t, err, "could not compile file request")
resp := "test-data\r\n1.1.1.1\r\n" resp := "test-data\r\n1.1.1.1\r\n"
event := request.toDSLMap(&fileStatus{raw: resp, inputFilePath: "one.one.one.one", matchedFileName: "one.one.one.one"}) event := request.responseToDSLMap(&fileStatus{raw: resp, inputFilePath: "one.one.one.one", matchedFileName: "one.one.one.one"})
require.Len(t, event, 10, "could not get correct number of items in dsl map") require.Len(t, event, 10, "could not get correct number of items in dsl map")
require.Equal(t, resp, event["raw"], "could not get correct resp") require.Equal(t, resp, event["raw"], "could not get correct resp")
@ -108,7 +108,7 @@ func TestFileOperatorMatch(t *testing.T) {
t.Run("caseInsensitive", func(t *testing.T) { t.Run("caseInsensitive", func(t *testing.T) {
resp := "TEST-DATA\r\n1.1.1.1\r\n" resp := "TEST-DATA\r\n1.1.1.1\r\n"
event := request.toDSLMap(&fileStatus{raw: resp, inputFilePath: "one.one.one.one", matchedFileName: "one.one.one.one"}) event := request.responseToDSLMap(&fileStatus{raw: resp, inputFilePath: "one.one.one.one", matchedFileName: "one.one.one.one"})
require.Len(t, event, 10, "could not get correct number of items in dsl map") require.Len(t, event, 10, "could not get correct number of items in dsl map")
require.Equal(t, resp, event["raw"], "could not get correct resp") require.Equal(t, resp, event["raw"], "could not get correct resp")
@ -147,7 +147,7 @@ func TestFileOperatorExtract(t *testing.T) {
require.Nil(t, err, "could not compile file request") require.Nil(t, err, "could not compile file request")
resp := "test-data\r\n1.1.1.1\r\n" resp := "test-data\r\n1.1.1.1\r\n"
event := request.toDSLMap(&fileStatus{raw: resp, inputFilePath: "one.one.one.one", matchedFileName: "one.one.one.one"}) event := request.responseToDSLMap(&fileStatus{raw: resp, inputFilePath: "one.one.one.one", matchedFileName: "one.one.one.one"})
require.Len(t, event, 10, "could not get correct number of items in dsl map") require.Len(t, event, 10, "could not get correct number of items in dsl map")
require.Equal(t, resp, event["raw"], "could not get correct resp") require.Equal(t, resp, event["raw"], "could not get correct resp")
@ -265,7 +265,7 @@ func testFileMakeResult(t *testing.T, matchers []*matchers.Matcher, matcherCondi
matchedFileName := "test.txt" matchedFileName := "test.txt"
fileContent := "test-data\r\n1.1.1.1\r\n" fileContent := "test-data\r\n1.1.1.1\r\n"
event := request.toDSLMap(&fileStatus{raw: fileContent, inputFilePath: "/tmp", matchedFileName: matchedFileName}) event := request.responseToDSLMap(&fileStatus{raw: fileContent, inputFilePath: "/tmp", matchedFileName: matchedFileName})
require.Len(t, event, 10, "could not get correct number of items in dsl map") require.Len(t, event, 10, "could not get correct number of items in dsl map")
require.Equal(t, fileContent, event["raw"], "could not get correct resp") require.Equal(t, fileContent, event["raw"], "could not get correct resp")

View File

@ -13,6 +13,7 @@ import (
"github.com/remeh/sizedwaitgroup" "github.com/remeh/sizedwaitgroup"
"github.com/projectdiscovery/gologger" "github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
"github.com/projectdiscovery/nuclei/v2/pkg/output" "github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols" "github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator"
@ -56,9 +57,19 @@ func (request *Request) ExecuteWithResults(input string, metadata, previous outp
totalBytes := units.BytesSize(float64(stat.Size())) totalBytes := units.BytesSize(float64(stat.Size()))
fileReader := io.LimitReader(file, request.maxSize) fileReader := io.LimitReader(file, request.maxSize)
var bytesCount, linesCount, wordsCount int var bytesCount, linesCount, wordsCount int
isResponseDebug := request.options.Options.Debug || request.options.Options.DebugResponse
var result *operators.Result
scanner := bufio.NewScanner(fileReader) scanner := bufio.NewScanner(fileReader)
buffer := []byte{} buffer := []byte{}
scanner.Buffer(buffer, int(chunkSize)) scanner.Buffer(buffer, int(chunkSize))
outputEvent := request.responseToDSLMap(&fileStatus{
inputFilePath: input,
matchedFileName: filePath,
})
for k, v := range previous {
outputEvent[k] = v
}
for scanner.Scan() { for scanner.Scan() {
fileContent := scanner.Text() fileContent := scanner.Text()
n := len(fileContent) n := len(fileContent)
@ -68,7 +79,7 @@ func (request *Request) ExecuteWithResults(input string, metadata, previous outp
processedBytes := units.BytesSize(float64(currentBytes)) processedBytes := units.BytesSize(float64(currentBytes))
gologger.Verbose().Msgf("[%s] Processing file %s chunk %s/%s", request.options.TemplateID, filePath, processedBytes, totalBytes) gologger.Verbose().Msgf("[%s] Processing file %s chunk %s/%s", request.options.TemplateID, filePath, processedBytes, totalBytes)
outputEvent := request.toDSLMap(&fileStatus{ chunkOutputEvent := request.responseToDSLMap(&fileStatus{
raw: fileContent, raw: fileContent,
inputFilePath: input, inputFilePath: input,
matchedFileName: filePath, matchedFileName: filePath,
@ -77,20 +88,28 @@ func (request *Request) ExecuteWithResults(input string, metadata, previous outp
bytes: bytesCount, bytes: bytesCount,
}) })
for k, v := range previous { for k, v := range previous {
outputEvent[k] = v chunkOutputEvent[k] = v
} }
event := eventcreator.CreateEvent(request, outputEvent, request.options.Options.Debug || request.options.Options.DebugResponse) chunkEvent := eventcreator.CreateEvent(request, chunkOutputEvent, isResponseDebug)
if chunkEvent.OperatorsResult != nil {
dumpResponse(event, request.options, fileContent, filePath) if result == nil {
callback(event) result = chunkEvent.OperatorsResult
} else {
result.Merge(chunkEvent.OperatorsResult)
}
dumpResponse(chunkEvent, request.options, filePath, linesCount)
}
currentLinesCount := 1 + strings.Count(fileContent, "\n") currentLinesCount := 1 + strings.Count(fileContent, "\n")
linesCount += currentLinesCount linesCount += currentLinesCount
wordsCount += strings.Count(fileContent, " ") wordsCount += strings.Count(fileContent, " ")
bytesCount = currentBytes bytesCount = currentBytes
request.options.Progress.IncrementRequests()
} }
callback(eventcreator.CreateEventWithResults(request, outputEvent, isResponseDebug, result))
request.options.Progress.IncrementRequests()
}(data) }(data)
}) })
wg.Wait() wg.Wait()
@ -102,43 +121,62 @@ func (request *Request) ExecuteWithResults(input string, metadata, previous outp
return nil return nil
} }
func dumpResponse(event *output.InternalWrappedEvent, requestOptions *protocols.ExecuterOptions, fileContent string, filePath string) { func dumpResponse(event *output.InternalWrappedEvent, requestOptions *protocols.ExecuterOptions, filePath string, line int) {
cliOptions := requestOptions.Options cliOptions := requestOptions.Options
if cliOptions.Debug || cliOptions.DebugResponse { if cliOptions.Debug || cliOptions.DebugResponse {
fileContent := event.InternalEvent["raw"].(string)
hexDump := false hexDump := false
if responsehighlighter.HasBinaryContent(fileContent) { if responsehighlighter.HasBinaryContent(fileContent) {
hexDump = true hexDump = true
fileContent = hex.Dump([]byte(fileContent)) fileContent = hex.Dump([]byte(fileContent))
} }
highlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, fileContent, cliOptions.NoColor, hexDump) highlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, fileContent, cliOptions.NoColor, hexDump)
gologger.Debug().Msgf("[%s] Dumped file request for %s\n\n%s", requestOptions.TemplateID, filePath, highlightedResponse) gologger.Debug().Msgf("[%s] Dumped match/extract file snippet for %s at line %d\n\n%s", requestOptions.TemplateID, filePath, line, highlightedResponse)
} }
} }
func getAllStringSubmatchIndex(content string, word string) []int { func getAllStringSubmatchIndex(filePath string, word string) []int {
file, _ := os.Open(filePath)
defer file.Close()
indexes := []int{} indexes := []int{}
start := 0 b := 0
for { scanner := bufio.NewScanner(file)
v := strings.Index(content[start:], word) for scanner.Scan() {
if v == -1 { content := scanner.Text()
break if v := strings.Index(content, word); v != -1 {
indexes = append(indexes, b+v)
} }
indexes = append(indexes, v+start) b += len(content) + 1
start += len(word) + v
} }
return indexes return indexes
} }
func calculateLineFunc(contents string, linesOffset int, words map[string]struct{}) []int { func calculateLineFunc(filePath string, words map[string]struct{}) []int {
var lines []int var lines []int
for word := range words { for word := range words {
matches := getAllStringSubmatchIndex(contents, word) matches := getAllStringSubmatchIndex(filePath, word)
for _, index := range matches { for _, index := range matches {
lineCount := 1 + strings.Count(contents[:index], "\n") f, _ := os.Open(filePath)
lines = append(lines, linesOffset+lineCount) scanner := bufio.NewScanner(f)
lineCount := 0
b := 0
for scanner.Scan() {
lineCount++
b += len(scanner.Text()) + 1
if b > index {
break
}
}
if lineCount > 0 {
lines = append(lines, lineCount)
}
f.Close()
} }
} }
sort.Ints(lines) sort.Ints(lines)

View File

@ -79,7 +79,11 @@ func TestFileExecuteWithResults(t *testing.T) {
} }
func TestGenerateNewLineIndexes(t *testing.T) { func TestGenerateNewLineIndexes(t *testing.T) {
lines := calculateLineFunc(`aaa tempDir, err := ioutil.TempDir("", "test-*")
require.Nil(t, err, "could not create temporary directory")
defer os.RemoveAll(tempDir)
v := `aaa
bbb bbb
ccc ccc
RequestDataTooBig RequestDataTooBig
@ -88,6 +92,10 @@ eeee
RequestDataTooBig RequestDataTooBig
dd dd
RequestDataTooBig3 RequestDataTooBig3
SuspiciousOperation`, 0, map[string]struct{}{"SuspiciousOperation": {}, "RequestDataTooBig": {}}) SuspiciousOperation`
filename := filepath.Join(tempDir, "test")
err = os.WriteFile(filename, []byte(v), os.ModePerm)
require.Nil(t, err, "could not write temporary file")
lines := calculateLineFunc(filename, map[string]struct{}{"SuspiciousOperation": {}, "RequestDataTooBig": {}})
require.ElementsMatch(t, []int{4, 7, 9, 10}, lines, "could not calculate correct lines") require.ElementsMatch(t, []int{4, 7, 9, 10}, lines, "could not calculate correct lines")
} }