Skip to content
wmeyer edited this page Mar 11, 2011 · 5 revisions

This chapter is about common security threats to web applications and how Roads helps to prevent them. Roads does not totally avoid these threats, so as a developer you have to be familiar with them.

Roads does currently not support the HTTPS protocol and is therefore not suited for high-security applications at this point of its developement.

Contents

Cross-site Scripting (XSS)

This vulnerability is about harmful code injected into a web app by malicious users with the intention to be executed in web pages viewed by other users. The most important rule for mitigation is to

NEVER TRUST USER INPUT!

Roads supports this necessary paranoia by (semi-)automatically HTML-escaping user input and by providing a mechanism for input validation.

Escaping

If you revisit the “Arc Challenge” example of the first chapter and enter something like <b>bold text</b>, you will find that the text will not appear bold in the result page. Instead, the literal HTML code will be shown. However, it is still possible to access the unescaped input. How is this implemented?
POST/GET parameters are presented to the application code as two-element record values. The above input looks like this to the application:

externalInput(original:"<b>bold text</b>"
              escaped:"&lt;b&gt;bold text&lt;&#x2F;b&gt;")
If such a composite value is embedded in HTML code, Roads will automatically choose the escaped version. When sending data to the model layer, the developer has to choose which value to use.

Note that Roads does currently not support automatic CSS, Javascript or URL escaping. If you want to use user input within CSS code, scripts or URLs, you have to ensure escaping manually.

If you use a database layer vulnerable to SQL injection, you need to sanitize the user input manually, too.

Input Validation for Form Submissions

If all input is properly escaped, input validation is strictly speaking not necessary to prevent XSS attacks. However, it is very useful to enforce other types of constraints on input data.

To validate input values from form submissions, we can specify a function for every input tag. Such a validation function takes two arguments: the id of the input field (as specified with the id attribute) and the input value as a string. It returns

  • true if the value is valid,
  • false if the value is not valid and no custom error message is required,
  • 'false'("Custom error message") if the value is not valid and we want to provide an error message, or
  • 'false'(AltFunction) if the value is not valid and we want to execute the function AltFunction instead of the action function.

Example (fragment)

This functions lets the user enter a natural number and displays it back to them:

fun {EnterInt Session}
  V
in
  form(
    input(type:text bind:V
          validate:fun {$ _ X} {All X Char.isDigit} end)
    input(type:submit)
    method:post
    action:fun {$ _} "You entered: " # {String.toInt V.original} end
  )
end

Some validation requirements are very common. Instead of a validation function, you may specify one of the following values for the validate attribute:

  • length_in(MinLen MaxLen) to constraint the string length to an interval.
  • length_is(Len) to insist on an exact length.
  • int to expect an integer as accepted by StringToInt.
  • float for floating point numbers as accepted by StringToFloat.
  • is(Val) to insist on one specific value.
  • one_of(Val1 Val2 ... ValN) to accept one of multiple constant values.
  • not_one_of(Val1 Val2 ... ValN) to accept all but a number of constant values.
  • regex("RegularExpression") to validate a parameter with a regular expression.
  • list(Validator) for parameters which are expected to occur multiple times (as with select elements with multiple selection). Validator can be any of the values above.

Example (fragment)

To restrict the length of the user name and the password in a login form, we could use the length_in validator:

form(
  table(
    tr(td(label('for':"Login" "Login: "))
       td(input(type:text id:"Login" bind:Login
                validate:length_in(4 12))))
    tr(td(label('for':"Password" "Password: "))
       td(input(type:password id:"Password" bind:Password
                validate:length_in(5 12))))
...
  )
)

Input Validation with public GET handlers

It is possible to explicitly register GET handler functions under a URL. This is useful to make the result of a GET request bookmarkable for the user.

Note that the validation mechanism of the previous section is not applicable for such functions because they are not necessarily called through form submissions. In this case you may validate the input parameters by an explicit call to Session.validateParameters which takes a list of parameter specifications.
As an example, let’s look at a version of the Arc Challenge implementation with a bookmarkable “click here” page. “Bookmarkable” means that the URL can be used from within a new session, e.g. from a different computer or after a server reboot.

Example

(available at /roads/examples/Bookmarkable.oz)

declare
[Roads] = {Module.link ['x-ozlib://wmeyer/roads/Roads.ozf']}

functor Pages
export
   Said
   HandleSaid
   After
define
   fun {Said Session}
      form(input(type:text name:foo)
           input(type:submit)
           method:get  %% using GET instead of POST to encode the input in the URL
           action:url(function:handleSaid)
	  )
   end

   fun {HandleSaid S}
      {S.validateParameters [foo(validate:length_in(1 10))]}
      Foo = {S.getParam foo}
   in
      p(a("click here"
          href:fun {$ _}
                  p("you said: " # Foo)
               end
       ))
   end

   fun {After Session Doc}
      html(head(title("Said"))
           body(Doc)
	  )
   end
end

in

{Roads.registerFunctor '' Pages}
{Roads.run}

The HandleSaid function expects exactly one GET parameter with its length constrained to 1 to 10. If the validation fails, an error message (including the problematic parameter) will be shown to the user.

Note that we specify the record value url(function:handleSaid) for the action attribute. We could have written "/handleSaid" instead. However, if we ever change the (currently empty) path of the functor, we have to manually adjust the path for action. By using url(...), we automatically get a URL relativ to the current functor.

Session Hijacking

Session hijacking is a technique where an attacker tries to find out the session id which is used to identify a user to a web application, and uses this id to execute requests in the name of the user.

Roads uses random 64-bit values as session ids. These random values are created using dev/urandom on Unix-like systems and CryptGenRandom on Windows, so they are cryptographically secure. It is therefore virtually impossible to simply guess a session id.

Roads applies the HttpOnly flag to prevent the session id to be stolen in case of XSS vulnerabilities. (This does only work in some browsers.)

It is possible for an application to request a new session id for the current session. This is done by calling {Session.regenerateSessionId}. It makes sense to call this function after a successfull login attempt to prevent session fixation.

It should be noted that these measures are not sufficient to prevent session hijacking completely. Roads does currently not support the HTTPS protocol, which would be necessary to prevent attacks by packet sniffing. With packet sniffing, it is possible to steal both session ids and credentials used during the login process.

Cross-site Request Forgery (CSRF)

This type of ‘attack works by including a link or script in a page that accesses a site to which the user is known (or is supposed) to have authenticated’.

When a form is submitted in Roads, a generated URL is requested that leads to an internal function which validates inputs, binds variables and calls a application-specific handler function . This URL contains a random id which can usually not be guessed.
Additionally, all Roads forms (with a function value as the action handler) contain a hidden element with a one-time secret token. If a request with an invalid token is received, validation will fail.

The same is true for links with procedure values as href attributes.

Therefore, with normal usage patterns, CSRF attacks are not possible with Roads applications.

However, if you write a POST/GET handler function and explicitly register it under a URL to make it bookmarkable, the function will be reachable from the outside and CSRF attacks are possible. You should therefore only do this with functions which are free of side effects and therefore uninteresting for attackers.

 

Previous: The Session Object    Next: Application Development

Wolfgang.Meyer@gmx.net

Clone this wiki locally