Skip to content
This repository was archived by the owner on Oct 14, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion AjaxControlToolkit.Tests/HtmlEditorExtenderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public void StripScriptTagWithoutAttributes() {
var text = @" <script>";
var actual = extender.Decode(text);

Assert.AreEqual("", actual);
Assert.AreEqual(" ", actual);
}

[Test]
Expand All @@ -78,5 +78,49 @@ public void DoNotStripDataEncodedImage() {
Assert.False(decodedText.Contains("data:text/javascript"));
}
}

[Test]
public void RemoveInsecureHtml() {
using(var html = new HtmlEditorExtender()) {
html.EnableSanitization = false;

// was "Z"
Assert.AreEqual("A", html.Decode("A<script>Z"));

// was "<script>Z"
Assert.AreEqual("", html.Decode("<script>Z"));

Assert.AreEqual("AZ", html.Decode("A<script attr>...</script attr>Z"));

// was "Hello world!alert()" - see https://github.com/DevExpress/AjaxControlToolkit/issues/525
Assert.AreEqual(
"Hello world! ",
html.Decode("Hello world! <script>alert();</script>")
);

// was "<style</style>" - see https://github.com/DevExpress/AjaxControlToolkit/issues/513
var issue513html = "<style><!-- body { font-family: Script; } --></style>";
Assert.AreEqual(issue513html, html.Decode(issue513html));

// was "<pClick me</a></p>"
Assert.AreEqual(
"<p><a>Click me</a></p>",
html.Decode("<p><a href='javascript:alert()'>Click me</a></p>")
);

var comment = "<!-- behavior of expression filter data:text/plain -->";
Assert.AreEqual(comment, html.Decode(comment));

Assert.AreEqual(
"<p><p><p><p>",
html.Decode(
"<p style='width: expression(1)'>" +
"<p style='position: absolute'>" +
"<p style='filter: inherit'>" +
"<p style='behavior: url(something.htc)>"
)
);
}
}
}
}
52 changes: 35 additions & 17 deletions AjaxControlToolkit/HtmlEditorExtender/HtmlEditorExtender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,23 +173,6 @@ public string Decode(string value) {

result = Regex.Replace(result, "&amp;", "&", RegexOptions.IgnoreCase);
result = Regex.Replace(result, "&nbsp;", "\xA0", RegexOptions.IgnoreCase);
result = Regex.Replace(result, "[^<]<[^>]*expression[^>]*>", "", RegexOptions.IgnoreCase | RegexOptions.ECMAScript);

result = Regex.Replace(result, "[^<]<([^>]*)(data\\:[^>]*)>", m => {
var tagGroup = m.Groups[1].Value.ToLower();
var urlGroup = m.Groups[2].Value.ToLower();

if(tagGroup.StartsWith("img") && urlGroup.StartsWith("data:image/"))
return m.Value;

return "";
}, RegexOptions.IgnoreCase | RegexOptions.ECMAScript);

result = Regex.Replace(result, "[^<]<[^>]*script(?!\\w)[^>]*>", "", RegexOptions.IgnoreCase | RegexOptions.ECMAScript);
result = Regex.Replace(result, "[^<]<[^>]*filter[^>]*>", "", RegexOptions.IgnoreCase | RegexOptions.ECMAScript);
result = Regex.Replace(result, "[^<]<[^>]*behavior[^>]*>", "", RegexOptions.IgnoreCase | RegexOptions.ECMAScript);
result = Regex.Replace(result, "[^<]<[^>]*javascript\\:[^>]*>", "", RegexOptions.IgnoreCase | RegexOptions.ECMAScript);
result = Regex.Replace(result, "[^<]<[^>]*position\\:[^>]*>", "", RegexOptions.IgnoreCase | RegexOptions.ECMAScript);

// Check Whether EnableSanitization is disabled or not.
if(EnableSanitization && Sanitizer != null) {
Expand All @@ -205,6 +188,8 @@ public string Decode(string value) {
elementWhiteList.Add("img", new[] { "src" });

result = Sanitizer.GetSafeHtmlFragment(result, elementWhiteList);
} else {
result = RemoveInsecureHtml(result);
}

// HtmlAgilityPack vanishes self-closing <hr /> tag, so replace it after sanitization
Expand All @@ -213,6 +198,39 @@ public string Decode(string value) {
return result;
}

static string RemoveInsecureHtml(string html) {
var reFlags = RegexOptions.IgnoreCase | RegexOptions.Singleline;

html = Regex.Replace(html, @"<script[^>]*>.*?(</script[^>]*>|$)", "", reFlags);

html = Regex.Replace(html, @"<(/?[^\s>]+)([^>]*)>", m => {
var tag = m.Groups[1].Value;

if(tag.StartsWith("!--"))
return m.Value;

var attrs = m.Groups[2].Value;
var dropAttrs = false;

// Non-image data URLs
dropAttrs = dropAttrs || Regex.IsMatch(attrs, @"\bdata:(?!image/)", reFlags);

// Insecure/harmful CSS props
dropAttrs = dropAttrs || Regex.IsMatch(attrs, @"\b(filter|behavior|position)\s*:", reFlags);
dropAttrs = dropAttrs || Regex.IsMatch(attrs, @":\s*expression\b", reFlags);

// JavaScript URLs
dropAttrs = dropAttrs || Regex.IsMatch(attrs, @"\bjavascript:", reFlags);

if(dropAttrs)
return "<" + tag + ">";

return m.Value;
}, reFlags);

return html;
}

// On Init add popup div and ajaxfileupload control to support Add image
protected override void OnInit(EventArgs e) {
base.OnInit(e);
Expand Down