Monday, April 24, 2017

Sending email when audisp program sees event

I've been working our way up to writing an audispd plugin that will send an email whenever a specific event occurs. To do this, we will need a plan of action. The way it should work is that we need a way of identifying the event that we are interested in. We will use this to trigger the sending of email.

Needle in a Haystack
The first issue is we need to find the event. The easiest way to do this is to add a key to the audit rule. Then what we can do is write an audispd plugin that will have a callback function that uses the auparse_normalize API to normalize the event. Then we have the subject readily available. The normalizer API also hands us the key without needing to search for it. But that leads us to the question of what are we looking for?

Remember that audisp plugins can take a command line argument. So instead of opening a config file and parsing it, we''ll just ask that whatever the key is be passed as an argument on the command line. And just in case you want to match on multiple but related keys, we'll do substring matching using the strstr function. A more professional program might have a config file with many things or pairs of things to match against before alerting. But I'm going to keep it simple while illustrating the point.

So, the initial code looks like this:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/select.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <libaudit.h>
#include <auparse.h>

const char *needle = NULL;

// send_alert goes here

static void handle_event(auparse_state_t *au,
        auparse_cb_event_t cb_event_type, void *user_data)
    char msg[256], *name = NULL, *key = NULL;
    if (cb_event_type != AUPARSE_CB_EVENT_READY)

    /* create a message */
    if (!auparse_normalize(au, NORM_OPT_NO_ATTRS)) {
        if (auparse_normalize_key(au) == 1)
            key = auparse_interpret_field(au);
            if (key && strstr(needle, key)) {
                if (auparse_normalize_subject_primary(au) == 1)
                    name = strdup(auparse_interpret_field(au));
                /* send a message */
                printf("Alert, %s triggered our rule\n", name);

int main(int argc, char *argv[])
    auparse_state_t *au = NULL;
    char tmp[MAX_AUDIT_MESSAGE_LENGTH+1], bus[32];

    if (argc != 2) {
        fprintf(stderr, "Missing key to look for\n");
        return 1;
    needle = argv[1];

    /* Initialize the auparse library */
    au = auparse_init(AUSOURCE_FEED, 0);
    auparse_add_callback(au, handle_event, NULL, NULL);

    do {
        int retval;
        fd_set read_mask;

        FD_SET(0, &read_mask);

        do {
            retval = select(1, &read_mask, NULL, NULL, NULL);
        } while (retval == -1 && errno == EINTR);

        /* Now the event loop */
         if (retval > 0) {
            if (fgets_unlocked(tmp, MAX_AUDIT_MESSAGE_LENGTH,
                stdin)) {
                auparse_feed(au, tmp,
                    strnlen(tmp, MAX_AUDIT_MESSAGE_LENGTH));
        } else if (retval == 0)
        if (feof(stdin))
    } while (1);

    /* Flush any accumulated events from queue */

    return 0;

We can compile it like this:

gcc -o audisp-example4 audisp-example4.c -lauparse -laudit

And let's test it by triggering any time the "w" command is used. Let's add the following audit rule:

auditctl -a always,exit -F path=/usr/bin/w -F perm=x -F key=alert
Then run "w" and let's collect the audit log for testing.

$ w
 16:15:00 up 16 min,  1 user,  load average: 0.34, 0.40, 0.37
sgrubb   tty2      16:04   16:04   2:10  49.27s /usr/lib64/firefox/firefox -con

$ ausearch --start recent --raw > test.log

Now we have some data to test the plugin.

$ cat test.log | ./audisp-example4 alert
Alert, sgrubb triggered our rule

The next trick is that we need to send an email notifying that something we're interested in happened. It turns out that there is a program called sendmail which is not exactly the MTA daemon sendmail, but a command line helper program. Its found at /usr/lib/sendmail. Which seems a bit odd but this is the historic place where it was located since the 1990's. I also think all MTA's provide their own stub for compatibility with this ancient standard. Fedora uses postfix, so let's just verify that we have a sendmail stub.

$ file /usr/lib/sendmail
/usr/lib/sendmail: symbolic link to /etc/alternatives/mta-sendmail
$ file /etc/alternatives/mta-sendmail
/etc/alternatives/mta-sendmail: symbolic link to /usr/lib/sendmail.postfix
$ file /usr/lib/sendmail.postfix
/usr/lib/sendmail.postfix: symbolic link to ../sbin/sendmail.postfix
$ file ../sbin/sendmail.postfix
../sbin/sendmail.postfix: cannot open `../sbin/sendmail.postfix' (No such file or directory)
$ file /sbin/sendmail.postfix
/sbin/sendmail.postfix: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/, for GNU/Linux 2.6.32, BuildID[sha1]=d82eedab7c1ad5c7b01a2a82a3b07919fc1b9089, stripped

OK. There it is. After 4 symlinks we land on the real program.

The way that you use it is to send via stdin an email composed line by line like you would a normal email. You have to have a To, From, Subject, and then the body of the email. It knows the email is finished when it sees a period followed by 2 consecutive newlines. So we might want an email like this:

To: security-team@company
From: root
Subject: Audit Alert
Just wanted to let you know that %s triggered the rule


This is pretty much what would show up in your inbox. OK, so let's add this to our program. The trick here is that we open sendmail with popen() writable and then pass our pre-formatted email to it and then close. It will take care of the rest assuming that you substitute a valid email address that is routable for our stand-in on the "To" line.

The code for send alert looks like this:

static void send_alert(const char *name)
    FILE *mail;
    mail = popen("/usr/lib/sendmail", "w");
    if (mail) {
        fprintf(mail, "To: %s\n", mail_acct);
        fprintf(mail, "From: root\n");
        fprintf(mail, "Subject: Audit Alert\n\n");
        fprintf(mail, "Just wanted to let you know %s triggered the audit rule\n",
        fprintf(mail, ".\n\n");         // Close it up...

So, copy and paste this into the program above the event_handler. Delete printf in event_handler and uncomment the call to send_alert. You should still be able to test this by cat'ing the test.log file into stdin of the program.

If you are happy with this, then you need to install the program by copying it to /sbin and then setup the configuration file so that audispd starts and passes the right key to the program to look for. The conf file is almost identical to the one from the audispd plugin blog post.

active = yes
direction = out
path = /sbin/audisp-example4
type = always
args = alert
format = string

Copy this to /etc/audisp/plugins.d/audisp-example4.conf and restart the audit daemon. You may need to disable selinux to get this to run. Selinux wraps the audit daemon and anything that is a child of it gets the same type as audit. So, the child is bound by auditd's selinux policy. Auditd can send emails, so there is hope. Ideally you would put this program in either a permissive domain or write a simple policy for it if you used this long term.

To test the program run the "w" program again and see if you get email. A more professional program would also want to build some hysteresis or rate limiting into the program so it cannot flood the recipient of the alert.

Once you have a basic audispd plugin, you can alert in many ways. You might even prefer to create a snmp trap. The beauty of this is that the logs can be tested in realtime as something occurs so that you can react to it right away. The technique that we used in this post was to give the event we are interested in a special key, we filter for that key. When we see our event, we send an email. This is one of the tricks you can use to spot things for alerting.

No comments: