我正在尝试在代码中实现XSS预防功能。我有一个PHP XSS函数,可以很好地工作,还有一个javascript端,用于不立即发送到数据库的元素。问题是,在我自己的测试中,我可以通过输入类似于
的内容来“破坏”我的代码“ />破坏代码比应该的要容易
我希望这很可能是一个问题。我正在使用称为“ tag-it”的资源,您可以检出here。
顺便说一下,这是主项目的一个分支,据说可以解决不使用jQuery 3的问题,但尚未实现。我已经通过执行提交注释所说的操作来解决了该问题,即用.andSelf()
.addBack()
目前,我的代码要多于此处的有用代码,因此,我将在下面放置一个更简单的示例代码片段。
/*--------tag-it.js (fixed for jQuery 3--------*/
/*--------Must be placed first to allow for proper functionality--------*/
(function(b) {
b.widget("ui.tagit", {
options: {
allowDuplicates: !1,
caseSensitive: !0,
fieldName: "tags",
placeholderText: null,
readOnly: !1,
removeConfirmation: !1,
tagLimit: null,
availableTags: [],
autocomplete: {},
showAutocompleteOnFocus: !1,
allowSpaces: !1,
singleField: !1,
singleFieldDelimiter: ",",
singleFieldNode: null,
animate: !0,
tabIndex: null,
beforeTagAdded: null,
afterTagAdded: null,
beforeTagRemoved: null,
afterTagRemoved: null,
onTagClicked: null,
onTagLimitExceeded: null,
onTagAdded: null,
onTagRemoved: null,
tagSource: null
},
_create: function() {
var a =
this;
this.element.is("input") ? (this.tagList = b("<ul></ul>").insertAfter(this.element), this.options.singleField = !0, this.options.singleFieldNode = this.element, this.element.addClass("tagit-hidden-field")) : this.tagList = this.element.find("ul, ol").addBack().last();
this.tagInput = b('<input type="text" />').addClass("ui-widget-content");
this.options.readOnly && this.tagInput.attr("disabled", "disabled");
this.options.tabIndex && this.tagInput.attr("tabindex", this.options.tabIndex);
this.options.placeholderText && this.tagInput.attr("placeholder",
this.options.placeholderText);
this.options.autocomplete.source || (this.options.autocomplete.source = function(a, e) {
var d = a.term.toLowerCase(),
c = b.grep(this.options.availableTags, function(a) {
return 0 === a.toLowerCase().indexOf(d)
});
this.options.allowDuplicates || (c = this._subtractArray(c, this.assignedTags()));
e(c)
});
this.options.showAutocompleteOnFocus && (this.tagInput.focus(function(b, d) {
a._showAutocomplete()
}), "undefined" === typeof this.options.autocomplete.minLength && (this.options.autocomplete.minLength =
0));
b.isFunction(this.options.autocomplete.source) && (this.options.autocomplete.source = b.proxy(this.options.autocomplete.source, this));
b.isFunction(this.options.tagSource) && (this.options.tagSource = b.proxy(this.options.tagSource, this));
this.tagList.addClass("tagit").addClass("ui-widget ui-widget-content ui-corner-all").append(b('<li class="tagit-new"></li>').append(this.tagInput)).click(function(d) {
var c = b(d.target);
c.hasClass("tagit-label") ? (c = c.closest(".tagit-choice"), c.hasClass("removed") || a._trigger("onTagClicked",
d, {
tag: c,
tagLabel: a.tagLabel(c)
})) : a.tagInput.focus()
});
var c = !1;
if (this.options.singleField)
if (this.options.singleFieldNode) {
var d = b(this.options.singleFieldNode),
f = d.val().split(this.options.singleFieldDelimiter);
d.val("");
b.each(f, function(b, d) {
a.createTag(d, null, !0);
c = !0
})
} else this.options.singleFieldNode = b('<input type="hidden" style="display:none;" value="" name="' + this.options.fieldName + '" />'), this.tagList.after(this.options.singleFieldNode);
c || this.tagList.children("li").each(function() {
b(this).hasClass("tagit-new") ||
(a.createTag(b(this).text(), b(this).attr("class"), !0), b(this).remove())
});
this.tagInput.keydown(function(c) {
if (c.which == b.ui.keyCode.BACKSPACE && "" === a.tagInput.val()) {
var d = a._lastTag();
!a.options.removeConfirmation || d.hasClass("remove") ? a.removeTag(d) : a.options.removeConfirmation && d.addClass("remove ui-state-highlight")
} else a.options.removeConfirmation && a._lastTag().removeClass("remove ui-state-highlight");
if (c.which === b.ui.keyCode.COMMA && !1 === c.shiftKey || c.which === b.ui.keyCode.ENTER || c.which ==
b.ui.keyCode.TAB && "" !== a.tagInput.val() || c.which == b.ui.keyCode.SPACE && !0 !== a.options.allowSpaces && ('"' != b.trim(a.tagInput.val()).replace(/^s*/, "").charAt(0) || '"' == b.trim(a.tagInput.val()).charAt(0) && '"' == b.trim(a.tagInput.val()).charAt(b.trim(a.tagInput.val()).length - 1) && 0 !== b.trim(a.tagInput.val()).length - 1)) c.which === b.ui.keyCode.ENTER && "" === a.tagInput.val() || c.preventDefault(), a.options.autocomplete.autoFocus && a.tagInput.data("autocomplete-open") || (a.tagInput.autocomplete("close"), a.createTag(a._cleanedInput()))
}).blur(function(b) {
a.tagInput.data("autocomplete-open") ||
a.createTag(a._cleanedInput())
});
if (this.options.availableTags || this.options.tagSource || this.options.autocomplete.source) d = {
select: function(b, c) {
a.createTag(c.item.value);
return !1
}
}, b.extend(d, this.options.autocomplete), d.source = this.options.tagSource || d.source, this.tagInput.autocomplete(d).bind("autocompleteopen.tagit", function(b, c) {
a.tagInput.data("autocomplete-open", !0)
}).bind("autocompleteclose.tagit", function(b, c) {
a.tagInput.data("autocomplete-open", !1)
}), this.tagInput.autocomplete("widget").addClass("tagit-autocomplete")
},
destroy: function() {
b.Widget.prototype.destroy.call(this);
this.element.unbind(".tagit");
this.tagList.unbind(".tagit");
this.tagInput.removeData("autocomplete-open");
this.tagList.removeClass("tagit ui-widget ui-widget-content ui-corner-all tagit-hidden-field");
this.element.is("input") ? (this.element.removeClass("tagit-hidden-field"), this.tagList.remove()) : (this.element.children("li").each(function() {
b(this).hasClass("tagit-new") ? b(this).remove() : (b(this).removeClass("tagit-choice ui-widget-content ui-state-default ui-state-highlight ui-corner-all remove tagit-choice-editable tagit-choice-read-only"),
b(this).text(b(this).children(".tagit-label").text()))
}), this.singleFieldNode && this.singleFieldNode.remove());
return this
},
_cleanedInput: function() {
return b.trim(this.tagInput.val().replace(/^"(.*)"$/, "$1"))
},
_lastTag: function() {
return this.tagList.find(".tagit-choice:last:not(.removed)")
},
_tags: function() {
return this.tagList.find(".tagit-choice:not(.removed)")
},
assignedTags: function() {
var a = this,
c = [];
this.options.singleField ? (c = b(this.options.singleFieldNode).val().split(this.options.singleFieldDelimiter),
"" === c[0] && (c = [])) : this._tags().each(function() {
c.push(a.tagLabel(this))
});
return c
},
_updateSingleTagsField: function(a) {
b(this.options.singleFieldNode).val(a.join(this.options.singleFieldDelimiter)).trigger("change")
},
_subtractArray: function(a, c) {
for (var d = [], f = 0; f < a.length; f++) - 1 == b.inArray(a[f], c) && d.push(a[f]);
return d
},
tagLabel: function(a) {
return this.options.singleField ? b(a).find(".tagit-label:first").text() : b(a).find("input:first").val()
},
_showAutocomplete: function() {
this.tagInput.autocomplete("search",
"")
},
_findTagByLabel: function(a) {
var c = this,
d = null;
this._tags().each(function(f) {
if (c._formatStr(a) == c._formatStr(c.tagLabel(this))) return d = b(this), !1
});
return d
},
_isNew: function(a) {
return !this._findTagByLabel(a)
},
_formatStr: function(a) {
return this.options.caseSensitive ? a : b.trim(a.toLowerCase())
},
_effectExists: function(a) {
return Boolean(b.effects && (b.effects[a] || b.effects.effect && b.effects.effect[a]))
},
createTag: function(a, c, d) {
var f = this;
a = b.trim(a);
this.options.preprocessTag && (a = this.options.preprocessTag(a));
if ("" === a) return !1;
if (!this.options.allowDuplicates && !this._isNew(a)) return a = this._findTagByLabel(a), !1 !== this._trigger("onTagExists", null, {
existingTag: a,
duringInitialization: d
}) && this._effectExists("highlight") && a.effect("highlight"), !1;
if (this.options.tagLimit && this._tags().length >= this.options.tagLimit) return this._trigger("onTagLimitExceeded", null, {
duringInitialization: d
}), !1;
var g = b(this.options.onTagClicked ? '<a class="tagit-label"></a>' : '<span class="tagit-label"></span>').text(a),
e = b("<li></li>").addClass("tagit-choice ui-widget-content ui-state-default ui-corner-all").addClass(c).append(g);
this.options.readOnly ? e.addClass("tagit-choice-read-only") : (e.addClass("tagit-choice-editable"), c = b("<span></span>").addClass("ui-icon ui-icon-close"), c = b('<a><span class="text-icon">\u00d7</span></a>').addClass("tagit-close").append(c).click(function(a) {
f.removeTag(e)
}), e.append(c));
this.options.singleField || (g = g.html(), e.append('<input type="hidden" value="' + g + '" name="' + this.options.fieldName + '" class="tagit-hidden-field" />'));
!1 !== this._trigger("beforeTagAdded", null, {
tag: e,
tagLabel: this.tagLabel(e),
duringInitialization: d
}) && (this.options.singleField && (g = this.assignedTags(), g.push(a), this._updateSingleTagsField(g)), this._trigger("onTagAdded", null, e), this.tagInput.val(""), this.tagInput.parent().before(e), this._trigger("afterTagAdded", null, {
tag: e,
tagLabel: this.tagLabel(e),
duringInitialization: d
}), this.options.showAutocompleteOnFocus && !d && setTimeout(function() {
f._showAutocomplete()
}, 0))
},
removeTag: function(a, c) {
c = "undefined" === typeof c ? this.options.animate : c;
a = b(a);
this._trigger("onTagRemoved",
null, a);
if (!1 !== this._trigger("beforeTagRemoved", null, {
tag: a,
tagLabel: this.tagLabel(a)
})) {
if (this.options.singleField) {
var d = this.assignedTags(),
f = this.tagLabel(a),
d = b.grep(d, function(a) {
return a != f
});
this._updateSingleTagsField(d)
}
if (c) {
a.addClass("removed");
var d = this._effectExists("blind") ? ["blind", {
direction: "horizontal"
}, "fast"] : ["fast"],
g = this;
d.push(function() {
a.remove();
g._trigger("afterTagRemoved", null, {
tag: a,
tagLabel: g.tagLabel(a)
})
});
a.fadeOut("fast").hide.apply(a, d).dequeue()
} else a.remove(),
this._trigger("afterTagRemoved", null, {
tag: a,
tagLabel: this.tagLabel(a)
})
}
},
removeTagByLabel: function(a, b) {
var d = this._findTagByLabel(a);
if (!d) throw "No such tag exists with the name '" + a + "'";
this.removeTag(d, b)
},
removeAll: function() {
var a = this;
this._tags().each(function(b, d) {
a.removeTag(d, !1)
})
}
})
})(jQuery);
/*--------My example JS--------*/
// Create the tag "box"
let tagBox = $('#tagMe');
tagBox.tagit();
//Html escape function
function htmlEscape(str) {
return str
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
/*-------- Default Tag-it Stylesheet (altered)--------*/
ul.tagit {
padding: 1px 5px;
overflow: auto;
margin: 0 0 8px;
margin-left: inherit;
margin-right: inherit
}
ul.tagit li {
display: block;
float: left;
margin: 2px 5px 2px 0
}
input.tagit-hidden-field,
ul.tagit li.tagit-choice .tagit-close .text-icon {
display: none
}
ul.tagit li.tagit-choice {
position: relative;
line-height: inherit
}
ul.tagit li.tagit-choice-read-only {
padding: .2em .5em
}
ul.tagit li.tagit-choice-editable {
padding: .2em 18px .2em .5em
}
ul.tagit li.tagit-new {
padding: .25em 4px .25em 0
}
ul.tagit li.tagit-choice a.tagit-label {
cursor: pointer;
text-decoration: none
}
ul.tagit li.tagit-choice .tagit-close {
cursor: pointer;
position: absolute;
right: .1em;
top: 50%;
margin-top: -8px;
line-height: 17px
}
ul.tagit li.tagit-choice input {
display: block;
float: left;
margin: 2px 5px 2px 0
}
ul.tagit input[type=text] {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
border: none;
margin: 0;
padding: 2px;
width: inherit;
background-color: inherit;
outline: 0
}
/*--------My Example Custom Theming--------*/
ul.tagit {
padding: 1px 5px;
border: solid thin green;
}
ul.tagit li.tagit-choice {
border: solid thin #6aa1cc;
background: #dafaff;
border-radius: 4px;
font: inherit;
}
.ui-icon-close:after {
content: "\00d7";
display: inline-block;
font-weight: bold;
}
.ui-icon-close {
padding: 4px 4px;
}
.ui-icon-close:hover {
color: #e74c18;
}
.ui-helper-hidden-accessible {
display: none !important;
}
kbd {
background-color: #E8E8E8;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<form>
<p>Type in a single word without spaces (I haven't enabled input with spaces in this example), then press comma, tab, or enter.</p>
<ul id="tagMe"></ul>
<p>After you have a tag, inspect the tag element, especially the hidden input that is generated below it. If you put in a none XSS string, you will see that string has been placed inside the value attribute of that input element. BUT if you put an XSS
string like <kbd>"/> Breakingbad</kbd> then you see that the string has broken out of the value attribute. I have also inluded a function that I have been trying to find a place where it works in the tag-it.js but haven't had success yet.</p>
</form>
编辑 我最初将错误的html转义功能放到JS中,我已经纠正了这一点。