FogBugz Plugin API > Plugin Security
This article will demonstrate how functions exposed by CSecurityApi and a set of best practices can help protect your plugin (and your FogBugz site) from cross-site scripting attacks, SQL injection attacks, and cross-site request forgery attacks.
Cross-Site Scripting Attacks
Cross-site scripting (XSS) attacks inject malicious active content into web pages. If the active content is inserted via an input field, then embedded into the website's HTML upon a future reload, the injected code (in the form of Javascript, VBScript, ActiveX, Flash ActionScript, etc.) can perform any number of malicious actions, such as initiating an AJAX request to delete application data.
Prevent XSS attacks by using HTMLEncode
The .NET framework's System.Web.HttpUtility class includes an HTMLEncode method that converts potentially dangerous symbols (including HTML brackets, semicolons, etc.) into their harmless "escaped" HTML equivalent.
When embedding a string in HTML content, it's best practice to encode the string if it could have been entered or affected by an external user. For example, the following block of code uses HTMLEncode to "sanitize" data regarding a "kiwi" record that was entered into the database:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: |
for (int i = 0; i < ds.Tables[0].Rows.Count; i++) { sHTML += "<tr><td>"; sHTML += ds.Tables[0].Rows[i]["ixKiwi"].ToString(); sHTML += "</td><td>"; sHTML += HttpUtility.HtmlEncode( ds.Tables[0].Rows[i]["sKiwiFullName"].ToString()); sHTML += "</td><td>"; sHTML += api.TimeZone.CTZFromUTC( Convert.ToDateTime( ds.Tables[0].Rows[i]["dtDateOfBirth"])).ToString(); sHTML += "</td><td>"; sHTML += HttpUtility.HtmlEncode( ds.Tables[0].Rows[i]["sOwnerFullName"].ToString()); sHTML += "</td></tr>"; } |
Note that HtmlEncode and HtmlAttributeEncode do not escape single quotes ('). Because of this, html that is constructed using single-quoted attributes isn't secure against XSS unless additional escaping steps are taken. To solve this, we strongly recommend using double-quoted html attributes in conjunction with HtmlEncode- e.g.:
"<div title=\"" + HttpUtility.HtmlEncode(sTitle) + "\">" //(safe)
instead of
"<div title='" + HttpUtility.HtmlEncode(sTitle) + "'>" //(Not secure)
SQL Injection Attacks
SQL injection attacks insert malicious SQL commands into database queries. Applications are vulnerable to such attacks if they assemble database queries by concatonating a series of variables (set by an external user) and SQL clauses.
Prevent SQL injection attacks by using named parameters
The article Generic Database Access introduced the set of database query classes exposed to FogBugz plugins. The CUpdateQuery, CDeleteQuery, CSelectQuery, CInsertQuery, and CInsertSelectQuery classes allow developers to build platform-agnostic queries, one clause at a time.
They also allow developers to introduce named parameters into their queries, which can safely be assigned the value of a variable set by an external user. This is because the SQL interpreter treats them as substituted values rather than semantically meaningful portions of the query.
The following example uses CSelectQuery to get all the cases assigned to a particular user, as well as perform a join with the "Person" table in order to get data regarding the user. Note that the "ixPerson" value passed in by the request object (and presumably set by an external user) is assigned to a named parameter rather than tacked on the SQL Query directly:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: |
//"api" is an instance of CPluginApi CSelectQuery query = api.Database.NewSelectQuery("Bug"); query.AddSelect("Bug.ixBug, Bug.ixPersonAssignedTo"); query.AddLeftJoin("Person", "Bug.ixPersonAssignedTo = Person.ixPerson"); query.AddSelect("Person.sFullName, Person.sEmail, Person.sPhone"); query.AddWhere("Person.ixPerson = @ixPerson"); query.SetParamInt("ixPerson", api.Request["ixPerson"]); query.AddOrderBy("Person.sFullName"); query.Limit = 50; int count = query.Count(); |
Cross-Site Forgery Attacks
Cross-Site Request Forgery (CSRF) Attacks exploit the trust a website has established with a user's web browser by transmitting to the website unathorized, malicious commands from that browser. CSRF attacks often operate by serving content to the user's browser that references the URL of the target website and appends malicious commands to the querystring of that URL.
Prevent CSRF attacks by using FogBugz action tokens
The CSecurityApi class exposes two methods designed to effectively combat CSRF attacks, GetActionToken() and ValidateActionToken().
A FogBugz "Action Token" is a globally unique identifier automatically created and stored by FogBugz when the method GetActionToken() is called. The action token returned by GetActionToken() is normally embedded in a hidden input field by server-side ASP.NET code.
Using Action Tokens with Forms
When the user submits a plugin-created form, this token should be posted back to the server, where it is then validated using ValidateActionToken(). This function compares the passed action token against the set of valid tokens currently active on the FogBugz site. Even though action tokens will remain active for a whole week, developers are encouraged to generate a new action token each time a secure page is served.
Note that GetActionToken() and ValidateActionToken() take either 1 or 2 arguments because action tokens can optionally be assigned a name. If a call to GetActionToken() assigns a name to a token, then the name / token pair must be passed to ValidateActionToken() in order for validation to be successful.
The following example form allows users to add a new kiwi to a database of kiwis. Notice that it contains a hidden field containing the action token. For an example plugin which uses action tokens, see Tutorial- A Simple Todo List.
When the form is submitted, we just have to make sure that the action token is valid before actually inserting the new kiwi into the database:
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: |
string formHTML = String.Format( @"<form action=""{0}"" method=""POST""> {1} <p>Add a Kiwi to your collection!</p> <p> </p> <p><b>Kiwi Name:</b> {2} {3}</p> </form>", HttpUtility.HtmlEncode(api.Url.PluginPageUrl()), // We're creating an action token to send to the client. It will be stored there in a // hidden input field and posted back to us. Note: Don't forget to prepend the plugin prefix // to the names of all form elements! Forms.HiddenInput(api.PluginPrefix() + "actionToken", api.Security.GetActionToken("addKiwi")), Forms.TextInput(api.PluginPrefix() + "kiwiName", ""), Forms.SubmitButton("idSubmit", "Add Kiwi") ); // Check the action token to make sure that the request is valid! if (api.Request["kiwiName"] != null && api.Request["actionToken"] != null && api.Security.ValidateActionToken(api.Request["actionToken"], "addKiwi")) { InsertKiwi(api.Request["kiwiName"].ToString()); } |
Using Action Tokens with AJAX requests
Using action tokens to verify the authenticity of an AJAX request is very similar. Rather than including the token in a form, the developer need only include it as an additional querystring parameter sent along with the AJAX request:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
/* This is an AJAX URL generated on the server side and sent to the client as * part of a block of javascript. */ string fullAjaxUrl = api.Url.PluginRawPageUrl() + String.Format("&{0}kiwiName={1}&{0}kiwiZoo={2}&{0}kiwiRecDate={3}&" + "{0}action=addKiwi&{0}data=kiwiList&{0}actionToken={4}", api.PluginPrefix(), kiwiName, kiwiZoo, kiwiRecDate, api.Security.GetActionToken("addKiwi") ); |