-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrails_security.html
More file actions
1017 lines (965 loc) · 70.8 KB
/
rails_security.html
File metadata and controls
1017 lines (965 loc) · 70.8 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
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!doctype html>
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Rails Security — Ruby on Rails Guides</title>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" data-turbo-track="reload">
<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print">
<link rel="stylesheet" type="text/css" href="stylesheets/highlight.css" data-turbo-track="reload">
<link rel="icon" href="images/backend-development.svg" sizes="any">
<script src="javascripts/@hotwired--turbo.js" data-turbo-track="reload"></script>
<script src="javascripts/clipboard.js" data-turbo-track="reload"></script>
<script src="javascripts/guides.js" data-turbo-track="reload"></script>
<meta property="og:title" content="Rails Security — Ruby on Rails Guides" />
<meta name="description" content="Rails SecurityThis guide will give you an introduction to the security features included in Ruby on Rails, how to use them, and how to mess up in spite of all the help the framework is giving you.By referring to this guide, you will be able to: Use rails's security features Appreciate how hard security is You can fork the code of the example app. This app is full of security holes. While reading this guide you can work on the app and fix those problems one by one." />
<meta property="og:description" content="Rails SecurityThis guide will give you an introduction to the security features included in Ruby on Rails, how to use them, and how to mess up in spite of all the help the framework is giving you.By referring to this guide, you will be able to: Use rails's security features Appreciate how hard security is You can fork the code of the example app. This app is full of security holes. While reading this guide you can work on the app and fix those problems one by one." />
<meta property="og:locale" content="en_US" />
<meta property="og:site_name" content="Textbook Backend Developemnt" />
<meta property="og:image" content="images/backend-development.svg" />
<meta property="og:type" content="website" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Arabic:wght@100..900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Heebo:wght@100..900&family=Noto+Sans+Arabic:wght@100..900&display=swap" rel="stylesheet">
<meta name="theme-color" content="#2e56e9">
</head>
<body class="guide">
<header id="page_header">
<div class="wrapper clearfix">
<nav id="feature_nav">
<div class="header-logo">
<a href="/">Backend Development</a>
</div>
<ul class="nav">
<li><a class="nav-item" id="home_nav" href="/">Home</a></li>
<li class="guides-index guides-index-large">
<a href="index.html" id="guidesMenu" class="guides-index-item nav-item">Index</a>
<div id="guides" class="clearfix" style="display: none;">
<hr />
<dl class="guides-section-container">
<div class="guides-section">
<dt>Ruby on Rails</dt>
<dd><a href="ruby_commandline.html">Ruby Commandline</a></dd>
<dd><a href="rails_database_and_model.html">Models and ActiveRecord</a></dd>
<dd><a href="rails_db.html">Database and Migrations</a></dd>
<dd><a href="rails_associations_and_validations.html">Associations and Validations</a></dd>
<dd><a href="rails_view_and_controller.html">Routing, View and Controller</a></dd>
<dd><a href="rails_authentication.html">Simple Authentication</a></dd>
<dd><a href="assets_and_import_map.html">The Asset Pipeline and Importmaps</a></dd>
<dd><a href="testing.html">Getting started with Testing</a></dd>
<dd><a href="refactoring_rails.html">Refactoring Rails</a></dd>
<dd><a href="deploy-to-paas.html">Deploy to PAAS</a></dd>
<dd><a href="rails_gems.html">Ruby Gems for your Rails Project</a></dd>
<dd><a href="deploying_rails.html">Deploying Rails</a></dd>
</div>
<div class="guides-section">
<dt>Ruby on Rails - Advanced Topics</dt>
<dd><a href="deploy-to-paas.html">Deploy to PAAS</a></dd>
<dd><a href="rest-api.html">REST API</a></dd>
<dd><a href="graphql-api.html">GraphQL API</a></dd>
<dd><a href="rails_websockets.html">Websocket in Rails</a></dd>
<dd><a href="jobs_and_tasks.html">Jobs and Tasks in Rails</a></dd>
<dd><a href="rails_security.html">Rails Security</a></dd>
</div>
<div class="guides-section">
<dt>Overarching Concerns</dt>
<dd><a href="issue.html">Issue Lifecycle</a></dd>
<dd><a href="security.html">Security</a></dd>
<dd><a href="adv_authentication.html">Advanced Authentication</a></dd>
<dd><a href="caching.html">Caching</a></dd>
<dd><a href="advanced_testing.html">Advanced Testing</a></dd>
<dd><a href="internationalization.html">Internationalization (I18n)</a></dd>
<dd><a href="git_rebasing.html">Git Rebasing</a></dd>
</div>
<div class="guides-section">
<dt>Nodes.js</dt>
<dd><a href="node_vs_rails.html">Node vs. Rails</a></dd>
<dd><a href="node_basics.html">Node Basics</a></dd>
<dd><a href="node_websockets.html">Node Websockets</a></dd>
<dd><a href="node_express.html">Node Web App</a></dd>
<dd><a href="node_cluster.html">Scaling Node</a></dd>
</div>
<div class="guides-section">
<dt>Next.js</dt>
<dd><a href="nextjs.html">Next.js</a></dd>
</div>
</dl>
</div>
</li>
<li class="guides-index guides-index-small">
<select class="guides-index-item nav-item">
<option value="index.html">Index</option>
<optgroup label="Ruby on Rails">
<option value="ruby_commandline.html">Ruby Commandline</option>
<option value="rails_database_and_model.html">Models and ActiveRecord</option>
<option value="rails_db.html">Database and Migrations</option>
<option value="rails_associations_and_validations.html">Associations and Validations</option>
<option value="rails_view_and_controller.html">Routing, View and Controller</option>
<option value="rails_authentication.html">Simple Authentication</option>
<option value="assets_and_import_map.html">The Asset Pipeline and Importmaps</option>
<option value="testing.html">Getting started with Testing</option>
<option value="refactoring_rails.html">Refactoring Rails</option>
<option value="deploy-to-paas.html">Deploy to PAAS</option>
<option value="rails_gems.html">Ruby Gems for your Rails Project</option>
<option value="deploying_rails.html">Deploying Rails</option>
</optgroup>
<optgroup label="Ruby on Rails - Advanced Topics">
<option value="deploy-to-paas.html">Deploy to PAAS</option>
<option value="rest-api.html">REST API</option>
<option value="graphql-api.html">GraphQL API</option>
<option value="rails_websockets.html">Websocket in Rails</option>
<option value="jobs_and_tasks.html">Jobs and Tasks in Rails</option>
<option value="rails_security.html">Rails Security</option>
</optgroup>
<optgroup label="Overarching Concerns">
<option value="issue.html">Issue Lifecycle</option>
<option value="security.html">Security</option>
<option value="adv_authentication.html">Advanced Authentication</option>
<option value="caching.html">Caching</option>
<option value="advanced_testing.html">Advanced Testing</option>
<option value="internationalization.html">Internationalization (I18n)</option>
<option value="git_rebasing.html">Git Rebasing</option>
</optgroup>
<optgroup label="Nodes.js">
<option value="node_vs_rails.html">Node vs. Rails</option>
<option value="node_basics.html">Node Basics</option>
<option value="node_websockets.html">Node Websockets</option>
<option value="node_express.html">Node Web App</option>
<option value="node_cluster.html">Scaling Node</option>
</optgroup>
<optgroup label="Next.js">
<option value="nextjs.html">Next.js</option>
</optgroup>
</select>
</li>
</ul>
</nav>
</div>
</header>
<hr class="hide" />
<section id="feature">
<div class="wrapper">
<h1>Rails Security</h1><p>This guide will give you an introduction
to the security features included in Ruby on Rails,
how to use them, and how to mess up in spite of all the
help the framework is giving you.</p><p>By referring to this guide, you will be able to:</p>
<ul>
<li>Use rails's security features</li>
<li>Appreciate how hard security is</li>
</ul>
<div class="interstitial repo"><p>You can fork the <a href="https://github.com/backend-development/rails-example-security">code of the example app</a>. This app is full of security holes. While reading this guide you can
work on the app and fix those problems one by one.</p></div>
<nav id="subCol">
<h3 class="chapter">
<picture>
<!-- Using the `source` HTML tag to set the dark theme image -->
<source
srcset="images/icon_book-close-bookmark-1-wht.svg"
media="(prefers-color-scheme: dark)"
/>
<img src="images/icon_book-close-bookmark-1.svg" alt="Chapter Icon" />
</picture>
Chapters
</h3>
<ol class="chapters">
<li><a href="#where-to-learn-about-security">Where to learn about security</a></li>
<li><a href="#what-a-framework-can-t-do-for-you">What a framework can't do for you</a></li>
<li><a href="#regression-tests-for-security-problems">Regression Tests for Security Problems</a></li>
<li><a href="#injection">Injection</a>
<ul>
<li><a href="#sql-injection-and-activerecord">SQL Injection and ActiveRecord</a></li>
<li><a href="#links">Links</a></li>
</ul></li>
<li><a href="#broken-authentication">Broken Authentication</a>
<ul>
<li><a href="#well-known-passwords">well known passwords</a></li>
</ul></li>
<li><a href="#sensitive-data-exposure">Sensitive Data Exposure</a>
<ul>
<li><a href="#encryption-in-the-database">Encryption in the Database</a></li>
<li><a href="#removing-from-the-logfile">Removing from the Logfile</a></li>
<li><a href="#ensuring-that-https-is-used">Ensuring that HTTPS is used</a></li>
</ul></li>
<li><a href="#broken-access-control">Broken Access Control</a>
<ul>
<li><a href="#remove-unused-routes">remove unused routes</a></li>
<li><a href="#check-user-roles-and-premissions-in-every-controller">check user roles and premissions in every controller</a></li>
<li><a href="#use-uuids-instead-of-bigint-as-id">use UUIDs instead of bigint as id</a></li>
<li><a href="#set-cors-for-your-api">set CORS for your API</a></li>
</ul></li>
<li><a href="#security-misconfiguration">Security Misconfiguration</a>
<ul>
<li><a href="#use-environment-variables">Use Environment Variables</a></li>
<li><a href="#storing-secrets-in-an-encrypted-file">Storing Secrets in an encrypted file</a></li>
</ul></li>
<li><a href="#cross-site-scripting-xss">Cross-Site Scripting (XSS)</a>
<ul>
<li><a href="#use-a-content-security-policy-csp">Use a Content Security Policy (CSP)</a></li>
<li><a href="#escape-for-the-correct-context">Escape for the correct context:</a></li>
</ul></li>
<li><a href="#insecure-deserialization">Insecure Deserialization</a></li>
<li><a href="#using-components-with-known-vulnerabilities">Using Components with Known Vulnerabilities</a></li>
<li><a href="#cross-site-request-forgery-csrf">Cross Site Request Forgery (CSRF)</a></li>
<li><a href="#see-also">See Also</a></li>
</ol>
</nav>
<hr>
</div>
</section>
<main id="container">
<div class="wrapper">
<div id="mainCol">
<div class='slide'>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-0' href='slides_rails_security.html#/0'>◻</a></p>
<h2 id="where-to-learn-about-security"><a class="anchorlink" href="#where-to-learn-about-security"><span>1</span> Where to learn about security</a></h2><p>For a real world project, follow the <a href="https://github.com/OWASP/ASVS/blob/master/4.0/OWASP%20Application%20Security%20Verification%20Standard%204.0.2-de.pdf">OWASP Application Security Verification Standard 4.0.2</a>.</p><p>To get a first impression learn about the <a href="https://owasp.org/www-project-top-ten/">OWASP Top 10</a>.</p><p>For configuring your web server follow <a href="https://infosec.mozilla.org/guidelines/web_security.html">mozillas web security guidelines</a></p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-1' href='slides_rails_security.html#/1'>◻</a></p>
<h2 id="what-a-framework-can-t-do-for-you"><a class="anchorlink" href="#what-a-framework-can-t-do-for-you"><span>2</span> What a framework can't do for you</a></h2><p>Later on this Guide will follow the OWASP Top 10 to discuss
security features of Ruby on Rails. But first a word of warning:</p><p>Rails offers a lot of security features. But all those clever features
<strong>cannot save you from yourself</strong>.</p><p>In the example below, all the passwords
are displayed on "/users". If you as a programmer decide to do that, no framework can prevent it!</p><p><img src="images/security-password-shown.png" alt=""></p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-2' href='slides_rails_security.html#/2'>◻</a></p>
<h2 id="regression-tests-for-security-problems"><a class="anchorlink" href="#regression-tests-for-security-problems"><span>3</span> Regression Tests for Security Problems</a></h2><p>Let's use this as an example of how to fix a security problem
once you've found it: First we write a test for the problem: <code>rails g integration_test security</code></p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="nb">require</span> <span class="s1">'test_helper'</span>
<span class="k">class</span> <span class="nc">SecurityTest</span> <span class="o"><</span> <span class="no">ActionDispatch</span><span class="o">::</span><span class="no">IntegrationTest</span>
<span class="n">fixtures</span> <span class="ss">:users</span>
<span class="nb">test</span> <span class="s1">'users are listed publicly'</span> <span class="k">do</span>
<span class="n">get</span> <span class="s1">'/users'</span>
<span class="n">assert_response</span> <span class="ss">:success</span>
<span class="n">assert_select</span> <span class="s1">'td'</span><span class="p">,</span> <span class="ss">text: </span><span class="n">users</span><span class="p">(</span><span class="ss">:one</span><span class="p">).</span><span class="nf">email</span>
<span class="k">end</span>
<span class="nb">test</span> <span class="s1">'users password is not shown publicly'</span> <span class="k">do</span>
<span class="n">get</span> <span class="s1">'/users'</span>
<span class="n">assert_response</span> <span class="ss">:success</span>
<span class="n">assert_select</span> <span class="s1">'td'</span><span class="p">,</span> <span class="ss">text: </span><span class="n">users</span><span class="p">(</span><span class="ss">:one</span><span class="p">).</span><span class="nf">password</span><span class="p">,</span> <span class="ss">count: </span><span class="mi">0</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="require 'test_helper'
class SecurityTest < ActionDispatch::IntegrationTest
fixtures :users
test 'users are listed publicly' do
get '/users'
assert_response :success
assert_select 'td', text: users(:one).email
end
test 'users password is not shown publicly' do
get '/users'
assert_response :success
assert_select 'td', text: users(:one).password, count: 0
end
end
">Copy</button>
</div>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-3' href='slides_rails_security.html#/3'>◻</a></p>
<p>When we run this test it fails, because right now passwords are displayed:</p><p><img src="images/security-password-test-fails.png" alt=""></p><p>Now we change the view to not display the passwords any more. We can
run the test to make sure we succeeded.</p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-4' href='slides_rails_security.html#/4'>◻</a></p>
<p>But to be fair: in this example a lot more has gone wrong than
displaying the passwords: storing the passwords in plain text in the
database was the first mistake!</p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-5' href='slides_rails_security.html#/5'>◻</a></p>
<h2 id="injection"><a class="anchorlink" href="#injection"><span>4</span> Injection</a></h2>
<blockquote>
<p>Injection flaws, such as SQL, NoSQL, OS, and LDAP injection, occur when untrusted data is sent to an interpreter as part of a command or query. The attacker's hostile data can trick the interpreter into executing unintended commands or accessing data without proper authorization. <a href="https://www.owasp.org/index.php/Top_10-2017_A1-Injection">OWASP Wiki</a></p></blockquote>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-6' href='slides_rails_security.html#/6'>◻</a></p>
<h3 id="sql-injection-and-activerecord"><a class="anchorlink" href="#sql-injection-and-activerecord"><span>4.1</span> SQL Injection and ActiveRecord</a></h3><p>A good ORM like ActiveRecord will protect against SQL-Injection if used correctly.
For ActiveRecord: if you use the methods <code>find</code> or <code>where</code> without string interpolation
Rails will turn them into prepared statements - since <a href="https://patshaughnessy.net/2011/10/22/show-some-love-for-prepared-statements-in-rails-3-1">Rails 3.1, 2011</a>.</p><p>You can see this in the Rails console: The SQL statements contain placeholders $1, $2, and the
bound values are supplied separately:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">Project</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
<span class="c1"># SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT $2</span>
<span class="c1"># [["id", 42], ["LIMIT", 1]]</span>
<span class="no">Project</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">title: </span><span class="n">params</span><span class="p">[</span><span class="ss">:title</span><span class="p">])</span>
<span class="c1"># SELECT "projects".* FROM "projects" WHERE "projects"."title" = $1</span>
<span class="c1"># [["title", "Marios World"]]</span>
<span class="no">Project</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="s2">"publication_date > ?"</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="nf">year</span><span class="p">.</span><span class="nf">ago</span><span class="p">)</span>
<span class="c1"># SELECT "projects".* FROM "projects" WHERE (publication_date > '2018-06-03 12:15:54.952581')</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="Project.find(42)
# SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT $2
# [["id", 42], ["LIMIT", 1]]
Project.where(title: params[:title])
# SELECT "projects".* FROM "projects" WHERE "projects"."title" = $1
# [["title", "Marios World"]]
Project.where("publication_date > ?", 1.year.ago)
# SELECT "projects".* FROM "projects" WHERE (publication_date > '2018-06-03 12:15:54.952581')
">Copy</button>
</div>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-7' href='slides_rails_security.html#/7'>◻</a></p>
<p>But if you use string interpolation to build up SQL queries,
you open up your application to injection attacks. An example that is vunerable:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="vi">@projects</span> <span class="o">=</span> <span class="no">Project</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="s2">"title = '</span><span class="si">#{</span><span class="n">params</span><span class="p">[</span><span class="ss">:title</span><span class="p">]</span><span class="si">}</span><span class="s2">'"</span><span class="p">)</span>
<span class="c1"># SELECT "projects".* FROM "projects" WHERE (title = 'Marios World')</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="@projects = Project.where("title = '#{params[:title]}'")
# SELECT "projects".* FROM "projects" WHERE (title = 'Marios World')
">Copy</button>
</div>
<p>If a malicious user enters <code>' OR ''='</code> as the name parameter, the resulting SQL query is:</p><div class="interstitial code">
<pre><code class="highlight sql"><span class="k">SELECT</span> <span class="nv">"projects"</span><span class="p">.</span><span class="o">*</span> <span class="k">FROM</span> <span class="nv">"projects"</span> <span class="k">WHERE</span> <span class="p">(</span><span class="n">title</span> <span class="o">=</span> <span class="s1">''</span> <span class="k">OR</span> <span class="s1">''</span><span class="o">=</span><span class="s1">''</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="SELECT "projects".* FROM "projects" WHERE (title = '' OR ''='')
">Copy</button>
</div>
<p>As you can see the SQL Fragment was incorporated into the SQL query before
the string was handed to ActiveRecord. The resulting query returns all records from the projects table.
This is because the condition is true for all records.</p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-8' href='slides_rails_security.html#/8'>◻</a></p>
<p>To test for SQL Injection you can also use an integration test.
Check if the result-table has the right number of rows.</p><div class="interstitial code">
<pre><code class="highlight plaintext"> test "should search for users - result table shows 1 row" do
...
assert_select 'table tbody tr', count: 1
end
test "should not allow sql injections - result table has not rows" do
...
end
</code></pre>
<button class="clipboard-button" data-clipboard-text=" test "should search for users - result table shows 1 row" do
...
assert_select 'table tbody tr', count: 1
end
test "should not allow sql injections - result table has not rows" do
...
end
">Copy</button>
</div>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-9' href='slides_rails_security.html#/9'>◻</a></p>
<h3 id="links"><a class="anchorlink" href="#links"><span>4.2</span> Links</a></h3>
<ul>
<li><a href="https://guides.rubyonrails.org/security.html#sql-injection">SQL Injection chapter</a> of the Rails Guide "Securing Rails Applications"</li>
<li><a href="https://rails-sqli.org/">rails-sqli.org</a></li>
<li><a href="https://brakemanscanner.org/docs/warning_types/sql_injection/">brakeman will warn about possible sql injections</a></li>
</ul>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-10' href='slides_rails_security.html#/10'>◻</a></p>
<h2 id="broken-authentication"><a class="anchorlink" href="#broken-authentication"><span>5</span> Broken Authentication</a></h2>
<blockquote>
<p>Application functions related to authentication and session management are often implemented incorrectly, allowing attackers to compromise passwords, keys, or session tokens, or to exploit other implementation flaws to assume other users' identities temporarily or permanently. <a href="https://owasp.org/www-project-top-ten/2017/A2_2017-Broken_Authentication">OWASP Wiki</a></p></blockquote>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-11' href='slides_rails_security.html#/11'>◻</a></p>
<p>Rails comes with basic built in functionality to handle authentication:</p>
<ul>
<li><a href="https://edgeapi.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password">has_secure_password</a> adds methods to set and authenticate against a BCrypt password to a model.</li>
</ul>
<p>For most real world projects you will be using a gem:</p>
<ul>
<li><a href="https://github.com/plataformatec/devise">devise</a> to handle typical authentication flows like confimation mail or blocking accounts</li>
<li><a href="https://github.com/omniauth/omniauth/wiki/List-of-Strategies">omniauth</a> to use other authentication providers</li>
</ul>
<p>We discussed using this in the chapter on <a href="rails_authentication.html">Rails Authentication</a>.</p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-12' href='slides_rails_security.html#/12'>◻</a></p>
<h3 id="well-known-passwords"><a class="anchorlink" href="#well-known-passwords"><span>5.1</span> well known passwords</a></h3><p>Use the gem <code>pwned</code> to access an API that will tell you if a password is too common and has
been featured in password lists:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># Gemfile</span>
<span class="n">gem</span> <span class="s2">"pwned"</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# Gemfile
gem "pwned"
">Copy</button>
</div>
<div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># app/models/user.rb</span>
<span class="k">class</span> <span class="nc">User</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="n">has_secure_password</span>
<span class="n">validates</span> <span class="ss">:password</span><span class="p">,</span> <span class="ss">not_pwned: </span><span class="kp">true</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class User < ApplicationRecord
has_secure_password
validates :password, not_pwned: true
end
">Copy</button>
</div>
<p><a href="https://www.twilio.com/blog/2018/03/better-passwords-in-ruby-applications-pwned-passwords-api.html">Blog article on pwned gem</a></p><p>Friendly reminder: this is how you test if a user can be created:</p><div class="interstitial code">
<pre><code class="highlight plaintext"> test "can create user with password ljkw8723kjasf889r" do
get "/sign_up"
assert_response :success
assert_difference('User.count',1) do
post "/users", params: {
user: {
name:"Me Stupid",
email:"peter@prayalot.com",
password:'ljkw8723kjasf889r',
homepage:'https://some.where'
}
}
end
assert_select 'span', text: 'has previously appeared in a data breach and should not be used', count: 0
follow_redirect!
assert_select 'li', text: /Me Stupid/
end
</code></pre>
<button class="clipboard-button" data-clipboard-text=" test "can create user with password ljkw8723kjasf889r" do
get "/sign_up"
assert_response :success
assert_difference('User.count',1) do
post "/users", params: {
user: {
name:"Me Stupid",
email:"peter@prayalot.com",
password:'ljkw8723kjasf889r',
homepage:'https://some.where'
}
}
end
assert_select 'span', text: 'has previously appeared in a data breach and should not be used', count: 0
follow_redirect!
assert_select 'li', text: /Me Stupid/
end
">Copy</button>
</div>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-13' href='slides_rails_security.html#/13'>◻</a></p>
<h2 id="sensitive-data-exposure"><a class="anchorlink" href="#sensitive-data-exposure"><span>6</span> Sensitive Data Exposure</a></h2>
<blockquote>
<p>Many web applications and APIs do not properly protect sensitive data, such as financial, healthcare, and Peronally Identifiable Information ... Sensitive data may be compromised without extra protection, such as encryption at rest or in transit, and requires special precautions when exchanged with the browser. <a href="https://www.owasp.org/index.php/Top_10-2017_A3-Sensitive_Data_Exposure">OWASP Wiki</a></p></blockquote>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-14' href='slides_rails_security.html#/14'>◻</a></p>
<p>The OWASP advises: Determine the protection needs of data <strong>in transit</strong> and <strong>at rest</strong>. For example, passwords, credit card numbers, health records, personal information and business secrets require extra protection, particularly if that data falls under privacy laws, e.g. EU's General Data Protection Regulation (GDPR), or regulations, e.g. financial data protection such as PCI Data Security Standard (PCI DSS).</p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-15' href='slides_rails_security.html#/15'>◻</a></p>
<h3 id="encryption-in-the-database"><a class="anchorlink" href="#encryption-in-the-database"><span>6.1</span> Encryption in the Database</a></h3><p>From Rails version 7 on <a href="https://edgeguides.rubyonrails.org/active_record_encryption.html">ActiveRecord offers encryption</a>. For older versions you can use the <a href="https://github.com/attr-encrypted/attr_encrypted">attr_encrypted gem</a> to encrypt certain attributes in the database transparently.</p><p>While choosing to encrypt at the attribute level is the most secure solution, it is not without drawbacks. Namely, you cannot search the encrypted data, and because you can't search it, you can't index it either. You also can't use joins on the encrypted data.</p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-16' href='slides_rails_security.html#/16'>◻</a></p>
<h3 id="removing-from-the-logfile"><a class="anchorlink" href="#removing-from-the-logfile"><span>6.2</span> Removing from the Logfile</a></h3><p>By default, Rails logs all requests being made to the web application. 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 replaced by "[FILTERED]" in the log.</p><div class="interstitial code">
<pre><code class="highlight plaintext">Started POST "/user/sign_in" for 127.0.0.1 at 2021-01-05 08:46:01 +0100
Processing by Devise::SessionsController#create as HTML
Parameters: {"utf8"=>"✓", "user"=>{"email"=>"brigitte.jellinek@fh-salzburg.ac.at", "password"=>"[FILTERED]", "remember_me"=>"1"}
In"}
</code></pre>
<button class="clipboard-button" data-clipboard-text="Started POST "/user/sign_in" for 127.0.0.1 at 2021-01-05 08:46:01 +0100
Processing by Devise::SessionsController#create as HTML
Parameters: {"utf8"=>"✓", "user"=>{"email"=>"brigitte.jellinek@fh-salzburg.ac.at", "password"=>"[FILTERED]", "remember_me"=>"1"}
In"}
">Copy</button>
</div>
<div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># in initializers/filter_parameter_logging.rb</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">filter_parameters</span> <span class="o">+=</span> <span class="p">[</span><span class="ss">:password</span><span class="p">]</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# in initializers/filter_parameter_logging.rb
Rails.application.config.filter_parameters += [:password]
">Copy</button>
</div>
<p>Provided parameters will be filtered out by partial matching regular expression. Rails adds default <code>:password</code> in the appropriate initializer, which will take care of <code>password</code> and <code>password_confirmation</code>.</p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-17' href='slides_rails_security.html#/17'>◻</a></p>
<h3 id="ensuring-that-https-is-used"><a class="anchorlink" href="#ensuring-that-https-is-used"><span>6.3</span> Ensuring that HTTPS is used</a></h3><p>In the appropriate environment(s) force ssl:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># in config/environments/production.rb</span>
<span class="c1"># Force all access to the app over SSL,</span>
<span class="c1"># use Strict-Transport-Security, and use secure cookies.</span>
<span class="n">config</span><span class="p">.</span><span class="nf">force_ssl</span> <span class="o">=</span> <span class="kp">true</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# in config/environments/production.rb
# Force all access to the app over SSL,
# use Strict-Transport-Security, and use secure cookies.
config.force_ssl = true
">Copy</button>
</div>
<p>This will do three things:</p>
<ol>
<li>Redirect all http requests to their https equivalents.</li>
<li>Set secure flag on cookies <a href="https://tools.ietf.org/html/rfc6265#section-4.1.2.5">rfc 6265</a> to tell browsers that these cookies must only be sent through https requests.</li>
<li>Add HSTS headers to response. <a href="https://tools.ietf.org/html/rfc6797">rfc 6797</a></li>
</ol>
<p>See <a href="https://blog.bigbinary.com/2016/08/24/rails-5-adds-more-control-to-fine-tuning-ssl-usage.html">this blog article</a> for more details on configuring this</p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-18' href='slides_rails_security.html#/18'>◻</a></p>
<h2 id="broken-access-control"><a class="anchorlink" href="#broken-access-control"><span>7</span> Broken Access Control</a></h2>
<blockquote>
<p>Restrictions on what authenticated users are allowed to do are often not properly enforced. Attackers can exploit these flaws to access unauthorized functionality and/or data, such as access other users' accounts, view sensitive files, modify other users' data, change access rights, etc. <a href="https://www.owasp.org/index.php/Top_10-2017_A5-Broken_Access_Control">OWASP Wiki</a></p></blockquote>
<p>Use all lines of defence on the server to restrict access:</p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-19' href='slides_rails_security.html#/19'>◻</a></p>
<h3 id="remove-unused-routes"><a class="anchorlink" href="#remove-unused-routes"><span>7.1</span> remove unused routes</a></h3><p><a href="https://rails-bestpractices.com/posts/2011/08/19/restrict-auto-generated-routes/">rails_best_practices: Restrict auto-generated routes</a></p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-20' href='slides_rails_security.html#/20'>◻</a></p>
<h3 id="check-user-roles-and-premissions-in-every-controller"><a class="anchorlink" href="#check-user-roles-and-premissions-in-every-controller"><span>7.2</span> check user roles and premissions in every controller</a></h3><p>The simplest way is to use <code>current_user</code> to control access to models.
Instead of simply loading data:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="vi">@project</span> <span class="o">=</span> <span class="no">Project</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:id</span><span class="p">])</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="@project = Project.find(params[:id])
">Copy</button>
</div>
<p>Instead, <em>query the user's access rights, too</em>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="vi">@project</span> <span class="o">=</span> <span class="n">current_user</span><span class="p">.</span><span class="nf">projects</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:id</span><span class="p">])</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="@project = current_user.projects.find(params[:id])
">Copy</button>
</div>
<p>For more complex setups with different roles that have different permissions
use a gem like <code>cancancan</code> which will let you <a href="https://github.com/CanCanCommunity/cancancan/blob/develop/docs/Defining-Abilities.md#readme">define access in a declarative way</a> and give you an <code>authorize!</code> method for controllers:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="vi">@project</span> <span class="o">=</span> <span class="no">Project</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:id</span><span class="p">])</span>
<span class="n">authorize!</span> <span class="ss">:read</span><span class="p">,</span> <span class="vi">@project</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="@project = Project.find(params[:id])
authorize! :read, @project
">Copy</button>
</div>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-21' href='slides_rails_security.html#/21'>◻</a></p>
<h3 id="use-uuids-instead-of-bigint-as-id"><a class="anchorlink" href="#use-uuids-instead-of-bigint-as-id"><span>7.3</span> use UUIDs instead of bigint as id</a></h3><p>In some circumstances you need to have a resource that is available
to anyone with the URL - think google docs or doodle.</p><p>But you do not want users to be able to enumerate all possible URLs:</p><div class="interstitial code">
<pre><code class="highlight plaintext">https://my-schedule.at/calendar/17
https://my-schedule.at/calendar/18
https://my-schedule.at/calendar/19 ...
</code></pre>
<button class="clipboard-button" data-clipboard-text="https://my-schedule.at/calendar/17
https://my-schedule.at/calendar/18
https://my-schedule.at/calendar/19 ...
">Copy</button>
</div>
<p>To avoid the enumeration attack you can switch from using serial/autocincrement as
the primary key in the database to using UUIDs. Then the URLs will look like this:</p><div class="interstitial code">
<pre><code class="highlight plaintext">https://my-schedule.at/calendar/0d60a85e-0b90-4482-a14c-108aea2557aa
https://my-schedule.at/calendar/39240e9f-ae09-4e95-9fd0-a712035c8ad7
https://my-schedule.at/calendar/a3240e9e-1209-4e95-9fd0-a712035c8ad4 ...
</code></pre>
<button class="clipboard-button" data-clipboard-text="https://my-schedule.at/calendar/0d60a85e-0b90-4482-a14c-108aea2557aa
https://my-schedule.at/calendar/39240e9f-ae09-4e95-9fd0-a712035c8ad7
https://my-schedule.at/calendar/a3240e9e-1209-4e95-9fd0-a712035c8ad4 ...
">Copy</button>
</div>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-22' href='slides_rails_security.html#/22'>◻</a></p>
<p>In postgresql you can use the extentions <strong>pgcrypto</strong> or <strong>uuid-ossp</strong></p><div class="interstitial code">
<pre><code class="highlight sql"><span class="k">CREATE</span> <span class="n">EXTENSION</span> <span class="n">pgcrypto</span><span class="p">;</span><span class="err"> </span>
<span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">calendar</span><span class="p">(</span> <span class="n">id</span> <span class="n">UUID</span> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="k">DEFAULT</span> <span class="n">gen_random_uuid</span><span class="p">(),</span> <span class="n">name</span> <span class="nb">TEXT</span> <span class="p">);</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">calendar</span> <span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="k">VALUES</span><span class="err"> </span>
<span class="p">(</span><span class="s1">'meeting on grdp'</span><span class="p">),</span><span class="err"> </span>
<span class="p">(</span><span class="s1">'security audit'</span><span class="p">);</span>
<span class="k">SELECT</span> <span class="o">*</span> <span class="k">from</span> <span class="n">calendar</span><span class="p">;</span>
<span class="mi">0</span><span class="n">d60a85e</span><span class="o">-</span><span class="mi">0</span><span class="n">b90</span><span class="o">-</span><span class="mi">4482</span><span class="o">-</span><span class="n">a14c</span><span class="o">-</span><span class="mi">108</span><span class="n">aea2557aa</span> <span class="o">|</span> <span class="n">meeting</span> <span class="k">on</span> <span class="n">grdp</span>
<span class="mi">39240</span><span class="n">e9f</span><span class="o">-</span><span class="n">ae09</span><span class="o">-</span><span class="mi">4</span><span class="n">e95</span><span class="o">-</span><span class="mi">9</span><span class="n">fd0</span><span class="o">-</span><span class="n">a712035c8ad7</span> <span class="o">|</span> <span class="k">security</span> <span class="n">audit</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="CREATE EXTENSION pgcrypto;
CREATE TABLE calendar( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT );
INSERT INTO calendar (name) VALUES
('meeting on grdp'),
('security audit');
SELECT * from calendar;
0d60a85e-0b90-4482-a14c-108aea2557aa | meeting on grdp
39240e9f-ae09-4e95-9fd0-a712035c8ad7 | security audit
">Copy</button>
</div>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-23' href='slides_rails_security.html#/23'>◻</a></p>
<p>Rails can handle all this for you:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># in config/application.rb</span>
<span class="n">config</span><span class="p">.</span><span class="nf">generators</span> <span class="k">do</span> <span class="o">|</span><span class="n">g</span><span class="o">|</span>
<span class="n">g</span><span class="p">.</span><span class="nf">orm</span> <span class="ss">:active_record</span><span class="p">,</span> <span class="ss">primary_key_type: :uuid</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# in config/application.rb
config.generators do |g|
g.orm :active_record, primary_key_type: :uuid
end
">Copy</button>
</div>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-24' href='slides_rails_security.html#/24'>◻</a></p>
<h3 id="set-cors-for-your-api"><a class="anchorlink" href="#set-cors-for-your-api"><span>7.4</span> set CORS for your API</a></h3><p>Browsers restrict cross-origin HTTP requests initiated by scripts. For example, <strong>XMLHttpRequest</strong> and the <strong>Fetch API</strong> follow the same-origin policy. This means that a web application using those APIs can only request HTTP resources from the same origin the application was loaded from, unless the response <strong>from the other origin</strong> includes the right CORS headers.</p><p><img src="images/cors_principle.png" alt="cors principle"></p><p>(Text and Image originally from <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">MDN</a> by <a href="https://wiki.developer.mozilla.org/en-US/docs/Web/HTTP/CORS$history">many contributres</a> is licensed under <a href="https://creativecommons.org/licenses/by-sa/2.5/">CC BY-SA 2.5</a></p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-25' href='slides_rails_security.html#/25'>◻</a></p>
<p>In the most simple case, when doing a cross-origin request:</p>
<ul>
<li>the client send a <code>Origin</code> header</li>
<li>the server responds with a <code>Access-Control-Allow-Origin</code> header</li>
</ul>
<p>for example:</p><div class="interstitial code">
<pre><code class="highlight plaintext">GET /resources/data.json HTTP/1.1
Host: bar.other
Origin: https://foo.example
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json
</code></pre>
<button class="clipboard-button" data-clipboard-text="GET /resources/data.json HTTP/1.1
Host: bar.other
Origin: https://foo.example
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json
">Copy</button>
</div>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-26' href='slides_rails_security.html#/26'>◻</a></p>
<p>If a scripts tries to fetch a ressource cross-origin from server X and no
<code>Access-Control-Allow-Origin</code> Header
is set on server X, then the browser will throw an error and not
continue with the script:</p><div class="interstitial code">
<pre><code class="highlight plaintext">fetch("https://iou-brigitte.herokuapp.com/users.json")
.then(function(response) {
return response.json();
})
.then(function(data) {
console.log(data);
});
</code></pre>
<button class="clipboard-button" data-clipboard-text="fetch("https://iou-brigitte.herokuapp.com/users.json")
.then(function(response) {
return response.json();
})
.then(function(data) {
console.log(data);
});
">Copy</button>
</div>
<p><img src="images/cors-error.png" alt=""></p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-27' href='slides_rails_security.html#/27'>◻</a></p>
<p>After setting the right Headers for the HTTP Response,
the request goes through:</p><div class="interstitial code">
<pre><code class="highlight plaintext">HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
</code></pre>
<button class="clipboard-button" data-clipboard-text="HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
">Copy</button>
</div>
<p><img src="images/cors-ok.png" alt=""></p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-28' href='slides_rails_security.html#/28'>◻</a></p>
<p>In Rails you
can use the <code>rack-cors</code> gem to set the Header in middleware:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># Gemfile</span>
<span class="n">gem</span> <span class="s1">'rack-cors'</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# Gemfile
gem 'rack-cors'
">Copy</button>
</div>
<p>The configuration is done in an initializer:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># config/initializers/cors.rb</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">middleware</span><span class="p">.</span><span class="nf">insert_before</span> <span class="mi">0</span><span class="p">,</span> <span class="no">Rack</span><span class="o">::</span><span class="no">Cors</span> <span class="k">do</span>
<span class="n">allow</span> <span class="k">do</span>
<span class="n">origins</span> <span class="s1">'*'</span>
<span class="n">resource</span> <span class="s1">'/*'</span><span class="p">,</span> <span class="ss">headers: :any</span><span class="p">,</span> <span class="ss">methods: :get</span>
<span class="k">end</span>
<span class="n">allow</span> <span class="k">do</span>
<span class="n">origins</span> <span class="s1">'localhost:3000'</span><span class="p">,</span> <span class="s1">'127.0.0.1:3000'</span><span class="p">,</span> <span class="s1">'https://my-frontend.org'</span>
<span class="n">resource</span> <span class="s1">'/api/v1/*'</span><span class="p">,</span>
<span class="ss">methods: </span><span class="sx">%i(get post put patch delete options head)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '/*', headers: :any, methods: :get
end
allow do
origins 'localhost:3000', '127.0.0.1:3000', 'https://my-frontend.org'
resource '/api/v1/*',
methods: %i(get post put patch delete options head)
end
end
">Copy</button>
</div>
<p>In this example get-requests are allowed for all origins,
while using the full api is only allowed for three specific domains.</p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-29' href='slides_rails_security.html#/29'>◻</a></p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">MDN: CORS</a></li>
<li><a href="https://github.com/cyu/rack-cors">rack-cors</a></li>
</ul>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-30' href='slides_rails_security.html#/30'>◻</a></p>
<h2 id="security-misconfiguration"><a class="anchorlink" href="#security-misconfiguration"><span>8</span> Security Misconfiguration</a></h2>
<blockquote>
<p>Security misconfiguration is a result of insecure default configurations, incomplete or ad hoc configurations, open cloud storage, misconfigured HTTP headers, and verbose error messages containing sensitive information. Not only must all operating systems, frameworks, libraries, and applications be securely configured, but they must be patched/upgraded in a timely fashion. <a href="https://www.owasp.org/index.php/Top_10-2017_A6-Security_Misconfiguration">OWASP Wiki</a></p></blockquote>
<p>This is especially relevant if you are running your own virtual machine:</p>
<ul>
<li>upgrade the operating system, apply security patches</li>
<li>remove unused components, e.g. a wordpress installation you no longer need</li>
<li>upgrade ruby after <a href="https://www.ruby-lang.org/en/news/">security problems are fixed</a></li>
</ul>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-31' href='slides_rails_security.html#/31'>◻</a></p>
<h3 id="use-environment-variables"><a class="anchorlink" href="#use-environment-variables"><span>8.1</span> Use Environment Variables</a></h3><p>The files <code>config/database.yml</code> and <code>config/secrets.yml</code> should not be added
to the repository - unless you extract out all the secrets into environment
variables (as a <a href="https://12factor.net/config">12 factor app</a>)</p><div class="interstitial code">
<pre><code class="highlight plaintext"># in config/database.yml
default: &default
adapter: postgresql
encoding: unicode
# For details on connection pooling, see Rails configuration guide
# https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
database: myapp_development
test:
<<: *default
database: myapp_test
production:
<<: *default
url: <%= ENV['DATABASE_URL'] %>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# in config/database.yml
default: &default
adapter: postgresql
encoding: unicode
# For details on connection pooling, see Rails configuration guide
# https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
database: myapp_development
test:
<<: *default
database: myapp_test
production:
<<: *default
url: <%= ENV['DATABASE_URL'] %>
">Copy</button>
</div>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-32' href='slides_rails_security.html#/32'>◻</a></p>
<h3 id="storing-secrets-in-an-encrypted-file"><a class="anchorlink" href="#storing-secrets-in-an-encrypted-file"><span>8.2</span> Storing Secrets in an encrypted file</a></h3><p>Rails 5.2 and later generates two files to handle credentials (passwords, api keys, ...):</p>
<ul>
<li><code>config/credentials.yml.enc</code> to store the credentials within the repo</li>
<li><code>config/master.key</code> or <code>ENV["RAILS_MASTER_KEY"]</code> to read the encryption key from</li>
</ul>
<p>The master key is never stored in the repo.</p><p>To edit stored credentials use <code>rails credentials:edit</code>.</p><p>By default, this file contains the application's
<code>secret_key_base</code>, but it could also be used to store other credentials such as
access keys for external APIs.</p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-33' href='slides_rails_security.html#/33'>◻</a></p>
<p>The credentials are accessible in
the running Rails app via <code>Rails.application.credentials</code>.</p><p>For example, with the following decrypted <code>config/credentials.yml.enc</code>:</p><div class="interstitial code">
<pre><code class="highlight plaintext">secret_key_base: 3b7cd727ee24e8444053437c36cc66c3
some_api_key: 123454321
</code></pre>
<button class="clipboard-button" data-clipboard-text="secret_key_base: 3b7cd727ee24e8444053437c36cc66c3
some_api_key: 123454321
">Copy</button>
</div>
<p><code>Rails.application.credentials.some_api_key</code> returns <code>123454321</code> in any environment.</p><p>If you want an exception to be raised when some key is blank, use the bang
version:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">credentials</span><span class="p">.</span><span class="nf">some_api_key!</span>
<span class="c1"># => raises KeyError: :some_api_key is blank</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="Rails.application.credentials.some_api_key!
# => raises KeyError: :some_api_key is blank
">Copy</button>
</div>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-34' href='slides_rails_security.html#/34'>◻</a></p>
<h2 id="cross-site-scripting-xss"><a class="anchorlink" href="#cross-site-scripting-xss"><span>9</span> Cross-Site Scripting (XSS)</a></h2>
<blockquote>
<p>XSS flaws occur whenever an application includes untrusted data in a new web page without proper validation or escaping, or updates an existing web page with user-supplied data using a browser API that can create HTML or JavaScript. XSS allows attackers to execute scripts in the victim's browser which can hijack user sessions, deface web sites, or redirect the user to malicious sites. <a href="https://www.owasp.org/index.php/Top_10-2017_A7-Cross-Site_Scripting_(XSS)">OWASP Wiki</a></p></blockquote>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-35' href='slides_rails_security.html#/35'>◻</a></p>
<h3 id="use-a-content-security-policy-csp"><a class="anchorlink" href="#use-a-content-security-policy-csp"><span>9.1</span> Use a Content Security Policy (CSP)</a></h3><p>The modern solution to XSS ist a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP">Content Security Policy</a>(CSP).
In Rails you can configure the Content Security Policy
for your application in an initializer.</p><p>Example global policy:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># config/initializers/content_security_policy.rb</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">content_security_policy</span> <span class="k">do</span> <span class="o">|</span><span class="n">policy</span><span class="o">|</span>
<span class="n">policy</span><span class="p">.</span><span class="nf">default_src</span> <span class="ss">:self</span>
<span class="n">policy</span><span class="p">.</span><span class="nf">font_src</span> <span class="ss">:self</span><span class="p">,</span> <span class="s1">'https://fonts.gstatic.com'</span>
<span class="n">policy</span><span class="p">.</span><span class="nf">img_src</span> <span class="s1">'*'</span>
<span class="n">policy</span><span class="p">.</span><span class="nf">object_src</span> <span class="ss">:none</span>
<span class="n">policy</span><span class="p">.</span><span class="nf">script_src</span> <span class="ss">:self</span><span class="p">,</span> <span class="s1">'https://code.jquery.com'</span>
<span class="n">policy</span><span class="p">.</span><span class="nf">style_src</span> <span class="ss">:self</span><span class="p">,</span> <span class="s1">'https://fonts.googleapis.com'</span>
<span class="c1"># Specify URI for violation reports</span>
<span class="n">policy</span><span class="p">.</span><span class="nf">report_uri</span> <span class="s2">"/csp-violation-report-endpoint"</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="Rails.application.config.content_security_policy do |policy|
policy.default_src :self
policy.font_src :self, 'https://fonts.gstatic.com'
policy.img_src '*'
policy.object_src :none
policy.script_src :self, 'https://code.jquery.com'
policy.style_src :self, 'https://fonts.googleapis.com'
# Specify URI for violation reports
policy.report_uri "/csp-violation-report-endpoint"
end
">Copy</button>
</div>
<p>This automatically forbids all 'unsave-inline' script: <code><script></code>-tags
in the html code and event-handler-attributes like <code><button onclick=...></code>.</p><p>To allow certain <code><script></code>-tags in your code you can give
them a "nonce":</p><div class="interstitial code">
<pre><code class="highlight plaintext"><script nonce="2726c7f26c">
var inline = 1; // good javascript
</script>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<script nonce="2726c7f26c">
var inline = 1; // good javascript
</script>
">Copy</button>
</div>
<p>This must be the same nonce given in the CSP:</p><div class="interstitial code">
<pre><code class="highlight plaintext">Content-Security-Policy: script-src 'nonce-2726c7f26c'
</code></pre>
<button class="clipboard-button" data-clipboard-text="Content-Security-Policy: script-src 'nonce-2726c7f26c'
">Copy</button>
</div>
<p>Rails can generate separate nonces for separate sessions automatically,
see <a href="https://guides.rubyonrails.org/security.html#adding-a-nonce">the Rails Security Guide</a>.</p><p>If you want to handle violation reports, you need to set up a model, controller and route <a href="https://bauland42.com/ruby-on-rails-content-security-policy-csp/#cspviolationreports">as described here</a>.</p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-36' href='slides_rails_security.html#/36'>◻</a></p>
<h3 id="escape-for-the-correct-context"><a class="anchorlink" href="#escape-for-the-correct-context"><span>9.2</span> Escape for the correct context:</a></h3><p>erb automatically escapes for HTML:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="o"><</span><span class="sx">%= @article.title %>
</span></code></pre>
<button class="clipboard-button" data-clipboard-text="<%= @article.title %>
">Copy</button>
</div>
<p>This escaping is not apporpriate for attributes:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="o"><!--</span> <span class="no">DANGER</span><span class="p">,</span> <span class="k">do</span> <span class="ow">not</span> <span class="n">use</span> <span class="n">this</span> <span class="n">code</span> <span class="o">--></span>
<span class="o"><</span><span class="nb">p</span> <span class="k">class</span><span class="o">=<</span><span class="sx">%= params[:style] %> >...</p>
<!-- DANGER, do not use this code -->
</span></code></pre>
<button class="clipboard-button" data-clipboard-text="<!-- DANGER, do not use this code -->
<p class=<%= params[:style] %> >...</p>
<!-- DANGER, do not use this code -->
">Copy</button>
</div>
<p>An attacker could insert a space into the style parameter like so: <code>x%22onmouseover=alert('hacked')</code>
resulting in the following html</p><div class="interstitial code">
<pre><code class="highlight html"><span class="c"><!-- DANGER, do not use this code --></span>
<span class="nt"><p</span> <span class="na">class=</span><span class="s">x</span> <span class="na">onmouseover=</span><span class="s">alert('hacked')</span> <span class="nt">></span>...<span class="nt"></p></span>
<span class="c"><!-- DANGER, do not use this code --></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<!-- DANGER, do not use this code -->
<p class=x onmouseover=alert('hacked') >...</p>
<!-- DANGER, do not use this code -->
">Copy</button>
</div>
<p>To construct HTML Attributes that are properly escaped
it is easiest to use view helpers like <code>tag</code> and <code>content_tag</code>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="o"><</span><span class="sx">%= content_tag :p, "...", class: params[:style] %>
</span></code></pre>
<button class="clipboard-button" data-clipboard-text="<%= content_tag :p, "...", class: params[:style] %>
">Copy</button>
</div>
<p>When transferring data from the backend to the frontend
through JSON you need to use <code>json_encode</code> with the additional
option</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="o"><</span><span class="n">script</span><span class="o">></span>
<span class="n">const</span> <span class="n">attributes</span> <span class="o">=</span> <span class="o"><</span><span class="sx">%= raw json_encode(@attrs, escape_html_entities_in_json =</span> <span class="kp">true</span><span class="p">)</span> <span class="o">%></span>
<span class="o"><</span><span class="sr">/script>
</span></code></pre>
<button class="clipboard-button" data-clipboard-text="<script>
const attributes = <%= raw json_encode(@attrs, escape_html_entities_in_json = true) %>
</script>
">Copy</button>
</div>
<p>See <a href="https://brakemanscanner.org/docs/warning_types/cross_site_scripting_to_json/">brakeman</a> for the rationale.</p><p>When building a JSON API use <code>jbuilder</code> or <code>active_model_serializers</code> as described in <a href="/apis.html#rendering-json">chapter APIs</a>.</p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-37' href='slides_rails_security.html#/37'>◻</a></p>
<h2 id="insecure-deserialization"><a class="anchorlink" href="#insecure-deserialization"><span>10</span> Insecure Deserialization</a></h2>
<blockquote>
<p>Insecure deserialization often leads to remote code execution. Even if deserialization flaws do not result in remote code execution, they can be used to perform attacks, including replay attacks, injection attacks, and privilege escalation attacks. <a href="https://www.owasp.org/index.php/Top_10-2017_A8-Insecure_Deserialization">OWASP Wiki</a></p></blockquote>
<p>Brakeman will warn about <a href="https://brakemanscanner.org/docs/warning_types/unsafe_deserialization/index.html">Unsafe Deserialization</a></p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-38' href='slides_rails_security.html#/38'>◻</a></p>
<h2 id="using-components-with-known-vulnerabilities"><a class="anchorlink" href="#using-components-with-known-vulnerabilities"><span>11</span> Using Components with Known Vulnerabilities</a></h2>
<blockquote>
<p>Components, such as libraries, frameworks, and other software modules, run with the same privileges as the application. If a vulnerable component is exploited, such an attack can facilitate serious data loss or server takeover. Applications and APIs using components with known vulnerabilities may undermine application defenses and enable various attacks and impacts. <a href="https://www.owasp.org/index.php/Top_10-2017_A9-Using_Components_with_Known_Vulnerabilities">OWASP Wiki</a></p></blockquote>
<p>Before using a gem, look it up in the <a href="https://www.ruby-toolbox.com/">ruby-toolbox</a>
and check if it is actively maintained and if there are better alternatives.</p><p>There are several tools that check for vulnerabilities in dependencies:</p>
<ul>
<li><a href="https://www.ruby-toolbox.com/projects/bundler-audit">bundler-audit</a> will read the Gemfile.lock, looking for gem versions with vulnerabilities reported in the <a href="https://github.com/rubysec/ruby-advisory-db">Ruby Advisory Database</a>.</li>
<li><a href="https://snyk.io/">snyk</a> works for ruby and javascript (and more languages).</li>
</ul>
<p>Use <code>bundle update --conservative gem_name</code> to safely update vulnerable dependencies.</p><p>When using script-tags to include javascript (e.g. bootstrap, react) from a cdn
use Subresource Integrity checks. This way, if a hacker manages to change
the script on the CDN your application will not be affected:</p><div class="interstitial code">
<pre><code class="highlight plaintext"><script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
">Copy</button>
</div>
<p>(This example from jquery also includes the CORS attribte <code>crossorigin</code> set to <code>anonymous</code>.
This way no user credentials will every be sent to <code>code.jquery.com</code>).</p>
<ul>
<li>Report: <a href="https://www.privateinternetaccess.com/blog/2016/12/comcast-still-uses-mitm-javascript-injection-serve-unwanted-ads-messages/">Comcast uses MITM javascript injection to serve unwanted ads and messages</a></li>
<li>MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity">Subresource Integrity - SRI</a></li>
</ul>
</div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-39' href='slides_rails_security.html#/39'>◻</a></p>
<h2 id="cross-site-request-forgery-csrf"><a class="anchorlink" href="#cross-site-request-forgery-csrf"><span>12</span> Cross Site Request Forgery (CSRF)</a></h2><p>This security problem used to be No 8 on the list, but was no longer listed in the 2017.</p>
<blockquote>
<p>A CSRF attack forces a logged-on victim’s browser to send a forged HTTP request, including the victim’s session cookie and any other automatically included authentication information, to a vulnerable web application. This allows the attacker to force the victim’s browser to generate requests the vulnerable application thinks are legitimate requests from the victim. <a href="https://owasp.org/www-community/attacks/csrf">OWASP</a></p></blockquote>
<p>First use GET and POST appropriately. Secondly, a security token in non-GET requests will protect your application from CSRF. Rails can handle this for you:</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, and is the default for newly created Rails applications:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># in app/controller/application_controller.rb</span>
<span class="n">protect_from_forgery</span> <span class="ss">with: :exception</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# in app/controller/application_controller.rb
protect_from_forgery with: :exception
">Copy</button>
</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, an exception will be thrown.</p><p>By default, Rails includes an <a href="https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts">unobtrusive scripting adapter</a>,
which adds a header called <code>X-CSRF-Token</code> with the security token on every non-GET
Ajax call. Without this header, non-GET Ajax requests won't be accepted by Rails.
When using another library to make Ajax calls, it is necessary to add the security
token as a default header for Ajax calls in your library.</p><p>Note that cross-site scripting (XSS) vulnerabilities bypass all CSRF protections. 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.</p></div>
<div class='slide'>
<p class='slide_break_block'><a class='slide_break' id='slide-40' href='slides_rails_security.html#/40'>◻</a></p>
<h2 id="see-also"><a class="anchorlink" href="#see-also"><span>13</span> See Also</a></h2>
<ul>
<li><a href="https://guides.rubyonrails.org/security.html">Rails Guide: Security</a></li>
<li>Tool: <a href="https://github.com/flavorjones/loofah">loofah</a></li>
<li>Tool: <a href="https://github.com/presidentbeef/brakeman">brakeman</a></li>
</ul>