507 lines
No EOL
23 KiB
XML
507 lines
No EOL
23 KiB
XML
<?xml version='1.0' encoding='utf-8' ?>
|
|
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
|
|
<!ENTITY % BOOK_ENTITIES SYSTEM "Secure_Ruby_Development_Guide.ent">
|
|
%BOOK_ENTITIES;
|
|
]>
|
|
<chapter>
|
|
<title>Web Application Security</title>
|
|
<para>
|
|
Web application development is one of the most popular usages of Ruby language thanks to the popularity of Ruby on Rails. Following chapter is dedicated to security of web applications with most of the content being framework-independent, while examples and implmentation specific problems are targeted to Ruby on Rails.
|
|
</para>
|
|
|
|
<para>
|
|
Ruby on Rails as a popular web framework already helps with a web application security by providing secure defaults, useful helper methods, automatic html escaping etc.
|
|
</para>
|
|
|
|
<section>
|
|
<title>Authentication and session management</title>
|
|
<para>
|
|
|
|
</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Authorization and user management</title>
|
|
<para>
|
|
|
|
</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Common attacks and mitigations</title>
|
|
<section>
|
|
<title>Cross site scripting (XSS)</title>
|
|
<para></para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Cross site request forgery (CSRF)</title>
|
|
<para></para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>SQL injection attack</title>
|
|
<para></para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>LDAP injection attack</title>
|
|
<para></para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Cross site tracing (XST)</title>
|
|
<para></para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Guidelines and principles</title>
|
|
<para></para>
|
|
</section>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Client-side security</title>
|
|
<section>
|
|
<title>Same origin policy</title>
|
|
<para>
|
|
One of the most important concepts of web applications is same origin policy. It is a protection mechanism implemented by modern web browsers that isolates web applications from each other on the client side. This isolation is performed on domain names under the assumption that content from different domains comes from different entities. In theory, this means every domain has its own trust domain and interaction across domains is restricted. In practice, there are multiple ways of bypassing this mechanism, malicious ones often creating confused deputy problem where client`s browser is tricked into submitting attacker-specified request under his authority.
|
|
</para>
|
|
|
|
<para>
|
|
Same origin policy prevents Javascript and other scripting languages to access DOM across domains. In addition it also applies to XMLHttpRequest Javascript API provided by browsers and prohibits page of sending XMLHttpRequest requests against different domains. On the downside, actual implementation by different browsers may vary in important details. Since the actual behaviour depends on implementation in each browser, each vendor usually implements some exceptions intended to help web developers, which reduce the reliability of this mechanism.
|
|
</para>
|
|
|
|
<para>
|
|
<variablelist>
|
|
<varlistentry>
|
|
<term>Same origin policy</term>
|
|
<listitem>
|
|
<para>Two pages share the same origin if the protocol, hostname and port are the same for both.</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
</variablelist>
|
|
</para>
|
|
<para>
|
|
Following is a table with outcome of same origin policy check against URL http://web.company.com/~user1
|
|
</para>
|
|
<para>
|
|
|
|
<table frame='all'>
|
|
<title>Sample CALS Table</title>
|
|
<tgroup cols='3' align='left' colsep='1' rowsep='1'>
|
|
<thead>
|
|
<row><entry>URL</entry><entry>Outcome</entry><entry>Reason</entry></row>
|
|
</thead>
|
|
<tbody>
|
|
<row><entry>http://web.company.com/~user2</entry><entry>Success</entry><entry></entry></row>
|
|
<row><entry>https://web.company.com/~user1</entry><entry>Fail</entry><entry>Different protocol</entry></row>
|
|
<row><entry>http://store.company.com/~user1</entry><entry>Fail</entry><entry>Different hostname</entry></row>
|
|
<row><entry>https://web.company.com:81/~user1</entry><entry>Fail</entry><entry>Different port</entry></row>
|
|
</tbody>
|
|
</tgroup>
|
|
</table>
|
|
</para>
|
|
|
|
<para>
|
|
As the example above shows, if a company servers webpages of users from the same domain web.company.com, then pages of individual users are not restricted by same origin policy when accessing each other, as they are coming from the same domain.
|
|
</para>
|
|
|
|
<para>
|
|
Browsers treat hostname of server as string literal, which creates another exceptional case: even if IP address of company.com is 10.20.30.40, browser will enforce same origin policy between http://company.com and http://10.20.30.40.
|
|
</para>
|
|
|
|
<section>
|
|
<title>Setting document.domain</title>
|
|
<para>
|
|
A page can also define its origin by setting <code>document.domain</code> property to a fully-qualified suffix of the current hostname. When two pages have defined the same <code>document.domain</code>, same origin policy is not applied. However, <code>document.domain</code> has to be specified mutually - it is not enough for just one page to specify its <code>document.domain</code>. Also, when <code>document.domain</code> property is set, port is set to null, while still being checked. This means company.com:8080 cannot bypass same origin policy and access company.com by setting <code>document.domain = "company.com"</code>, as their ports (null vs 80) differ.
|
|
</para>
|
|
|
|
<para>
|
|
However, <code>document.domain</code> has several issues:
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>When web.company.com and storage.company.com need to share resources and set <code>document.domain = company.com</code>, any subdomain can set its <code>document.domain</code> and access both of them, even though this access was not intended to be permitted.</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
When this mechanism cannot be used, cross-domain requests are forbidden even for legitimate use, which creates problem for websites that use multiple (sub)domains.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Unrestricted operations</title>
|
|
<para>Same Origin Policy restricts Javascript access to DOM and XMLHttpRequest across domains. However, there are multiple operations that are not restricted:
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
Javascript embedding with <code><script src=".."><script></code>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
CSS embedding with <code><link rel="stylesheet" href="..."></code>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Anything with <code><frame></code> and <code><iframe></code>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
.. and others
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Additional resources</title>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
Browser Security Handbook <ulink url="http://code.google.com/p/browsersec/wiki/Part2">http://code.google.com/p/browsersec/wiki/Part2</ulink>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Same Origin Policy article on Mozilla Developer Network <ulink url="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Same_origin_policy_for_JavaScript">https://developer.mozilla.org/en-US/docs/Web/JavaScript/Same_origin_policy_for_JavaScript</ulink>
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</section>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Bypassing same origin policy</title>
|
|
<para>
|
|
Same Origin Policy as security mechanism leaves a lot to be desired: on one hand, it is not flexible enough to allow web developers use cross-domain resources in several legitimate usecases without exceptions to the rule and workarounds, on the other hand, such exceptions create opportunities for attacker.
|
|
</para>
|
|
|
|
<para>
|
|
There are several other mechanisms except <code>document.domain</code> that provide a way to relax Same Origin Policy.
|
|
</para>
|
|
|
|
<section id='CORS'>
|
|
<title>Cross-origin resource sharing (CORS)</title>
|
|
<para>
|
|
Cross-origin resource sharing is a mechanism that allows web application to inform browser, whether cross domain requests against the requested resource are expected.
|
|
</para>
|
|
|
|
<para>
|
|
Web browsers that conform to the CORS alter their behaviour of handling XMLHttpRequests: instead of denying the cross-domain request immediately, HTTP request is sent with <code>Origin</code> header. Let's assume http://example.com/testpage is making a XMLHttpRequest against http://content.com/wanted_image. Request would contain:
|
|
|
|
<programlisting>
|
|
GET /wanted_image HTTP/1.1
|
|
Referrer: http://example.com/testpage
|
|
Origin: http://example.com
|
|
</programlisting>
|
|
|
|
If the server allows sharing of the resource with domain that originated the request, the response would include:
|
|
|
|
<programlisting>
|
|
HTTP/1.1 200 OK
|
|
Access-Control-Allow-Origin: http://example.com
|
|
..
|
|
</programlisting>
|
|
|
|
By sending <constant>Access-Control-Allow-Origin</constant> header, server explicitly tells browser that this cross domain request shall be allowed. Allowed values of <constant>Access-Control-Allow-Origin</constant> are: * (denoting any domain, effectively marking the resource public) or space separated list of allowed origins (in practice, this usually contains just a single domain - one that was specified in Origin header in request).
|
|
</para>
|
|
|
|
<para>
|
|
If the resource should not be accessible by the originating domain, server ought not include Access-Control-Allow-Origin header in the response. By default, upon receiving such response from server browser will not pass the response back to the page that originated the request.
|
|
</para>
|
|
|
|
<para>
|
|
Several additional considerations:
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
If the browser is outdated and does not conform to CORS, cross domain request will be denied immediately without sending the request to the server. This means usability of web applications relying on CORS might be restricted on old browsers.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
If the web server does not conform to CORS, the Access-Control-Allow-Origin header will not be included in the response and the request will be denied on the client side.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Cross-domain access to resources is enforced on the side of the client. However, since the request includes Origin header, server may also restrict access to resources from other domains (e.g. by returning nothing).
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
If the origin of page is unknown (for example webpage is running from a file), browsers will send
|
|
<programlisting>
|
|
Origin: null
|
|
</programlisting>
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
|
|
<section>
|
|
<title>Using CORS in Rack-based applications</title>
|
|
<para>
|
|
CORS support for Rack-based applications is provided by <ulink url="https://github.com/cyu/rack-cors">rack-cors</ulink> gem. After adding it to the applications Gemfile
|
|
<programlisting>
|
|
gem 'rack-cors', :require => 'rack/cors'
|
|
</programlisting>
|
|
and configure Rails by modifying config/application.rb:
|
|
|
|
<programlisting>
|
|
module YourApp
|
|
class Application < Rails::Application
|
|
|
|
# ...
|
|
|
|
config.middleware.use Rack::Cors do
|
|
allow do
|
|
origins '*'
|
|
resource '*', :headers => :any, :methods => [:get, :post, :options]
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
</programlisting>
|
|
This configuration permits all origins access to any resource on the server via GET, POST and OPTIONS methods. Customizing the configuration, developer of the application can restrict cross-domain acess to resources by origin, headers and methods.
|
|
</para>
|
|
</section>
|
|
</section>
|
|
|
|
<section>
|
|
<title>JSON with padding (JSONP)</title>
|
|
<para>
|
|
JSONP is a very common way of hacking around the Same Origin Policy. This mechanism makes use of <code><script></code> tag and the fact that embedding Javascript code from other domains is not resctricted by the same origin policy. Since the code references by src attribute of <code><script></code> tag is loaded, it can be used as a vehicle to carry data and return them after evaluation.
|
|
</para>
|
|
|
|
<para>
|
|
Lets assume webpage needs to access resource at http://example.com/resource/1, which returns JSON data like:
|
|
<programlisting>
|
|
{"Key1": "Value1", "Key2": "Value2"}
|
|
</programlisting>
|
|
|
|
When webpage requests the resource with
|
|
|
|
<programlisting>
|
|
<source src="http://example.com/resource/1"></source>
|
|
</programlisting>
|
|
after receiving the response, browser will try to evaluate received data. Since data are not executable, interpreter would end with error and data would not be accessible to the code that requested it.
|
|
</para>
|
|
|
|
<para>
|
|
To work around this, it would be enough if the returned data were enclosed with function, that would be able to parse them on the client side. Suppose function <code>parseData</code> can accept JSON data as argument, parse it and make it accessible to the rest of the page:
|
|
|
|
<programlisting>
|
|
parseData({"Key1": "Value1", "Key2": "Value2"})
|
|
</programlisting>
|
|
</para>
|
|
|
|
<para>
|
|
However, web server does not know the name of the function that will parse data. Final piece is to pass the name of data-parsing function to server as parameter in request:
|
|
<programlisting>
|
|
<script src="http://example.com/resource/1?jsonp=parseData"></script>
|
|
</programlisting>
|
|
</para>
|
|
|
|
<para>
|
|
This technique of sharing resources across domains carries bigger security risks than CORS. Since <command>source</command> tag does not fall under Same Origin Policy on the client side, browser sends normal HTTP GET request without <constant>Origin</constant> header. Server that receives request has no means to know that the request was generated on behalf of page from other domain. Since neither the browser nor the server checks this kind of cross-domain requests, last obstacle that prevents exploitation is the fact that returned response is evaluated as Javascript code.
|
|
</para>
|
|
|
|
<para>
|
|
Example of this type of vulnerability is <ulink url="https://access.redhat.com/security/cve/CVE-2013-6443">CVE-2013-6443</ulink>. Cloud Forms Manage IQ application has been found vulnerable to cross-domain requests issued using JSONP. UI of application makes heavy use of Javascript and in this particular case changing the tab to "Authentication" would generate this HTTP request through XMLHttpRequest API:
|
|
|
|
<programlisting>
|
|
GET /ops/change_tab/?tab_id=settings_authentication&callback=...
|
|
Referrer: ...
|
|
Cookie: ...
|
|
</programlisting>
|
|
Response returned by the server would look like this:
|
|
|
|
<programlisting>
|
|
HTTP/1.1 200 OK
|
|
....
|
|
|
|
|
|
miqButtons('hide');
|
|
Element.replace("ops_tabs", "<div id=\"ops_tabs\" ...");
|
|
</programlisting>
|
|
where ops_tabs div contained html code of the Authentication tab including form with hidden CSRF token. To exploit this vulnerability, attacker would patch <command>Element.replace</command> function on his page and issue a JSONP request against CFME server.
|
|
<programlisting>
|
|
<script src='http://code.jquery.com/jquery-1.10.2.min.js'></script>
|
|
<script>
|
|
function test() {
|
|
$.ajax({
|
|
url: $( "input[name=url]" ).val() + '/ops/change_tab/?tab_id=settings_authentication',
|
|
dataType: 'jsonp'
|
|
});
|
|
};
|
|
|
|
var Element = { replace: function (a,text) {
|
|
...
|
|
}
|
|
>/script>
|
|
</programlisting>
|
|
|
|
This way attacker can run arbitrary code on returned response from the server: since the request also contains CSRF token, it is easy for attacker to steal it and issue successful CSRF request on behalf of currently logged-in user.
|
|
</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Additional resources</title>
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
W3C Recommendation - Cross-Origin Resouce Sharing <ulink url="http://www.w3.org/TR/access-control/">http://www.w3.org/TR/access-control/</ulink>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
cross-site xmlhttprequest with CORS <ulink url="http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/">http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/</ulink>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Ajax and Mashup Security - Open Ajax Alliance <ulink url="http://www.openajax.org/whitepapers/Ajax%20and%20Mashup%20Security.php">http://www.openajax.org/whitepapers/Ajax%20and%20Mashup%20Security.php</ulink>
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
CVE-2013-6443 and reproducer by Martin Povolný <ulink url="https://access.redhat.com/security/cve/CVE-2013-6443">https://access.redhat.com/security/cve/CVE-2013-6443</ulink>
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</section>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Content Security Policy</title>
|
|
<para></para>
|
|
</section>
|
|
|
|
<section id="HSTS">
|
|
<title>HTTP Strict Transport Security</title>
|
|
<para>HTTP Strict Transport Security is a mechanism that allows server to inform client that any interactions with the server shall be carried over secure HTTPS connection. </para>
|
|
|
|
<para>
|
|
HTTPS provides a secure tunnel between client and the server, yet there are still ways through which data can leak to the attacker. One of the most practical attacks on SSL is SSL stripping attack introduced by Moxie Marlinspike, in which active network attacker transparently converts HTTPS connection to insecure one. To the client it seems like web application does not support HTTPS and has no means to verify whether this is the case.
|
|
</para>
|
|
|
|
<para>HTTP Strict Transport Security mechanism allows server to inform client's user agent that the web application shall be accessed only through secure HTTPS connection. When client`s UA conformant with HSTS receives such notice from server, it enforces following behaviour:
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
all references to HSTS host are converted into secure ones before dereferencing
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
connection is terminated upon any and all secure transport errors or warnings without interaction with user
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
<para>
|
|
User agents which receive response with HSTS header need to retain data about host enforcing strict transport security for the timespan declared by the host. User agent builds a list of known HSTS hosts and whenever request is sent to known HSTS host, HTTPS is used.
|
|
</para>
|
|
|
|
<para>
|
|
HSTS header sent by the server includes timespan during which UA should enforce strict transport security in seconds:
|
|
<programlisting>
|
|
Strict-Transport-Security: max-age=631138519
|
|
</programlisting>
|
|
</para>
|
|
<para>
|
|
Optionally, server can also specify that HSTS be enforced on all subdomains:
|
|
<programlisting>
|
|
Strict-Transport-Security: max-age=631138519; includeSubDomains
|
|
</programlisting>
|
|
</para>
|
|
<para>
|
|
Setting timespan to zero
|
|
<programlisting>
|
|
Strict-Transport-Security: max-age=0
|
|
</programlisting>
|
|
allows the server to indicate that UA should delete HSTS policy associated with the host.
|
|
</para>
|
|
|
|
<section>
|
|
<title>Configuring HSTS in Rails</title>
|
|
<para>
|
|
A single directive in Rail configuration
|
|
<programlisting>
|
|
config.force_ssl = true
|
|
</programlisting>
|
|
enables HSTS for the application.
|
|
</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>References</title>
|
|
<para>
|
|
RFC 6797 <ulink url="http://tools.ietf.org/html/rfc6797">http://tools.ietf.org/html/rfc6797</ulink>
|
|
</para>
|
|
</section>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Security related HTTP headers</title>
|
|
<para>
|
|
There are several HTTP headers that increase security of web application in various ways. Below is non-exhaustive list of most important ones, some of which were already mentioned and explained in previous sections:
|
|
</para>
|
|
<section>
|
|
<title>X-Frame-Options</title>
|
|
<para></para>
|
|
</section>
|
|
<section>
|
|
<title>X-XSS-Protection</title>
|
|
<para></para>
|
|
</section>
|
|
<section>
|
|
<title>X-Content-Type-Options</title>
|
|
<para></para>
|
|
</section>
|
|
<section>
|
|
<title>X-Content-Security-Policy</title>
|
|
<para></para>
|
|
</section>
|
|
<section>
|
|
<title>Access-Control-Allow-Origin</title>
|
|
<para>This response header is part of the implemetation of Cross Origin Resource Sharing described in <xref linkend='CORS'/>. Via this header server explicitly tells client`s browser whether cross-domain access to resource shall be permitted for the Origin specified.</para>
|
|
</section>
|
|
<section>
|
|
<title>Strict-Transport-Security</title>
|
|
<para>This header is part of HTTP Strict Transport Security described in <xref linkend='HSTS'/>. Using it server indicates that any requests from client shall be made through secure SSL connection and such policy be enforced for a declared timespan.</para>
|
|
</section>
|
|
</section>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Application and server configuration and hardening</title>
|
|
<section>
|
|
<title>Logging</title>
|
|
<para></para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>User content storage</title>
|
|
<para></para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Storing passwords securely</title>
|
|
<para></para>
|
|
</section>
|
|
</section>
|
|
|
|
|
|
</chapter> |