Categories
ASP.NET

ASP.NET, ReportViewer y Firefox

Firefox es, desde que pude pagar por mis propios medios una conexión ADSL --a mediados del 2004 aproximadamente 🙂 , el navegador que uso casi para todo; aunque inicialmente me subí al coche sólo por ser Software Libre, ahora sin duda es una herramienta que no puede faltar en mi entorno de desarrollo.

Retomando el tema de la entrada, puesto que generalmente las cosas que salen de Microsoft sólo funcionan bien en Internet Explorer, el control ReportViewer no genera el HTML/CSS adecuado para que los reportes se muestren correctamente en Firefox:

Salida del control ReportViewer en Firefox

Luego de revisar un poco la estructura del documento que genera ese control (gracias a Firebug), pude preparar un pequeño código para que al menos se muestre el reporte completo, las líneas que importan están resaltadas:

html:

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<%@ Register Assembly="Microsoft.ReportViewer.WebForms, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
    Namespace="Microsoft.Reporting.WebForms" TagPrefix="rsweb" %>

<!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 runat="server">
    <title>Untitled Page</title>
   
    <script type="text/javascript">   

    window.onload=function() {
        window.frames['ReportFrame<%= ReportViewer1.ClientID %>'].
            window.frames['report'].
                document.getElementById('oReportCell').
                    style.width='100%';
    }   

    </script>
   
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Button ID="cmdExportar" runat="server" OnClick="cmdExportar_Click" Text="Button" />
        <rsweb:ReportViewer ID="ReportViewer1" runat="server" Font-Names="Verdana" Font-Size="8pt"
            Height="400px" Width="100%">

            <LocalReport ReportPath="Productos.rdlc">
            </LocalReport>
        </rsweb:ReportViewer>
        </div>
       
    </form>
</body>
</html>

Es evidente que este hack sólo funcionará si el reporte está en el mismo servidor que la aplicación Web, esto debido a las restricciones de seguridad que imponen los navegadores sobre los i/frames.

No sé si a alguien más aparte de mi le vaya a servir esta entrada, sólo lo comento por el desazón que tuve durante algunas jornadas de trabajo 😉

Categories
Seguridad Sql Injection Web WordPress

Inyección de SQL en WordPress

Luego de que alguien hiciera eco sobre un falso problema de inyección de SQL en WordPress 2.3.1, esta vez han publicado detalles de un bug que permite realizar este tipo de ataques en las ramas 2.2 y 2.3 (podría afectar a versiones anteriores también). Antes de pegar un grito al cielo y maldecir a los programadores de WordPress, vale aclarar que este bug sólo se puede reproducir siempre y cuando la codificación de la base de datos sea SJIS, BIG5 o GBK.

El problema radica en que en los juegos de caracteres de ancho variable mencionados, es posible que a partir de secuencias de caracteres no válidas y luego de aplicar la función addslashes, se pueda realizar ataques de inyección de SQL. Por ejemplo en la prueba de concepto del mencionado bug envían la secuencia 0xb327 (caracter multi-byte no válido en Big5) que luego de aplicarle la función addslashes la cadena resultante será 0xb35c27 (notar que el caracter \ = 0x5c se agregó antes de la comilla simple ' = 0x27), sin embargo en esta codificación la secuencia 0xb35c (許) es un caracter multi-byte válido por lo que en realidad la cadena resultante tendría la comilla simple sin escapar (許').

Dado que WordPress cambia la codificación de la conexión con SET NAMES 'GBK' (que es lo que hace cuando se especifica un valor para DB_CHARSET en el archivo de configuración), este problema de seguridad tendrá los mismos efectos aún usando la función mysql_real_escape_string.

Actualización: He subido un ejemplo que ilustra el problema descrito. El código de ese ejemplo es el siguiente por si quieren hacer pruebas:

php:

<?php
header('Content-Type: text/plain; charset=Big5');

$login = chr(0xB3).chr(0x27) . ' UNION ALL SELECT * FROM foo /*';
if ( isset($_GET['login']) )
        $login = stripslashes($_GET['login']);

$sql = "SELECT * FROM wp_posts WHERE login = '%s'\n";

echo sprintf($sql, addslashes($login));

mysql_connect('localhost', 'tests', '1234');
mysql_query('SET NAMES Big5');

echo sprintf($sql, mysql_real_escape_string($login));

mysql_close();
?>

Lectura recomendada

Categories
Frases Miniposts

Javascript doesn’t suck, you do!

Javascript doesn’t suck, you do!

Justice Gray.

Categories
PHP WordPress

Tip: Optimización de consultas SQL en plugins de WordPress

Durante el tiempo que no hubo actividad alguna en este blog y por algunos trabajos que me encomendaron relacionados al desarrollo de plugins y modificación del código de WordPress, he visto algo que se repite casi en todos los plugins que he visto hasta el momento: usan varias consultas pequeñas cuando sólo una puede hacer el trabajo (no tengo idea de porqué hacen las cosas de ese modo).

php:

$a = query("select ID from tabla1 ...");
$b = array();
foreach($a as $v) {
        $b[] = query("select ... from tabla2 where ID = $v");
}

El problema del código mostrado es que se hacen consultas innecesarias a la base de datos cuando en este caso un simple JOIN serviría para el mismo propósito.

Como ejemplo voy a poner dos plugins que acompañaron a este blog desde hace más de un año y por los que el número de consultas SQL aunmentaba en 55 para generar la página principa. Éstos son:

  • Real Fast Latest Comments: En este caso el plugin realiza una consulta para obtener las entradas que recibieron comentarios recientemente y luego itera para mostrar los datos devueltos:

    php:

    function rflc_show_comments($comment_limit = 5, $show_trackbacks = false) {
            global $wpdb;

            if(!$show_trackbacks) {
                    $activity = $wpdb->get_results("SELECT $wpdb->comments.comment_date, $wpdb->comments.comment_author,
                                                    $wpdb->comments.comment_ID, $wpdb->posts.post_title,
                                                    $wpdb->posts.ID FROM $wpdb->comments INNER JOIN
                                                    $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID WHERE
                                                    $wpdb->comments.comment_approved = '1' AND $wpdb->comments.comment_type
                                                    NOT LIKE '%back%' ORDER BY $wpdb->comments.comment_date DESC
                                                    LIMIT $comment_limit");
            } else {
                    $activity = $wpdb->get_results("SELECT $wpdb->comments.comment_date, $wpdb->comments.comment_author,
                                                    $wpdb->comments.comment_ID, $wpdb->posts.post_title,
                                                    $wpdb->posts.ID FROM $wpdb->comments INNER JOIN
                                                    $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID WHERE
                                                    $wpdb->comments.comment_approved = '1'
                                                    ORDER BY $wpdb->comments.comment_date DESC
                                                    LIMIT $comment_limit");
            }
           
            if($activity) {
                    echo '<ul>';
                    foreach($activity as $comment) {

                            echo '<li>'.wp_specialchars($comment->comment_author).' en: <a href="'. get_permalink($comment->ID) .'#comment-'. $comment->comment_ID .'">'. wp_specialchars($comment->post_title) .'</a></li>' . "\n";

                    }
                    echo '</ul>';
            }
    }

    El problema en este caso es que para mostrar el enlace (permalink) de una entrada, el plugin invoca en cada iteración a la función get_permalink, la misma que hace una nueva consulta (o varias dependiendo de la estructura de permalinks) para recuperar la entrada y formatear el enlace, esto pasa siempre y cuando el argumento pasado no sea un objeto o la entrada ya se encuentre en la caché de objetos.

  • Related Posts: El problema es el mismo que se comenta en el anterior, a continuación muestro la parte relevante:

    php:

    $sql = "SELECT ID, post_title, post_content,"
             . "MATCH (post_name, post_content) "
             . "AGAINST ('$terms') AS score "
             . "FROM $wpdb->posts WHERE "
             . "MATCH (post_name, post_content) "
             . "AGAINST ('$terms') "
             . "AND post_date <= '$now' "
             . "AND (post_status IN ( 'publish',  'static' ) && ID != '$post->ID') ";
    if ($show_pass_post=='false') { $sql .= "AND post_password ='' "; }
    $sql .= "ORDER BY score DESC LIMIT $limit";
    $results = $wpdb->get_results($sql);
    $output = '';
    if ($results) {
            foreach ($results as $result) {
                    $title = stripslashes(apply_filters('the_title', $result->post_title));

                    $permalink = get_permalink($result->ID);

                    $post_content = strip_tags($result->post_content);
                    $post_content = stripslashes($post_content);
                    $output .= $before_title .'<a href="'. $permalink .'" rel="bookmark" title="Permanent Link: ' . $title . '">' . $title . '</a>' . $after_title;

                    if ($show_excerpt=='true') {
                            $words=split(" ",$post_content);
                            $post_strip = join(" ", array_slice($words,0,$len));
                            $output .= $before_post . $post_strip . $after_post;
                    }
            }
            echo $output;
    }

Para evitar este comportamiento pero con algunas posibles consecuencias no deseadas**, se debe recuperar también el campo post_name en la primera consulta de ambos casos y a continuación invocar a la función get_permalink con un objeto como parámetro -- get_permalink($comment); y get_permalink($result); respectivamente.

**: Al usar la función get_permalink con un objeto como parámetro, éste se almacena en el caché de objetos y cualquier función que haga uso de get_post puede mostrar entradas "incompletas". Una forma de evitar esto es recuperar todos los campos de la tabla posts o quitar la entrada almacenada en cache luego de invocar a la función get_permalink.

Categories
ASP.NET

ASP.NET MVC

Siguiendo con esta serie de entradas cortas, acaba de salir ASP.NET 3.5 Extensions Preview, que entre las novedades más importantes incluye la primera versión para el público del Framework MVC desarrollado por Microsoft.

Plantilla para crear un proyecto MVC
Estructura de la aplicación MVC

Habrá que ver que tan bueno es esta implementación del patrón MVC en comparación a otras que ya llevan algo de tiempo en esto como MonoRail y Spring.NET. Aunque con respecto a este tema, el equipo que está tras el desarrollo de MonoRail ya expresó su posición:

Regarding the Microsoft MVC

To The .Net Community,

You are probably wondering how the recently announced Microsoft MVC project will compete with Castle's MonoRail.

We think that any attempt to offer more productive tools, better testability and better separation of concerns is valuable, no matter who is the author. We are certainly pleased to see that Microsoft is delivering something that allows a more agile and productive type of web software development.

We also believe that MonoRail has been providing the same thing for the past two and half years, and will continue to do so. We're grateful that MS has chosen to offer integration points for Monorail and the Castle stack and as soon as it's available we will be working to integrate it with the rest of our projects.

Is MS' MVC better? Worse? Only once we have used both will we be able to tell.