Agile Web Development with Rails phần 9 pdf

55 464 0
Agile Web Development with Rails phần 9 pdf

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

CROSS-SITE SCRIPTING (CSS/XSS) 431 But by planting the cookie in a comment form, the attacker has entered a time bomb into our system. When the store administrator asks the appli- cation to display the comments received from customers, the application might execute a Rails template that looks something like this. <div class="comment"> <%= order.comment %> </div> The attacker’s JavaScript is inserted into the page viewed by the adminis- trator. When this page is displayed, the browser executes the script and the document cookie is sent off to the attacker’s site. This time, how- ever, the cookie that is sent is the one associated with our own application (because it was our application that sent the page to the browser). The attacker now has the information from the cookie and can use it to mas- querade as the store administrator. Protecting Your Application from XSS Cross-site scripting attacks work when the attacker can insert their own JavaScript into pages that are displayed with an associated session cookie. Fortunately, these attacks are easy to prevent—never allow anything that comes in from the outside to be displayed directly on a page that you gen- erate. 3 Always convert HTML metacharacters (< and >) to the equivalent HTML entities ( &lt; and &gt;) in every string that is rendered in the web site. This will ensure that, no matter what kind of text an attacker enters in a form or attaches to an URL, the browser will always render it as plain text and never interpret any HTML tags. This is a good idea anyway, as a user can easily mess up your layout by leaving tags open. Be careful if you use a markup language such as Textile or Markdown, as they allow the user to add HTML fragments to your pages. Rails provides the helper method h(string) (an alias for html_escape()) that performs exactly this escaping in Rails views. The person coding the com- ment viewer in the vulnerable store application could have eliminated the issue by coding the form using <div class="comment"> <%= h(order.comment) %> </div> 3 This stuff that comes in from the outside can arrive in the data associated with a POST request (for example, from a form). But it can also arrive as parameters in a GET. For example, if you allow your users to pass you parameters that add text to the pages you display, they could add < script > tags to these. Report erratum CROSS-SITE SCRIPTING (CSS/XSS) 432 Joe Asks. . . Why Not Just Strip <script> Tags? If the problem is that people can inject <script> tags into content we display, you might think that the simplest solution would be some code that just scanned for and removed these tags? Unfortunately, that won’t work. Browsers will now execute JavaScript in a surprisingly large number of contexts (for example, when onclick= handlers are invoked or in the src= attribute of <img> tags). And the problem isn’t just limited to JavaScript—allowing people to include off-site links in con- tent could allow them to use your site for nefarious purposes. You could try to detect all these cases, but the HTML-escaping approach is safer and is less likely to break as HTML evolves. Get accustomed to using h( ) for any variable that is rendered in the view, even if you think you can trust it to be from a reliable source. And when you’re reading other people’s source, be vigilant about the use of the h() method—folks tend not to use parentheses with h(), and it’s often hard to spot. Sometimes you need to substitute strings containing HTML into a tem- plate. In these circumstances the sanitize( ) method removes many poten- tially dangerous constructs. However, you’d be advised to review whether sanitize( ) gives you the full protection you need: new HTML threats seem to arise every week. XSS Attacks Using an Echo Service The echo service is a service running on TCP port 7 that returns back everything you send to it. On Debian, it is active by default. This is a security problem. Imagine the server that runs the web site target.domain is also running an echo service. The attacker creates a form such as the following on his own web site. <form action="http://target.domain:7/" method="post"> <input type="hidden" name="code" value="some_javascript_code_here" /> <input type="submit" /> </form> Report erratum AVOID SESSION FIXATION ATTACKS 433 The attacker finds a way of attracting people who use the target.domain application to his own form. Those people will probably have cookies from target.domain in their browser. If these people submit the attacker’s form, the content of the hidden field is sent to the echo server on target.domain’s port 7. The echo server dutifully echos this back to the browser. If the browser decides to display the returned data as HTML (some versions of Internet Explorer do), it will execute the JavaScript code. Because the originating domain is target.domain the session cookie is made available to the script. This isn’t really a Rails development issue; it works on the client side. However, to reduce the probability of a successful attack on your applica- tion, you should deactivate any echo services on your web servers. This alone does not provide full security, as there are also other services (such as FTP and POP3) that can also be used instead of the echo server. 21.3 Avoid Session Fixation Attacks If you know someone’s session id, then you could create HTTP requests that use it. When Rails receives those requests, it thinks they’re associated with the original user, and so will let you do whatever that user can do. Rails goes a long way towards preventing people from guessing other peo- ple’s session ids, as it constructs these ids using a secure hash function. In effect they’re very large random numbers. However, there are ways of achieving almost the same effect. In a session fixation attack, the bad guy gets a valid session id from our application, then passes this on to a third party in such a way that the third party will use this same session. If that person uses the session to log in to our application, the bad guy, who also has access to that session id, will also be logged in. 4 A couple of techniques help eliminate session fixation attacks. First, you might find it helpful to keep the IP address of the request that created the session in the session data. If this changes, you can cancel the session. This will penalize users who move their laptops across networks and home users whose IP addresses change when PPPOE leases expire. 4 Session fixation attacks are described in great detail in a document from ACROS Secu- rity, available at http://www.secinf.net/uplarticle/11/session_fixation.pdf. Report erratum CREATING RECORDS DIRECTLY FROM FORM PARAMETERS 434 Second, you should consider creating a new session every time someone logs in. That way the legimate user will continue with their use of the application while the bad guy will be left with an orphaned session id. 21.4 Creating Records Directly from Form Parameters Let’s say you want to implement a user registration system. Your users tablelookslikethis. create table users ( id integer primary key, name varchar(20) not null, password varchar(20) not null, role varchar(20) not null default "user", approved integer not null default 0 ); create unique index users_name_unique on users(name); The role column contains one of admin, moderator,oruser, and it defines this user’s privileges. The approved column is set to 1 once an administra- tor has approved this user’s access to the system. The corresponding registration form looks like this. <form method="post" action="http://website.domain/user/register"> <input type="text" name="user[name]" /> <input type="text" name="user[password]" /> </form> Within our application’s controller, the easiest way to create a user object from the form data is to pass the form parameters directly to the create() method of the User model. def register User.create(params[:user]) end But what happens if someone decides to save the registration form to disk and play around by adding a few fields? Perhaps they manually submit a webpagethatlookslikethis. <form method="post" action="http://website.domain/user/register"> <input type="text" name="user[name]" /> <input type="text" name="user[password]" /> <input type="text" name="user[role]" value="admin" /> <input type="text" name="user[approved]" value="1" /> </form> Although the code in our controller intended only to initialize the name and password fields for the new user, this attacker has also given himself administrator status and approved his own account. Report erratum DON’T TRUST ID PARAMETERS 435 Active Record provides two ways of securing sensitive attributes from being overwritten by malicious users who change the form. The first is to list the attributes to be protected as parameters to the attr_protected() method. Any attribute flagged as protected will not be assigned using the bulk assignment of attributes by the create() and new( ) methods of the model. We can use attr_protected( ) to secure the User model. class User < ActiveRecord::Base attr_protected :approved, :role # rest of model end This ensures that User.create(params[:user]) will not set the approved and role attributes from any corresponding values in params. If you wanted to set them in your controller, you’d need to do it manually. (This code assumes the model does the appropriate checks on the values of approved and role.) user = User.new(params[:user]) user.approved = params[:user][:approved] user.role = params[:user][:role] If you’re afraid that you might forget to apply attr_protected( ) to the right attributes before making your model available to the cruel world, you can specify the protection in reverse. The method attr_accessible( ) allows you to list the attributes that may be assigned automatically—all other attributes will be protected. This is particularly useful if the structure of the underly- ing table is liable to change, as any new columns you add will be protected by default. Using attr_accessible, we can secure the User models like this. class User < ActiveRecord::Base attr_accessible :name, :password # rest of model end 21.5 Don’t Trust ID Parameters When we first discussed retrieving data, we introduced the find() method, which retrieved a row based on its primary key value. This method takes an optional hash parameter, which can be used to impose additional con- straints on the rows returned. Given that a primary key uniquely identifies a row in a table, why would we want to apply additional search criteria when fetching rows using that key? It turns out to be a useful security device. Report erratum DON’T EXPOSE CONTROLLER METHODS 436 Perhaps our application lets customers see a list of their orders. If a cus- tomer clicks an order in the list, the application displays order details—the click calls the action order/show/nnn,wherennn is the order id. An attacker might notice this URL and attempt to view the orders for other customers by manually entering different order ids. We can prevent this by using a constrained find( ) in the action. In this example, we qualify the search with the additional criteria that the owner of the order must match the current user. An exception will be thrown if no order matches, which we handle by redisplaying the index page. def show id = params[:id] user_id = session[:user_id] || -1 @order = Order.find(id, :conditions => [ "user_id = ?", user_id]) rescue redirect_to :action => "index" end This problem is not restricted to the find( ) method. Actions that delete or destroy rows based on an id (or ids) returned from a form are equally dan- gerous. Unfortunately, neither delete() nor destroy( ) supports additional :conditions parameters. You’ll need to do the checking yourself, either by first reading the row to check ownership or by constructing an SQL where clause and passing it to delete_all() or destroy_all(). Another solution to this issue is to use associations in your application. If we declare that a user has_many orders, then we can constrain the search to find only orders for that user with code such as user.orders.find(params[:id]) 21.6 Don’t Expose Controller Methods An action is simply a public method in a controller. This means that if you’re not careful, you may expose as actions methods that were intended to be called only internally in your application. Sometimes an action is used as a helper, but is never intended to be invoked directly by the end user. For example, the e-mail program might display a list showing the subject lines of all the mail for a particular user. Next to each entry in the list is a Read E-Mail button. These buttons link back to actions using a URL such as http://website.domain/email/read/1357 In this URL, the string 1357 is the id of the e-mail to be read. Report erratum DON’T EXPOSE CONTROLLER METHODS 437 When you design this type of application, it’s easy to forget that the read() method is publicly exposed. In your mind, the only way that read() gets called is when a user clicks the link from the list of e-mails. However, an adventurous user might have a look at the URL and wonder what would happen if they typed it in manually, giving different numbers at the end. Unless your application was written with security in mind, it’s perfectly possible that these users will be able to read other people’s e-mail. An incorrect implementation of the read() action would be def read @email = Email.find(params[:id]) end This method returns an e-mail given an id, regardless of the e-mail’s owner. One possible solution is to add a test for ownership. def read @email = Email.find(params[:id]) unless @email.owner_id == session[:user_id] flash[:notice] = "E-Mail not found" redirect_to(:action => "index") end end (Notice how the error message is deliberately nonspecific; had we said, “This e-mail belongs to someone else,” we’re giving away information that we really shouldn’t be sharing.) Even better than testing in the controller is to delegate the checking to the model. This way, we can arrange things so that we never even read someone else’s e-mail into memory. Our action method would become def read @email = Email.find_by_id_and_user(params[:id], session[:user_id]) unless @email flash[:notice] = "E-Mail not found" redirect_to(:action => "index") end end This uses a dynamically generated finder method that returns an e-mail by id only if it also belongs to the current user. Remember that all your public actions can be invoked directly from a browser or by using hand-crafted HTML. Make sure these methods ver- ify access rights if required. Report erratum FILE UPLOADS 438 21.7 File Uploads Some community-oriented web sites allow their participants to upload files for other participants to download. Unless you’re careful, these uploaded files could be used to attack your site. For example, imagine someone uploading a file whose name ended with . rhtml or .cgi (or any other extension associated with executable content on your site). If you link directly to these files on the download page, when the file is selected your webserver might be tempted to execute its contents, rather than simply download it. This would allow an attacker to run arbitrary code on your server. The solution is never to allow users to upload files that are subsequently made accessible directly to other users. Instead, upload files into a direc- tory that is not accessible to your web server (outside the DocumentRoot in Apache terms). Then provide a Rails action that allows people to view these files. Within this action, be sure that you • Validate that the name in the request is a simple, valid filename matching an existing file in the directory or row in the table. Do not accept filenames such as / /etc/passwd (see the sidebar Input Validation Is Difficult). You might even want to store uploaded files in a database table and use ids, rather than names, to refer to them. • When you download a file that will be displayed in a browser, be sure to escape any HTML sequences it contains to eliminate the potential for XSS attacks. If you allow the downloading of binary files, make sure you set the appropriate Content-type HTTP header to ensure that the file will not be displayed in the browser accidentally. The descriptions starting on page 297 describe how to download files from a Rails application, and the section on uploading files starting on page 350 shows an example that uploads image files into a database table and pro- vides an action to display them. 21.8 Don’t Cache Authenticated Pages Remember that page caching bypasses any security filters in your appli- cation. Use action or fragment caching if you need to control access based on session information. See Section 16.8, Caching, Part One,onpage318, and Section 17.10, Caching, Part Two,onpage366, for more information. Report erratum KNOWING THAT IT WORKS 439 Input Validation Is Difficult Johannes Brodwall wrote the following in a review of this chapter: When you validate input, it is important to keep in mind the following. • Validate with a whitelist. There are many ways of encoding dots and slashes that may escape your validation, but be interpreted by the underlying systems. For example, /, \, %2e%2e%2f, %2e%2e%5c and %c0%af (Unicode) may bring you up a directory level. Accept a very small set of characters (try [a-zA-Z][a-zA-Z0-9_]* for a start). • Don’t try to recover from weird paths by replacing, stripping, and the like. For example, if you strip out the string /, a malicious input such as // will still get though. If there is anything weird going on, someone is trying something clever. Just kick them out with a terse, non-informative message, such as “Intrusion attempt detected. Inci- dent logged.” I often check that dirname(full_file_name_from_user) isthesameasthe expected directory. That way I know that the filename is hygenic. 21.9 Knowing That It Works When we want to make sure the code we write does what we want, we write tests. We should do the same when we want to ensure that our code is secure. Don’t hesitate to do the same when you’re validating the security of your new application. Use Rails functional tests to simulate potential user attacks. And should you ever find a security hole in your code, write a test to ensure that once fixed, it won’t somehow reopen in the future. At the same time, realize that testing can only check the things you’ve thought of. It’s the things that the other guy thinks of that’ll bite you. Report erratum If you w anted to find the person with the m ost experience deploying and scaling Rails applications, y ou’d tur n to Rails’ creator David Heinemeier Hansson. He’s suc- cessfully used Rails in a number of wildly successful sites, including Basecamp ( http://www.basecamphq.com) and Backpack (http://backpackit.com/). I’m thrilled that in addition to his technical advice and the David Says sidebars, David was kind enough to contribute this chapter to the book. Chapter 22 Deployment and Scaling Deployment is supposed to be the happy celebration of an application that is ready for the world. But in order to realize your dreams, you’ll need to prepare yourself and your application for the dangers, risks, and pitfalls of going live. Addressing concerns is exactly what this chapter is about. We’ll examine options that need to be tweaked and the software that needs to be injected as the development setting is replaced by the production setting. Now that you have built it, they will come. You better be ready for them. As part of deployment process, we’ll discuss how to set your application up so that it will scale. Thankfully, Rails minimizes the concerns of scaling as an up-front activity and postpones most of the necessary steps until the masses are knocking down your door. But if we deal with the anxiety of the attacking hordes in advance, you can rest safely with the comfort of having a known path to follow. 22.1 Picking a Production Platform Rails runs on a wide variety of web servers and runtimes. Just about any web server implements the CGI protocol, which is the baseline for run- ning Rails. 1 In this sea of options, we’ll pay special attention to three web servers and three ways of serving the application. Unless you’re bound to other technology choices, it would be wise to pick from the combina- tions presented next for a minimum of fuss and a maximum of available assistance. 1 But you wouldn’t want to use CGI for real-life applications. [...]... 200 99 99 , Exceptions: 0) 0 99 95 2431 bytes 898 350 bytes 13.20 [#/sec] (mean) 303.00 [ms] (mean) 75.75 [ms] (mean, across all concurrent requests) 62.87 [Kbytes/sec] received Connnection Times (ms) min mean[+/-sd] median Connect: 0 0 0.0 0 Processing: 246 300 56.6 298 Waiting: 246 300 56.6 298 Total: 246 300 56.6 298 max 0 608 608 608 Percentage of the requests served within a certain time (ms) 50% 298 ... mod_fastcgi/2.4.2 Set-Cookie: _session_id=a94c 090 f0 895 aefba381ca 597 4fbddd9; path=/ Cache-Control: no-cache Content-Type: text/html; charset=utf-8 As you can see, the server returned a cookie called _session_id that represents the session created We can grab this cookie and reuse it for our ab calls like this: myapp>ab -c 4 -n 200 -C "_session_id=a94c 090 f0 895 aefba381ca 597 4fbddd9" \ http://www.example.com/controller/action... that making a choice doesn’t paint you into a corner Rails is almost indifferent of the underlying web server—you could be running WEBrick in the morning, Apache in the afternoon, and lighttpd in the evening without changing a single comma in your application code WEBrick: All Ruby, No Configuration WEBrick is a pure-Ruby web server that comes bundled with Ruby It isn’t particularly fast or particularly... production and development For quick tests of changing environments, you could hack config/environment.rb and force the constant RAILS_ ENV to be something other than "development" , but that’s messy That’s why the Rails environment is also changeable through an external environment variable, also called RAILS_ ENV If the environment variable is set, Rails uses its value to define the environment If RAILS_ ENV... Apache-fcgi ★★ ★★★★ ★★★★ lighttpd-fcgi ★ ★★★★★ ★★★★★ WEBrick Figure 22.1: Comparing Deployment Options Choosing a Web Server The primary choices for serving a Rails application are WEBrick, Apache, and lighttpd.2 In some ways, that order also represents the progression most live Rails applications have gone through (or are aiming for) Start out with the ease and comfort of a Ruby-based server, then... RAILS_ ENV isn’t set, Rails defaults to "development" To run your application in the production environment, you have to make sure that ENV[ RAILS_ ENV’] is set to "production" before Ruby compiles environment.rb This is easier said than done The problem is that the three different web servers each have a unique way of setting environment variables Report erratum 4 49 I TERATING IN THE W ILD Webrick: /script/server... stallion if it’s also saddled Report erratum 4 59 F INDING AND D EALING WITH B OTTLENECKS with an expensive database query weighing in at 0.5 seconds In that case, the query is the bottleneck When it comes to dealing with performance, all you care about is bottlenecks How do you find the bottlenecks in Rails? By measurement, of course But measuring the performance of a Rails application is more than being quick... RODUCTION P LATFORM to boot the entire Rails environment All that work just to serve one lousy request And as the next request comes in, the work repeats all over again So why bother with CGI at all? First, all web servers support it out of the box When you’re setting up Apache with Rails for the first time, for example, it’s a good idea to start out by making it work with CGI By doing so you sort out all... 298 Waiting: 246 300 56.6 298 Total: 246 300 56.6 298 max 0 608 608 608 Percentage of the requests served within a certain time (ms) 50% 298 66% 307 75% 312 80% 317 90 % 340 95 % 446 98 % 512 99 % 517 100% 608 (last request) On some systems with really fast actions, it may well be the session system that’s the bottleneck When you just use vanilla ab, every request will start a new session That’s not a particularly... CGI Additionally, the FastCGI processes are not married to the web server process, so you can have 100 web server processes that deal with all the static requests and perhaps just 10 FastCGIs dealing with the dynamic requests This isn’t the case with servlets, CGI, and even mod_ruby (another deprecated approach to serving applications for Rails) This is crucially important for memory consumption, as . the web server pro- cess, so you can have 100 web server processes that deal with all the static requests and perhaps just 10 FastCGIs dealing with the dynamic requests. This isn’t the case with. can rest safely with the comfort of having a known path to follow. 22.1 Picking a Production Platform Rails runs on a wide variety of web servers and runtimes. Just about any web server implements. you into a corner. Rails is almost indifferent of the underlying web server—you could be run- ning WEBrick in the morning, Apache in the afternoon, and lighttpd in the evening without changing

Ngày đăng: 07/08/2014, 00:22

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan