[PHP] Si no estas seguro que tu codigo es invulnerable ante injections, LEEME

Colapsar
X
 
  • Tiempo
  • Mostrar
Limpiar Todo
nuevos mensajes
  • judío ginger daywalker petiso
    Pelotudo
    • 14-03-2005
    • 16758

    #1

    [PHP] Si no estas seguro que tu codigo es invulnerable ante injections, LEEME

    El Motivo
    Bueno, el motivo de escribir esto es que estube leyendo un par de los threads de PHP de cami y vi que nadie se detubo a opinar sobre los agujeros de seguridad en el 99% (diria 100% pero no lei todo el codigo posteado) del codigo...

    El Problema
    Me estoy refiriendo en particular a los errores de injeccion, tanto SQL Injections como Cross-site scripting (XSS para los amigos).

    Primero, una descripcion con ejemplos sacados de sus ejemplos:
    SQL Injection
    esta vulnerabilidad consiste en colocar valores levantados directamente de un formulario en queries SQL. De este modo, un usuario podria ingresar codigo SQL en el campo, que al insertarse directamente en el querry le permitiria ejecutar practicamente lo que quiera contra la DB.

    Ejemplo de codigo vulnerable (http://www.egelforum.net/forum/showp...7&postcount=12, cortesia de Sam Fisher):
    Código PHP:
    $query = sprintf("delete from orcos where nombre='%s'",$_POST["nombre"]);
    mysql_query($query); 
    
    se ve claramente que el campo nombre del formulario esta insertado directamente en el querry string. bastaria que ingresara como nombre "'; DROP TABLE orcos; SELECT * FROM otra_tabla WHERE algun_campo = '" para que nos llevaramos una desagradable sorpresa al mirar el sitio nuevamente... (y si piensan "pero si no conoce otra_tabla y algun_campo no hace nada", estan muy equivocados. he visto flacos en seminarios de seguridad mandarse cada SQL Injection que sinceramente le hacen a uno no subestimar jamas este tipo de vulnerabilidades)

    Cross-site Scripting
    Esta vulnerabilidad consiste en escribir en la pagina web contenido levantado directamente de la DB, que a su vez tiene que poder ser dado de alta por los usuarios. De este modo, un usuario podria ingresar como valor de un campo "<script language=JavaScript>alert('Hola, tu pagina ha sido hackeada por TuViejaEnTanga TEAM!');</script>", que al momento de ser levantado de la DB y escrito en pantalla va a causar que el el codigo HTML ingresado se imprima como tal, ejecutandose en el navegador del cliente.

    Ejemplo de codigo vulnerable (http://www.egelforum.net/forum/showp...5&postcount=13, cortesia de CcCamikaze):
    Código PHP:
    while ($fila = mysql_fetch_object($rs)){
      echo <<< HTML
      <tr bgcolor="#76AEC9">
        <td height="8" align="center" valign="middle" bgcolor="#C2DBE7">
        <font size="2" face="Arial, Helvetica, sans-serif">$fila->nombre</font></td>
        <td height="8" align="center" valign="middle" bgcolor="#C2DBE7">
        <font size="2" face="Arial, Helvetica, sans-serif">$fila->clan</font></td>
        <td height="8" align="center" valign="middle" bgcolor="#C2DBE7">
        <font size="2" face="Arial, Helvetica, sans-serif">$fila->lugar</font></td>
        <td height="8" align="center" valign="middle" bgcolor="#C2DBE7">
        <font size="2" face="Arial, Helvetica, sans-serif">$fila->edad</font></td>
      </tr>
    HTML;
    } 
    
    como ven, los campos nombre, clan, lugar y edad son levantados directamente del recordset e impresos en pantalla, haciendo posible el ataque.

    La Solucion
    Espero que para este momento ya se hayan convencido de que esto ES un problema serio de seguridad y que si bien quiza en sus proyectos actuales no sea un tema de vida o muerte, es algo que en el mercado laboral puede terminar siendo bastante contraproducente...
    Aparte, la solucion es lo suficientemente facil como para que valga la pena acostumbrarse a programar siempre asi y dormir tranquilo por las noches.

    SQL Injection
    Aqui tenemos dos tipos posibles de causas. La primera es la del ejemplo que cite: hay un '$cadena' en el querry SQL. Basta impedir que el usuario ingrese comillas en $cadena para asegurarnos de que no puedan usurpar el querry string. Pero hay un caso mas grave aun: cuando se inserta $cadena directamente en el querry string, por ejemplo "... WHERE orco.edad = $edad". Resolver esta vulnerabilidad requiere tres cuidados:
    Primero, asegurarse de que todo lo que vayamos a poner en un querry string que sea una cadena de caracteres este entre comillas simples ("nombre = '$nombre'" y no "nombre = $nombre").
    Segundo, asegurarse que los valores levantados de un form SON DEL TIPO QUE ESPERO QUE SEAN. aca no importa que el submit del form tenga una validacion x javascript que controle todo. al momento de protejerte contra fallas de seguridad, SIEMPRE ASUMI QUE LOS SUBMITS VIENEN DE PAGINAS QUE EL ATACANTE ARMO, COPIANDO EL FORM Y SU ACTION PERO QUITANDO TODA LA VALIDACION PREVIA QUE PUEDAS HABER PUESTO. esto es molesto pero puede formalizarse y llevarse a algo metodico y rapido/simple de implementar: por cada parametro $_POST o $_GET que vayas a insertar en un querry SQL, haces lo siguiente (este codigo es un ejemplo, siguiendo con el $edad de antes, donde el valor va a insertarse en un campo del tipo entero):
    Opcion 1: el valor x defecto del campo es 0
    Código PHP:
    $p_edad = (int) $_POST['edad']; 
    
    El casteo de cadena a numeros en PHP empieza a leer caracter x caracter de la cadena hasta leer algo que ya no pueda ser interpretado como un numero, y toma este valor y lo pasa a lo que corresponda (int, float, etc.). si la cadena no empieza con algo numericamente valido, devuelve 0.
    por ejemplo, si el valor de $_POST['edad'] era "12 monos", devuelve el entero 12. si era "monos", devuelve 0. y si el valor numerico resultara ser de punto flotante, se va a redondear hacia el 0 (osea, "1.2 monos" daria 1 y "-1.2 monos" daria -1. coloquialmente, se truncan los decimales).
    Opcion 2: el valor x defecto del campo es otro, o tengo que parar y avisarle al usuario en caso de que haya ingresado un dato invalido.
    Código PHP:
    if (is_int($_POST['edad'])) {
       $p_edad = $_POST['edad'];
       if (!es_edad_valida($p_edad)) $p_edad = -1;
    } else $p_edad = -1;
    // ahora, si $p_edad = -1, o la edad ingresada no era un valor numerico, o no estaba dentro del rango aceptado por es_edad_valida, nuestra funcion de validacion interna, por lo que podemos decidir que accion tomar, sea asignandole un valor x defecto a $p_edad o mostrando un mensaje de error apropiado. 
    
    Ahora ya sabemos que lo que no es cadena esta protejido y que las cadenas estan siempre entre comillas en el querry string, por lo que nos resta impedir que el usuario ingrese comillas al querry. La forma fea es validar que no hayan comillas en la cadena, pero puede resultar imposible (un campo de apellidos deberia permitirle al usuario irlandes ingresar su O'Connor sin problemas). Por suerte de la misma manera en que ciertos caracteres pueden escaparse en las cadenas de PHP (por ejemplo, "\"", que es una cadena conteniendo un unico caracter, el de las comillas), lo mismo ocurre con SQL. y tenemos una hermosa funcion que se encarga de hacer esto por nosotros. Simplemente adaptamos la rutina anterior para que en el caso de un campo string quede asi:
    Código PHP:
    $p_nombre = mysql_real_escape_string($_POST['nombre'], $mysql_connection);
    // o simplemente $p_nombre = mysql_real_escape_string($_POST['nombre']); 
    
    ahora $p_nombre es una cadena que al insertarla en un querry string (SIEMPRE Y CUANDO LA INSERTEMOS ENTRE COMILLAS) estamos seguros que no va a alterar el comportamiento del querry. ojo que dependiendo del tipo de validacion que requiramos puede ser necesario hacerla ANTES de ejecutar mysql_real_escape_string. por ejemplo, si estamos validando por longitud, ya que la cadena segura contiene una \ extra por cada caracter escapado. y al momento de comparar valores de una cadena ya escapada, hay que escapar tambien el valor de comparacion. es decir que si quiero testear que no se registre ningun O'Connor, deberia comparar $p_nombre con mysql_real_escape_string("O'Connor") (o, a mano, con "O\\'Connor").

    Con estos tres recaudos en mente, evitamos por completo los ataques de este tipo.

    Cross-site Scripting
    Nuevamente voy a optar por una solucion que no limite lo que el usuario pueda escribir (ya que una alternativa seria rechazar cadenas del form que contengan > o <, y pedirle al user que ingrese otra cosa).
    Aqui la solucion requiere conocimientos de HTML antes que de PHP. Como escribirian en una pagina web el caracter > o < sin que te lo tome como un tag mal abierto/cerrado? Usando entidades! en este caso en particular, &gt; y &lt;. Por suerte nuevamente PHP nos brinda una funcion para hacer esta tarea por nosotros:
    Código PHP:
    while ($fila = mysql_fetch_object($rs)){
       $f_nombre = htmlspecialchars($fila->nombre);
       echo <<< HTML
       <tr bgcolor="#76AEC9">
          <td height="8" align="center" valign="middle" bgcolor="#C2DBE7"><font size="2" face="Arial, Helvetica, sans-serif">$f_nombre</font></td>
       </tr>
    HTML;
    } 
    
    Basta colocar esta funcion en todos los lugares donde estemos escribiendo en pantalla un valor que no podamos controlar (no tiene porque ser de una DB, puede ser un valor recivido de un formulario, o peor aun: uno que nosotros mismos nos mandamos hardcodeado!! por ejemplo: supongamos que para mostrar mensajes de error tenemos un error.php que recive por GET una cadena descriptiva para mostrar. asi mostrar errores es tan facil como hacer un redirect a, por ejemplo, "error.php?desc=Clave%20incorrecta". en este caso el valor de desc siempre lo hardcodeamos nosotros, pero alguien que se percate de que estamos escribiendolo directamente en pantalla podria aprovecharlo para realizar un ataque XSS) para evitar esta vulnerabilidad.

    La conclusion
    Al momento de validar los datos, hay que ser muy ciudadosos en evitar que valores dise&#241;ados malignamente puedan afectar el funcionamiento del sistema. Espero que las descripciones y ejemplos de estas dos vulnerabilidades, asi como las formas que sujeri para evitarlas, les sirvan de ayuda para ver en que mundo se estan metiendo, y para que tengan claro que hay que estar al dia con el enemigo, y conocer sus metodos para poder protejernos. Tambien espero que les haya dado un pantallazo de como piensan los "malos", y a no subestimar el potencial danino de un usuario. Asumir que los datos los valido otro modulo es un potencial error de seguridad, nunca hay que confiar en lo que quedo atras. Hay que suponer que todas las demas barreras fueron rotas y la que estamos programando va a ser la ultima de ellas, y que es nuestra responsabilidad el desarollar un sistema robusto y seguro.

    A continuacion les dejo las paginas del help pertinentes a las funciones que estube usando. Leanlas asi saben bien QUE es lo que hacen y que parametros les pueden pasar:
    is_*
    mysql_real_escape_string
    htmlspecialchars
    htmlentities
    Esta ultima no la use. Es una version mas fuerte de htmlspecialchars ya que pasa a entidades TODO lo que pueda ser pasado a entidades (por ejemplo, si el usuario ingreso "hab&#237;a", se va a escribir "hab&iacute;a", permitiendole al navegador encargarse de los charsets correctamente)

    K
    Editado por última vez por judío ginger daywalker petiso; 06-03-2007, 01:18 AM.
  • Analrapist
    Blackbolt
    • 07-05-2004
    • 18929

    #2
    Muy buen thread, osea, al menos no posteas solamente los problemas, traes soluciones
    Le problème de nos jours c'est que le futur n'est plus ce qu'il était.

    Comentario

    • MoaLaiSkirulais
      ^^(V)^^
      • 08-07-2003
      • 23268

      #3


      otra alternativa al sqlInjection es la puntillosa [] administracion de permisos sobres la tablas...

      aunke hay casos de injection q tb modifican permisos... sobre todo si se usa una cuenta admin para las consultas web.....

      para restringir selects a veces no alcanza con permisos, ya que estos son sobre columnas y no sobre filas.... para restringir sobre filas se puede encapsular el acceso a las tablas vias procedures (mySql 5.1+)

      o bien con algunas tecnicas de intercambio de credenciales (pass+user) [Esto vale mas para insert/update sin uso de procedures]

      ...

      Comentario

      • judío ginger daywalker petiso
        Pelotudo
        • 14-03-2005
        • 16758

        #4
        nono. esas medidas son insuficientes. osea, si, te salvas de que te dropeen una tabla o hagan cambios fuleros, pero si no controlas lo que el usuario ingresa, pueden romperte un login facilmente, o simplemente dedicarse a mirar donde no deberian...

        igual, si vamos a ponernos "serios", el user que se conecta a la DB dde el php no deberia tener mas permisos que los absolutamente necesarios, lo cual en la mayoria de los sistemas vendria a ser SELECT, UPDATE, INSERT y DELETE en algunas tablas, y nada mas. asi que un DROP TABLE nunca deberia ser posible de ejecutar si los permisos estan bien seteados.

        los procedures tambien los ejecutas con una querry string. si tienen parametros y no estas controlando, te pueden inyectar codigo dps del proc. tendrias que validar que NADA excepto el procedure pueda hacer NADA sobre la tabla. y aun asi, si dentro del stored procedure estas usando un valor de un parametro sin cuidado, quiza no haces mas que sacar el bug de un lugar y meterlo en otro mas rebuscado y dificil de controlar (a menos que tu T-SQL o similar te ofrezca las facilidades de escapado que el PHP te da)

        K
        Editado por última vez por judío ginger daywalker petiso; 06-03-2007, 01:41 AM.

        Comentario

        • Nico'OuU!
          Te estoy mirando
          • 10-04-2005
          • 6540

          #5
          Bueno como este tema es muy importante propongo que se haga stick y que cada persona que sepa estos temas lo ponga aca, tambien links no se....

          [.ιl.lιllι]

          Comentario

          • MoaLaiSkirulais
            ^^(V)^^
            • 08-07-2003
            • 23268

            #6
            s&#237; en caso de usar SP's hay q validar dentro los parametros, sino tambien estas en la misma...

            el tema del insert/update/delete es como limitar a q cada user solo modifique su info, y los permisos son sobre las columnas... [sin usar sp's claro]

            ahi tambien empieza a jugar si te conectas a la DB via un unico login manteniendo una tabla paralela de users/pass (encriptada y toda la bola) o cada user tiene su propia cuenta correspondiente a un login real de la DB.... en este ultimo caso es un poco (y casi nada) mas sencillo....

            ::

            Comentario

            • judío ginger daywalker petiso
              Pelotudo
              • 14-03-2005
              • 16758

              #7
              mi corta experiencia laboral me dio a entender que la segunda opcion que propones, si bien es la mejor en el sentido de encapsular privilegios solo donde importan, practicamente nunca es implementada, en preferencia por la primera... con mas razon si hay mas de una DB en el mismo servidor...

              K

              Comentario

              • MoaLaiSkirulais
                ^^(V)^^
                • 08-07-2003
                • 23268

                #8
                si el escenario 1 es el mas usado, de hecho las app "grandes" de gestion permiten elegir q esquema usar -1 o 2 capas- previendo el costo de performance/mantenimiento q implican multiples logins...

                bueno en ese escenario una manera de conocer la identidad autorizada de quien "updatea" info es a traves de un loginId, q seria un campo adicional en las tablas q se quieran proteger...

                el loginId no admitiria valor null y via trigger se verifica q exista en una tabla de logins (q exista y este activo)... el loginId se provee desde la web via variables de session (entonces aca no hay injection ni nada, en todo caso hay q esmerarse solo con el formulario de logon)....

                por tabla de logins me refiero a una tabla transaccional similar a esta:
                login (idLogin, idUsuario, perfil, fechaHoraLogon, fechaHoraLogoff, estado, etc, etc)

                del resto se encarga el trigger y las business rules pertinentes....



                Supongo q este modelo existe en la teoria yo lo hice asi y por ahora va ;/

                Comentario

                • Piluex
                  Away
                  • 10-10-2004
                  • 3038

                  #9
                  El otro d&#237;a estaba probando algo con php5 y mysql nosecuanto y automaticamente escapeaba todo, sin que yo haga nada, osea, yo estaba haciendo una boludez y como qued&#243; fea mand&#233; "; TRUNCATE TABLE pepe" y nada, voy a phpmyadmin y veo todo escapeado.

                  Comentario

                  • Piluex
                    Away
                    • 10-10-2004
                    • 3038

                    #10
                    Ah&#237; lo volv&#237; a probar, lo &#250;nico que no cuida es <>, porque tranquilamente podr&#237;a ser una entrada de un editor html para una noticia, por ejemplo.

                    Comentario

                    • Piluex
                      Away
                      • 10-10-2004
                      • 3038

                      #11
                      Osea
                      Código PHP:
                      ... '$_POST[desc]' ... 
                      
                      Asi tomado directo del formulario, en mis php (??) todo lo que sea ; o ' o etc lo escapea solo (??). No puedo hackearme =( (??).
                      Editado por última vez por Piluex; 06-03-2007, 12:01 PM.

                      Comentario

                      • judío ginger daywalker petiso
                        Pelotudo
                        • 14-03-2005
                        • 16758

                        #12
                        che, pero es basura eso...
                        y si quiero programarme algo tipo phpMyAdmin, donde el usuario SI PUEDE ingresar querrys SQL dde un form y TIENEN que ejecutarse??

                        K

                        Comentario

                        • Sam Fisher
                          Egelito activo
                          • 24-04-2005
                          • 389

                          #13
                          Ven&#237;a a defender mi c&#243;digo... cuando empec&#233; con php+mysql, le mostr&#233; el mini-foro que hab&#237;a hecho a un amigo, y me pregunt&#243;: "che, es vulnerable a sql injection?" le dije: "no se... "y prob&#243; ah&#237;... no pas&#243; nada =/

                          Comentario

                          • Piluex
                            Away
                            • 10-10-2004
                            • 3038

                            #14
                            No se (??), es raro igual, pude ingresar en un campo algo como:
                            'pija pija' jejeje ; hola (chau).
                            Y qued&#243; talcual. Bleh quiz&#225; tenga que ver por como armo yo las querys que hago una variable $query y voy agregando todo onda
                            $query="SELECT a,b,c FROM d WHERE";
                            $query.=" a='" . $_POST[aa] . "'";

                            (eso fue un ejemplo muy ilustrativo, en realidad voy haciendo eso para que sea m&#225;s f&#225;cil de leer, modificar y debuguear).

                            Cuando tenga tiempo investigo m&#225;s el tema ese-
                            Editado por última vez por Piluex; 06-03-2007, 02:21 PM.

                            Comentario

                            • judío ginger daywalker petiso
                              Pelotudo
                              • 14-03-2005
                              • 16758

                              #15
                              no es raro. en el bondi de vuelta del laburo me acorde lo que estaba pasando.
                              tenes encendido el parametro "magic_quotes_gpc", lo que ejecuta addslashes automaticamente para todos los elementos de las superglobals $_GET, $_POST y $_COOKIES.

                              a menos que chekees a mano en tu script el valor de esa setting, estas generando una vulnerabilidad si "confias" en que esta encendido para olvidarte de escapar los valores...
                              probablemente Sam Fisher tambien la tenga encendida (ya que x defecto esta activado), y eso justifica que no haya pasado nada...





                              K

                              Comentario

                              Trabajando...