Skip to content

Commit

Permalink
Convert patterns to JSON input files.
Browse files Browse the repository at this point in the history
Tallow will now read JSON files from /usr/share/tallow/ and /etc/tallow
and parse them to retrieve filters and patterns. The sshd patterns
are converted to JSON and used to test this change.

If a file exists in /etc/tallow with the same name as a file in
/usr/share/tallow, only the file in /etc/tallow will be parsed.

This change allows much more dynamic insertion of rules and people
to create custom patterns and filters and monitor the logs of other
daemons besides sshd that may be subject to brutefoce login attempts.

Potential use cases:
- IMAP/POP services
- SMTP
- HTTP services permitted they log to syslog
- DNS servers logging malformed requests
- etc.
  • Loading branch information
ahkok committed Jan 23, 2019
1 parent 14152b1 commit 9174590
Show file tree
Hide file tree
Showing 10 changed files with 581 additions and 162 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ missing
tallow
tallow-*.tar.gz
tallow-*/
tallow.o
*.o
tallow.service
*~
DEADJOE
23 changes: 17 additions & 6 deletions Makefile.am
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@

AM_CFLAGS = -g $(PCRE_CFLAGS) $(LIBSYSTEMD_CFLAGS) -Wall -Wno-uninitialized -W -D_FORTIFY_SOURCE=2
AM_CFLAGS = -g \
$(JSON_C_CFLAGS) $(PCRE_CFLAGS) $(LIBSYSTEMD_CFLAGS) \
-Wall -Wno-uninitialized -W -D_FORTIFY_SOURCE=2

AM_CPPFLAGS = \
-DDATADIR='"$(datadir)"' -DSYSCONFDIR='"$(sysconfdir)"'

systemdsystemunitdir = @SYSTEMD_SYSTEMUNITDIR@
systemdsystemunit_DATA = tallow.service
systemdsystemunit_DATA = data/tallow.service

sbin_PROGRAMS = tallow
tallow_SOURCES = tallow.c
tallow_LDADD = $(PCRE_LIBS) $(LIBSYSTEMD_LIBS)
tallow_SOURCES = tallow.c json.c json.h data.c data.h
tallow_LDADD = $(JSON_C_LIBS) $(PCRE_LIBS) $(LIBSYSTEMD_LIBS)

pkgdata_DATA = data/sshd.json

EXTRA_DIST = AUTHORS COPYING INSTALL README.md tallow.service.in tallow.conf.5.md tallow.1.md
EXTRA_DIST = \
AUTHORS COPYING INSTALL README.md \
data/tallow.service.in \
data/sshd.json \
tallow.conf.5.md \
tallow.1.md

dist_man_MANS = tallow.1 tallow.conf.5

Expand All @@ -23,4 +35,3 @@ tallow.conf.5: tallow.conf.5.md

tallow.1: tallow.1.md
ronn -r tallow.1.md --pipe > tallow.1

5 changes: 3 additions & 2 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.64])
AC_INIT([tallow], [14], [[email protected]])
AC_INIT([tallow], [15], [[email protected]])
AM_INIT_AUTOMAKE([foreign])
AC_CONFIG_FILES([Makefile])

Expand All @@ -11,6 +11,7 @@ AC_PROG_CC
AC_PROG_INSTALL

PKG_CHECK_MODULES(PCRE, libpcre)
PKG_CHECK_MODULES(JSON_C, json-c)
PKG_CHECK_MODULES(LIBSYSTEMD, libsystemd,, [PKG_CHECK_MODULES(LIBSYSTEMD, libsystemd-journal)])
AC_SUBST(LIBSYSTEMD_CFLAGS)
AC_SUBST(LIBSYSTEMD_LIBS)
Expand All @@ -28,5 +29,5 @@ fi
AC_CHECK_HEADERS([stdlib.h stdio.h string.h stdarg.h limits.h sys/time.h])

AC_OUTPUT([
tallow.service
data/tallow.service
])
171 changes: 171 additions & 0 deletions data.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
* data.h - IP block sshd login abuse
*
* (C) Copyright 2019 Intel Corporation
* Authors:
* Auke Kok <[email protected]>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 3
* of the License.
*/

#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/time.h>

#include <pcre.h>

#include "data.h"

struct block_struct *blocks;
struct pattern_struct *patterns;
struct filter_struct *filters;
struct whitelist_struct *whitelist;

void filter_add(const char *filter)
{
struct filter_struct *h = filters;

/* filter duplicates */
while (h) {
if (strcmp(h->filter, filter) == 0)
return;

h = h->next;
}

struct filter_struct *f = calloc(1, sizeof(struct filter_struct));
if (!f) {
perror("calloc()");
exit(EXIT_FAILURE);
}

f->filter = strdup(filter);

h = filters;
if (!h) {
filters = f;
} else {
while (h->next)
h = h->next;
h->next = f;
}
}

void pattern_add(const char *pattern, int ban, double score)
{
struct pattern_struct *p = calloc(1, sizeof(struct pattern_struct));
if (!p) {
perror("calloc()");
exit(EXIT_FAILURE);
}

p->pattern = strdup(pattern);
p->instant_block = ban;
p->weight = score;

const char *pcre_err;
int err;
p->re = pcre_compile(pattern, 0, &pcre_err, &err, NULL);
if (!p->re) {
fprintf(stderr, "PCRE compilation failed. Offset %d: %s\n",
err, pcre_err);
exit(EXIT_FAILURE);
}

struct pattern_struct *h = patterns;
if (!h) {
patterns = p;
} else {
while (h->next)
h = h->next;
h->next = p;
}
}

void whitelist_add(const char *ip)
{
struct whitelist_struct *w = calloc(1, sizeof(struct whitelist_struct));

if (!w) {
perror("calloc()");
exit(EXIT_FAILURE);
}

w->ip = strdup(ip);

size_t l = strlen(ip);
if ((ip[l-1] == '.') || (ip[l-1] == ':'))
w->len = l;
else
w->len = -1;

struct whitelist_struct *h = whitelist;
if (!h) {
whitelist = w;
} else {
while (h->next)
h = h->next;
h->next = w;
}
}

bool whitelist_find(const char *ip)
{
struct whitelist_struct *w = whitelist;
while (w) {
if (w->len > 0) {
if (!strncmp(w->ip, ip, w->len))
return (true);
} else {
if (!strcmp(w->ip, ip))
return (true);
}
w = w->next;
}

return (false);
}

void prune(int expires)
{
struct block_struct *s = blocks;
struct block_struct *p;
struct timeval tv;

(void) gettimeofday(&tv, NULL);
p = NULL;

while (s) {
/*
* Expire all records, but if they are blocked, make sure to
* expire them *before* the ipset rule expires, otherwise
* you might get an IP to bypass checks.
*/
time_t age = tv.tv_sec - s->time.tv_sec;
if ((age > expires) ||
((s->blocked) && (age > expires / 2))) {
dbg("Expired record for %s\n", s->ip);
if (p) {
p->next = s->next;
free(s->ip);
free(s);
s = p->next;
continue;
} else {
blocks = s->next;
free(s->ip);
free(s);
s = blocks;
p = NULL;
continue;
}
}
p = s;
s = s->next;
}
}
60 changes: 60 additions & 0 deletions data.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* data.h - IP block sshd login abuse
*
* (C) Copyright 2019 Intel Corporation
* Authors:
* Auke Kok <[email protected]>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 3
* of the License.
*/

#pragma once

#include <pcre.h>

#ifdef DEBUG
#define dbg(args...) fprintf(stderr, ##args)
#else
#define dbg(args...) do {} while (0)
#endif

struct block_struct {
char *ip;
float score;
struct timeval time;
struct block_struct *next;
bool blocked;
};

struct whitelist_struct {
char *ip;
size_t len;
struct whitelist_struct *next;
};

struct pattern_struct {
int instant_block;
float weight;
char *pattern;
pcre *re;
struct pattern_struct *next;
};

struct filter_struct {
char *filter;
struct filter_struct *next;
};

extern struct block_struct *blocks;
extern struct pattern_struct *patterns;
extern struct filter_struct *filters;
extern struct whitelist_struct *whitelist;

void filter_add(const char *filter);
void pattern_add(const char *pattern, int ban, double score);
void whitelist_add(const char *ip);
bool whitelist_find(const char *ip);
void prune(int expires);
57 changes: 57 additions & 0 deletions data/sshd.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
[
{
"filter": "SYSLOG_IDENTIFIER=sshd",
"items": [
{
"ban": 0,
"score": 0.2,
"pattern": "MESSAGE=Failed .* for .* from ([0-9a-z:.]+) port \\d+ ssh2"
},
{
"ban": 0,
"score": 0.2,
"pattern": "MESSAGE=error: PAM: Authentication failure for .* from ([0-9a-z:.]+)"
},
{
"ban": 10,
"score": 0.2,
"pattern": "MESSAGE=Invalid user .* from ([0-9a-z:.]+) port \\d+"
},
{
"ban": 10,
"score": 0.3,
"pattern": "MESSAGE=Did not receive identification string from ([0-9a-z:.]+) port \\d+"
},
{
"ban": 15,
"score": 0.4,
"pattern": "MESSAGE=Bad protocol version identification .* from ([0-9a-z:.]+)"
},
{
"ban": 15,
"score": 0.4,
"pattern": "MESSAGE=Connection closed by authenticating user .* ([0-9a-z:.]+) port \\d+"
},
{
"ban": 10,
"score": 0.3,
"pattern": "MESSAGE=Received disconnect from ([0-9a-z:.]+) port .*\\[preauth\\]"
},
{
"ban": 10,
"score": 0.3,
"pattern": "MESSAGE=Connection closed by ([0-9a-z:.]+) port .*\\[preauth\\]"
},
{
"ban": 30,
"score": 0.5,
"pattern": "MESSAGE=Failed .* for root from ([0-9a-z:.]+) port \\d+ ssh2"
},
{
"ban": 60,
"score": 0.6,
"pattern": "MESSAGE=Unable to negotiate with ([0-9a-z:.]+) port \\d+: no matching key exchange method found."
}
]
}
]
File renamed without changes.
Loading

0 comments on commit 9174590

Please sign in to comment.