29 October, 2008

Snort shared object rules with Sguil

More and more Sourcefire VRT Snort rules are being released in the shared object (SO) rules format. As examples, Sourcefire released rules on 14 October that were designed to detect attacks targeting MS08-057, MS08-058, MS08-059, MS08-060, MS08-062, MS08-063 and MS08-065 vulnerabilities. On 23 October, Sourcefire released rules related to MS08-067, a vulnerability that has garnered a lot of attention.

It looks like all these recently released rules are SO rules. It is easy to tell a SO rule from a traditional Snort rule using the Generator ID, because SO rules use GID 3 while the old rule format uses GID 1.

Because Sourcefire is issuing all these rules in SO format, if you don't want to miss rules for recent vulnerabilities it is definitely important to test and implement the new format and keep the rules updated. When I implemented the rules in production, I used How to use shared object rules in Snort by Richard Bejtlich as a guide for adding shared object rules to Snort. It seems fairly complete and I was able to get the precompiled rules working on RHEL/CentOS.

Unfortunately, getting the rules working and updating them is not as simple and certainly not identical to updating rules that use the old format. Oinkmaster will edit the stub files to enable and disable the SO rules, but it requires running Oinkmaster a second time with a separate configuration file. From the Oinkmaster FAQ:

Q34: Can Oinkmaster update the shared object rules (so_rules)?

A34: Yes, but you have to run Oinkmaster separately with its own
configuration file. Copy your regular oinkmaster.conf file
to oinkmaster-so-rules.conf (or create a new one) and set
"rules_dir = so_rules". Then run Oinkmaster with
-C <path tooinkmaster-so-rules.conf> and use an output directory
(-o <dir>) different than your regular rules directory. This is
important as the "rules" and "so_rules" directories contains
files with identical filenames. See the Snort documentation on how
to use shared object rules. The shared object rules are currently
disabled by default so you have to use "enablesid" or "modifysid"
to activate the ones you want to use.
To update my rules, I first download the rules and run Oinkmaster to update my GID 1 rules. Then I extract the so_rules so I can run Snort with the '--dump-dynamic-rules' option. This will generate the required stub files, but they will not contain any changes you made to enable or disable specific rules. To change which are enabled or disabled, I run Oinkmaster again with the oinkmaster-so-rules.conf.

For Sguil to properly show the rule msg in the 'Event Message' column on the client, you must generate a new gen-msg.map file. David Bianco gave me a couple shell commands, one of which uses 'create-sidmap.pl' from the Oinkmaster contrib directory, to update the gen-msg.map file.
create-sidmap.pl ruledir/so_rules | perl -ne 'm/(\d+) \|\| ([^\|]+) \|\|/g; print "3 || $1 || $2\n";' > ruledir/so_rules/gen-msg.map

cat ruledir/gen-msg.map ruledir/so_rules/gen-msg.map | sort -n | uniq > ruledir/so_rules/gen-msg-merged.map
After you check that the new file is correct, it can be moved to overwrite the old gen-msg.map. The alert name will be found based on the GID and SID, so without the update you would only see the numerical GID and SID when viewing alerts in Sguil instead of the actual text of the alert message.

One last thing to do is get the "Show Rule" checkbox in Sguil to show the rule stub when viewing SO rules. A quick temporary fix is fairly simple. Either rename and place all the rule stub files in the directory on your sguild server with all the other rules, or use a symbolic link. Either way, you have to use alternate names since the SO rules stub files use the same naming scheme as the standard rule files.

Once that is done, it just requires a simple change to 'extdata.tcl' in the 'lib' directory of the Sguil client. Change the following line:
 if { $genID != "1" } {
to
 if { $genID != "1" && $genID != "3" } {
David Bianco pointed out that there is nothing to prevent SO rules and standard rules from sharing a SID since they have a different GID, so the above solution could get the wrong rule message if there are identical SIDs. He said he will probably add code to look in the ruledir for GID 1, and look in ruledir/so_rules for GID 3. This is definitely the better way and not too complicated, so hopefully Sguil CVS will have the code added in the near future.

06 October, 2008

OpenLDAP continued

After initially configuring, setting up and testing LDAP, I still had a lot to resolve. Issues included password maintenance, 'sudo' only looking in the local sudoers files, adding a sudoers file to LDAP, setting up groups, replication of the LDAP database, and configuring fail-over.

The OpenLDAP Administrator's Guides are extensive and useful, so don't forget to refer to the documentation for initial setup and more advanced topics.

Passwords

OpenLDAP has a default schema, nis.schema, that contains definitions for both Posix accounts and shadow accounts. By including the object class 'shadowAccount' when creating users, it allows defining some password requirements in LDAP.
shadowMin: 0
shadowMax: 180
shadowWarning: 14
shadowInactive: 30
shadowExpire: -1
They are mostly self-explanatory and correspond to local 'passwd' or 'chage' options. On my setup, I also was able to make users change passwords on first login. I tried both 'passwd' and 'chage' to do this, but neither recognized the LDAP accounts when used with options to expire passwords. I can only assume that defining the 'shadowAccount' variables caused the behavior requiring an initial password change, because new users were not forced to change passwords prior to defining the shadow variables. I will edit this with updates if I figure out exactly how to require a password change at initial logon when using LDAP accounts.

Changing passwords with OpenLDAP works the same as local accounts. Simply use 'passwd', enter current LDAP password, then the new password.

Although I have various password requirements set using cracklib in /etc/pam.d/system-auth on RHEL, the checks did not seem to be run when changing LDAP user passwords. After reading the manual for pam_cracklib, I saw that some of my settings were not correct. After correcting the settings, pam_cracklib started enforcing my password complexity requirements. Note that pam_cracklib must be configured on each local system even when the passwords are stored in LDAP. Below is an example configuration from the pam_cracklib manual.
 password  required pam_cracklib.so \
dcredit=-1 ucredit=-1 ocredit=-1 lcredit=0 minlen=8
OpenLDAP also has a password policy overlay, but it does not appear to include enforcement of password complexity. It will enforce password reuse policy, password expiration, and other similar policies. It is not available in OpenLDAP 2.2, but at least on RHEL5 with OpenLDAP 2.3 the password policy overlay seems to be included as a schema.

LDAP Groups

Just as LDAP allows you to centralize user accounts, you can also use it to centralize groups or add LDAP groups in addition to local groups. As with any bulk additions to LDAP, you can use a ldif file to add the groups. For example, the following could be used to add a group called "analysts" and a group called "sr_analysts". The "Group" OU must exist prior to adding these groups, or you can add the Group OU from the same file as long as it comes before the groups that will be within the OU.
dn: cn=sr_analysts,ou=Group,dc=security,dc=test,dc=com
objectClass: top
objectClass: posixGroup
gidNumber: 1010
cn: sr_analysts
memberUid: nexample
memberUid: nother

dn: cn=analysts,ou=Group,dc=security,dc=test,dc=com
objectClass: top
objectClass: posixGroup
gidNumber: 1011
cn: analysts
memberUid: luser
To add the groups and test:
$ ldapadd -x -D "cn=Manager,dc=security,dc=test,dc=com" -W -f groups.ldif
Enter LDAP Password:
adding new entry "cn=sr_analysts,ou=groups,dc=security,dc=test,dc=com"

adding new entry "cn=analysts,ou=groups,dc=security,dc=test,dc=com"

$ getent group
----- snip -----
sr_analysts:x:1010:nexample,nother
analysts:x:1011:luser
sudo

The version of 'sudo' provided in RHEL/CentOS v4 is not compiled for LDAP, while in RHEL/CentOS v5 the package was built using --with-ldap.
$ ldd $(type -p sudo) | grep ldap
libldap-2.3.so.0 => /usr/lib/libldap-2.3.so.0
Whether you need to compile or build a 'sudo' RPM with LDAP support on RHEL4 may depend on how complex your 'sudoers' needs to be. If you only need to add support for certain people to be in the wheel group then you can easily rely on the standard RHEL/CentOS v4 'sudo' by uncommenting wheel in the local 'sudoers' file and creating the needed LDAP user accounts with gidNumber: 10, which is the default for the wheel group on RHEL. As long as the local system has wheel with GID of 10 then the LDAP account will be seen as a local sudoer on the system. If 'sudo' needs are more complex, it may be worth creating a custom RPM for 'sudo' using --with-ldap. I have not tried this with 'sudo', but I have downloaded RHEL5 source RPMs for other software and successfully built a RPM for RHEL4.

If using LDAP on RHEL5, it definitely makes sense to move the 'sudoers' to LDAP. Since the purpose of groups is to ease system administration by grouping users logically, it makes sense to create a 'sudoers' LDAP container and then allow groups to perform 'sudo' commands rather than adding and removing individual accounts.

Prior to adding 'sudoers' and related information into LDAP, I had to add 'schema.OpenLDAP', which is included with the 'sudo' source, to my schema directory and include it in my 'slapd.conf'. I also renamed it to 'sudo.schema'.

After adding the schema, I used another file from the 'sudo' source, README.LDAP, as an example for creating the following ldif file. I tried using sudoOption: env_keep, but kept getting errors saying the option was not recognized despite the examples I've seen showing its use.
dn: ou=SUDOers,ou=role,dc=security,dc=test,dc=com
objectClass: top
objectClass: organizationalUnit
ou: SUDOers

dn: cn=defaults,ou=SUDOers,ou=role,dc=security,dc=test,dc=com
objectClass: top
objectClass: sudoRole
cn: defaults
description: Default sudo options go here
sudoOption: requiretty
sudoOption: env_reset

dn: cn=analyst_script,ou=SUDOers,ou=role,dc=security,dc=test,dc=com
objectClass: top
objectClass: sudoRole
cn: analyst_script
sudoUser: %analysts
sudoHost: ALL
sudoCommand: /path/to/script
sudoOption: !authenticate

dn: cn=sr_analysts_all,ou=SUDOers,ou=role,dc=security,dc=test,dc=com
objectClass: top
objectClass: sudoRole
cn: sr_wheel
sudoUser: %sr_analysts
sudoHost: ALL
sudoCommand: ALL
Those in the analysts LDAP group will be able to run /path/to/script with no password while those in the sr_analysts group can run all commands but a password is required. As another example, changing sudoUser to '%wheel' would allow all accounts in the 'wheel' group to execute sudoCommand. The 'cn' for the 'sudoRole' does not have to correspond to anything, but it makes sense to name it something that reflects what the role is for.

The percent sign for 'sudoUser' is only used in front of group names, not users, the same syntax as the local 'sudoers' file.

Finally, don't forget to modify /etc/ldap.conf to include sudoers_base and sudoers_debug. Debug can normally be set to '0' but setting it to '2' for troubleshooting is extremely useful. The following shows the output of sudo -l with sudoers_debug 2. It is the result of the wheel group being in both the local and LDAP sudoers.
[nr@ldap ~]$ sudo -l
LDAP Config Summary
===================
host 192.168.1.10
port 389
ldap_version 3
sudoers_base ou=SUDOers,ou=roles,dc=security,dc=test,dc=com
binddn cn=proxyuser,ou=roles,dc=security,dc=test,dc=com
bindpw PASSWORD
ssl start_tls
===================
ldap_set_option(LDAP_OPT_X_TLS_REQUIRE_CERT,0x00)
ldap_init(192.168.1.10,389)
ldap_set_option(LDAP_OPT_PROTOCOL_VERSION,0x03)
ldap_start_tls_s() ok
ldap_bind() ok
found:cn=defaults,ou=SUDOers,ou=roles,dc=security,dc=test,dc=com
ldap sudoOption: 'requiretty'
ldap sudoOption: 'env_reset'
ldap search '(|(sudoUser=nr)(sudoUser=%wheel)(sudoUser=%wheel)(sudoUser=%sr_analysts)(sudoUser=ALL))'
found:cn=analyst_script,ou=SUDOers,ou=roles,dc=security,dc=test,dc=com
ldap sudoHost 'ALL' ... MATCH!
found:cn=sr_analysts_all,ou=SUDOers,ou=roles,dc=security,dc=test,dc=com
ldap sudoHost 'ALL' ... MATCH!
ldap search 'sudoUser=+*'
user_matches=-1
host_matches=-1
sudo_ldap_check(50)=0x02
Password:
User nr may run the following commands on this host:
(ALL) ALL

LDAP Role: sr_analysts_all
Commands:
ALL
Also note that the OpenLDAP Administrator's Guide has an overlay for dynamic lists, which includes the functions available in the deprecated dynamic groups overlay.

Replication


Replication and fail-over are both relatively simple to configure on the OpenLDAP server. Replication either uses 'slurpd' or 'Syncrepl', depending on the OpenLDAP version. For replication with 'slurpd', the replogfile , replica host or replica uri, binddn, bindmethod, and credentials variables need to be set in slapd.conf. Because a 'binddn' and password ('credentials') need to be used for replication, the binddn and password also have to be added to LDAP before replication will work.

Once the primary server is prepared, slapd needs to be stopped or set to read-only so the existing OpenLDAP database files can be manually copied over to the server receiving the replications. Configuration files on the receiving server also need to be edited, though I was able to just copy my 'slapd.conf' and schemas. The only changes needed to 'slapd.conf' were to remove all the replication directives and replace them with the 'updatedn' that was identical to the replication 'binddn' on the primary server. I also had to make sure that 'binddn' was allowed 'write' so it could add the replicated data. If not, you will get Error: insufficent access when trying to replicate.

Once the configuration files and database files are copied to the secondary server, 'slapd' needs to be (re)started on both systems. Then 'slurpd' needs to be started so it can periodically check the log file for changes that can be pushed. On RHEL, the 'ldap' init script handles stops, starts, and restarts for both 'slapd' and 'slurpd' at the same time rather than having separate scripts, so service ldap restart would restart both daemons.

For failover, the LDAP clients' host variable can have a list of hosts separated by spaces, and bind_timelimit is used to determine when the client fails over to the next server. If the first server is unreachable, the client will go on to the next one in the list of hosts.

LDAP Management

Although using LDIF files is fine at first, managing LDAP entries can become a chore from the command line. The 'sudo' README has a few recommendations that I quote below.
Doing a one-time bulk load of your ldap entries is fine.  However what if you
need to make minor changes on a daily basis? It doesn't make sense to delete
and re-add objects. (You can, but this is tedious).

I recommend using any of the following LDAP browsers to administer your SUDOers.
* GQ - The gentleman's LDAP client - Open Source - I use this a lot on Linux
and since it is Schema aware, I don't need to create a sudoRole template.
http://biot.com/gq/

* LDAP Browser/Editor - by Jarek Gawor - I use this a lot on Windows
and Solaris. It runs anywhere in a Java Virtual Machine including
web pages. You have to make a template from an existing sudoRole entry.
http://www.iit.edu/~gawojar/ldap
http://www.mcs.anl.gov/~gawor/ldap
http://ldapmanager.com

* Apache Directory Studio - Open Source - an Eclipse-based LDAP
development platform. Includes an LDAP browser, and LDIF editor,
a schema editor and more.
http://directory.apache.org/studio

There are dozens of others, some Open Source, some free, some not.