mirror of
https://github.com/php/doc-es.git
synced 2026-03-25 16:02:13 +01:00
git-svn-id: https://svn.php.net/repository/phpdoc/es/trunk@336634 c90b9560-bf6c-de11-be94-00142212c4b1
487 lines
21 KiB
XML
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
|
|
-->
|