OSSEC HIDS Extension - Accumulator

If you haven't looked at OSSEC HIDS, here's the overview:

OSSEC is a scalable, multi-platform, open source Host-based Intrusion Detection System (HIDS). It has a powerful correlation and analysis engine, integrating log analysis, file integrity checking, Windows registry monitoring, centralized policy enforcement, rootkit detection, real-time alerting and active response.

It runs on most operating systems, including Linux, OpenBSD, FreeBSD, MacOS, Solaris and Windows.

OSSEC is a great product, but I ran into an issue when attempting to fulfill a require for PCI-DSS which involved reviewing our LDAP logs. I knew OSSEC would make this simple. I started writing a rule and realized I had hit a significant roadblock. OpenLDAP logs events as they happen and only logs data relevant to that particular event. A connect event has the ports and IPs, and the bind event contains the username, but only the connection id is the same in the two events.

Out of the box, OSSEC can handle multiline events if those events are a fixed number of sequential lines (which Windows Event Logging). Unfortunately, the OpenLDAP logs were not fixed line and the ability to alert on multiple unsuccessful logins from the same ip is not available for this type of logging demonstrated below:

A connection is established:

Jan 11 09:26:57 hostname slapd[20872]: conn=999999 fd=64 ACCEPT from IP=10.1.2.37:33957 (IP=10.1.2.2:389)

A bind (or login event):

Jan 11 09:26:57 hostname slapd[20872]: conn=999999 op=0 BIND dn="uid=example,ou=People,dc=example,dc=com" method=128

An unsuccessful login:

Jan 11 09:26:57 hostname slapd[20872]: conn=999999 op=0 RESULT tag=97 err=49 text=

A retry:

Jan 11 09:26:57 hostname slapd[20872]: conn=999999 op=1 BIND dn="uid=example,ou=People,dc=example,dc=com" method=128

Success:

Jan 11 09:26:57 hostname slapd[20872]: conn=999999 op=1 RESULT tag=97 err=0 text=

Connection is closed:

Jan 11 09:26:57 hostname slapd[20872]: conn=999999 op=2 UNBIND
Jan 11 09:26:57 hostname slapd[20872]: conn=999999 fd=64

You can see that all the events have the same conn=999999 in the log event. My accumulator patch is currently merged into my OSSEC GitHub Repo allows events to accumulate data using this connection id by means of a decoder extension.

The patch replaces the standard openldap decoder with my decoder extension, which is as simple as adding an <accumulate/> tag to every decoded event you'd like to accumulate data. Here's the relevant section from /var/ossec/etc/decoders.xml:

<decoder name="openldap">
    <program_name>^slapd</program_name>
    <accumulate/>
</decoder>

<decoder name="openldap-connect">
    <parent>openldap</parent>
    <prematch>ACCEPT</prematch>
    <regex>^conn=(\d+) fd=\d+ ACCEPT from IP=(\S+):</regex>
    <order>id, srcip</order>
    <accumulate/>
</decoder>

<decoder name="openldap-bind">
    <parent>openldap</parent>
    <prematch>BIND </prematch>
    <regex>^conn=(\d+) op=\d+ BIND dn="\w+=(\w+),</regex>
    <order>id, dstuser</order>
    <accumulate/>
</decoder>

<decoder name="openldap-result">
    <accumulate/>
    <parent>openldap</parent>
    <prematch> RESULT </prematch>
    <regex>^conn=(\d+) op=\d+ RESULT </regex>
    <order>id</order>
</decoder>

In the above decoder, when the connect event happens, we start an accumulate cache for the event with a key of "hostname openldap id" which stores srcip. The subsequently decoded BIND event uses the same key and adds "dstuser" to the "hostname openldap id" cache. When we get to a result line, that line pulls both those values from the accumulator cache and they will be available for the rules to trigger using a <sameip/> aggregation.

Accumulate() requires an id to be extracted by the decoder, and uses that recurring id to add data to events as they are parsed using an in memory cache. The default, right now only configurable at build, is to keep the data in memory for up to 5 minutes. Old entries are expired from the hash every 100 lookups or 10 minutes, whichever happens first.

Using this patch, as new data comes in, that data is then available to be used by the rule engine for smarter rules. Here's an example using the above decoder configuraiton to alert with 5 unsuccessful login attempts in 60 minutes from the same IP address:

<rule id="100000" level="1">
  <decoded_as>openldap</decoded_as>
  <match> RESULT tag=97 err=49</match>
</rule>

<rule id="100001" level="10" frequency="5" timeframe="60">
  <if_matched_sid>100000</if_matched_sid>
  <same_source_ip/>
  <description>Multiple failed-logins from same source IP</description>
</rule>

Now, I can close an item on my PCI-DSS backlog! The other nice piece here is because OSSEC is distributed, I can do this match across multiple OpenLDAP servers. I'm working on an active response script to automatically disable accounts across a multi-datacenter, OpenLDAP infrastructure with proper reporting and automatic Help Desk notificaitons!

I'm looking for testers and any suggestions for improvements on the interface as there is now a decent amount of interest on the OSSEC development mailing list for incorporating this feature into the 2.8 release.



comments powered by Disqus