[Asp.Net Core] 网站中的XSS跨站脚本攻击和防范

漏洞说明:
跨站脚本攻击(Cross Site Scripting),为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。恶意攻击者往Web页面里插入恶意Web脚本代码(html、javascript、css等),当用户浏览该页面时,嵌入其中的Web脚本代码会被执行,从而达到恶意攻击用户的特殊目的。

测试步骤
访问系统网站,点击基础报告库进行编辑,使用Burp抓包并重新构造数据包

[Asp.Net Core] 网站中的XSS跨站脚本攻击和防范_第1张图片

重新访问,成功触发了XSS弹窗

[Asp.Net Core] 网站中的XSS跨站脚本攻击和防范_第2张图片

解决方法:

将危险内容过滤去除,用HTML转义字符串(Escape Sequence)表达的则保留
添加脚本过滤类

    /// 
    /// Html 脚本过滤
    /// 
    public class NHtmlFilter
    {
        protected static readonly RegexOptions REGEX_FLAGS_SI = RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled;

        private static string P_COMMENTS = "";
        private static Regex P_COMMENT = new Regex("^!--(.*)--$", REGEX_FLAGS_SI);
        private static string P_TAGS = "<(.*?)>";
        private static Regex P_END_TAG = new Regex("^/([a-z0-9]+)", REGEX_FLAGS_SI);
        private static Regex P_START_TAG = new Regex("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI);
        private static Regex P_QUOTED_ATTRIBUTES = new Regex("([a-z0-9|(a-z0-9\\-a-z0-9)]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI);
        private static Regex P_UNQUOTED_ATTRIBUTES = new Regex("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI);
        private static Regex P_PROTOCOL = new Regex("^([^:]+):", REGEX_FLAGS_SI);
        private static Regex P_ENTITY = new Regex("&#(\\d+);?");
        private static Regex P_ENTITY_UNICODE = new Regex("&#x([0-9a-f]+);?");
        private static Regex P_ENCODE = new Regex("%([0-9a-f]{2});?");
        private static Regex P_VALID_ENTITIES = new Regex("&([^&;]*)(?=(;|&|$))");
        private static Regex P_VALID_QUOTES = new Regex("(>|^)([^<]+?)(<|$)", RegexOptions.Singleline | RegexOptions.Compiled);
        private static string P_END_ARROW = "^>";
        private static string P_BODY_TO_END = "<([^>]*?)(?=<|$)";
        private static string P_XML_CONTENT = "(^|>)([^<]*?)(?=>)";
        private static string P_STRAY_LEFT_ARROW = "<([^>]*?)(?=<|$)";
        private static string P_STRAY_RIGHT_ARROW = "(^|>)([^<]*?)(?=>)";
        private static string P_AMP = "&";
        private static string P_QUOTE = "\"";
        private static string P_LEFT_ARROW = "<";
        private static string P_RIGHT_ARROW = ">";
        private static string P_BOTH_ARROWS = "<>";

        // @xxx could grow large... maybe use sesat's ReferenceMap
        private static Dictionary P_REMOVE_PAIR_BLANKS = new Dictionary();
        private static Dictionary P_REMOVE_SELF_BLANKS = new Dictionary();
        /** 
         * flag determining whether to try to make tags when presented with "unbalanced"
         * angle brackets (e.g. "" becomes " text ").  If set to false,
         * unbalanced angle brackets will be html escaped.
         */
        protected static bool alwaysMakeTags = true;

        /**
         * flag determing whether comments are allowed in input String.
         */
        protected static bool stripComment = true;


        /// 
        /// 不允许
        /// 
        private string[] vDisallowed { get; set; }
        /// 
        /// 允许
        /// 
        protected Dictionary> vAllowed { get; set; }

        /** counts of open tags for each (allowable) html element **/
        protected Dictionary vTagCounts;

        /** html elements which must always be self-closing (e.g. "") **/
        protected string[] vSelfClosingTags;

        /** html elements which must always have separate opening and closing tags (e.g. "") **/
        protected string[] vNeedClosingTags;

        /** attributes which should be checked for valid protocols **/
        protected string[] vProtocolAtts;

        /** allowed protocols **/
        protected string[] vAllowedProtocols;

        /** tags which should be removed if they contain no content (e.g. "" or "") **/
        protected string[] vRemoveBlanks;

        /** entities allowed within html markup **/
        protected string[] vAllowedEntities;


        /// 
        /// 是否为调试
        /// 
        protected bool vDebug;

        public NHtmlFilter() : this(false) { }

        public NHtmlFilter(bool debug)
        {
            //List vAllowed = new List();
            vAllowed = new Dictionary>();
            #region 允许通过数组

            vAllowed.Add("a", new List() { "target", "href", "title", "class", "style" });
            vAllowed.Add("addr", new List() { "title", "class", "style" });
            vAllowed.Add("address", new List() { "class", "style" });
            vAllowed.Add("area", new List() { "shape", "coords", "href", "alt" });
            vAllowed.Add("article", new List() { });
            vAllowed.Add("aside", new List() { });
            vAllowed.Add("audio", new List() { "autoplay", "controls", "loop", "preload", "src", "class", "style" });
            vAllowed.Add("b", new List() { "class", "style" });
            vAllowed.Add("bdi", new List() { "dir" });
            vAllowed.Add("bdo", new List() { "dir" });
            vAllowed.Add("big", new List() { });
            vAllowed.Add("blockquote", new List() { "cite", "class", "style" });
            vAllowed.Add("br", new List() { });
            vAllowed.Add("caption", new List() { "class", "style" });
            vAllowed.Add("center", new List() { });
            vAllowed.Add("cite", new List() { });
            vAllowed.Add("code", new List() { "class", "style" });
            vAllowed.Add("col", new List() { "align", "valign", "span", "width", "class", "style" });
            vAllowed.Add("colgroup", new List() { "align", "valign", "span", "width", "class", "style" });
            vAllowed.Add("dd", new List() { "class", "style" });
            vAllowed.Add("del", new List() { "datetime" });
            vAllowed.Add("details", new List() { "open" });
            vAllowed.Add("div", new List() { "class", "style" });
            vAllowed.Add("dl", new List() { "class", "style" });
            vAllowed.Add("dt", new List() { "class", "style" });
            vAllowed.Add("em", new List() { "class", "style" });
            vAllowed.Add("font", new List() { "color", "size", "face" });
            vAllowed.Add("footer", new List() { });
            vAllowed.Add("h1", new List() { "class", "style" });
            vAllowed.Add("h2", new List() { "class", "style" });
            vAllowed.Add("h3", new List() { "class", "style" });
            vAllowed.Add("h4", new List() { "class", "style" });
            vAllowed.Add("h5", new List() { "class", "style" });
            vAllowed.Add("h6", new List() { "class", "style" });
            vAllowed.Add("header", new List() { });
            vAllowed.Add("hr", new List() { });
            vAllowed.Add("i", new List() { "class", "style" });
            vAllowed.Add("img", new List() { "src", "alt", "title", "style", "width", "height", "id", "_src", "loadingclass", "class", "data-latex", "data-id", "data-type", "data-s" });
            vAllowed.Add("ins", new List() { "datetime" });
            vAllowed.Add("li", new List() { "class", "style" });
            vAllowed.Add("mark", new List() { });
            vAllowed.Add("nav", new List() { });
            vAllowed.Add("ol", new List() { "class", "style" });
            vAllowed.Add("p", new List() { "class", "style" });
            vAllowed.Add("pre", new List() { "class", "style" });
            vAllowed.Add("s", new List() { });
            vAllowed.Add("section", new List() { });
            vAllowed.Add("small", new List() { });
            vAllowed.Add("span", new List() { "class", "style" });
            vAllowed.Add("sub", new List() { "class", "style" });
            vAllowed.Add("sup", new List() { "class", "style" });
            vAllowed.Add("strong", new List() { "class", "style" });
            vAllowed.Add("table", new List() { "width", "border", "align", "valign", "class", "style" });
            vAllowed.Add("tbody", new List() { "align", "valign", "class", "style" });
            vAllowed.Add("td", new List() { "width", "rowspan", "colspan", "align", "valign", "class", "style" });
            vAllowed.Add("tfoot", new List() { "align", "valign", "class", "style" });
            vAllowed.Add("th", new List() { "width", "rowspan", "colspan", "align", "valign", "class", "style" });
            vAllowed.Add("thead", new List() { "align", "valign", "class", "style" });
            vAllowed.Add("tr", new List() { "rowspan", "align", "valign", "class", "style" });
            vAllowed.Add("tt", new List() { });
            vAllowed.Add("u", new List() { });
            vAllowed.Add("ul", new List() { "class", "style" });
            vAllowed.Add("video", new List() { "autoplay", "controls", "loop", "preload", "src", "height", "width", "class", "style" });
            #endregion


            vDebug = debug;
            vTagCounts = new Dictionary();

            vSelfClosingTags = new string[] { "img" };
            vNeedClosingTags = new string[] { "a", "b", "strong", "i", "em" };
            vDisallowed = new string[] { "script" };
            vAllowedProtocols = new string[] { "http", "mailto" }; // no ftp.
            vProtocolAtts = new string[] { "src", "href" };
            vRemoveBlanks = new string[] { "a", "b", "strong", "i", "em" };
            vAllowedEntities = new string[] { "amp", "gt", "lt", "quot" };
            stripComment = true;
            alwaysMakeTags = true;
        }


        protected void reset()
        {
            vTagCounts = new Dictionary();
        }

        protected void debug(string msg)
        {
            if (vDebug)
                System.Diagnostics.Debug.WriteLine(msg);
        }

        //---------------------------------------------------------------
        // my versions of some PHP library functions

        public static string chr(int dec)
        {
            return "" + (char)dec;
        }

        /// 
        /// 转换成实体字符
        /// 
        /// 
        /// 
        public static string htmlSpecialChars(string str)
        {
            str = str.Replace(P_QUOTE, "\"");

            str = str.Replace(P_LEFT_ARROW, "<");
            str = str.Replace(P_RIGHT_ARROW, ">");
            str = str.Replace("\n", "
"); return str; } //--------------------------------------------------------------- /** * given a user submitted input String, filter out any invalid or restricted * html. * * @param input text (i.e. submitted by a user) than may contain html * @return "clean" version of input, with only valid, whitelisted html elements allowed */ public string filter(string input) { reset(); string s = input; debug("************************************************"); debug(" INPUT: " + input); s = escapeComments(s); debug(" escapeComments: " + s); s = balanceHTML(s); debug(" balanceHTML: " + s); s = checkTags(s); debug(" checkTags: " + s); s = processRemoveBlanks(s); debug("processRemoveBlanks: " + s); s = validateEntities(s); debug(" validateEntites: " + s); debug("************************************************\n\n"); return s; } protected string escapeComments(string s) { return Regex.Replace(s, P_COMMENTS, new MatchEvaluator(ConverMatchComments), RegexOptions.Singleline); } protected string regexReplace(string regex_pattern, string replacement, string s) { return Regex.Replace(s, regex_pattern, replacement); } protected string balanceHTML(string s) { if (alwaysMakeTags) { // // try and form html // s = regexReplace(P_END_ARROW, "", s); s = regexReplace(P_BODY_TO_END, "<$1>", s); s = regexReplace(P_XML_CONTENT, "$1<$2", s); } else { // // escape stray brackets // s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s); s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s); // // the last regexp causes '<>' entities to appear // (we need to do a lookahead assertion so that the last bracket can // be used in the next pass of the regexp) // s = s.Replace(P_BOTH_ARROWS, ""); } return s; } protected string checkTags(string s) { //替换不允许标签 foreach (var item in vDisallowed) { s = Regex.Replace(s, string.Format(@"<{0}\b(.)*?>(.)+?", item), ""); } s = Regex.Replace(s, P_TAGS, new MatchEvaluator(ConverMatchTags), RegexOptions.Singleline); // these get tallied in processTag // (remember to reset before subsequent calls to filter method) foreach (string key in vTagCounts.Keys) { for (int ii = 0; ii < vTagCounts[key]; ii++) { s += ""; } } return s; } protected string processRemoveBlanks(string s) { foreach (string tag in vRemoveBlanks) { s = regexReplace("<" + tag + "(\\s[^>]*)?>", "", s); s = regexReplace("<" + tag + "(\\s[^>]*)?/>", "", s); } return s; } private string processTag(string s) { // ending tags Match m = P_END_TAG.Match(s); if (m.Success) { string name = m.Groups[1].Value.ToLower(); if (allowed(name)) { if (!inArray(name, vSelfClosingTags)) { if (vTagCounts.ContainsKey(name)) { vTagCounts[name] = vTagCounts[name] - 1; return ""; } } } } // starting tags m = P_START_TAG.Match(s); if (m.Success) { string name = m.Groups[1].Value.ToLower(); string body = m.Groups[2].Value; string ending = m.Groups[3].Value; //debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" ); if (allowed(name)) { string params1 = ""; MatchCollection m2 = P_QUOTED_ATTRIBUTES.Matches(body); MatchCollection m3 = P_UNQUOTED_ATTRIBUTES.Matches(body); List paramNames = new List(); List paramValues = new List(); foreach (Match match in m2) { paramNames.Add(match.Groups[1].Value); //([a-z0-9]+) paramValues.Add(match.Groups[3].Value); //(.*?) } foreach (Match match in m3) { paramNames.Add(match.Groups[1].Value); //([a-z0-9]+) paramValues.Add(match.Groups[3].Value); //([^\"\\s']+) } string paramName, paramValue; for (int ii = 0; ii < paramNames.Count; ii++) { paramName = paramNames[ii].ToLower(); paramValue = paramValues[ii]; if (allowedAttribute(name, paramName)) { if (inArray(paramName, vProtocolAtts)) { paramValue = processParamProtocol(paramValue); } params1 += " " + paramName + "=\"" + paramValue + "\""; } } if (inArray(name, vSelfClosingTags)) { ending = " /"; } if (inArray(name, vNeedClosingTags)) { ending = ""; } if (ending == null || ending.Length < 1) { if (vTagCounts.ContainsKey(name)) { vTagCounts[name] = vTagCounts[name] + 1; } else { vTagCounts.Add(name, 1); } } else { ending = " /"; } return "<" + name + params1 + ending + ">"; } else { return ""; } } // comments m = P_COMMENT.Match(s); if (!stripComment && m.Success) { return "<" + m.Value + ">"; } return ""; } private string processParamProtocol(string s) { s = decodeEntities(s); Match m = P_PROTOCOL.Match(s); if (m.Success) { string protocol = m.Groups[1].Value; if (!inArray(protocol, vAllowedProtocols)) { // bad protocol, turn into local anchor link instead s = "#" + s.Substring(protocol.Length + 1, s.Length - protocol.Length - 1); if (s.StartsWith("#//")) { s = "#" + s.Substring(3, s.Length - 3); } } } return s; } private string decodeEntities(string s) { s = P_ENTITY.Replace(s, new MatchEvaluator(ConverMatchEntity)); s = P_ENTITY_UNICODE.Replace(s, new MatchEvaluator(ConverMatchEntityUnicode)); s = P_ENCODE.Replace(s, new MatchEvaluator(ConverMatchEntityUnicode)); s = validateEntities(s); return s; } private string validateEntities(string s) { s = P_VALID_ENTITIES.Replace(s, new MatchEvaluator(ConverMatchValidEntities)); s = P_VALID_QUOTES.Replace(s, new MatchEvaluator(ConverMatchValidQuotes)); return s; } private static bool inArray(string s, string[] array) { foreach (string item in array) { if (item != null && item.Equals(s)) { return true; } } return false; } private bool allowed(string name) { return (vAllowed.Count == 0 || vAllowed.ContainsKey(name)) && !inArray(name, vDisallowed); } private bool allowedAttribute(string name, string paramName) { return allowed(name) && (vAllowed.Count == 0 || vAllowed[name].Contains(paramName)); } private string checkEntity(string preamble, string term) { return ";".Equals(term) && isValidEntity(preamble) ? '&' + preamble : "&" + preamble; } private bool isValidEntity(string entity) { return inArray(entity, vAllowedEntities); } private static string ConverMatchComments(Match match) { string matchValue = ""; return matchValue; } private string ConverMatchTags(Match match) { string matchValue = processTag(match.Groups[1].Value); return matchValue; } private string ConverMatchEntity(Match match) { string v = match.Groups[1].Value; int decimal1 = int.Parse(v); return chr(decimal1); } private string ConverMatchEntityUnicode(Match match) { string v = match.Groups[1].Value; int decimal1 = Convert.ToInt32("0x" + v, 16); return chr(decimal1); } private string ConverMatchValidEntities(Match match) { string one = match.Groups[1].Value; //([^&;]*) string two = match.Groups[2].Value; //(?=(;|&|$)) return checkEntity(one, two); } private string ConverMatchValidQuotes(Match match) { string one = match.Groups[1].Value; //(>|^) string two = match.Groups[2].Value; //([^<]+?) string three = match.Groups[3].Value;//(<|$) return one + regexReplace(P_QUOTE, "\"", two) + three; } public bool isAlwaysMakeTags() { return alwaysMakeTags; } public bool isStripComments() { return stripComment; } class Item { public string name { get; set; } public List parameter { get; set; } } }

源代码出自:https://www.cnblogs.com/OleRookie/p/5970167.html

在请求时对参数的内容进行过滤:

var nHtmlFilter = new NHtmlFilter(false);
surveyPayload.PayloadContent = nHtmlFilter.filter(surveyPayload.PayloadContent);

再次请求时,已将危险代码转成HTML转义字符串的形式了

[Asp.Net Core] 网站中的XSS跨站脚本攻击和防范_第3张图片

[Asp.Net Core] 网站中的XSS跨站脚本攻击和防范_第4张图片

你可能感兴趣的:(.NET,Web,asp.net,xss,后端)