1
0
mirror of https://github.com/php/doc-es.git synced 2026-03-25 16:02:13 +01:00
Files
archived-doc-es/security/database.xml
Pedro Antonio Gil Rodríguez 13b571f71f Correcciones menores
git-svn-id: https://svn.php.net/repository/phpdoc/es/trunk@336634 c90b9560-bf6c-de11-be94-00142212c4b1
2015-04-23 15:55:29 +00:00

487 lines
21 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: 6bfcf7d9af614b4155c72e84814a61607be21229 Maintainer: yago Status: ready -->
<!-- Reviewed: yes Maintainer: seros -->
<!-- splitted from ./index.xml, last change in rev 1.66 -->
<chapter xml:id="security.database" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Seguridad en bases de datos</title>
<simpara>
Hoy en día, las bases de datos son componentes esenciales de cualquier aplicación web,
permitiendo a los sitios web proveer variedad de contenido dinámico. Puesto que se puede
almacenar información muy sensible o secreta en una base de datos, debería considerarse
ampliamente la protección de las bases de datos.
</simpara>
<simpara>
Para obtener o almacenar cualquier información, es necesario conectarse a la base de datos,
enviar una consulta válida, obtener el resultado, y cerrar la conexión.
Hoy en día, el lenguaje de consultas más utilizado en esta interacción es el
Lenguaje Estructurado de Consultas (SQL, por sus siglas en inglés). Vea como un atacante puede <link
linkend="security.database.sql-injection">realizar manipulaciones maliciosas con una consulta SQL</link>.
</simpara>
<simpara>
Como es de suponer, <acronym>PHP</acronym> no puede proteger una base de datos por sí mismo. Las
siguientes secciones tienen como objetivo ser una introducción básica de cómo
acceder y manipular bases de datos dentro de scripts de <acronym>PHP</acronym>.
</simpara>
<simpara>
Tenga en mente esta sencilla regla: Protección en profundidad. En cuantos más sitios se
tomen acciones para aumentar la protección de una base de datos, menor es la
probabilidad de que un atacante tenga éxito en exponer o abusar de cualquier información
que tenga almacenada. Un buen diseño del esquema de la base de datos y de la aplicación
se ocupará de sus mayores temores.
</simpara>
<sect1 xml:id="security.database.design">
<title>Diseño de bases de datos</title>
<simpara>
El primer paso es siempre crear una base de datos, a menos que se quiera utilizar
una de un tercero. Cuando se crea una base de datos, esta es
asignada a un propietario, aquel que ejecutó la sentencia de creación. Usualmente, sólo
el propietario (o un superusuario) puede hacer cualquier cosa con los objetos de esa
base de datos. Para que otros usuarios puedan utilizarla, se les deben conceder
privilegios.
</simpara>
<simpara>
Las aplicaciones nunca deberían conectarse a la base de datos como su propietario o como
un superusuario, porque estos usuarios pueden ejecutar cualquier consulta a su antojo; por
ejemplo, modificar el esquema (p.ej., eliminar tablas) o borrar su contenido
por completo.
</simpara>
<simpara>
Se pueden crear distintos usuarios de una base de datos para cada aspecto de la
aplicación con permisos muy limitados a los objetos de dicha base de datos. Solamente
deberían otorgarse los privilegios necesarios, evitando así que el mismo usuario
pueda interactuar con la base de datos en diferentes casos y uso. Esto significa que si
un intruso obtiene acceso a una base de datos utilizando las credenciales de la aplicación,
solamente puede efectuar los cambios que la aplicación permita.
</simpara>
<simpara>
Se recomienda no implementar toda la lógica de negocio en la aplicación
web (esto es, en los scripts); se ha de hacer en su lugar en el esquema de la base de datos
utilizando vistas, disparadores o reglas. Si el sistema evoluciona, se tendrá por objeto
abrir nuevos puertos a la base de datos, teniendo así que reimplementar la lógica
en cada cliente de la base de datos por separado. Además, los disparadores se pueden utilizar
para manejar campos de forma transparente y automática, lo que a menudo ayuda en
la depuración de problemas con la aplicación o en el seguimiento de
transacciones.
</simpara>
</sect1>
<sect1 xml:id="security.database.connection">
<title>Conexión a una base de datos</title>
<simpara>
Se pueden establecer las conexiones sobre SSL para encriptar
las comunicaciones cliente/servidor y aumentar la seguridad, o también emplear ssh
para encriptar la conexión de red entre los clientes y el servidor de bases de datos.
Si se utiliza algunas de estas opciones, será difícil para un posible atacante
la monitorización del tráfico y la obtención de información de la base de datos.
</simpara>
<!--simpara>
Si el servidor de bases de datos tiene soporte nativo para SSL, considere utilizar <link
linkend="ref.openssl">funciones de OpenSSL</link> en la comunicación entre
<acronym>PHP</acronym> y la base de datos por medio de SSL.
</simpara-->
</sect1>
<sect1 xml:id="security.database.storage">
<title>Modelo de almacenamiento encriptado</title>
<simpara>
SSL/SSH protege los datos que viajan desde el cliente al servidor: SSL/SSH
no protege los datos persistentes almacenados en una base de datos. SSL es un
protocolo que protege los datos mientras viajan por el cable.
</simpara>
<simpara>
Una vez que un atacante obtiene acceso directo a una base de datos (eludiendo el
servidor web), los datos sensibles almacenados podrían ser divulgados o mal utilizados, a menos que
la información esté protegida por la base de datos misma. Encriptar los datos
es una buena forma de mitigar esta amenaza, pero muy pocas bases de datos ofrecen este
tipo de encriptación de datos.
</simpara>
<simpara>
La forma más sencilla para evitar este problema es crear primero un paquete de
encriptación propio y utilizarlo en los scripts de <acronym>PHP</acronym>. Hay muchas
extensiones de <acronym>PHP</acronym> que pueden ser de ayuda para esto, tales como <link
linkend="ref.mcrypt">Mcrypt</link> y <link
linkend="ref.mhash">Mhash</link>, cubriendo así una amplia variedad de algoritmos de
encriptación. El script encripta los datos antes de insertarlos en la base de datos, y los
desencripta al obtenerlos. Véanse las referencias para ejemplos adicionales del
funcionamiento de la encriptación.
</simpara>
<simpara>
En caso de datos que deban estar realmente ocultos, si no fuera necesaria su representación real,
(es decir, que no sean mostrados), quizás convenga utilizar algoritmos hash.
El ejemplo más típico del uso del hash es a la hora de almacenar el hash criptográfico de una
contraseña en una base de datos, en lugar de almacenar la contraseña en sí. Véase también
<function>crypt</function>.
</simpara>
<example>
<title>Utilización de campos de contraseña con hash</title>
<programlisting role="php">
<![CDATA[
<?php
// Almacenar el hash de la contraseña
// $caracteres_aleatorios se obtuvo, p.ej., utilizando /dev/random
$consulta = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');",
pg_escape_string($nombre_usuario),
pg_escape_string(crypt($contraseña, '$2a$07$' . $caracteres_aleatorios . '$')));
$resultado = pg_query($conexión, $consulta);
// Consultar si el usuario envió la contraseña correcta
$consulta = sprintf("SELECT pwd FROM users WHERE name='%s';",
pg_escape_string($nombre_usuario));
$fila = pg_fetch_assoc(pg_query($conexión, $consulta));
if ($fila && crypt($contraseña, $fila['pwd']) == $fila['pwd']) {
echo 'Bienvenido, ' . htmlspecialchars($nombre_usuario) . '!';
} else {
echo 'La autenticación ha fallado para ' . htmlspecialchars($nombre_usuario) . '.';
}
?>
]]>
</programlisting>
</example>
</sect1>
<sect1 xml:id="security.database.sql-injection">
<title>Inyección de SQL</title>
<simpara>
Muchos desarrolladores web no son conscientes de cómo las consultas SQL pueden ser manipuladas,
y asumen que una consulta SQL es una orden fiable. Esto significa que las consultas
SQL son capaces de eludir controles de acceso, evitando así las comprobaciones de
autenticación y autorización estándar, e incluso algunas veces, que las consultas SQL
podrían permitir el acceso a comandos al nivel del sistema operativo del equipo anfitrión.
</simpara>
<simpara>
La inyección directa de comandos SQL es una técnica donde un atacante crea o
altera comandos SQL existentes para exponer datos ocultos, sobrescribir los
valiosos, o peor aún, ejecutar comandos peligrosos a nivel de sistema en el equipo que hospeda
la base de datos. Esto se logra a través de la práctica de tomar la entrada del usuario y
combinarla con parámetros estáticos para elaborar una consulta SQL. Los siguientes ejemplos
están basados en historias reales, desafortunadamente.
</simpara>
<para>
Debido a la falta de validación en la entrada de datos y a la conexión a la base de datos
con privilegios de superusuario o de alguien con privilegios para crear usuarios, el atacante
podría crear un superusuario en la base de datos.
<example>
<title>
Dividir el conjunto de resultados en páginas ... y crear superusuarios
(PostgreSQL)
</title>
<programlisting role="php">
<![CDATA[
<?php
$índice = $argv[0]; // ¡Cuidado, no hay validación en la entrada de datos!
$consulta = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $índice;";
$resultado = pg_query($conexión, $consulta);
?>
]]>
</programlisting>
</example>
Un usuario común hace clic en los enlaces 'siguiente' o 'atrás' donde el <varname>$índice</varname>
está codificado en el <acronym>URL</acronym>. El script espera que el <varname>$índice</varname>
entrante sea un número décimal. Sin embargo, ¿qué pasa si alguien intenta
irrumpir anteponiendo a la <acronym>URL</acronym> algo como lo siguiente empleando
<function>urlencode</function>?
<informalexample>
<programlisting role="sql">
<![CDATA[
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
select 'crack', usesysid, 't','t','crack'
from pg_shadow where usename='postgres';
--
]]>
</programlisting>
</informalexample>
Si esto sucediera, el script podría otrogar un acceso de superusuario al atacante.
Observe que <literal>0;</literal> es para proveer un índcie válido a la
consulta original y así finalizarla.
</para>
<note>
<para>
Es una técnica común forzar al analizador de SQL a ignorar el resto de la
consulta escrita por el desarrollador con <literal>--</literal>, lo cual
representa un comentario en SQL.
</para>
</note>
<para>
Una forma factible de obtener contraseñas es burlar las páginas de búsqueda de resultados.
Lo único que el atacante necesita hacer es ver si hay variables que hayan sido enviadas
y sean empleadas en sentencias SQL que no sean manejadas apropiadamente. Estos filtros se pueden establecer
comunmente en un formulario anterior para personalizar las cláusulas <literal>WHERE, ORDER BY,
LIMIT</literal> y <literal>OFFSET</literal> en las sentencias <literal>SELECT</literal>.
Si la base de datos admite el constructor <literal>UNION</literal>,
el atacante podría intentar anteponer una consulta entera a la consulta original para enumerar las
contraseñas de una tabla arbitraria. Se recomienda encarecidamente utilizar campos de
contraseña encriptadas.
<example>
<title>
Enumerar artículos ... y algunas contraseñas (cualquier servidor de bases de datos)
</title>
<programlisting role="php">
<![CDATA[
<?php
$consulta = "SELECT id, name, inserted, size FROM products
WHERE size = '$tamaño'";
$resultado = odbc_exec($conexión, $consulta);
?>
]]>
</programlisting>
</example>
La parte estática de la consulta se puede combinar con otra sentencia
<literal>SELECT</literal> la cual revelará todas las contraseñas:
<informalexample>
<programlisting role="sql">
<![CDATA[
'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--
]]>
</programlisting>
</informalexample>
Si esta consulta (jugando con <literal>'</literal> y
<literal>--</literal>) fuera asignada a una de las variables utilizadas en
<varname>$consulta</varname>, despertaría a la consulta "monstruo".
</para>
<para>
Las sentecias UPDATE de SQL también son susceptibles a ataques. Estas consultas también
están amenazadas por el corte y la anteposición de una consulta completamente nueva.
El atacante podría juguetear con la cláusula <literal>SET</literal>, aunque en este
caso, debe poseer algo de información sobre los esquemas para manipular la consulta
con éxito. Esta información puede adquirirse examinando los nombres de las variables del formulario, o
sencillamente mediante la fuerza bruta. No hay muchas convenciones de nombres para
campos que almacenen contraseñas o nombres de usuarios.
<example>
<title>
Desde restablecer una contraseña ... hasta obtener más privilegios (cualquier servidor de bases de datos)
</title>
<programlisting role="php">
<![CDATA[
<?php
$consulta = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
]]>
</programlisting>
</example>
Pero un usuario malicioso podría enviar el valor
<literal>' or uid like'%admin%</literal> a <varname>$uid</varname> para
cambiar la contraseña del administrador, o simplemente cambiar <varname>$pwd</varname> a
<literal>jejeje', trusted=100, admin='yes</literal> para obtener más
privilegios. Entonces, la consulta se tornaría:
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
// $uid: ' or uid like '%admin%
$consulta = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";
// $pwd: jejeje', trusted=100, admin='yes
$consulta = "UPDATE usertable SET pwd='jejeje', trusted=100, admin='yes' WHERE
...;";
?>
]]>
</programlisting>
</informalexample>
</para>
<para>
Un ejemplo turbador de cómo se puede acceder a los comandos a nivel del sistema operativo
en algunos equipos anfitriones de bases de datos.
<example>
<title>Atacar el sistema operativo que hospeda la base de datos (Servidor MSSQL)</title>
<programlisting role="php">
<![CDATA[
<?php
$consulta = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$resultado = mssql_query($consulta);
?>
]]>
</programlisting>
</example>
Si un atacante envía el valor
<literal>a%' exec master..xp_cmdshell 'net user test testpass /ADD' --</literal>
a <varname>$prod</varname>, la <varname>$consulta</varname> será:
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
$consulta = "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD' --%'";
$resultado = mssql_query($consulta);
?>
]]>
</programlisting>
</informalexample>
El servidor MSSQL ejecuta la sentencia SQL en el lote incluyendo un comando
para añadir un usuario nuevo a la base de datos de cuentas local. Si esta aplicación
estuviera ejecutándose como <literal>sa</literal> y el servicio MSSQLSERVER se estuviera
ejecutando con los privilegios suficientes, el atacante ahora podría tener una cuenta
con la cual acceder a esta máquina.
</para>
<note>
<para>
Algunos de los ejemplos citados están vinculados a un servidor de bases de datos específico.
Esto no significa que un ataque similar sea imposible en otros productos.
Su servidor de base de datos también podría ser vulnerable de otra manera.
</para>
</note>
<para>
<mediaobject>
<alt>Un ejemplo comprobado de los problemas con respecto a las inyecciones de SQL</alt>
<imageobject>
<imagedata fileref="en/security/figures/xkcd-bobby-tables.png" format="PNG"/>
</imageobject>
</mediaobject>
Imagen cortesía de <link xlink:href="&url.xkcd;327">xkcd</link>
</para>
<sect2 xml:id="security.database.avoiding">
<title>Técnicas de evitación</title>
<simpara>
Pese a que pueda parecer obvio que un atacante debe tener al menos algún
conocimiento de arquitecturas de bases de datos para poder realizar un ataque
con éxito, la obtención de esta información suele ser muy sencilla. Por ejemplo,
cuando la base de datos forma parte de un software de código abierto o disponible
públicamente con una instalación predefinida, dicha información se encuentra
completamente libre y utilizable. Esta información podría haber sido divulgada
en proyectos de código cerrado (incluso si está codificada, ofuscada o compilada),
o incluso por el propio código mediante la visualización de mensajes de error.
Otros métodos incluyen el uso de nombres frecuentes de tablas y columnas. Por ejemplo,
un formulario de inicio de sesión que utiliza una tabla 'usuarios' con los nombres
de columna 'id', 'username', y 'password'.
</simpara>
<simpara>
Estos ataques se basan principalmente en explotar el código que no ha sido escrito
teniendo en cuenta la seguridad. Nunca se ha de confiar en ningún tipo de entrada, especialmente
la que viene del lado del cliente, aún cuando esta venga de un cuadro de selección,
un campo oculto o una cookie. El primer ejemplo muestra cómo una inofensiva consulta
puede causar desastres.
</simpara>
<itemizedlist>
<listitem>
<simpara>
Nunca se conecte como superusuario o como propietario de la base de datos.
Siempre utilice usuarios personalizados con privilegios muy limitados.
</simpara>
</listitem>
<listitem>
<simpara>
Emplee sentencias preparadas con variables vinculadas. Son proporcionadas por
<link linkend="pdo.prepared-statements">PDO</link>,
<link linkend="mysqli.quickstart.prepared-statements">MySQLi</link>
y otras bibliotecas.
</simpara>
</listitem>
<listitem>
<simpara>
Compruebe si la entrada proporcionada tiene el tipo de datos previsto. <acronym>PHP</acronym> tiene
un amplio rango de funciones para validar la entrada de datos, desde las más simples,
encontradas en <link linkend="ref.var">Funciones de variables</link> y en
<link linkend="ref.ctype">Funciones del tipo carácter</link>
(p.ej., <function>is_numeric</function>, <function>ctype_digit</function>
respectivamente), hasta el soporte para
<link linkend="ref.pcre">Expresiones regulares compatibles con Perl</link>.
</simpara>
</listitem>
<listitem>
<para>
Si la expresión espera una entrada numérica, considere verificar los datos
con la función <function>ctype_digit</function>, o silenciosamente cambie su tipo
utilizando <function>settype</function>, o emplee su representación numérica
por medio de <function>sprintf</function>.
<example>
<title>Una forma más segura de componer una consulta para paginación</title>
<programlisting role="php">
<![CDATA[
<?php
settype($índice, 'integer');
$consulta = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $índice;";
// Observe %d en el string de formato; el uso de %s podría no tener un resultado significativo
$consulta = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
$índice);
?>
]]>
</programlisting>
</example>
</para>
</listitem>
<listitem>
<simpara>
Si la capa de la base de datos no admite variables vinculadas,
entrecomille cada valor no numérico proporcionado por el usuario que sea pasado a la
base de datos con la función de escapado de cadenas específica de la base de datos (p.ej.
<function>mysql_real_escape_string</function>,
<function>sqlite_escape_string</function>, etc.).
Las funciones genéricas como <function>addslashes</function> son útiles solamente
en un entorno muy específico (p.ej., MySQL en un juego de caracteres monobyte
con <varname>NO_BACKSLASH_ESCAPES</varname> deshabilitada), por lo que es
mejor evitarlas.
</simpara>
</listitem>
<listitem>
<simpara>
Sea como sea, no muestre ninguna información específica de la base de datos,
especialmente sobre el esquema. Vea también <link
linkend="security.errors">Notificación de errores</link> y <link
linkend="ref.errorfunc">Manejo de errores y funciones de registro</link>.
</simpara>
</listitem>
<listitem>
<simpara>
Su pueden utilizar procedimientos almacenados y cursores previamente definidos para abstraer
el acceso a datos para que los usuarios no tengan acceso directo a las tablas o vistas, aunque
que esta solución tiene otros impactos.
</simpara>
</listitem>
</itemizedlist>
<simpara>
Además, se puede beneficiar del registro de consultas, ya sea dentro de un script
o mediante la base de datos en sí misma, si es que lo soporta. Obviamente, llevar un registro no
previene los intentos dañinos, aunque puede ser útil para realizar un seguimiento de las
aplicación que han sido eludidas. El registro no es útil por sí mismo, sino
por la información que contiene. Normalmente cuantos más detalles, mejor.
</simpara>
</sect2>
</sect1>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->