html input validator

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
using System;
using System.IO;
using System.Xml;
using System.Reflection;
using Sgml;
namespace efe.Defender
{
//Класс предназначен для фильтрации ввода произвольно
//форматированно html от потенциально опасных данных.
//Автор Lac, support@effetto.ru, effetto.ru
//Класс имеет один входной параметр (с возможностью передачи конструктору) -
//проверяемый код html, и один выходной - фильтрованный код.
//При вильтрации выживают только описанные в шаблоне данные, что надежнее чем
//простой поиск потенциально опасных выражений. Последнего недостатка не удалось
//избежать только при фильтрации строк значений атрибутов
//Класс использует SgmlReader, за авторством Криса Ловетта, Микрософт
//Класс использует XmlDocument который несколько медленнее Xml ридеров,
//но более удобен. При ограничениях на скорость или память легко переписывается
//с использованием ридера.
public class HtmlDefender
{
//Входные данные
private string _input = "";
//Шаблон
private XmlDocument _rules = null;
public HtmlDefender()
{
LoadRules();
}
public HtmlDefender(string Input)
: this()
{
this.Input = Input;
}
//Процедура загрузки шаблона для проверки. Шаблон, это документ
//в котором описано то - что можно оставить в исследуемом html.
//Все что не описано в шаблоне - подлежит удалению.
//
//Здесь теоретически можно загружать любой xml файл, формат примерно такой:
//<?xml version="1.0" encoding="utf-8" ?>
//<html>
// <p style="noscript;"></p>
// <img alt="" src="noscript;"/>
// <h1></h1>
// <br/>
//</html>
private void LoadRules()
{
Assembly assembly = Assembly.GetAssembly(Type.GetType("efe.Defender.HtmlDefender"));
StreamReader sr = new StreamReader(assembly.GetManifestResourceStream("HtmlDefender.Rules.xml"));
_rules = new XmlDocument();
_rules.LoadXml(sr.ReadToEnd());
}
//Исследуемый html
public string Input
{
get
{
return _input;
}
set
{
_input = value;
}
}
//Фильтрованный html
public string DefendedHtml
{
get
{
return ValidateHtml();
}
}
//Функция возвращает контейнер тега по имени тега, null если тег запрещен
XmlNode GetValidationNode(string Name)
{
foreach (XmlNode n in _rules.ChildNodes[1].ChildNodes)
if (n.Name == Name)
return n;
return null;
}
//Проверка на наличие в строке указания со скриптом, обычно нападающий
//пытается вставить конструкцию javascript: в параметры типа src тега img
bool ValidateNoScript(string Value)
{
if (Value.Contains("script")) return false;
return true;
}
// Процедура проверки всех атрибутов тега по шаблону
void ValidateAtrributes(XmlNode Tag)
{
if (Tag.Attributes != null)
{
//Индекс цикла инкрементится только в случае удачной валидации,
//иначе удаляется элемент.
//По этой причине не используется foreach
int Index = 0;
while (Index < Tag.Attributes.Count)
{
XmlAttribute Attribute = Tag.Attributes[Index];
if (IsValidTagAttribute(Tag.Name, Attribute))
{
Index++;
}
else
{
Tag.Attributes.Remove(Attribute);
}
}
}
}
//Возвращает true если тег допустим
bool IsValidTagName(string Name)
{
if (GetValidationNode(Name) != null)
return true;
else
return false;
}
//Возвращает true если атрибут тега может имет указанное значение
bool IsValidAttributeValue(string ValidationString, string AttributeValue)
{
bool Valid = true;
if (ValidationString.Contains("noscript;"))
if (!ValidateNoScript(AttributeValue)) Valid = false;
return Valid;
}
//Возвращает true если тег может иметь указанный атрибут
bool IsValidTagAttribute(string TagName, XmlAttribute Attribute)
{
XmlNode ValidationNode = GetValidationNode(TagName);
foreach (XmlAttribute a in ValidationNode.Attributes)
if (a.Name == Attribute.Name)
{
if (a.Value.Length > 0)
{
if (IsValidAttributeValue(a.Value, Attribute.Value))
return true;
}
else
{
return true;
}
}
return false;
}
//Возвращает true если тег с указанным именем и типом допустим
bool IsValidTag(XmlNode Tag)
{
if (Tag.NodeType == XmlNodeType.Text) return true;
if (IsValidTagName(Tag.Name))
{
return true;
}
else
{
return false;
}
}
//Рекурентная процедура прохождения дерева тегов
void ParseNodeList(XmlNodeList TagList)
{
//Индекс цикла инкрементится только в случае удачной валидации,
//иначе удаляется элемент.
//По этой причине не используется foreach
int Index = 0;
while (Index < TagList.Count)
{
XmlNode Tag = TagList[Index];
if (IsValidTag(Tag))
{
ValidateAtrributes(Tag);
ParseNodeList(Tag.ChildNodes);
Index++;
}
else
{
XmlNode Parent = Tag.ParentNode;
Parent.RemoveChild(Tag);
}
}
}
//Функция возвращает фильтрированный результат
private string ValidateHtml()
{
SgmlReader reader = new SgmlReader();
reader.InputStream = new StringReader("<html>" + Input + "</html>");
XmlDocument document= new XmlDocument();
document.Load(reader);
XmlNode HtmlNode = document.FirstChild;
ParseNodeList(HtmlNode.ChildNodes);
return document.InnerXml;
}
}
}