private bool ValidateNode(ElementWrapper node, InsertionsMap insertionsMap, ref RequestValidationParam dangerousParam) { var result = true; foreach ( var element in node.Children.OfType<ElementWrapper>()) { var elementStart = (int)element.StartPosition.offset; var elementEnd = (int)element.EndPosition.offset; foreach ( var insertionArea in insertionsMap.Where(ia => ia.Includes(elementStart, elementEnd))) { // Check if start position of node was included to insertions map if (insertionArea.Includes(elementStart)) { // Inclusion found (node was injected by request parameter) dangerousParam = insertionArea.Param; return false; } foreach (var attr in element.Attributes) { var attrNameStart = (int)attr.NameStart.offset; if (insertionArea.Includes(attrNameStart)) { // Inclusion found (attribute was injected by request parameter) dangerousParam = insertionArea.Param; return false; } var attrValueStart = (int)attr.ValueStart.offset; var attrValueEnd = (int)attr.ValueEnd.offset; // Skip if attribute value wasn't tainted by request parameter if (!insertionArea.Includes(attrValueStart, attrValueEnd)) continue; // Skip if attribute value passes validation if (ValidateAttribute(attr.Name, attr.Value, insertionArea.Param.Value)) continue; // Attribute value is dangerously tainted dangerousParam = insertionArea.Param; return false; } } if (element.Tag == GumboTag.GUMBO_TAG_SCRIPT && element.Children.Any(child => child is TextWrapper)) { var text = element.Children.OfType<TextWrapper>().FirstOrDefault(); if (text != null) { // Validate javscript code inside <script /> tag foreach ( var insertionArea in insertionsMap.Where( ia => ia.Includes((int) text.StartPosition.offset, (int) text.StartPosition.offset + text.Text.Length)) .Where(insertionArea => !ValidateJavascript(text.Text, insertionArea.Param.Value))) { // Javascript code is dangerously tainted dangerousParam = insertionArea.Param; return false; } } } // TODO: Add integrity validation of style nodes here result &= ValidateNode(element, insertionsMap, ref dangerousParam); } return result; }
public bool IsValidHtmlResponseString(List<RequestValidationParam> taintfulParams, string responseText, out RequestValidationParam dangerousParam) { dangerousParam = null; // HACK: Deny null-byte injection techniques. Perhaps, there are a better place and method to implement it. if (responseText.IndexOf('\0') != -1) { dangerousParam = new RequestValidationParam("null-byte", "Undefined", "...\\0"); return false; } var parsedResponse = new GumboWrapper(responseText); if (!parsedResponse.Document.Children.Any()) return true; // Get boundaries for all occurences of request params in response text var insertionsMap = InsertionsMap.FindAllPrecise( taintfulParams, responseText); if (insertionsMap.Count == 0) return true; // In case of parse errors, needed a check that positions of errors isn't included to insertions map if (parsedResponse.Errors != null && parsedResponse.Errors.Any()) { foreach (var error in parsedResponse.Errors) { foreach (var insertionArea in insertionsMap) { if (!insertionArea.Includes((int) error.position.offset)) continue; // Inclusion found (integrity of response has been violated by request parameter at error position) dangerousParam = insertionArea.Param; return false; } } } // Deny obviously dangerous insertions foreach (var insertionArea in from insertionArea in insertionsMap let paramValue = insertionArea.Param.Value where // paramValue ⊃ "<[A-Za-z%!/?]?" paramValue.Where( (t, i) => t == '<' && i < paramValue.Length - 1 && ( ( (paramValue[i + 1] >= 'a' && paramValue[i + 1] <= 'z') || (paramValue[i + 1] >= 'A' && paramValue[i + 1] <= 'Z') ) || paramValue[i + 1] == '%' || paramValue[i + 1] == '!' || paramValue[i + 1] == '/' || paramValue[i + 1] == '?' ) ).Any() select insertionArea) { dangerousParam = insertionArea.Param; return false; } var result = true; foreach (var node in parsedResponse.Document.Children.OfType<ElementWrapper>()) { result &= ValidateNode(node, insertionsMap, ref dangerousParam); } return result; }