Introduction
Introduction The purpose of this guide to assist the developer in ensuring the DotNetNuke (DNN) modules they write are written with security in mind.
In this guide we will highlight common issues, and offer guidance on how to identify and counter potential security issues that may occur whilst developing DNN modules.
In addition this document also covers general web security and recommended practices such as defense in depth. “Defense in depth” is the principle of adding layers of protection to defend against attempts by potential hackers. Whilst a hacker may be skilled in certain areas such as SQL injection, they may not have the skills necessary to break through a number of layers. If you code your modules so that they do not make any assumptions, and validate their functionality at a number of levels, your modules will be much more secure e.g. if you intend to write a module that allows file uploads, then it’s a good idea to add code to validate the user and limit the type of files to a safe subset of extensions, rather than assuming any call to this code can be trusted.
What Are Modules? Modules provide developers with the ability to extend the functionality of DNN. DNN provides a pluggable framework that can be expanded by the development of modules. Modules can be developed in any .NET language that support the Code Dom (e.g. Managed C++ cannot be used, but most of the other .Net languages can), even though DNN is developed in VB.NET, a C# developer can still create a module that plugs into the core framework provided by DNN. This pluggable framework is accomplished by creating compiled private assemblies that expose and utilize interfaces specific to DNN. Once you compile the assembly, and then just create a user interface (UI) in the form of ascx files that allow your user to interact with your module.
Modules provide you with maximum code reusability; you can key off the module ID value (provided by the DNN framework) which allows you to display unique data for each implementation of your module.
Why would modules be insecure? Web applications are popular targets for potential attackers. Whilst the DNN framework itself has a number of mechanisms that close common attack vectors, poorly written modules may open up holes that would allow attackers to alter content, gain access to data or even take over entire portals.
What do you mean by user input? Variations on the phrase “User input” are used a number of times in this document, so it’s worth considering what we mean by that. Whilst this may appear at first glance to refer only to values entered by the user, and retrieved from either the forms or querystring collection, really it covers any data that is passed between client and server.
HTTP requests and responses consist of both a head and a body, both of which can contain data. With a little effort, or utilizing one of many available tools, an attacker can alter any of this data to attempt to circumvent your web application protection. Whilst writing your modules, it is worth keeping in mind any time you read data from the following, that you should not implicitly trust it, but should make an effort to sanitise the input so it cannot do any harm. The key areas to watch for are:
- Forms collection (including text passed in hidden form fields )
- Cookies
- Querystring
- Items collection
- Data retrieved from HTTP Headers (e.g. it is possible to alter the user-agent string of a browser request to act as an XSS string)
Core filtering functions
Before looking at the various types of issues, it’s worthwhile looking at one of the core functions that can be used to provide protection against a number of issues.
DNN contains a routine called InputFilter which has a number of potential values, defined by the following enumeration.
Enum FilterFlag
MultiLine = 1
NoMarkup = 2
NoScripting = 4
NoSQL = 8
End Enum
- MultiLine is not really a security function, it was used in the past by some core functions to replace CRLF (carriage-return line feed) characters with the html equivalent
tags. - NoMarkup – when this value is used, the contents of the passed string are htmlencoded. This is a very safe mechanism particularly against cross-site scripting attacks, as it converts potential HTML to plain text converting characters such as < to their html encoding equivalent (“<”). This is the safest filter as it will stop arbitrary html being interpreted as javascript. In some cases this may not be suitable e.g. forums/bulletin boards that allow the user to use some HTML markup.
- NoScripting – this value is used to search and strip any suspect HTML from strings passed to it. Typically this includes script or html that a hacker would try to use during a cross-site scripting attack.
- NoSQL – when used, this value calls a function that searches the passed text and strip’s anything that might be arbitrary SQL i.e. to protect against SQL Injection attacks.
All of these values are designed to be used on inbound requests i.e. you pass the text/value that you have accepted from the user, so they can be converted/scrubbed accordingly. Doing this inbound offers more protection than having to remember to apply it to all outbound instances.
The values in the enumeration can be used individually or we can use the bitwise OR operator to combine the values e.g. this code will both apply the NoScripting filter, and the NoMarkup filter.
Example of two InputFilter functions being applied Dim objSecurity As New PortalSecurity
Dim myFilteredText as string=objSecurity.InputFilter(request.form("txtSearch"), PortalSecurity.FilterFlag.NoScripting Or PortalSecurity.FilterFlag.NoMarkup) lblSearchtext.text=”Searching for :” & myFilteredText
SQL Injection SQL Injection is a common issue, where the attacker alters input which eventually forms part of a database query. Commonly, additional SQL statements are concatenated to an existing query, to allow these secondary statements to run, accessing alternative data, or granting permissions, and in certain scenarios where the database user has sufficient permissions, gaining access to the database server itself. This guide focuses on databases that use the Transact-SQL Dialect (SQL2000/MSDE2000/SQL2005/SQL Express), though SQL injection affects many database applications such as MySQL and Oracle.
Identifying the issue There are a number of variations, but the most common one is where user input is trusted and forms part of either a concatenation or string replacement within a query, which is then executed e.g.
Dim myQuery as string="SELECT username from usertable where password=’" &
request.querystring("txtUsername") & “’”
Dim myCommand As New SqlCommand(myQuery, myConnection)
In this code snippet, a hacker could alters the URL to alter the query e.g.
default.aspx?txtUsername=admin could be changed to default.aspx?txtUsername=admin’;delete from sometable -- . As SQL Server uses the semicolon (;) as a delimiter for statements, this querystring alteration will cause 2 statements to run
Using parameterized stored procedures would help guard against this type of issue, but stored procedures that contain concatenation routines themselves could be an issue e.g.
create proc GetSearchResults(@searchTerm nvarchar(50)) as
declare @sql nvarchar(300)
set @sql = 'select * from searchResults where SearchTerm like ''%'
+ @searchTerm + '%''' exec sp_executesql @sql
go
All the DNN core code uses parameterized SQL Stored procedures, and you are highly recommended to follow this approach.
What to check your code for
- Search your code for any dynamic SQL strings
- Check stored procedures for code that uses the sp_executesql stored procedure or the T-SQL function exec/execute to run queries
Protecting against the issue
First and foremost, in an ideal world you would not allow user input to build a portion of a database query. If your module requires you to do this, then where possible ensure that all database access is conducted via stored procedures. In addition to this ensure that you allow for the smallest possible amount of text, validate any user input that is passed to it, and strongly type any variables used i.e. if accepting a parameter from a querystring that should be numeric, check for this.
If your module still needs to accept user input, then you should make use of the NoSQL enumeration of InputFilter. This filter is a blacklist filter (i.e. it pattern matches for known terms), and therefore like all blacklist filters may be fooled by carefully encoded content (e.g. if a hacker disguises their text via an encoding such as hex – please take a look at this guide for more details and examples of this), but does offer an additional layer of protection.
Original unsafe code
Dim myQuery as string="SELECT username from usertable where password=’" & request.querystring("txtUsername") & “’”
Dim myCommand As New SqlCommand(myQuery, myConnection)
Safer version
Dim objSecurity As New PortalSecurity
Dim myFilteredText as string=objSecurity.InputFilter(request.querystring("txtUsername"), PortalSecurity.FilterFlag.NoSQL)
Dim myQuery as string="SELECT username from usertable where password=’" & myFilteredText & “’”
Dim myCommand As New SqlCommand(myQuery, myConnection)
Cross site scripting
Cross site scripting (XSS), is another vulnerability that relies on un-sanitized user input. In this case, rather than being sent to the database server, the malicious script that’s sent to the application as input, is eventually echoed back to a users browser where it is executed. Commonly, this script will attempt to gain access to the user’s cookies, so an attacker can use them to access the application with the user’s credentials i.e. an impersonation attack. Other less common XSS attacks, will execute javascript in the users context, perhaps to redirect to another page that bears a close resemblance to the original (“a phishing attack”)
Identifying the issue Anywhere you return user input to the screen, or store if to be later returned, perhaps in a file or database store, or even in a application level object such as session, cookie, application etc., the code should be inspected to check if the values have not been sanitized e.g. this code snippet shows an example of unsafe code.
Dim mySearchTerm as string= request.querystring("txtSearch")
lblSearchtext.text=”Searching for :” & mySearchTerm
When it runs, it trusts the input it retrieves from the request.form(“txtSearch”) value, and renders it, but this input could be unsafe e.g. if you receive an email with a link that suggests you go to http://somesite.com/default.aspx?txtSearch=
The code above would render then render this javascript. Whilst this example is little more than an annoyance, it is not difficult to alter it so that it is more dangerous.
What to check your code for Examine your code for anywhere you reference values from HttpRequest, and check all outputting code, such as use of HttpResponse and places where you explicitly set variables values via .value or .text. If using inline code, examine your use of <%= %>
Protecting against the issue Once again the key point is to not trust user input . In this case the recommended practice is to utilize InputFilter with “NoMarkup”. If your input must allow HTML, then you can use “NoScripting” instead, but please think carefully whether or not your module really needs to support HTML text, as the “NoScripting” filter is a blacklist filter, and therefore like all blacklist filters may be fooled by carefully encoded content (e.g. please take a look at this guide for more details and examples of cross-site scripting(XSS) encoding).
Original unsafe code Dim mySearch as string= request.querystring("txtSearch")
lblSearchtext.text=”Searching for :” & mySearch
Better version that strips common XSS variants Dim mySearch as string= request.querystring("txtSearch")
Dim objSecurity As New PortalSecurity
mySearch =objSecurity.InputFilter(mySearch, PortalSecurity.FilterFlag.NoScripting) lblSearchtext.text=”Searching for :” & mySearchTerm
Best version that HTML-Encodes the string Dim mySearch as string= request.querystring("txtSearch")
Dim objSecurity As New PortalSecurity
mySearch =objSecurity.InputFilter(mySearch, PortalSecurity.FilterFlag.NoMarkup) lblSearchtext.text=”Searching for :” & mySearchTerm
Additional references
DotNetNuke Module Security: Filtering User-Entered Text