Categories
PHP Quiz Seguridad

Quiz sobre validación de datos en PHP

El código mostrado a continuación, es una versión reducida de una falla de seguridad presente en una aplicación algo conocida.

Indiquen la falla, lo que se puede hacer con ésta, una forma de explotarlo y la solución que plantean al mismo:

php:

// demo.php
<?php

include './db.php';

error_reporting(0);

$tb_url    = $_POST['url'];
$title     = $_POST['title'];
$excerpt   = $_POST['excerpt'];

if (empty($title) || empty($tb_url) || empty($excerpt)) {
        die ('Invalid values');
}

$titlehtmlspecialchars( strip_tags( $title ) );
$title = (strlen($title) > 150) ? substr($title, 0, 150) . '...' : $title;
$excerpt = strip_tags($excerpt);
$excerpt = (strlen($excerpt) > 200) ? substr($excerpt, 0, 200) . '...' : $excerpt;

$contents=@file_get_contents($tb_url);
if(!$contents) {       
        die('The provided URL does not seem to work.');
}

$query = "INSERT INTO tabla (url, title, excerpt) VALUES ('%s', '%s', '%s')";

$db->query
        (
                sprintf (
                        $query,
                        $db->escape($tb_url),
                        $db->escape($title),
                        $db->escape($excerpt)
                        )
        );

?>

php:

// show.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
        <head>
                <title>Bug</title>
        </head>
        <body>

                <ul>
                <?php

                include './db.php';
                $items = $db->get_results('SELECT url, title, excerpt FROM tabla');

                foreach ($items as $tb) :
                        echo '<li><a href="'.$tb->url.'" title="'.$tb->excerpt.'">'.$tb->title.'</a></li>';               
                endforeach;

                ?>
                </ul>

        </body>
</html>

Para el acceso a datos se usa la clase ez_sql, el método escape en mi versión, contiene lo siguiente:

php:

function escape($string) {
        if (get_magic_quotes_gpc())
                $string = stripslashes($string);
        return mysql_real_escape_string( $string, $this->dbh );
}
Categories
PHP

Escribiendo código seguro – Más sobre comparación de tipos

Tenemos la siguiente porción de código:

PHP:

<?php
    function foo($respuesta) {
        if ($respuesta > 10) {
            return true;
        } else {
            return $respuesta;
        }
    }
    if (foo(11)) {
        echo "11 es mayor que 10<br />";
    }       
    if (foo(9)) {
        echo "9 es mucho mayor que 10<br />";
    }
?>
 

Lo que hace la función es comparar la variable $respuesta con 10, si es mayor que este entonces devuelve true, caso contrario devuelve el valor de $respuesta. Veamos la salida:

CODE:

11 es mayor que 10
9 es mucho mayor que 10
 

El error está en que PHP toma a cualquier número mayor que cero como un valor verdadero, por eso es que vemos esta salida incoherente.

La Solución

Igual que en la entrada anterior, es mejor usar comparación estricta de tipos, esto es:

PHP:

<?php
    function foo($respuesta) {
        if ($respuesta > 10) {
            return true;
        } else {
            return $respuesta;
        }
    }
    if (foo(11) === true) {
        echo "11 es mayor que 10<br />";
    }       
    if (foo(9) === true) {
        echo "9 es mucho mayor que 10<br />";
    }
?>
 

Ahora si la salida será correcta:

CODE:

11 es mayor que 10
 

Espero sus comentarios y duras, mañana seguiremos con más 😉

Referencias

Categories
PHP

Escribiendo código seguro – Comparación de Tipos

Cuando en PHP comparamos diferentes variables, pero que en cierto contexto son iguales, los tipos de datos se pierden. Por ejemplo un valor booleano verdadero es representado como 1 y el valor falso es representado como 0. Veamos lo que hace PHP con el siguiente ejemplo:

PHP:

<?php
$entero = 1;
$cadena = "1";
$booleano = true;

var_dump($entero == $cadena);
var_dump($cadena == $booleano);
var_dump($entero == $booleano);
?>
 

En los tres casos se espera "false", pero sin embargo:

CODE:

bool(true) bool(true) bool(true)
 

Otro ejemplo mucho peor:

PHP:

<?php
var_dump('1' == '1.');
?>
 
CODE:

bool(true)
 

En todos los ejemplos hemos usado un "comparador flexible" (==), este solo compara una igualdad. Esto puede traer consecuencias no deseadas en nuestra aplicación, como comenté en la entrada anterior.

La solución

Si queremos código seguro, debemos usar comparadores seguros. Para solucionar nuestro pequeño error, debemos usar un "comparador estricto" (===), este operador sólo dará verdadero si dos variables son idénticas.

PHP:

<?php
$entero = 1;
$cadena = "1";
$booleano = true;

var_dump($entero === $cadena);
var_dump($cadena === $booleano);
var_dump($entero === $booleano);
?>
 

Ahora si la salida será correcta:

CODE:

bool(false) bool(false) bool(false)
 

Una pequeña reflexión: "Así vemos que por medios pequeños, podemos hacer grandes cosas"

Referencias

Categories
Varios

¿Compuntoes?

Gracias al post que publicó Braulio sobre compuntoes, recién me entero que es un nuevo concurso de posicionamiento, que al parecer ha generado bastante controversia.

Al intentar indagar un poco más en Technorati -que rara vez lo uso- para ver los últimos comentarios sobre el fucking término compuntoes, llego al sitio de uno de los participantes, quien, por el título y el contenido de su última entrada, está realmente descontento por el supuesto tercer lugar que ocupa este blog en la siguiente búsqueda. Lo curioso de esto, es que tiene una linda definición de este blog:

El tercer puesto es un blog cualquiera ahí, salido de las nada con 34 lectores en el feed.

No entiendo porque se preocupa por un blog cualquiera con tan pocos lectores... 😛

En fin, suerte a todos participantes de este concurso.

Categories
.NET

Implementación de un pequeño Servidor Web

El siguiente código, originalmente publicado por Eric Carter, muestra la implementación de un pequeño "Servidor Web" con C#, para hacerlo funcionar necesitarán del .NET Framework 2.0 (podría correr en versiones anteriores haciendo ligeras modificaciones al código).

csharp:

using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
using System.Collections.Specialized;

namespace http
{
    public class FakeWebServer
    {
        private const string URL_REPLACE = "{URL}";
        private static readonly Regex urlRegex = new Regex(@"^(GET|POST) /(.*?) (HTTP[^\s]+)",
                                        RegexOptions.Compiled | RegexOptions.IgnoreCase);

        // Almacena las URLs verdaderas y falsas
        public static readonly Dictionary<string, string> FakeUrls;

        static FakeWebServer()
        {
            FakeUrls = new Dictionary<string, string>();
        }

        private TcpListener listener;
        string contents;

        public FakeWebServer(int port, string responseContents)
        {
            this.contents = responseContents;

            // 'Escuchar' en cualquier dirección
            listener = new TcpListener(IPAddress.Any, port);
            listener.Start();

            Thread t = new Thread(delegate()
            {
                AcceptClients();
            });
            t.Start();
        }
        public void AcceptClients()
        {
            while (true)
            {
                using (TcpClient client = listener.AcceptTcpClient())
                {
                    if (client.Connected) // Nuevo cliente
                    {
                        // Leer los datos enviados
                        NetworkStream stream = client.GetStream();
                        byte[] data = new byte[1024];

                        stream.Read(data, 0, data.Length);

                        string request = Encoding.UTF8.GetString(data);

                        // Sólo tomar en cuenta los datos presentes en el QueryString

                        // Obtener la versión del protocolo y la URL del 'Request'
                        MatchCollection matches = urlRegex.Matches(request);

                        string qs = matches[0].Groups[2].Value.TrimStart('?');
                        NameValueCollection paramArray = HttpUtility.ParseQueryString(qs);

                        foreach (string key in paramArray.AllKeys)
                        {
                            if (FakeUrls.TryGetValue(paramArray[key], out qs))
                                break;
                        }                       
                       
                        System.Diagnostics.Debug.WriteLine("Query String: " + matches[0].Groups[2].Value);

                        // Reemplazar las URLs
                        if (!string.IsNullOrEmpty(qs) && !string.IsNullOrEmpty(contents))
                            contents = contents.Replace(URL_REPLACE, qs);

                        // Enviar las cabeceras necesarias y el contenido
                        SendHeaders(matches[0].Groups[3].Value, null, contents.Length, "200 OK", client);
                        SendToBrowser(Encoding.UTF8.GetBytes(contents), client);
                    }
                }
            }
        }

        public void SendHeaders(string httpVersion, string mimeHeader, int totalBytes, string statusCode, TcpClient tcpClient)
        {
            StringBuilder responseBuilder = new StringBuilder();

            if (string.IsNullOrEmpty(mimeHeader))
                mimeHeader = "text/html";

            responseBuilder.Append(httpVersion);
            responseBuilder.Append(' ');
            responseBuilder.AppendLine(statusCode);
            responseBuilder.AppendLine("Server: Fake Web Server");
            responseBuilder.Append("Content-Type: ");
            responseBuilder.AppendLine(mimeHeader);
            responseBuilder.AppendLine("Accept-Ranges: bytes");
            responseBuilder.Append("Content-Length: ");
            responseBuilder.AppendLine(totalBytes.ToString());
            responseBuilder.AppendLine("");

            Byte[] bSendData = Encoding.UTF8.GetBytes(responseBuilder.ToString());
            SendToBrowser(bSendData, tcpClient);

            System.Diagnostics.Debug.WriteLine("Total Bytes : " + totalBytes.ToString());
        }

        public void SendToBrowser(Byte[] data, TcpClient tcpClient)
        {
            if (tcpClient.Connected)
            {
                NetworkStream stream = tcpClient.GetStream();

                stream.Write(data, 0, data.Length);
                stream.Flush();
            }
            else
            {
                System.Diagnostics.Debug.WriteLine("Connection Dropped....");
            }

        }
    }
}

Como habrán podido observar el "Servidor Web", entrega tontamente casi el mismo contenido, sólo reemplaza cada aparición de {URL} en la respuesta en base a los parámetros solicitados.

En una siguiente entrada explicaré la valiosa ayuda que presta ese pedazo de código, en la explotación de otro bug de una aplicación ya algo conocida por este blog.