Détection des piratages web via des sondes linux

Frédéric VANNIÈRE  - Homere

f.vanniere@planet-work.com

Sysadmin #7 - 19-20 octobre 2017

Présentation

  • sysadmin depuis 2001
  • hébergement mutualisé
  • serveurs infogérés

Previously on ...

  • blocage des attaques web (bruteforce)
  • rsyslog, hekad, nginx/openresty
  • php-malware-scanner

Contexte

  • 10 000 sites
  • 30% Wordpress
  • nombreuses attaques, sites piratés
  • détection lors d'une action malveillante

Besoin d'un système de détection

Mutualisé

Apache / PHP

Apache / PHP

Apache / PHP

Openresty

NFS (NetApp)

Apache / PHP

Openresty

Dédié

ext4 / ZFS

Piratage web

  1. upload de scripts PHP
  2. injection de backdoors
  3. action malveillante :
    • envoi de spam
    • propagation, attaques de sites
    • connexion C&C / IRC
    • flood UDP
    • site de pishing

détection

Solutions envisagées

  • NetApp fpolicy
  • inotify
  • while true ; do netstat -natpe | grep -q 6667 && alert ; sleep 1 ; done

BCC / BPF

  • Linux ≥ 4.1
  • sonde noyau linux (module)
  • jeu d'instructions limité / sandbox
  • interface espace utilisateur        noyau
  • performant
int trace_read_entry(struct pt_regs *ctx, struct file *file,
    char __user *buf, size_t count)
{
    u32 tgid = bpf_get_current_pid_tgid() >> 32;
    if (TGID_FILTER)
        return 0;

    u32 pid = bpf_get_current_pid_tgid();

    // skip I/O lacking a filename
    struct dentry *de = file->f_path.dentry;
    int mode = file->f_inode->i_mode;
    if (de->d_name.len == 0 || TYPE_FILTER)
        return 0;

    // store counts and sizes by pid & file
    struct info_t info = {.pid = pid};
    bpf_get_current_comm(&info.comm, sizeof(info.comm));
    info.name_len = de->d_name.len;
    bpf_probe_read(&info.name, sizeof(info.name), (void *)de->d_name.name);
    if (S_ISREG(mode)) {
        info.type = 'R';
    } else if (S_ISSOCK(mode)) {
        info.type = 'S';
    } else {
        info.type = 'O';
    }

    struct val_t *valp, zero = {};
    valp = counts.lookup_or_init(&info, &zero);
    if (is_read) {
        valp->reads++;
        valp->rbytes += count;
    } else {
        valp->writes++;
        valp->wbytes += count;
    }

    return 0;
}


llvm

Linux

BPF

perf

Python BCC

__sys_execve()
__sys_open()
__tcp_v4_connect()
__vfs_read()

Exemple : opensnoop

root@celia:/usr/share/bcc/tools# ./opensnoop -n apache2
3749   apache2            16   0 /home/epingle/www/wp-includes/rest-api/endpoints/class-wp-rest-post-types-controller.php
3749   apache2            16   0 /home/epingle/www/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php
3749   apache2            16   0 /home/epingle/www/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php
3749   apache2            16   0 /home/epingle/www/wp-includes/rest-api/endpoints/class-wp-rest-taxonomies-controller.php
3749   apache2            16   0 /home/epingle/www/wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php
3749   apache2            16   0 /home/epingle/www/wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
3749   apache2            16   0 /home/epingle/www/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php
3749   apache2            16   0 /home/epingle/www/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php
3749   apache2            16   0 /home/epingle/www/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php
3749   apache2            16   0 /home/epingle/www/wp-includes/rest-api/fields/class-wp-rest-comment-meta-fields.php
3749   apache2            16   0 /home/epingle/www/wp-includes/rest-api/fields/class-wp-rest-post-meta-fields.php
3749   apache2            16   0 /home/epingle/www/wp-includes/rest-api/fields/class-wp-rest-term-meta-fields.php
3749   apache2            16   0 /home/epingle/www/wp-includes/rest-api/fields/class-wp-rest-user-meta-fields.php
3749   apache2            16   0 /home/epingle/www/wp-includes/vars.php
3750   apache2            13   0 /usr/share/zoneinfo/America/Argentina
3750   apache2            13   0 /usr/share/zoneinfo/America/Indiana
3749   apache2            16   0 /home/epingle/www/wp-content/plugins/akismet/akismet.php
3749   apache2            16   0 /home/epingle/www/wp-content/plugins/akismet/class.akismet.php
3749   apache2            16   0 /home/epingle/www/wp-content/plugins/akismet/class.akismet-widget.php
3749   apache2            16   0 /home/epingle/www/wp-content/plugins/akismet/wrapper.php
3749   apache2            16   0 /home/epingle/www/wp-content/plugins/counterize/counterize.php
3749   apache2            16   0 /dev/urandom
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/counterize/counterize_browsniff.php
3750   apache2            13   0 /usr/share/zoneinfo/America/Kentucky
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/counterize/counterize_iptocountry.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/counterize/ip_files/countries.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/counterize/counterize_feed.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/jw-player-plugin-for-wordpress/jwplayermodule.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/jw-player-plugin-for-wordpress/jwp6/jwp6-plugin.php
3750   apache2            13   0 /usr/share/zoneinfo/America/North_Dakota
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/jw-player-plugin-for-wordpress/jwp6/jwp6-class-plugin.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/jw-player-plugin-for-wordpress/jwp6/jwp6-class-player.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/jw-player-plugin-for-wordpress/jwp6/jwp6-class-shortcode.php
3750   apache2            13   0 /usr/share/zoneinfo/Antarctica
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/jw-player-plugin-for-wordpress/jwp6/jwp6-class-legacy.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/options-framework/options-framework.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/php-code-widget/execphp.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/related-posts/wp_related_posts.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/related-posts/init.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/related-posts/config.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/related-posts/lib/stemmer.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/related-posts/lib/mobile_detect.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/related-posts/admin_notices.php
3750   apache2            13   0 /usr/share/zoneinfo/Arctic
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/related-posts/widget.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/related-posts/thumbnailer.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/related-posts/settings.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/related-posts/uploader.php
3749   apache2            17   0 /home/epingle/www/wp-content/plugins/related-posts/recommendations.php

Exemple : tcpconnect

root@celia:/usr/share/bcc/tools# ./tcpconnect  -P 80
PID    COMM         IP SADDR            DADDR            DPORT
5187   apache2      4  192.168.3.27     109.1.151.41     80  
5187   apache2      4  192.168.3.27     109.1.151.41     80  
5246   apache2      4  192.168.3.27     212.27.63.116    80  
5249   apache2      4  192.168.3.27     54.239.39.136    80  
5249   apache2      4  192.168.3.27     54.239.39.136    80  
5249   apache2      4  192.168.3.27     54.239.39.136    80  
5249   apache2      4  192.168.3.27     54.239.39.136    80  
5255   apache2      4  192.168.3.27     109.1.151.41     80  
5255   apache2      4  192.168.3.27     109.1.151.41     80  
5358   apache2      4  192.168.3.27     212.27.63.116    80  
5479   apache2      4  192.168.3.27     79.99.164.4      80  
5479   apache2      4  192.168.3.27     178.237.36.10    80  
5633   apache2      4  192.168.3.27     91.240.109.21    80  
5640   apache2      4  192.168.3.27     212.27.63.116    80  
5671   apache2      4  192.168.3.27     195.154.241.86   80  
5677   apache2      4  192.168.3.27     79.99.164.4      80  
5720   apache2      4  192.168.3.27     35.187.79.8      80  
5720   apache2      4  192.168.3.27     35.187.79.8      80  
5720   apache2      4  192.168.3.27     35.187.79.8      80  
5720   apache2      4  192.168.3.27     35.187.79.8      80  

Webserver Gets a Probe

  • module noyau BCC/BPF
  • Python 3
  • sonde + collecteur
  • compatible container

Événements

  • Écriture de fichier : __sys_open

  • Connexion TCP  : __tcp_v[46]_connect
  • Écoute de port : __inet_listen
  • Éxécution de programme : __sys_execve

Hindsight

wgap-probe

events

BPF

pms

NFS / filesystem

API PW

__sys_execve()
__inet_listen()
__tcp_v4_connect()
__sys_open()
int trace_sys_open_entry(struct pt_regs *ctx, const char __user *filename, int flags, umode_t mode)
{
    struct val_t val = {};
    u32 tgid = bpf_get_current_pid_tgid() >> 32;
    u32 uid = (unsigned)(bpf_get_current_uid_gid() & 0xffffffff);

    val.mode = 'R';
    if (flags & (O_WRONLY | O_RDWR)) {
        val.mode = 'W';
    }  
    if (MODE_FILTER)
        return 0;

    if (bpf_get_current_comm(&val.comm, sizeof(val.comm)) == 0) {
        val.id = id;
        val.ts_us = bpf_ktime_get_ns() / 1000;
        val.data1 = filename;
        val.uid = uid;

    }
}

int trace_sys_open_return(struct pt_regs *ctx)
{
    u64 id = bpf_get_current_pid_tgid();
    struct val_t *valp;
    struct data_t data = {};
    u64 tsp = bpf_ktime_get_ns() / 1000;
    valp = infotmp.lookup(&id);

    bpf_probe_read(&data.comm, sizeof(data.comm), valp->comm);
    bpf_probe_read(&data.data1, sizeof(data.data1), (void *)valp->data1);

    data.id = valp->id;
    data.pid = id >> 32;
    data.uid = (u32) bpf_get_current_uid_gid();
    data.mode = valp->mode;
    data.ts_us = tsp / 1000;
    data.ret = PT_REGS_RC(ctx);
    events.perf_submit(ctx, &data, sizeof(data));
}
:Uuid: DD81D7FD-B6E3-4732-A980-AF464C1B5834
:Timestamp: 2017-10-18T07:03:08.415656192Z
:Type: wgap_callback
:Logger: upload_php
:Severity: 6
:Payload:
:EnvVersion: 0.8
:Pid: 2730
:Hostname: celia
:Fields:
    | name: pid type: 2 representation:  value: 6667
    | name: user type: 0 representation:  value: homere
    | name: filename type: 0 representation:  value: /home/homere/www/xx.php
    | name: tenant type: 0 representation:  value: mutupw
    | name: timestamp type: 0 representation:  value: 2017-10-18T09:03:08.415656+00:00
    | name: appname type: 0 representation:  value: touch
    | name: message type: 0 representation:  value: homere@mutupw touch[6667] file_write: /home/homere/www/xx.php
    | name: event type: 0 representation:  value: file_write
    | name: uid type: 2 representation:  value: 2000

Chez les autres ?

  1. Upload shell PHP (FilesMan)
  2. écoute sur port 6667 (C&C)
  3. exécution de getent passwd, ifconfig et uname
  4. paquets UDP vers port 123
  5. se connecter à 50 Wordpress différents en HTTP
  6. se connecter à 50 Wordpress différents en HTTPs

Aucune réaction (mail, blocage ...)

Plus loin avec BCC / BPF

  • nombreux outils
  • sondes espace utilisateur (MySQL)
  • Réseau : XDP

Cas pratique de BCC

  • surcharge NFS en lecture sur le mutualisé
  • saturation du réseau local, du stockage
  • aléatoire et peu fréquent (2 fois par an)
  • source inconnue

Merci !

Made with Slides.com