Skip to content

Commit

Permalink
xss improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
pyneda committed Dec 1, 2024
1 parent 5748184 commit bdd1cc4
Show file tree
Hide file tree
Showing 10 changed files with 433 additions and 43 deletions.
25 changes: 25 additions & 0 deletions lib/text.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package lib

import (
"strings"

"github.com/gosimple/slug"
"github.com/sergi/go-diff/diffmatchpatch"
)
Expand Down Expand Up @@ -32,3 +34,26 @@ func ComputeSimilarity(aBody, bBody []byte) float64 {
similarity := 1 - float64(distance)/float64(maxLen)
return similarity
}

// ContainsAnySubstring checks if any string from the list appears as a substring
// in the original string
func ContainsAnySubstring(original string, substrings []string) bool {
for _, s := range substrings {
if strings.Contains(original, s) {
return true
}
}
return false
}

// ContainsAnySubstringIgnoreCase checks if any string from the list appears as a substring
// in the original string, ignoring case differences
func ContainsAnySubstringIgnoreCase(original string, substrings []string) bool {
original = strings.ToLower(original)
for _, s := range substrings {
if strings.Contains(original, strings.ToLower(s)) {
return true
}
}
return false
}
140 changes: 140 additions & 0 deletions lib/text_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,143 @@ func TestGuessDataType(t *testing.T) {
}
}
}

func TestContainsAnySubstring(t *testing.T) {
tests := []struct {
name string
original string
substrings []string
want bool
}{
{
name: "basic match",
original: "Hello World",
substrings: []string{"Hello", "Goodbye"},
want: true,
},
{
name: "no match",
original: "Hello World",
substrings: []string{"Goodbye", "Hi"},
want: false,
},
{
name: "partial match",
original: "Hello World",
substrings: []string{"llo", "rld"},
want: true,
},
{
name: "case sensitive no match",
original: "Hello World",
substrings: []string{"hello", "world"},
want: false,
},
{
name: "empty original string",
original: "",
substrings: []string{"test"},
want: false,
},
{
name: "empty substring list",
original: "Hello World",
substrings: []string{},
want: false,
},
{
name: "empty string in list",
original: "Hello World",
substrings: []string{""},
want: true,
},
{
name: "special characters",
original: "Hello! @World#",
substrings: []string{"!"},
want: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ContainsAnySubstring(tt.original, tt.substrings)
if got != tt.want {
t.Errorf("ContainsAnySubstring() = %v, want %v", got, tt.want)
}
})
}
}

func TestContainsAnySubstringIgnoreCase(t *testing.T) {
tests := []struct {
name string
original string
substrings []string
want bool
}{
{
name: "exact match",
original: "Hello World",
substrings: []string{"Hello", "Goodbye"},
want: true,
},
{
name: "case insensitive match",
original: "Hello World",
substrings: []string{"hello", "world"},
want: true,
},
{
name: "mixed case match",
original: "Hello World",
substrings: []string{"hELLo", "WoRLD"},
want: true,
},
{
name: "no match",
original: "Hello World",
substrings: []string{"goodbye", "hi"},
want: false,
},
{
name: "partial case insensitive match",
original: "Hello World",
substrings: []string{"LLO", "RLD"},
want: true,
},
{
name: "empty original string",
original: "",
substrings: []string{"test"},
want: false,
},
{
name: "empty substring list",
original: "Hello World",
substrings: []string{},
want: false,
},
{
name: "empty string in list",
original: "Hello World",
substrings: []string{""},
want: true,
},
{
name: "special characters with case",
original: "Hello! @World#",
substrings: []string{"WORLD#", "hello!"},
want: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ContainsAnySubstringIgnoreCase(tt.original, tt.substrings)
if got != tt.want {
t.Errorf("ContainsAnySubstringIgnoreCase() = %v, want %v", got, tt.want)
}
})
}
}
62 changes: 45 additions & 17 deletions pkg/active/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func (x *AlertAudit) requestHasAlert(history *db.History, browserPool *browser.B

taskLog := log.With().Uint("history", history.ID).Str("method", history.Method).Str("task", "ensure no alert").Str("url", history.URL).Logger()
hasAlert := false
done := make(chan struct{})

taskLog.Debug().Msg("Getting a browser page")
web.IgnoreCertificateErrors(page)
Expand All @@ -56,6 +57,7 @@ func (x *AlertAudit) requestHasAlert(history *db.History, browserPool *browser.B
go pageWithCancel.EachEvent(
func(e *proto.PageJavascriptDialogOpening) (stop bool) {
hasAlert = true
close(done)
return true
})()

Expand Down Expand Up @@ -87,23 +89,6 @@ func (x *AlertAudit) requestHasAlert(history *db.History, browserPool *browser.B
taskLog.Debug().Msg("Page fully loaded on browser")
}

// NOTE: The event types should be based on the payload and context where it's being reflected
if !hasAlert {
err = browser.TriggerMouseEvents(
pageWithCancel,
browser.EventTypes{
Click: false,
Hover: true,
Movement: true,
Drag: false,
},
&browser.DefaultMovementOptions,
)
if err != nil {
taskLog.Error().Err(err).Msg("Failed to trigger mouse events")
}
}

return hasAlert
}

Expand Down Expand Up @@ -285,6 +270,11 @@ func (x *AlertAudit) testRequest(scanRequest *http.Request, insertionPoint scan.
func(e *proto.PageJavascriptDialogOpening) (stop bool) {
alertOpenEventChan <- e
taskLog.Warn().Str("browser_url", e.URL).Str("type", string(e.Type)).Str("dialog_text", e.Message).Bool("has_browser_handler", e.HasBrowserHandler).Msg("Reflected XSS Verified")

disableDialogErr := browser.CloseAllJSDialogs(pageWithCancel)
if disableDialogErr != nil {
taskLog.Error().Err(disableDialogErr).Msg("Error disabling javascript dialogs")
}
err := proto.PageHandleJavaScriptDialog{
Accept: true,
// PromptText: "",
Expand Down Expand Up @@ -325,6 +315,44 @@ func (x *AlertAudit) testRequest(scanRequest *http.Request, insertionPoint scan.
taskLog.Debug().Str("url", testurl).Msg("Page fully loaded on browser")
}

// select {
// case alertOpenEvent, ok := <-alertOpenEventChan:
// if !ok {
// return fmt.Errorf("no events received before channel was closed")
// }
// x.reportIssue(history, scanRequest, *alertOpenEvent, insertionPoint, payload, issueCode)
// return nil
// case <-time.After(3 * time.Second):
// return fmt.Errorf("operation timed out while waiting for events")

// }
select {
case alertOpenEvent, ok := <-alertOpenEventChan:
if !ok {
return fmt.Errorf("no events received before channel was closed")
}
x.reportIssue(history, scanRequest, *alertOpenEvent, insertionPoint, payload, issueCode)
return nil
case <-time.After(500 * time.Millisecond):
}

done := make(chan struct{})
eventTypes := browser.EventTypesForAlertPayload(payload)
if eventTypes.HasEventTypesToCheck() {
taskLog.Info().Str("url", testurl).Msg("No alert triggered, trying to trigger with mouse events")
err = browser.TriggerMouseEvents(
pageWithCancel,
eventTypes,
&browser.DefaultMovementOptions,
done,
)
if err != nil {
taskLog.Error().Err(err).Msg("Failed to trigger mouse events")
} else {
taskLog.Info().Str("url", testurl).Str("payload", payload).Msg("Mouse events triggering completed")
}
}

select {
case alertOpenEvent, ok := <-alertOpenEventChan:
if !ok {
Expand Down
41 changes: 36 additions & 5 deletions pkg/active/xss.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func (x *XSSAudit) Run(targetUrl string, params []string, wordlistPath string, u
payload := scanner.Text()
p.Go(func() {
b := browserPool.NewBrowser()

taskLog.Debug().Msg("Got scan browser from the pool")
hijackResultsChannel := make(chan browser.HijackResult)
hijackContext, hijackCancel := context.WithCancel(context.Background())
Expand Down Expand Up @@ -157,6 +158,12 @@ func (x *XSSAudit) TestUrlParamWithAlertPayload(item lib.ParameterAuditItem, b *
cancel()
}()
taskLog.Debug().Str("url", testurl).Msg("Navigating to the page")
hasAlert := false
done := make(chan struct{})
var closeOnce sync.Once

defer closeOnce.Do(func() { close(done) })

go pageWithCancel.EachEvent(func(e *proto.RuntimeConsoleAPICalled) {
// consoleData := pageWithCancel.MustObjectsToJSON(e.Args)

Expand All @@ -171,10 +178,16 @@ func (x *XSSAudit) TestUrlParamWithAlertPayload(item lib.ParameterAuditItem, b *
func(e *proto.PageJavascriptDialogOpening) (stop bool) {
//log.Printf("XSS Verified on url: %s - PageHandleJavaScriptDialog triggered!!!", testurl)
//log.Printf("[XSS Verified] - Dialog type: %s - Message: %s - Has Browser Handler: %t - URL: %s", e.Type, e.Message, e.HasBrowserHandler, e.URL)

closeOnce.Do(func() { close(done) })
hasAlert = true
disableDialogErr := browser.CloseAllJSDialogs(pageWithCancel)
if disableDialogErr != nil {
taskLog.Error().Err(disableDialogErr).Msg("Error disabling javascript dialogs")
}
taskLog.Warn().Str("browser_url", e.URL).Str("param", item.Parameter).Str("type", string(e.Type)).Str("dialog_text", e.Message).Bool("has_browser_handler", e.HasBrowserHandler).Msg("Reflected XSS Verified")
history := x.GetHistory(e.URL)
if history.ID == 0 {
taskLog.Warn().Str("url", testurl).Msg("Could not find history for XSS, sleeping and trying again")
history = x.GetHistory(testurl)
}

Expand Down Expand Up @@ -213,16 +226,16 @@ func (x *XSSAudit) TestUrlParamWithAlertPayload(item lib.ParameterAuditItem, b *
// log.Printf("Taking screenshot of XSS and saving to: %s", screenshot)
// pageWithCancel.MustScreenshot(screenshot)
//defer restore()

log.Info().Str("url", testurl).Str("param", item.Parameter).Msg("Reflected XSS detected, reported and closed dialog")

err := proto.PageHandleJavaScriptDialog{
Accept: true,
// PromptText: "",
}.Call(pageWithCancel)
if err != nil {
//log.Printf("Dialog from %s was already closed when attempted to close: %s", e.URL, err)
taskLog.Error().Err(err).Msg("Error handling javascript dialog")
// return true
} else {
taskLog.Debug().Msg("PageHandleJavaScriptDialog succedded")
taskLog.Info().Msg("Closing original javascript dialog succedded")
}

return true
Expand All @@ -238,6 +251,24 @@ func (x *XSSAudit) TestUrlParamWithAlertPayload(item lib.ParameterAuditItem, b *
} else {
taskLog.Debug().Str("url", testurl).Msg("Page fully loaded on browser")
}
if !hasAlert {
eventTypes := browser.EventTypesForAlertPayload(item.Payload)
if eventTypes.HasEventTypesToCheck() {
taskLog.Info().Str("url", testurl).Msg("No alert triggered, trying to trigger with mouse events")

err = browser.TriggerMouseEvents(
pageWithCancel,
eventTypes,
&browser.DefaultMovementOptions,
done,
)
if err != nil {
taskLog.Error().Err(err).Msg("Failed to trigger mouse events")
} else {
taskLog.Info().Str("url", testurl).Bool("alert_found", hasAlert).Msg("Mouse events triggering completed")
}
}
}
err = pageWithCancel.Close()
if err != nil {
taskLog.Error().Err(err).Msg("Error closing browser page")
Expand Down
Loading

0 comments on commit bdd1cc4

Please sign in to comment.