diff --git a/xss_decls.go b/xss_decls.go
index 6a02ec1..4f227f4 100644
--- a/xss_decls.go
+++ b/xss_decls.go
@@ -13,6 +13,332 @@ type stringType struct {
attributeType int
}
+// You can get these using:
+//
+// curl https://raw.githubusercontent.com/WebKit/WebKit/main/Source/WebCore/dom/EventNames.json | \
+// jq -r 'keys[] as $k | $k | ascii_upcase | "{\"" + . +"\", attributeTypeBlack },"' | sort
+var blackEvents = []stringType{
+ {"ABORT", attributeTypeBlack},
+ {"ACTIVATE", attributeTypeBlack},
+ {"ACTIVE", attributeTypeBlack},
+ {"ADDSOURCEBUFFER", attributeTypeBlack},
+ {"ADDSTREAM", attributeTypeBlack},
+ {"ADDTRACK", attributeTypeBlack},
+ {"AFTERPRINT", attributeTypeBlack},
+ {"ANIMATIONCANCEL", attributeTypeBlack},
+ {"ANIMATIONEND", attributeTypeBlack},
+ {"ANIMATIONITERATION", attributeTypeBlack},
+ {"ANIMATIONSTART", attributeTypeBlack},
+ {"AUDIOEND", attributeTypeBlack},
+ {"AUDIOPROCESS", attributeTypeBlack},
+ {"AUDIOSTART", attributeTypeBlack},
+ {"AUTOCOMPLETE", attributeTypeBlack},
+ {"AUTOCOMPLETEERROR", attributeTypeBlack},
+ {"BACKGROUNDFETCHABORT", attributeTypeBlack},
+ {"BACKGROUNDFETCHCLICK", attributeTypeBlack},
+ {"BACKGROUNDFETCHFAIL", attributeTypeBlack},
+ {"BACKGROUNDFETCHSUCCESS", attributeTypeBlack},
+ {"BEFORECOPY", attributeTypeBlack},
+ {"BEFORECUT", attributeTypeBlack},
+ {"BEFOREINPUT", attributeTypeBlack},
+ {"BEFORELOAD", attributeTypeBlack},
+ {"BEFOREPASTE", attributeTypeBlack},
+ {"BEFOREPRINT", attributeTypeBlack},
+ {"BEFORETOGGLE", attributeTypeBlack},
+ {"BEFOREUNLOAD", attributeTypeBlack},
+ {"BEGINEVENT", attributeTypeBlack},
+ {"BLOCKED", attributeTypeBlack},
+ {"BLUR", attributeTypeBlack},
+ {"BOUNDARY", attributeTypeBlack},
+ {"BUFFEREDAMOUNTLOW", attributeTypeBlack},
+ {"BUFFEREDCHANGE", attributeTypeBlack},
+ {"CACHED", attributeTypeBlack},
+ {"CANCEL", attributeTypeBlack},
+ {"CANPLAY", attributeTypeBlack},
+ {"CANPLAYTHROUGH", attributeTypeBlack},
+ {"CHANGE", attributeTypeBlack},
+ {"CHARGINGCHANGE", attributeTypeBlack},
+ {"CHARGINGTIMECHANGE", attributeTypeBlack},
+ {"CHECKING", attributeTypeBlack},
+ {"CLICK", attributeTypeBlack},
+ {"CLOSE", attributeTypeBlack},
+ {"CLOSING", attributeTypeBlack},
+ {"COMPLETE", attributeTypeBlack},
+ {"COMPOSITIONEND", attributeTypeBlack},
+ {"COMPOSITIONSTART", attributeTypeBlack},
+ {"COMPOSITIONUPDATE", attributeTypeBlack},
+ {"CONFIGURATIONCHANGE", attributeTypeBlack},
+ {"CONNECT", attributeTypeBlack},
+ {"CONNECTING", attributeTypeBlack},
+ {"CONNECTIONSTATECHANGE", attributeTypeBlack},
+ {"CONTENTVISIBILITYAUTOSTATECHANGE", attributeTypeBlack},
+ {"CONTEXTMENU", attributeTypeBlack},
+ {"CONTROLLERCHANGE", attributeTypeBlack},
+ {"COOKIECHANGE", attributeTypeBlack},
+ {"COORDINATORSTATECHANGE", attributeTypeBlack},
+ {"COPY", attributeTypeBlack},
+ {"COUPONCODECHANGED", attributeTypeBlack},
+ {"CUECHANGE", attributeTypeBlack},
+ {"CURRENTENTRYCHANGE", attributeTypeBlack},
+ {"CUT", attributeTypeBlack},
+ {"DATAAVAILABLE", attributeTypeBlack},
+ {"DATACHANNEL", attributeTypeBlack},
+ {"DBLCLICK", attributeTypeBlack},
+ {"DEQUEUE", attributeTypeBlack},
+ {"DEVICECHANGE", attributeTypeBlack},
+ {"DEVICEMOTION", attributeTypeBlack},
+ {"DEVICEORIENTATION", attributeTypeBlack},
+ {"DISCHARGINGTIMECHANGE", attributeTypeBlack},
+ {"DISCONNECT", attributeTypeBlack},
+ {"DISPOSE", attributeTypeBlack},
+ {"DOMACTIVATE", attributeTypeBlack},
+ {"DOMCHARACTERDATAMODIFIED", attributeTypeBlack},
+ {"DOMCONTENTLOADED", attributeTypeBlack},
+ {"DOMNODEINSERTED", attributeTypeBlack},
+ {"DOMNODEINSERTEDINTODOCUMENT", attributeTypeBlack},
+ {"DOMNODEREMOVED", attributeTypeBlack},
+ {"DOMNODEREMOVEDFROMDOCUMENT", attributeTypeBlack},
+ {"DOMSUBTREEMODIFIED", attributeTypeBlack},
+ {"DOWNLOADING", attributeTypeBlack},
+ {"DRAG", attributeTypeBlack},
+ {"DRAGEND", attributeTypeBlack},
+ {"DRAGENTER", attributeTypeBlack},
+ {"DRAGLEAVE", attributeTypeBlack},
+ {"DRAGOVER", attributeTypeBlack},
+ {"DRAGSTART", attributeTypeBlack},
+ {"DROP", attributeTypeBlack},
+ {"DURATIONCHANGE", attributeTypeBlack},
+ {"EMPTIED", attributeTypeBlack},
+ {"ENCRYPTED", attributeTypeBlack},
+ {"END", attributeTypeBlack},
+ {"ENDED", attributeTypeBlack},
+ {"ENDEVENT", attributeTypeBlack},
+ {"ENDSTREAMING", attributeTypeBlack},
+ {"ENTER", attributeTypeBlack},
+ {"ENTERPICTUREINPICTURE", attributeTypeBlack},
+ {"ERROR", attributeTypeBlack},
+ {"EXIT", attributeTypeBlack},
+ {"FETCH", attributeTypeBlack},
+ {"FINISH", attributeTypeBlack},
+ {"FOCUS", attributeTypeBlack},
+ {"FOCUSIN", attributeTypeBlack},
+ {"FOCUSOUT", attributeTypeBlack},
+ {"FORMDATA", attributeTypeBlack},
+ {"FULLSCREENCHANGE", attributeTypeBlack},
+ {"FULLSCREENERROR", attributeTypeBlack},
+ {"GAMEPADCONNECTED", attributeTypeBlack},
+ {"GAMEPADDISCONNECTED", attributeTypeBlack},
+ {"GATHERINGSTATECHANGE", attributeTypeBlack},
+ {"GESTURECHANGE", attributeTypeBlack},
+ {"GESTUREEND", attributeTypeBlack},
+ {"GESTURESCROLLEND", attributeTypeBlack},
+ {"GESTURESCROLLSTART", attributeTypeBlack},
+ {"GESTURESCROLLUPDATE", attributeTypeBlack},
+ {"GESTURESTART", attributeTypeBlack},
+ {"GESTURETAP", attributeTypeBlack},
+ {"GESTURETAPDOWN", attributeTypeBlack},
+ {"GOTPOINTERCAPTURE", attributeTypeBlack},
+ {"HASHCHANGE", attributeTypeBlack},
+ {"ICECANDIDATE", attributeTypeBlack},
+ {"ICECANDIDATEERROR", attributeTypeBlack},
+ {"ICECONNECTIONSTATECHANGE", attributeTypeBlack},
+ {"ICEGATHERINGSTATECHANGE", attributeTypeBlack},
+ {"INACTIVE", attributeTypeBlack},
+ {"INPUT", attributeTypeBlack},
+ {"INPUTSOURCESCHANGE", attributeTypeBlack},
+ {"INSTALL", attributeTypeBlack},
+ {"INVALID", attributeTypeBlack},
+ {"INVOKE", attributeTypeBlack},
+ {"KEYDOWN", attributeTypeBlack},
+ {"KEYPRESS", attributeTypeBlack},
+ {"KEYSTATUSESCHANGE", attributeTypeBlack},
+ {"KEYUP", attributeTypeBlack},
+ {"LANGUAGECHANGE", attributeTypeBlack},
+ {"LEAVEPICTUREINPICTURE", attributeTypeBlack},
+ {"LEVELCHANGE", attributeTypeBlack},
+ {"LOAD", attributeTypeBlack},
+ {"LOADEDDATA", attributeTypeBlack},
+ {"LOADEDMETADATA", attributeTypeBlack},
+ {"LOADEND", attributeTypeBlack},
+ {"LOADING", attributeTypeBlack},
+ {"LOADINGDONE", attributeTypeBlack},
+ {"LOADINGERROR", attributeTypeBlack},
+ {"LOADSTART", attributeTypeBlack},
+ {"LOSTPOINTERCAPTURE", attributeTypeBlack},
+ {"MARK", attributeTypeBlack},
+ {"MERCHANTVALIDATION", attributeTypeBlack},
+ {"MESSAGE", attributeTypeBlack},
+ {"MESSAGEERROR", attributeTypeBlack},
+ {"MOUSEDOWN", attributeTypeBlack},
+ {"MOUSEENTER", attributeTypeBlack},
+ {"MOUSELEAVE", attributeTypeBlack},
+ {"MOUSEMOVE", attributeTypeBlack},
+ {"MOUSEOUT", attributeTypeBlack},
+ {"MOUSEOVER", attributeTypeBlack},
+ {"MOUSEUP", attributeTypeBlack},
+ {"MOUSEWHEEL", attributeTypeBlack},
+ {"MUTE", attributeTypeBlack},
+ {"NAVIGATE", attributeTypeBlack},
+ {"NAVIGATEERROR", attributeTypeBlack},
+ {"NAVIGATESUCCESS", attributeTypeBlack},
+ {"NEGOTIATIONNEEDED", attributeTypeBlack},
+ {"NEXTTRACK", attributeTypeBlack},
+ {"NOMATCH", attributeTypeBlack},
+ {"NOTIFICATIONCLICK", attributeTypeBlack},
+ {"NOTIFICATIONCLOSE", attributeTypeBlack},
+ {"NOUPDATE", attributeTypeBlack},
+ {"OBSOLETE", attributeTypeBlack},
+ {"OFFLINE", attributeTypeBlack},
+ {"ONLINE", attributeTypeBlack},
+ {"OPEN", attributeTypeBlack},
+ {"ORIENTATIONCHANGE", attributeTypeBlack},
+ {"OVERFLOWCHANGED", attributeTypeBlack},
+ {"PAGEHIDE", attributeTypeBlack},
+ {"PAGESHOW", attributeTypeBlack},
+ {"PASTE", attributeTypeBlack},
+ {"PAUSE", attributeTypeBlack},
+ {"PAYERDETAILCHANGE", attributeTypeBlack},
+ {"PAYMENTAUTHORIZED", attributeTypeBlack},
+ {"PAYMENTMETHODCHANGE", attributeTypeBlack},
+ {"PAYMENTMETHODSELECTED", attributeTypeBlack},
+ {"PLAY", attributeTypeBlack},
+ {"PLAYING", attributeTypeBlack},
+ {"POINTERCANCEL", attributeTypeBlack},
+ {"POINTERDOWN", attributeTypeBlack},
+ {"POINTERENTER", attributeTypeBlack},
+ {"POINTERLEAVE", attributeTypeBlack},
+ {"POINTERLOCKCHANGE", attributeTypeBlack},
+ {"POINTERLOCKERROR", attributeTypeBlack},
+ {"POINTERMOVE", attributeTypeBlack},
+ {"POINTEROUT", attributeTypeBlack},
+ {"POINTEROVER", attributeTypeBlack},
+ {"POINTERUP", attributeTypeBlack},
+ {"POPSTATE", attributeTypeBlack},
+ {"PREVIOUSTRACK", attributeTypeBlack},
+ {"PROCESSORERROR", attributeTypeBlack},
+ {"PROGRESS", attributeTypeBlack},
+ {"PUSH", attributeTypeBlack},
+ {"PUSHNOTIFICATION", attributeTypeBlack},
+ {"PUSHSUBSCRIPTIONCHANGE", attributeTypeBlack},
+ {"QUALITYCHANGE", attributeTypeBlack},
+ {"RATECHANGE", attributeTypeBlack},
+ {"READYSTATECHANGE", attributeTypeBlack},
+ {"REJECTIONHANDLED", attributeTypeBlack},
+ {"RELEASE", attributeTypeBlack},
+ {"REMOVE", attributeTypeBlack},
+ {"REMOVESOURCEBUFFER", attributeTypeBlack},
+ {"REMOVESTREAM", attributeTypeBlack},
+ {"REMOVETRACK", attributeTypeBlack},
+ {"RESET", attributeTypeBlack},
+ {"RESIZE", attributeTypeBlack},
+ {"RESOURCETIMINGBUFFERFULL", attributeTypeBlack},
+ {"RESULT", attributeTypeBlack},
+ {"RESUME", attributeTypeBlack},
+ {"RTCTRANSFORM", attributeTypeBlack},
+ {"SCROLL", attributeTypeBlack},
+ {"SEARCH", attributeTypeBlack},
+ {"SECURITYPOLICYVIOLATION", attributeTypeBlack},
+ {"SEEKED", attributeTypeBlack},
+ {"SEEKING", attributeTypeBlack},
+ {"SELECT", attributeTypeBlack},
+ {"SELECTEDCANDIDATEPAIRCHANGE", attributeTypeBlack},
+ {"SELECTEND", attributeTypeBlack},
+ {"SELECTIONCHANGE", attributeTypeBlack},
+ {"SELECTSTART", attributeTypeBlack},
+ {"SHIPPINGADDRESSCHANGE", attributeTypeBlack},
+ {"SHIPPINGCONTACTSELECTED", attributeTypeBlack},
+ {"SHIPPINGMETHODSELECTED", attributeTypeBlack},
+ {"SHIPPINGOPTIONCHANGE", attributeTypeBlack},
+ {"SHOW", attributeTypeBlack},
+ {"SIGNALINGSTATECHANGE", attributeTypeBlack},
+ {"SLOTCHANGE", attributeTypeBlack},
+ {"SOUNDEND", attributeTypeBlack},
+ {"SOUNDSTART", attributeTypeBlack},
+ {"SOURCECLOSE", attributeTypeBlack},
+ {"SOURCEENDED", attributeTypeBlack},
+ {"SOURCEOPEN", attributeTypeBlack},
+ {"SPEECHEND", attributeTypeBlack},
+ {"SPEECHSTART", attributeTypeBlack},
+ {"SQUEEZE", attributeTypeBlack},
+ {"SQUEEZEEND", attributeTypeBlack},
+ {"SQUEEZESTART", attributeTypeBlack},
+ {"STALLED", attributeTypeBlack},
+ {"START", attributeTypeBlack},
+ {"STARTED", attributeTypeBlack},
+ {"STARTSTREAMING", attributeTypeBlack},
+ {"STATECHANGE", attributeTypeBlack},
+ {"STOP", attributeTypeBlack},
+ {"STORAGE", attributeTypeBlack},
+ {"SUBMIT", attributeTypeBlack},
+ {"SUCCESS", attributeTypeBlack},
+ {"SUSPEND", attributeTypeBlack},
+ {"TEXTINPUT", attributeTypeBlack},
+ {"TIMEOUT", attributeTypeBlack},
+ {"TIMEUPDATE", attributeTypeBlack},
+ {"TOGGLE", attributeTypeBlack},
+ {"TONECHANGE", attributeTypeBlack},
+ {"TOUCHCANCEL", attributeTypeBlack},
+ {"TOUCHEND", attributeTypeBlack},
+ {"TOUCHFORCECHANGE", attributeTypeBlack},
+ {"TOUCHMOVE", attributeTypeBlack},
+ {"TOUCHSTART", attributeTypeBlack},
+ {"TRACK", attributeTypeBlack},
+ {"TRANSITIONCANCEL", attributeTypeBlack},
+ {"TRANSITIONEND", attributeTypeBlack},
+ {"TRANSITIONRUN", attributeTypeBlack},
+ {"TRANSITIONSTART", attributeTypeBlack},
+ {"UNCAPTUREDERROR", attributeTypeBlack},
+ {"UNHANDLEDREJECTION", attributeTypeBlack},
+ {"UNLOAD", attributeTypeBlack},
+ {"UNMUTE", attributeTypeBlack},
+ {"UPDATE", attributeTypeBlack},
+ {"UPDATEEND", attributeTypeBlack},
+ {"UPDATEFOUND", attributeTypeBlack},
+ {"UPDATEREADY", attributeTypeBlack},
+ {"UPDATESTART", attributeTypeBlack},
+ {"UPGRADENEEDED", attributeTypeBlack},
+ {"VALIDATEMERCHANT", attributeTypeBlack},
+ {"VERSIONCHANGE", attributeTypeBlack},
+ {"VISIBILITYCHANGE", attributeTypeBlack},
+ {"VOICESCHANGED", attributeTypeBlack},
+ {"VOLUMECHANGE", attributeTypeBlack},
+ {"WAITING", attributeTypeBlack},
+ {"WAITINGFORKEY", attributeTypeBlack},
+ {"WEBGLCONTEXTCREATIONERROR", attributeTypeBlack},
+ {"WEBGLCONTEXTLOST", attributeTypeBlack},
+ {"WEBGLCONTEXTRESTORED", attributeTypeBlack},
+ {"WEBKITANIMATIONEND", attributeTypeBlack},
+ {"WEBKITANIMATIONITERATION", attributeTypeBlack},
+ {"WEBKITANIMATIONSTART", attributeTypeBlack},
+ {"WEBKITBEFORETEXTINSERTED", attributeTypeBlack},
+ {"WEBKITBEGINFULLSCREEN", attributeTypeBlack},
+ {"WEBKITCURRENTPLAYBACKTARGETISWIRELESSCHANGED", attributeTypeBlack},
+ {"WEBKITENDFULLSCREEN", attributeTypeBlack},
+ {"WEBKITFULLSCREENCHANGE", attributeTypeBlack},
+ {"WEBKITFULLSCREENERROR", attributeTypeBlack},
+ {"WEBKITKEYADDED", attributeTypeBlack},
+ {"WEBKITKEYERROR", attributeTypeBlack},
+ {"WEBKITKEYMESSAGE", attributeTypeBlack},
+ {"WEBKITMOUSEFORCECHANGED", attributeTypeBlack},
+ {"WEBKITMOUSEFORCEDOWN", attributeTypeBlack},
+ {"WEBKITMOUSEFORCEUP", attributeTypeBlack},
+ {"WEBKITMOUSEFORCEWILLBEGIN", attributeTypeBlack},
+ {"WEBKITNEEDKEY", attributeTypeBlack},
+ {"WEBKITNETWORKINFOCHANGE", attributeTypeBlack},
+ {"WEBKITPLAYBACKTARGETAVAILABILITYCHANGED", attributeTypeBlack},
+ {"WEBKITPRESENTATIONMODECHANGED", attributeTypeBlack},
+ {"WEBKITREMOVESOURCEBUFFER", attributeTypeBlack},
+ {"WEBKITSOURCECLOSE", attributeTypeBlack},
+ {"WEBKITSOURCEENDED", attributeTypeBlack},
+ {"WEBKITSOURCEOPEN", attributeTypeBlack},
+ {"WEBKITTRANSITIONEND", attributeTypeBlack},
+ {"WHEEL", attributeTypeBlack},
+ {"WRITE", attributeTypeBlack},
+ {"WRITEEND", attributeTypeBlack},
+ {"WRITESTART", attributeTypeBlack},
+ {"ZOOM", attributeTypeBlack},
+}
+
var blackTags = []string{
"APPLET",
"BASE",
diff --git a/xss_helpers.go b/xss_helpers.go
index debf4a3..89527fc 100644
--- a/xss_helpers.go
+++ b/xss_helpers.go
@@ -35,22 +35,26 @@ func isBlackAttr(s string) int {
return attributeTypeNone
}
- upperS := strings.ToUpper(strings.ReplaceAll(s, "\x00", ""))
+ sUpperWithoutNulls := strings.ToUpper(strings.ReplaceAll(s, "\x00", ""))
if length >= 5 {
- // javascript on.*
- if strings.ToUpper(s[:2]) == "ON" {
- // got javascript on- attribute name
- return attributeTypeBlack
- }
-
- if upperS == "XMLNS" || upperS == "XLINK" {
+ if sUpperWithoutNulls == "XMLNS" || sUpperWithoutNulls == "XLINK" {
// got xmlns or xlink tags
return attributeTypeBlack
}
+ // JavaScript on.* event handlers
+ if sUpperWithoutNulls[:2] == "ON" {
+ eventName := sUpperWithoutNulls[2:]
+ // got javascript on- attribute name
+ for _, event := range blackEvents {
+ if eventName == event.name {
+ return event.attributeType
+ }
+ }
+ }
}
for _, black := range blacks {
- if upperS == black.name {
+ if sUpperWithoutNulls == black.name {
// got banner attribute name
return black.attributeType
}
diff --git a/xss_test.go b/xss_test.go
index 3fd4fbd..113f050 100644
--- a/xss_test.go
+++ b/xss_test.go
@@ -8,34 +8,47 @@ import (
"testing"
)
+// Examples can be read at https://portswigger.net/web-security/cross-site-scripting/cheat-sheet
func TestIsXSS(t *testing.T) {
- examples := []string{
- "",
- ">",
- "x >",
- "' >",
- "\">",
- "red;",
- "red;}",
- "red;\"/>",
- "');}",
- "onerror=alert(1)>",
- "x onerror=alert(1);>",
- "x' onerror=alert(1);>",
- "x\" onerror=alert(1);>",
- "",
- "",
- "",
- "",
- "",
- "",
+ examples := []struct {
+ input string
+ isXSS bool
+ }{
+ // True positives
+ {input: "", isXSS: true},
+ {input: ">", isXSS: true},
+ {input: "x >", isXSS: true},
+ {input: "' >", isXSS: true},
+ {input: "\">", isXSS: true},
+ {input: "red;", isXSS: true},
+ {input: "red;}", isXSS: true},
+ {input: "red;\"/>", isXSS: true},
+ {input: "');}", isXSS: true},
+ {input: "onerror=alert(1)>", isXSS: true},
+ {input: "x onerror=alert(1);>", isXSS: true},
+ {input: "x' onerror=alert(1);>", isXSS: true},
+ {input: "x\" onerror=alert(1);>", isXSS: true},
+ {input: "", isXSS: true},
+ {input: "", isXSS: true},
+ {input: "", isXSS: true},
+ {input: "", isXSS: true},
+ {input: "", isXSS: true},
+ {input: "", isXSS: true},
+ {input: "", isXSS: true},
+ {input: "\">", isXSS: true},
+ {input: "javascript:/*-->