HackIt! 2014 _ Level 4

«Bitcode. Esta vez te toca investigar lo profundo de Python.» Para ello, nos pasan un fichero tar.gz que contiene un archivo main.py (texto plano) y otro routines.pyc (binario).

# main.py
from routines import chk_serial
 
_in = raw_input("Do you feel lucky?: ")
print chk_serial(_in)

Así que lo que queda por hacer es entender la función chk_serial… que está en el binario. Hay que descompilar el routines.pyc y analizar… Vamos allá. Lo primero es bajarse e instalar la aplicación uncompyle:

git clone https://github.com/gstarnberger/uncompyle.git
cd uncompyle/
sudo ./setup.py install

descompilar:

uncompyler.py routines.pyc > routines.py

y analizar:

import base64
import re
F = 'TWVB2Xut3bfdmQ0l2Qk91Mo9HNwggc=='
 
def a(b):
    _tmp = b[:-2]
    return base64.b64decode(''.join([ e[::-1] for e in re.findall('..', _tmp) ]) + b[-2:])
 
 
 
def b(c):
    try:
        c = int(c)
        if c < 9999999999:
            return False
        else:
            if c <= 3:
                if c <= 1:
                    return False
                return True
            if not c % 2 or not c % 3:
                return False
            for i in xrange(5, int(c ** 0.5) + 1, 6):
                if not c % i or not c % (i + 2):
                    return False
 
            return True
    except:
        return False
 
 
 
def chk_serial(s):
    if b(s):
        return a(F)
    return 'Fail'
 
 
 
def main():
    a(F)
    print 'Nothing to do'
 
 
if __name__ == '__main__':
    main()

Bueno, no parece difícil 🙂

/tmp/uncompyle$ python
>>> F = 'TWVB2Xut3bfdmQ0l2Qk91Mo9HNwggc=='
>>> _tmp = F[:-2]
>>> _tmp
'TWVB2Xut3bfdmQ0l2Qk91Mo9HNwggc'
>>> F[-2:]
'=='
>>> b = F
>>> import base64
>>> import re
>>> base64.b64decode(''.join([ e[::-1] for e in re.findall('..', _tmp) ]) + b[-2:])
'Y0U_know_BitCod3_h4x0r'
>>>

Done!

HackIt! 2014 _ Level 3

poor_mans_stego_ nyan0verflow, «No siempre todo es lo que parece. Y lo que aparece no es siempre el todo». Con la foto de un gato (no es nyan cat 😉 y el texto anterior empieza el tercer reto del HackIt! Atendiendo al nombre de la imagen, a simple vista parece que han usado algún algoritmo de esteganografía (¿simple?). Probando con outguess y steghide, no parece que saquemos nada en claro.

Aquí Joserra sacó un truco de su chistera. Si abres el jpg en un editor (vim, por ejemplo) y eliminas al azar algunas líneas (entre 3 y 5), grabas y abres el jpg, verás que el gráfico esconde unas líneas más de las que se muestran sin hacer ningún cambio (poor man’s stegano 😉 Si sigues eliminando, verás el mensaje oculto:

poor_mans_stego

Dos cosas: primera, como veis, la resolución fue de chiripa. Segunda: realmente lo que está ocurriendo es que el jpg que nos pasan tiene codificada en las cabeceras una altura distinta a la que realmente ocupa la imagen. Modificando con un editor hexadecimal la altura de la imagen (0xc9 = 201) y añadiéndole algunos pixels más (por ejemplo, cambiando el 0xc9 por un 0xf0), obtendremos una forma más ortodoxa de pasarse el nivel 🙂

HackIt! 2014 _ Level 2

El nivel 2 del HackIt! tiene como título «Nivel Cromado». Nos indican que es un nivel sólo compatible con Google Chrome (o Chromium). También hay un enlace a un fichero hackit.crx (una extensión para Chrome). Para instalarla en Chrome lo tuvimos que hacer descomprimiéndola y cargándola desde el botón «Cargar extensión descomprimida» del menú chrome://extensions/ .

Así es como luce en Chrome:

level2

Podemos dedicarle un rato a jugar con la extensión desde el propio navegador, pero poco podremos avanzar. La idea es ir al código fuente de la misma a investigar. Si accedemos a la carpeta descomprimida de la extensión, veremos un fichero ek.js. Si lo abrimos, encontraremos la siguiente monstruosidad:

var _0x5544="click;val;#password;https://hackit2014.marcansoft.com/crx;get;text;show;#mal;#bien;NETWORK ERROR!;ajax;bind;#sendButton;ready".split(";");$(document)[_0x5544[13]](function(){$(_0x5544[12])[_0x5544[11]](_0x5544[0],function(){var a={password:$(_0x5544[2])[_0x5544[1]]()};$[_0x5544[10]]({url:_0x5544[3],type:_0x5544[4],async:!1,data:a,dataType:_0x5544[5],success:function(a){Boolean(parseInt(a))?$(_0x5544[7])[_0x5544[6]]():$(_0x5544[8])[_0x5544[6]]()},error:function(){alert(_0x5544[9])}})})});

Bien, habrá que limpiarlo un poco con el mismo beautifier que el level anterior…

var _0x5544 = "click;val;#password;https://hackit2014.marcansoft.com/crx;get;text;show;#mal;#bien;NETWORK ERROR!;ajax;bind;#sendButton;ready".split(";");
 
$(document)[_0x5544[13]](function() {
    $(_0x5544[12])[_0x5544[11]](_0x5544[0], function() {
        var a = {
            password: $(_0x5544[2])[_0x5544[1]]()
        };
        $[_0x5544[10]]({
            url: _0x5544[3],
            type: _0x5544[4],
            async: !1,
            data: a,
            dataType: _0x5544[5],
            success: function(a) {
                Boolean(parseInt(a)) ? $(_0x5544[7])[_0x5544[6]]() : $(_0x5544[8])[_0x5544[6]]();
            },
            error: function() {
                alert(_0x5544[9]);
            }
        });
    });
});

Vaya… código algo ofuscado. Realmente se puede entender que está lanzando peticiones AJAX a https://hackit2014.marcansoft.com/crx (con el payload: { password : xxxxx }. El resultado veremos que es un número (realmente un string de dígitos). Ese número interpretado como boolean debe dar 0 para que el resultado sea «#bien». Aquí hicimos unas cuantas pruebas y no dimos con el algoritmo interno de la parte servidor (si es que lo hay). Lo que vimos es que podíamos ir generando 0’s dependiendo de las letras con las que comenzara nuestro pass, así que lanzamos un script para probar todas las posibilidades hasta obtener una ristra de todo 0’s O:-) La longitud del pass asumimos que era el tamaño máximo del campo input de la extensión (24 caracteres). Y éste es el script que obtiene la clave (cortesía de @ochoto). Ojo, debe ejecutarse desde la consola Chrome abierta en la propia página del Hackit:

var datos = "click;val;#password;https://hackit2014.marcansoft.com/crx;get;text;show;#mal;#bien;NETWORK ERROR!;ajax;bind;#sendButton;ready".split(";");
function enviar(pass) {
        resultado = ""
 
        $[datos[10]]({
            url: datos[3],
            type: datos[4],
            async: !1,
            data: {password: pass},
            dataType: datos[5],
            success: function(a) {
                resultado = a
            },
            error: function(e) {
                resultado = "error: " + e
            }
        })
 
        return resultado
    };
 
String.prototype.replaceAt=function(index, character) {
    return this.substr(0, index) + character + this.substr(index+character.length);
}
 
var lastLength=100
var pass=Array(24).join("a")
 
for(var p=0;p<24;p++) {
    for(var i=32;i<127;i++){ 
        var caracter = String.fromCharCode(i)
        pass = pass.replaceAt(p, caracter)
        var res = enviar(pass)
 
        if( res.charAt(p)==0) {
            console.log(res + ": Encontrada posicion:" + p + ", caracter:" + caracter + ", codigo:" + i)
            break
        }
    }
}

HackIt! 2014 _ Level 1

24 de julio, a alguna hora de la noche cercana a las 22:00. Mis compañeros de DL me indican que marcan ha dado comienzo al HackIt de este año. Esta vez no podré acudir a la presentación y me tendré que aguantar hasta el viernes 25 a eso de las 18:00, así que estaré un día ayudando como pueda a través del móvil :-O Cuando el 25 llego por fin a mi puesto, abro el portátil y me sumerjo en el reto… del que no me despegaré hasta el domingo 27 a las 14:00 de la tarde… o para ser más exactos, a las 14:01, como podréis comprobar en los siguientes posts.

A decir verdad, los retos comenzaron mucho antes… en la lista de correo del HackIt, donde, como todos los años marcan nos pidió colaboración en la elaboración de las pruebas. Llevaba dándole vueltas a la idea de crear un nivel donde hiciera falta explotar la vulnerabilidad HeartBleed, y este año, a diferencia de otros, conseguí pasar de la idea a la realidad 🙂 Como de costumbre, marcan hizo que esa realidad fuera aún más enrevesada (¿a quién demonios se le ocurrió si no, desplegar el level en una máquina con arquitectura Big Endian?).

Coincidimos con la gente de amn3s1a, w0pr, NavarParty y TimoSoft y nos «peleamos» por el segundo y tercer puesto (el primero, it goes without saying, está reservado para w0pr, juegan en otra liga). También estuvo Willix Team, aunque no pudimos hablar con ellos pues no les vimos cerca de la Raspberry Pi (enseguida sabréis la razón de que aparezca aquí este dispositivo).

En fin, buenos recuerdos, muchas cosas que aprendimos por el camino y muchas más cosas que apunté para profundizar y aprender hasta el HackIt de 2015 😉 En esta serie de posts, siguiendo la recomendación del propio admin de la prueba, pasaré a comentar nuestra forma de superar algunos de los niveles. No todos… la prueba de reversing en ARM se nos atragantó (aquí agradecería que la gente de W0pr – o tal vez NavarParty, no recuerdo bien si consiguieron superarla – nos iluminara con un write-up); y con respecto a la última, la «marcanada del año» (sic), llegamos sobre la campana (un minuto más allá) y no pudimos ni intentarlo. También se agradecería un write-up, especialmente a @abeaumont, del que sabemos que estuvo muuuuchas horas pegado a la pantalla hasta solucionarlo 😉

El primer nivel, Lienzo Digital, empieza diciendo que podrás superarlo rápidamente, a no ser que uses Internet Explorer. Analizando el código fuente, nos encontramos con un script en JS, que tras pasarlo por un beautifier, nos dice algo como:

$(document).ready(function() {
    var a = new Image();
    a.addEventListener("load", function() {
        $("#password").keyup(function(b) {
            var c = $("#password").val();
            if (27 != c.length) {
                $("#password").css({
                    "background-color": "#f88"
                });
                return;
            }
            var d = 0;
            var e = document.createElement("canvas");
            e.width = 9;
            e.height = 1;
            var f = e.getContext("2d");
            f.globalCompositeOperation = "difference";
            var g = f.createImageData(9, 1);
            for (var h = 0; h < 36; h++) g.data[h] = 255;
            for (var h = 0; h < c.length; h++) g.data[h + Math.floor(h / 3)] = Math.min(c.charCodeAt(h), 255);
            f.putImageData(g, 0, 0);
            for (var h = 0; h < 9; h++) f.drawImage(a, 0, -h);
            var g = f.getImageData(0, 0, 9, 1);
            for (var h = 0; h < 27; h++) d |= g.data[h + Math.floor(h / 3)];
            $("#password").css({
                "background-color": d ? "#f88" : "#8f8"
            });
        });
    });
    a.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAIAAABv85FHAAABB0lEQVR4nAH8AAP/APXX7L74zhsb6Bni6gjRu84PCwPnFRrD/iQJEADp+fop4w7/ClXcP1SiE/cmvMOp+MHm/T7ppegA1O7CJP/IGQvj+xTHyPU3uRbJ6gA34E3f4dNfAOsR5vkP0TXLIO7E3uYm1Auzux4bx+fsD9bN9QD3IbM6yxDkNLjgussk5SUKCxXK1dMIHKPVMf8A2REc1MHrOtrT962///YfuB++E8fdDzAQoanoAOKODsDtBa31xuj6IizvxQYV3AIv4+wkxgy1vgAJFqLRFxD7HQCh7MwE3Aq0qwzg6yr69d28AtUBnV1+0/HzLiLX19o25jfnBP/hIDkMDBUX3YAUC0mGddt48B4AAAAASUVORK5CYII=";
});

Lo primero que vemos es que la longitud del pass debe ser 27 (en otro caso, el css que se aplica es de color rojizo #f88). Lo siguiente es que el level crea un elemento canvas (lienzo digital…) con el que empieza a jugar. Dentro del canvas (‘f’) tenemos representado un array de 9×1 pixels en una variable de nombre ‘g’. Cada pixel, a su vez, viene representado en RGBA (por tanto, con 4 bytes por pixel, necesitamos 36 bytes para gestionar dicho array). Por otro lado, en la variable de nombre ‘a’ tenemos una extraña imagen usando un Data URI Scheme (RFC 2397). Si copias y pegas esa imagen como una URL normal en el navegador, podrás visualizarla. La idea para pasar este nivel es que con los 27 caracteres del password (su código ASCII) rellenamos los datos del array g. ¿Por qué 27 y no 36? Porque en el bucle vemos que cada 3 bytes, nos saltamos uno (g.data[h + Math.floor(h/3)] = código ascii).

A continuación hay tres líneas que vuelcan el valor del array g (construido con los valores ASCII del pass, recuerda) en el canvas ‘f’ y calculan una diferencia de ‘f’ con ‘a’ (el f.globalCompositeOperation = ‘difference’ entra en juego ahora), dejando el resultado en ‘g’.

El último «meneo» aplica un OR a los datos de ‘g’ y deja el resultado en ‘d’.

d = 0;
for (var h = 0; h < 27; h++) d |= g.data[h + Math.floor(h / 3)];

El objetivo es conseguir satisfacer que ‘d’ valga 0 al salir del bucle. Si lo conseguimos (para ello, todos los datos de g.data deben ser 0), el valor del background del campo pass será verde: («background-color»: d ? «#f88» : «#8f8»), y por tanto, sabremos que el password es correcto.

Abramos la consola de Chrome y comencemos a jugar. Pegamos el siguiente trozo de código:

var a = new Image();
a.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAIAAABv85FHAAABB0lEQVR4nAH8AAP/APXX7L74zhsb6Bni6gjRu84PCwPnFRrD/iQJEADp+fop4w7/ClXcP1SiE/cmvMOp+MHm/T7ppegA1O7CJP/IGQvj+xTHyPU3uRbJ6gA34E3f4dNfAOsR5vkP0TXLIO7E3uYm1Auzux4bx+fsD9bN9QD3IbM6yxDkNLjgussk5SUKCxXK1dMIHKPVMf8A2REc1MHrOtrT962///YfuB++E8fdDzAQoanoAOKODsDtBa31xuj6IizvxQYV3AIv4+wkxgy1vgAJFqLRFxD7HQCh7MwE3Aq0qwzg6yr69d28AtUBnV1+0/HzLiLX19o25jfnBP/hIDkMDBUX3YAUC0mGddt48B4AAAAASUVORK5CYII=";
var d = 0;
var e = document.createElement("canvas");
e.width = 9;
e.height = 1;
var f = e.getContext("2d");
f.globalCompositeOperation = "difference";
var g = f.createImageData(9, 1);
for (var h = 0; h < 36; h++) g.data[h] = 255;

Inspeccionando un poco el entorno, vemos lo siguiente:

// creamos un pass de 27 'a's
> var c = Array(27).join("a")
> g.data
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,255, 255, 255, 255, 255, 255, 255, 255, 255]
> for (var h = 0; h < c.length; h++) g.data[h + Math.floor(h / 3)] = Math.min(c.charCodeAt(h), 255);
> f.putImageData(g, 0, 0);
> f.getImageData(0,0,9,1).data
[97, 97, 97, 255, 97, 97, 97, 255, 97, 97, 97, 255, 97, 97, 97, 255, 97, 97, 97, 255, 97, 97, 97, 255, 97, 97, 97, 255, 97, 97, 97, 255, 97, 97, 255, 255]

¿Y qué datos tenemos en la imagen misteriosa?

var b = new Image()
var myCanvas = document.createElement("canvas")
var ctx = myCanvas.getContext("2d")
 b.onload = function(){ ctx.drawImage(b,0,0); } ; b.src = a.src;
 // primera fila de la imagen misteriosa
 
 ctx.getImageData(0,0,9,1).data
[245, 215, 236, 255, 190, 248, 206, 255, 27, 27, 232, 255, 25, 226, 234, 255, 8, 209, 187, 255, 206, 15, 11, 255, 3, 231, 21, 255, 26, 195, 254, 255, 36, 9, 16, 255]
 // segunda fila de la imagen misteriosa
 ctx.getImageData(0,1,9,1).data
[233, 249, 250, 255, 41, 227, 14, 255, 255, 10, 85, 255, 220, 63, 84, 255, 162, 19, 247, 255, 38, 188, 195, 255, 169, 248, 193, 255, 230, 253, 62, 255, 233, 165, 232, 255]

OK! Ahora sólo nos queda entender esto:

for (var h = 0; h < 9; h++) f.drawImage(a, 0, -h);

La primera iteración del bucle es sencilla: f.drawImage(a,0,0) . Lo que estamos haciendo es copiar en el canvas (desde la posición 0,0 del canvas) los bytes del array ‘a’. Pero como hemos definido que el globalCompositeOperation del canvas es «difference», realmente lo que hacemos no es machacar lo que ya hubiera en el canvas (la ristra de 97, 97, 97…) sino que se calcula la diferencia. Es decir, estamos calculando esta operación: abs( [97, 97, 97, 255, …, 97, 97, 255, 255] – [245, 215, 236, 255, …, 36, 9, 16, 255]), obteniendo como resultado: [148, 118, 139, 255, …, 61, 88, 239, 255].

El modo difference es uno de los posibles modos «composite» del canvas HTML5. Podéis investigar al respecto en este ejemplo de CodePen.io.

Este es un ejemplo de composite «normal» (fíjate en las zonas de solapamiento en los colores magenta, cyan y amarillo)

normal

y este otro un ejemplo de composite «difference».

difference

La segunda iteración del bucle tiene una pequeña complejidad: f.drawImage(a,0,-1) . ¿Qué es ese -1? Que la imagen misteriosa se solapa sobre el canvas, pero saltando una fila, es decir, estaremos haciendo:
abs ([148, 118, 139, 255, …, 61, 88, 239, 255] – [233, 249, 250, 255, …, 233, 165, 232, 255]) obteniendo [85, 131, 111, 255, …, 172, 77, 7, 255]

Y seguimos igual para f.drawImage(a,0,-2) … f.drawImage(a,0,-8). Recordemos que en la última iteración debemos obtener [0,0,0,255,0,0,0,255,…,0,0,0,255]. Así que, deshaciendo el entuerto tenemos que para la primera letra del pass hay que resolver:

|||||||245 (pos 0,0 de la imagen) – Letra del pass| – 233 (pos 0,1 de la imagen)| – …. – pos(0,8 de la imagen)| = 0

Es decir:

Math.abs(Math.abs(Math.abs(Math.abs(Math.abs(Math.abs(Math.abs(Math.abs(Math.abs(x – 245) – 233) – 212) – 235) – 247) – 217) – 226) – 9) – 157) = 0

Para la segunda letra del pass, de forma equivalente, hay que resolver:

|||||||215 (pos 1,0 de la imagen) – Letra del pass| – 249 (pos 1,1 de la imagen)| – …. – pos(1,8 de la imagen)| = 0

etc.

Resolviendo la ecuación para todas las letras del pass:

// preparar la imagen en el contexto del canvas
var b = new Image()
var myCanvas = document.createElement("canvas")
var ctx = myCanvas.getContext("2d")
b.onload = function(){ ctx.drawImage(b,0,0); } ; 
b.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAIAAABv85FHAAABB0lEQVR4nAH8AAP/APXX7L74zhsb6Bni6gjRu84PCwPnFRrD/iQJEADp+fop4w7/ClXcP1SiE/cmvMOp+MHm/T7ppegA1O7CJP/IGQvj+xTHyPU3uRbJ6gA34E3f4dNfAOsR5vkP0TXLIO7E3uYm1Auzux4bx+fsD9bN9QD3IbM6yxDkNLjgussk5SUKCxXK1dMIHKPVMf8A2REc1MHrOtrT962///YfuB++E8fdDzAQoanoAOKODsDtBa31xuj6IizvxQYV3AIv4+wkxgy1vgAJFqLRFxD7HQCh7MwE3Aq0qwzg6yr69d28AtUBnV1+0/HzLiLX19o25jfnBP/hIDkMDBUX3YAUC0mGddt48B4AAAAASUVORK5CYII=";
 
// resolver la ecuación
pass = ''
for (ind = 0; ind < 36; ind++) {
  for (letra=32;letra<127;letra++) {
   c = letra; 
   for(var i=0; i < 9; i++) {
       c = Math.abs(ctx.getImageData(0,i,9,1).data[ind] - c)
   } 
   if(c == 0)
      pass += String.fromCharCode(letra);
   }
}
console.log(pass)

Obtenemos lo que buscábamos 😉

Receta rápida para optimizar MySQL

highperformancemysqlSiguiendo con el post anterior (recordemos, tengo un servidor muy humilde en recursos que quiero aprovechar al máximo), le toca el turno al servidor MySQL.

Tengo un libro (High Performance MySQL, de la editorial O’Reilly) que -entre otras cosas- se dedica a explicar cómo obtener el mejor rendimiento de un servidor MySQL, así que en este post no voy a hacer más que abrir el melón 🙂

La cuestión es que, antes de entrar a configurar los cientos de parámetros del fichero de configuración my.cnf, quería ir a tocar los interruptores «evidentes». ¿Hay alguna forma de encontrar fallos de configuración obvios en MySQL para hacer que este sistema gestor rinda mejor? («rendir mejor» puede entenderse de varias formas… yo me refiero al sentido de que siga ofreciendo servicio pero que consuma menos RAM).

Pues sí… hay una pequeña aplicación, llamada mysqltuner.pl, que permite «tunear» el fichero my.cnf de forma muy sencilla. Realmente lo que hace es darte pistas y consejos sobre qué «interruptores» tocar para que el servidor rinda mejor.

Aprovechando un domain hack, el autor de la aplicación ha generado un dominio propio con el mismo nombre que la aplicación en Perl: http://mysqltuner.pl. Así que, para descargar y hacer ejecutable esta joyita, basta con hacer:

wget -O mysqltuner.pl http://mysqltuner.pl
chmod a+x mysqltuner.pl
sudo ./mysqltuner.pl

Si ejecutamos con permisos de sudoer, ni siquiera tenemos que configurar las credenciales de mysql, porque como la aplicación indica en el log:

[OK] Logged in using credentials from debian maintenance account.

Qué cosas… no sabía que hubiera una cuenta de mantenimiento debian con la que hacer… eso, mantenimiento de mysql 🙂 Entre otras cosas, lo primero que me dice mysqltuner es:

-------- Storage Engine Statistics -------------------------------------------
[--] Status: +ARCHIVE +BLACKHOLE +CSV -FEDERATED +InnoDB +MRG_MYISAM 
[--] Data in MyISAM tables: 9M (Tables: 14)
[--] Data in PERFORMANCE_SCHEMA tables: 0B (Tables: 17)
[!!] InnoDB is enabled but isn't being used
[!!] Total fragmented tables: 5
...
-------- Recommendations -----------------------------------------------------
General recommendations:
    Add skip-innodb to MySQL configuration to disable InnoDB
    Run OPTIMIZE TABLE to defragment tables for better performance
    MySQL started within last 24 hours - recommendations may be inaccurate
    Reduce your overall MySQL memory footprint for system stability
    Enable the slow query log to troubleshoot bad queries

Vamos, que el módulo InnoDB lo tengo activado (consumiendo memoria) y no lo uso. Sabiendo que mi server si algo tiene que cuidar es la RAM… vamos a hacerle caso… También me dice que tengo tablas fragmentadas que convendría arreglar. Apuntado. Lo primero, antes de pifiarla (desactivando InnoDB cuando por defecto MySQL crea bases de datos InnoDB, por ejemplo), vamos a comprobar…

mysql> use information_schema ;
Database changed
mysql> select * from ENGINES;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| ENGINE             | SUPPORT | COMMENT                                                        | TRANSACTIONS | XA   | SAVEPOINTS |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
| CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         |
| BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         |
| MyISAM             | YES     | MyISAM storage engine                                          | NO           | NO   | NO         |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         |
| ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
9 rows in set (0.00 sec)

Je… Murphy nunca falla. Vamos a arreglar eso (quiero dejar MyISAM por defecto antes de desactivar el soporte InnoDB en MySQL). En el fichero /etc/mysql/my.cnf añado lo siguiente:

default-storage-engine=myisam 
skip_innodb

Y listo. Vamos ahora a optimizar tablas…

mysqlcheck -u root -p -o --all-databases

Pasamos de nuevo mysqltuner.pl:

-------- Storage Engine Statistics -------------------------------------------
[--] Status: +ARCHIVE +BLACKHOLE +CSV -FEDERATED -InnoDB +MRG_MYISAM 
[--] Data in MyISAM tables: 9M (Tables: 14)
[--] Data in PERFORMANCE_SCHEMA tables: 0B (Tables: 17)
[OK] Total fragmented tables: 0

y ¡premio!

Dejo un ejercicio para el lector 🙂

-------- Performance Metrics -------------------------------------------------
[--] Up for: 4h 9m 7s (43K q [2.891 qps], 1K conn, TX: 152M, RX: 5M)
[--] Reads / Writes: 82% / 18%
[--] Total buffers: 192.0M global + 2.7M per thread (151 max threads)
[!!] Maximum possible memory usage: 597.8M (121% of installed RAM)
-------- Recommendations -----------------------------------------------------
General recommendations:
    ...
    Reduce your overall MySQL memory footprint for system stability
    ...

Eso de que el máximo de RAM que puede consumir MySQL es del 121% de la RAM instalada no me ha gustado (y a mysqltuner.pl tampoco). ¿Qué hacer para corregirlo? Lo veremos en otro post…