Introduction
In ASP.NET, if you want to allow your user to be able to enter HTML text into a text box, then it seems to be an all or nothing affair. You either turn off page validation or you get an error when someone tries to post the page with HTML text. If you don't want to turn off page validation, but just enable specific text boxes to be able to submit HTML tags or JavaScript; then this is for you.
Background
This solution is targeted at ASP.NET WebForms. It assumes that JavaScript is enabled, and jquery is included on the page. It is coded in C# and jquery-javascript with the translation to other languages left for the reader.
If you are building a form that generally accepts random input and you accept that you have to manually vet every input value, then just go with the default and turn off page validation. ASP.NET will no longer complain at you, and you can handle the input as you like.
<%@ page validaterequest="false" %>
If, on the other hand you have a large form, and you want the default security that the page validation puts in place, and you want to leave validaterequest
as true
, then you have a problem. A problem we are about to solve.
Exploring the Problem
When page validation is enabled and you try to submit HTML in a text box (and errors are turned on), you will receive a message like:
A potentially dangerous Request.Form value was detected from the client
You could trap the error, but we don't want to handle an error, we want to prevent it. In order to prevent it, we need to pre-encode the value so that it will get past the validation. Here's a portion of an example form:
<script language="javascript" type="text/javascript"
src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<asp:textbox id="txt0" runat="server"></asp:textbox>
<asp:textbox id="txt1" runat="server" cssclass="html"></asp:textbox>
<asp:textbox id="txt2" runat="server" cssclass="html"></asp:textbox>
<asp:button id="submit" runat="server" text="Submit" usesubmitbehavior="true" />
This page has jquery included (from CDN), three text boxes, and a submit button. Two text boxes have a class defined; "html
" so that we can tell jquery which text boxes to work with. But first let's test the page by trying to enter our simple script injection attack:
<script>alert(
Sure enough, we get the dangerous value detected message.
A jQuery Solution
Now let's add a little jquery to the equation:
$('input.html').each(function () {
var e = $(this);
var ex = $('<input type="hidden" name="' + e.attr('name') + '" value="' + escape(e.val()) + '" />');
e.before(ex);
e.removeAttr('name');
e.change(function () {
ex.val(escape(e.val()));
});
});
What does this code do? There are 7 important things:
- Selects the text boxes we wish to enable HTML input for:
$('input.html')
- Runs a function for each of them:
.each(function () { ... });
- Assigns a variable to reference the text box:
var e = $(this);
- Creates a hidden input control that has the same name as the text box, but with its value escaped:
var ex = $('<input type="hidden" name="' + e.attr('name') + '" value="' + escape(e.val()) + '" />');
- Inserts the hidden input to the page before the text box:
e.before(ex);
- Removes the name from the text box. This must be done or both the hidden and text box inputs will send back their values as a comma separated list, and the problem will continue:
e.removeAttr('name');
- Adds a change listener to the text box to keep the hidden input value up-to-date:
e.change(function () {
ex.val(escape(e.val()));
})
Now if we put our script injection attack text into the first text box; we will get the error message as before. If we put it in either of the HTML enabled text boxes, it will post back successfully.
Are we done? Not yet, because when the page comes back from the server (the submit button does nothing yet), the value in text box is displayed as encoded. We want to display pretty HTML to the user - the way they typed it, not an encoded value. So we need to add some more code... you could add it to the client side (when creating the hidden input field), but I wanted to store and validate the un-encoded form so I put it server-side:
protected void Page_Load(object sender, EventArgs e)
{
txt1.Text = HttpUtility.UrlDecode(txt1.Text);
txt2.Text = HttpUtility.UrlDecode(txt2.Text);
}
This simply decodes the text for the selected text boxes and puts it back where it came from - the text control itself handles the required escaping of the text value to ensure it doesn't mess up the page if someone types in something aimed at finishing the input tag and inserting something else.
Now that we have the un-encoded value in the text box, we can validate it, save it to the database, or whatever else you planned to do with it, and you have exactly what was typed into the textbox by the user, dangerous or not.
It Is All Over?
Nope, now we have succeeded in being able to post back "dangerous" values in selected fields, it is once again up to you to ensure that the intended use of this value will be safe. No inserting malicious code in web pages, etc... But that really is a long topic and is well covered by others:
Before You Comment...
As stated in the background, it is accepted that JavaScript must be enabled for this approach to work. In our case, this was not an issue, if it is in your situation, then you will have to take another approach; sorry.
I know jquery calls can be chained together, they are separated for readability, if you wish to try to put it all in one chained line, be my guest.
You could also argue that this would be better if it were encapsulated into an ASP control with the script in the ascx and ascx.cs (and you would probably be right). You could then select the textbox by id rather than class name, and just add the user-control to the web-page and almost forget about it. But I felt that would complicate the demonstration of the method.
History
- Version 1 - Posted 19 July 2012