From 95db8a64865bbb31c10889d2e787857a8bc3d479 Mon Sep 17 00:00:00 2001 From: BlackLight Date: Thu, 3 Feb 2011 01:01:36 +0100 Subject: [PATCH] Python support added --- Makefile.am | 3 +- Makefile.in | 3 +- README | 37 ++- corr_modules/example.py | 28 +++ correlation.c | 104 +++++++- htdocs/alerts.cgi | 86 +++++++ modules.c | 133 ++++++++++ pymodule/README | 20 ++ pymodule/module.py | 21 ++ pymodule/setup.py | 34 +++ pymodule/snortai_module.c | 496 ++++++++++++++++++++++++++++++++++++++ spp_ai.h | 77 +++++- webserv.c | 2 + 13 files changed, 1030 insertions(+), 14 deletions(-) create mode 100644 corr_modules/example.py create mode 100755 htdocs/alerts.cgi create mode 100644 pymodule/README create mode 100644 pymodule/module.py create mode 100644 pymodule/setup.py create mode 100644 pymodule/snortai_module.c diff --git a/Makefile.am b/Makefile.am index b276381..6dc9184 100644 --- a/Makefile.am +++ b/Makefile.am @@ -72,7 +72,8 @@ fi install -m 0644 "${PWD}/htdocs/manual_uncorrelations.xml" "${SHARE_PREFIX}/htdocs" install -m 0755 "${PWD}/htdocs/pcap.cgi" "${SHARE_PREFIX}/htdocs" install -m 0755 "${PWD}/htdocs/correlate.cgi" "${SHARE_PREFIX}/htdocs" - install -m 0755 "${PWD}/htdocs/default.xsl" "${SHARE_PREFIX}/htdocs" + install -m 0755 "${PWD}/htdocs/alerts.cgi" "${SHARE_PREFIX}/htdocs" + install -m 0644 "${PWD}/htdocs/default.xsl" "${SHARE_PREFIX}/htdocs" install -m 0644 "${PWD}/htdocs/js/Curry-1.0.1.js" "${SHARE_PREFIX}/htdocs/js" install -m 0644 "${PWD}/htdocs/js/dracula_algorithms.js" "${SHARE_PREFIX}/htdocs/js" install -m 0644 "${PWD}/htdocs/js/dracula_graffle.js" "${SHARE_PREFIX}/htdocs/js" diff --git a/Makefile.in b/Makefile.in index 7956509..30be185 100644 --- a/Makefile.in +++ b/Makefile.in @@ -877,7 +877,8 @@ fi install -m 0644 "${PWD}/htdocs/manual_uncorrelations.xml" "${SHARE_PREFIX}/htdocs" install -m 0755 "${PWD}/htdocs/pcap.cgi" "${SHARE_PREFIX}/htdocs" install -m 0755 "${PWD}/htdocs/correlate.cgi" "${SHARE_PREFIX}/htdocs" - install -m 0755 "${PWD}/htdocs/default.xsl" "${SHARE_PREFIX}/htdocs" + install -m 0755 "${PWD}/htdocs/alerts.cgi" "${SHARE_PREFIX}/htdocs" + install -m 0644 "${PWD}/htdocs/default.xsl" "${SHARE_PREFIX}/htdocs" install -m 0644 "${PWD}/htdocs/js/Curry-1.0.1.js" "${SHARE_PREFIX}/htdocs/js" install -m 0644 "${PWD}/htdocs/js/dracula_algorithms.js" "${SHARE_PREFIX}/htdocs/js" install -m 0644 "${PWD}/htdocs/js/dracula_graffle.js" "${SHARE_PREFIX}/htdocs/js" diff --git a/README b/README index e2bae56..6841851 100644 --- a/README +++ b/README @@ -132,7 +132,14 @@ by tools like tcpdump and Wireshark - XML::Simple Perl module (RECOMMENDED), used by 'correlate.cgi' CGI script for reading and writing manual (un)correlations XML files. A quick way for -installing it on a Unix system is by using CPAN: +installing it on a Unix system is by using CPAN. + +- Python 2.6 (OPTIONAL), used for interfacing SnortAI module to Python scripts +through snortai module (see README file in pymodule/) and writing new +correlation modules (see example.py in corr_modules/). +Compile the module passing --with-python option to the ./configure script if you +want this feature. You need Python interpreter and libpython2.6 installed on +your system. # cpan XML::Simple @@ -574,6 +581,34 @@ libsf_ai_preproc.la if you want to use some of the functions from the module, for example, for reading the alerts stored in the history file, in the database, the current correlations, and so on. +It is also possible to write your own modules in Python language. See the file +'example.py' in corr_modules/ for a quick overview. All you need to do is to +declare in your module the functions AI_corr_index (taking two arguments, two + alert descriptions) and AI_corr_index_weight (taking no argument), + both returning a real value descibing, respectively, the correlation + value between the two alerts and the weight of that index, both between + 0 and 1. You can also access the alert information and all the alerts + acquired so far by the module by importing in your Python code the + 'snortai' module. You can compile it and install it by moving to + 'pymodule/' directory and running + +$ python setup.py build +$ [sudo] python setup.py install + +You can acquire the current alerts by writing a code like the following: + +import snortai + +alerts = snortai.alerts() + +for alert in alerts: + # Access the alerts information + +The fields in the alert class can be viewed in pymodule/module.py and +corr_modules/example.py examples. Take these files as guides for interfacing +your Python scripts with SnortAI module or writing new correlation modules in +Python. + =========================== 9. Additional documentation diff --git a/corr_modules/example.py b/corr_modules/example.py new file mode 100644 index 0000000..b407fc7 --- /dev/null +++ b/corr_modules/example.py @@ -0,0 +1,28 @@ +#!/usr/bin/python + +# Example correlation index in Python + +# XXX You may have an 'undefined reference to PyNone_Struct +# after running Snort with your module, you're facing an +# annoying bug due to the dynamically linked Python library. +# I'm sorry, but I'm still looking for a solution for this, +# and anyway it only happens when you import the module +# 'snortai' + +# import snortai + +# Function that takes two alerts as arguments (arguments of +# alert object: +# id, gid, sid, rev, description, priority, classification, +# timestamp, from, to, from_port, to_port, latitude, +# longitude, alerts_count) and returns a correlation index +# between 0 and 1 expressing how correlated these two alerts are + +def AI_corr_index ( alert1, alert2 ): + return 0.0 + +# Return the weight of this index, between 0 and 1 + +def AI_corr_index_weight(): + return 0.0 + diff --git a/correlation.c b/correlation.c index 36e984f..3c28052 100644 --- a/correlation.c +++ b/correlation.c @@ -32,6 +32,25 @@ #include #endif +#ifdef HAVE_LIBPYTHON2_6 +/*******************************************/ +/* Avoid conflicts with Snort header files */ +#ifdef _POSIX_C_SOURCE +#undef _POSIX_C_SOURCE +#endif + +#ifdef _XOPEN_C_SOURCE +#undef _XOPEN_C_SOURCE +#endif + +#ifdef _XOPEN_SOURCE +#undef _XOPEN_SOURCE +#endif +/*******************************************/ + +#include +#endif + /** \defgroup correlation Module for the correlation of security alerts * @{ */ @@ -365,8 +384,27 @@ AI_alert_correlation_thread ( void *arg ) double (**corr_functions)( const AI_snort_alert*, const AI_snort_alert* ) = NULL; double (**corr_weights)() = NULL; - corr_functions = AI_get_corr_functions( &n_corr_functions ); + #ifdef HAVE_LIBPYTHON2_6 + PyAlert *pyA = NULL, + *pyB = NULL; + + PyObject *pArgs = NULL, + *pRet = NULL; + + PyObject **py_corr_functions = NULL; + PyObject **py_weight_functions = NULL; + + size_t n_py_corr_functions = 0; + size_t n_py_weight_functions = 0; + + double py_value = 0.0, + py_weight = 0.0; + #endif + + corr_functions = AI_get_corr_functions ( &n_corr_functions ); corr_weights = AI_get_corr_weights ( &n_corr_weights ); + py_corr_functions = AI_get_py_functions ( &n_py_corr_functions ); + py_weight_functions = AI_get_py_weights ( &n_py_weight_functions ); pthread_mutex_init ( &mutex, NULL ); @@ -466,6 +504,64 @@ AI_alert_correlation_thread ( void *arg ) } } + #ifdef HAVE_LIBPYTHON2_6 + if (( py_corr_functions )) + { + pyA = AI_alert_to_pyalert ( corr_key.a ); + pyB = AI_alert_to_pyalert ( corr_key.b ); + + if ( pyA && pyB ) + { + for ( i=0; i < n_py_corr_functions; i++ ) + { + if ( !( pArgs = Py_BuildValue ( "(OO)", pyA, pyB ))) + { + PyErr_Print(); + AI_fatal_err ( "Could not initialize the Python arguments for the call", __FILE__, __LINE__ ); + } + + if ( !( pRet = PyEval_CallObject ( py_corr_functions[i], pArgs ))) + { + PyErr_Print(); + AI_fatal_err ( "Could not call the correlation function from the Python module", __FILE__, __LINE__ ); + } + + if ( !( PyArg_Parse ( pRet, "d", &py_value ))) + { + PyErr_Print(); + AI_fatal_err ( "Could not parse the correlation value out of the Python correlation function", __FILE__, __LINE__ ); + } + + Py_DECREF ( pRet ); + Py_DECREF ( pArgs ); + + if ( !( pRet = PyEval_CallObject ( py_weight_functions[i], (PyObject*) NULL ))) + { + PyErr_Print(); + AI_fatal_err ( "Could not call the correlation function from the Python module", __FILE__, __LINE__ ); + } + + if ( !( PyArg_Parse ( pRet, "d", &py_weight ))) + { + PyErr_Print(); + AI_fatal_err ( "Could not parse the correlation weight out of the Python correlation function", __FILE__, __LINE__ ); + } + + Py_DECREF ( pRet ); + + if ( py_weight != 0.0 ) + { + corr->correlation += py_weight * py_value; + n_correlations++; + } + } + + free ( pyA ); free ( pyB ); + pyA = NULL; pyB = NULL; + } + } + #endif + if ( n_correlations != 0 ) { corr->correlation /= (double) n_correlations; @@ -548,10 +644,12 @@ AI_alert_correlation_thread ( void *arg ) ) ) ) { - if ( !( corr->key.a->derived_alerts = ( AI_snort_alert** ) realloc ( corr->key.a->derived_alerts, (++corr->key.a->n_derived_alerts) * sizeof ( AI_snort_alert* )))) + if ( !( corr->key.a->derived_alerts = ( AI_snort_alert** ) realloc ( corr->key.a->derived_alerts, + (++corr->key.a->n_derived_alerts) * sizeof ( AI_snort_alert* )))) AI_fatal_err ( "Fatal dynamic memory allocation error", __FILE__, __LINE__ ); - if ( !( corr->key.b->parent_alerts = ( AI_snort_alert** ) realloc ( corr->key.b->parent_alerts, (++corr->key.b->n_parent_alerts) * sizeof ( AI_snort_alert* )))) + if ( !( corr->key.b->parent_alerts = ( AI_snort_alert** ) realloc ( corr->key.b->parent_alerts, + (++corr->key.b->n_parent_alerts) * sizeof ( AI_snort_alert* )))) AI_fatal_err ( "Fatal dynamic memory allocation error", __FILE__, __LINE__ ); corr->key.a->derived_alerts[ corr->key.a->n_derived_alerts - 1 ] = corr->key.b; diff --git a/htdocs/alerts.cgi b/htdocs/alerts.cgi new file mode 100755 index 0000000..cb0df7a --- /dev/null +++ b/htdocs/alerts.cgi @@ -0,0 +1,86 @@ +#!/usr/bin/env perl + +use Env qw(DOCUMENT_ROOT QUERY_STRING); +use strict; +use warnings; + +my $method = 'xml'; + +if ( $QUERY_STRING ) +{ + if ( $QUERY_STRING =~ /method=([a-z]+)/ ) + { + if ( $1 eq 'json' or $1 eq 'xml' ) + { + $method = $1; + } + } +} + +my %mon2num = qw( jan 1 feb 2 mar 3 apr 4 may 5 jun 6 jul 7 aug 8 sep 9 oct 10 nov 11 dec 12 ); +my $json_file = (( $DOCUMENT_ROOT ) ? $DOCUMENT_ROOT : '.' ).'/correlation_graph.json'; +my $json_string = ''; + +open IN, $json_file or die "Alert JSON file not found"; +$json_string .= $_ while ( ); +close IN; + +if ( $method eq 'json' ) +{ + print "Content-Type: application/json\n\n"; + print $json_string; +} elsif ( $method eq 'xml' ) { + use JSON; + use Time::Local; + + my @json = @{JSON->new->utf8->decode ( $json_string )}; + print "Content-Type: application/xml\n\n". + "\n\n". + "\n"; + + for ( @json ) + { + print "\t\n"; + } + + print "\n"; +} + diff --git a/modules.c b/modules.c index 0b8b97c..1f183b5 100644 --- a/modules.c +++ b/modules.c @@ -33,6 +33,16 @@ PRIVATE size_t n_corr_functions = 0; PRIVATE double (**weight_functions)() = NULL; PRIVATE size_t n_weight_functions = 0; +#ifdef HAVE_LIBPYTHON2_6 + +PRIVATE PyObject **py_corr_functions = NULL; +PRIVATE size_t n_py_corr_functions = 0; + +PRIVATE PyObject **py_weight_functions = NULL; +PRIVATE size_t n_py_weight_functions = 0; + +#endif + /** * \brief Get the correlation functions from the extra correlation modules as array of function pointers * \param n_functions Number of function pointers in the array @@ -59,6 +69,71 @@ double return weight_functions; } /* ----- end of function AI_get_corr_weights ----- */ +#ifdef HAVE_LIBPYTHON2_6 +/** + * \brief Get the correlation functions from the Python modules, if Python support is enabled + * \param n_functions Reference to the number of functions in the array + * \return The array of Python correlation functions as PyObject** + */ + +PyObject** +AI_get_py_functions ( size_t *n_functions ) +{ + *n_functions = n_py_corr_functions; + return py_corr_functions; +} /* ----- end of function AI_get_py_functions ----- */ + +/** + * \brief Get the correlation index weights from the Python modules, if Python support is enabled + * \param n_functions Reference to the number of correlation weight functions in the array + * \return The array of correlation weight functions as PyObject** + */ + +PyObject** +AI_get_py_weights ( size_t *n_functions ) +{ + *n_functions = n_py_weight_functions; + return py_weight_functions; +} /* ----- end of function AI_get_py_weights ----- */ + +/** + * \brief Convert an AI_snort_alert object to a PyAlert object that can be managed by a Python module + * \param alert AI_snort_alert object to be converted + * \return A PyAlert object wrapping the original AI_snort_alert object + */ + +PyAlert* +AI_alert_to_pyalert ( AI_snort_alert *alert ) +{ + PyAlert *pyalert = NULL; + char src_addr[INET_ADDRSTRLEN] = { 0 }, + dst_addr[INET_ADDRSTRLEN] = { 0 }; + + if ( !( pyalert = (PyAlert*) malloc ( sizeof ( PyAlert )))) + { + AI_fatal_err ( "Fatal dynamic memory allocation error", __FILE__, __LINE__ ); + } + + inet_ntop ( AF_INET, &(alert->ip_src_addr), src_addr, INET_ADDRSTRLEN ); + inet_ntop ( AF_INET, &(alert->ip_dst_addr), dst_addr, INET_ADDRSTRLEN ); + + pyalert->classification = alert->classification ? PyString_FromString ( alert->classification ) : Py_None; + pyalert->clusteredAlertsCount = alert->grouped_alerts_count; + pyalert->desc = alert->desc ? PyString_FromString ( alert->desc ) : Py_None; + pyalert->gid = alert->gid; + pyalert->ip_src_addr = ( strlen ( src_addr ) > 0 ) ? PyString_FromString ( src_addr ) : Py_None; + pyalert->ip_dst_addr = ( strlen ( dst_addr ) > 0 ) ? PyString_FromString ( dst_addr ) : Py_None; + pyalert->priority = alert->priority; + pyalert->rev = alert->rev; + pyalert->sid = alert->sid; + pyalert->tcp_src_port = ntohs ( alert->tcp_src_port ); + pyalert->tcp_dst_port = ntohs ( alert->tcp_dst_port ); + pyalert->timestamp = alert->timestamp; + + return pyalert; +} /* ----- end of function AI_alert_to_pyalert ----- */ +#endif + /** * \brief Initialize the extra modules provided by the user */ @@ -73,6 +148,12 @@ AI_init_corr_modules () size_t n_dl_handles = 0; struct dirent *dir_info = NULL; + #ifdef HAVE_LIBPYTHON2_6 + char *pyPath = NULL; + PyObject *pObj = NULL; + BOOL isPyInit = false; + #endif + if ( !( dir = opendir ( config->corr_modules_dir ))) { return; @@ -141,6 +222,58 @@ AI_init_corr_modules () AI_fatal_err ( "dlsym error", __FILE__, __LINE__ ); } + } else if ( preg_match ( "\\.py$", dir_info->d_name, NULL, NULL )) { + #ifdef HAVE_LIBPYTHON2_6 + + if ( !( pyPath = (char*) malloc ( strlen ( config->corr_modules_dir ) + strlen ( Py_GetPath() ) + 4 ))) + { + AI_fatal_err ( "Fatal dynamic memory allocation error", __FILE__, __LINE__ ); + } + + fname = strdup ( dir_info->d_name ); + fname[strlen ( fname ) - 3] = 0; + + if ( !isPyInit ) + { + Py_Initialize(); + isPyInit = true; + } + + sprintf ( pyPath, "%s:%s", config->corr_modules_dir, Py_GetPath() ); + PySys_SetPath ( pyPath ); + + if ( !( pObj = PyImport_ImportModule ( fname ))) + { + PyErr_Print(); + AI_fatal_err ( "Could not load a Python correlation module", __FILE__, __LINE__ ); + } + + free ( fname ); + fname = NULL; + + if ( !( py_corr_functions = (PyObject**) realloc ( py_corr_functions, (++n_py_corr_functions) * sizeof ( PyObject* )))) + { + AI_fatal_err ( "Fatal dynamic memory allocation error", __FILE__, __LINE__ ); + } + + if ( !( py_corr_functions[ n_py_corr_functions - 1 ] = PyObject_GetAttrString ( pObj, "AI_corr_index" ))) + { + AI_fatal_err ( "AI_corr_index() method not found in the Python correlation module", __FILE__, __LINE__ ); + } + + if ( !( py_weight_functions = (PyObject**) realloc ( py_weight_functions, (++n_py_weight_functions) * sizeof ( PyObject* )))) + { + AI_fatal_err ( "Fatal dynamic memory allocation error", __FILE__, __LINE__ ); + } + + if ( !( py_weight_functions[ n_py_weight_functions - 1 ] = PyObject_GetAttrString ( pObj, "AI_corr_index_weight" ))) + { + AI_fatal_err ( "AI_corr_index_weight() method not found in the Python correlation module", __FILE__, __LINE__ ); + } + + Py_DECREF ( pObj ); + + #endif } } diff --git a/pymodule/README b/pymodule/README new file mode 100644 index 0000000..7b4ca19 --- /dev/null +++ b/pymodule/README @@ -0,0 +1,20 @@ +Python module for interfacing with SnortAI. Compile it and install it through + +$ python setup.py build +$ [sudo] python setup.py install + +You can then access the alerts information captured by Snort simply by writing a +code like the following (also see module.py): + +import snortai + +alerts = snortai.alerts() + +for alert in alerts: + # Access the information + +The alert class has the following members: + +# id, gid, sid, rev, description, priority, classification, timestamp +# from, to, from_port, to_port, latitude, longitude, alerts_count + diff --git a/pymodule/module.py b/pymodule/module.py new file mode 100644 index 0000000..9cbcba7 --- /dev/null +++ b/pymodule/module.py @@ -0,0 +1,21 @@ +#!/usr/bin/python + +# Compile snortai module by typing, in pymodule directory: +# $ python setup.py build +# $ [sudo] python setup.py install +import snortai + +# Get the alerts from Snort module as tuple +# (IMPORTANT: Snort and SnortAI module, as well as +# the web server running on top of the module, must +# be running in order to have this call successful) +alerts = snortai.alerts() + +# Navigate the tuple of alerts +# Fields: +# id, gid, sid, rev, description, priority, classification, +# timestamp, from, to, from_port, to_port, latitude, +# longitude, alerts_count +for alert in alerts: + print alert.gid, alert.sid, alert.rev, alert.description + diff --git a/pymodule/setup.py b/pymodule/setup.py new file mode 100644 index 0000000..e2f60aa --- /dev/null +++ b/pymodule/setup.py @@ -0,0 +1,34 @@ +#!/usr/bin/python + +from distutils.core import setup, Extension +import commands +import re + +xml_include = commands.getoutput ( 'pkg-config --cflags libxml-2.0' ) +m = re.match ( '^-I\s*(.+?)\s*$', xml_include ) + +if m: + xml_include = m.group ( 1 ) + +xml_libs = commands.getoutput ( 'pkg-config --libs libxml-2.0' ) +m = re.match ( '^-l\s*(.+?)\s*$', xml_libs ) + +if m: + xml_libs = m.group ( 1 ) + +setup ( + name = 'snortai', + version = '0.1', + description = 'Python interface to SnortAI module', + author = 'Fabio "BlackLight" Manganiello', + author_email = 'blacklight@autistici.org', + ext_modules = [ + Extension ( + 'snortai', + sources = ['snortai_module.c'], + include_dirs = ['..', '../include', '../uthash', xml_include], + libraries = [xml_libs] + ) + ] +) + diff --git a/pymodule/snortai_module.c b/pymodule/snortai_module.c new file mode 100644 index 0000000..fa7b98c --- /dev/null +++ b/pymodule/snortai_module.c @@ -0,0 +1,496 @@ +/* + * ===================================================================================== + * + * Filename: snortai_module.c + * + * Description: Python module for interfacing to SnortAI + * + * Version: 0.1 + * Created: 28/01/2011 21:33:57 + * Revision: none + * Compiler: gcc + * + * Author: BlackLight (http://0x00.ath.cx), + * Licence: GNU GPL v.3 + * Company: DO WHAT YOU WANT CAUSE A PIRATE IS FREE, YOU ARE A PIRATE! + * + * ===================================================================================== + */ + +#ifndef HAVE_LIBPYTHON2_6 +#define HAVE_LIBPYTHON2_6 1 +#endif + +#include "spp_ai.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifndef LIBXML_READER_ENABLED +#error "libxml2 reader not enabled\n" +#endif + +#ifndef PyMODINIT_FUNC +#define PyMODINIT_FUNC void +#endif + +#ifndef DEFAULT_WEBSERV +#define DEFAULT_WEBSERV "localhost" +#endif + +#ifndef DEFAULT_WEBPORT +#define DEFAULT_WEBPORT 7654 +#endif + +#ifndef DEFAULT_RESOURCE +#define DEFAULT_RESOURCE "/alerts.cgi" +#endif + +/** Enumeration for managing XML response tags */ +enum { inAlerts, inAlert, ALERT_TAG_NUM }; + +static PyObject* Py_alerts ( PyObject *self, PyObject *args ); +static PyObject* PyAlert_new ( PyTypeObject *type, PyObject *args, PyObject *kwds ); +static int PyAlert_init ( PyAlert *self, PyObject *args, PyObject *kwds ); +static void PyAlerts_free ( PyAlert *self ); +PyMODINIT_FUNC initsnortai ( void ); + +char resource[1024] = DEFAULT_RESOURCE; +char webserv[1024] = DEFAULT_WEBSERV; +short int webport = DEFAULT_WEBPORT; +PyObject *exception = NULL; + +/** Members of the Python binded alert object */ +static PyMemberDef alert_members[] = { + { "gid", T_INT, offsetof ( PyAlert, gid ), 0, "Snort alert gID" }, + { "sid", T_INT, offsetof ( PyAlert, sid ), 0, "Snort alert sID" }, + { "rev", T_INT, offsetof ( PyAlert, rev ), 0, "Snort alert revision number" }, + { "priority", T_INT, offsetof ( PyAlert, priority ), 0, "Snort alert priority" }, + { "description", T_OBJECT_EX, offsetof ( PyAlert, desc ), 0, "Snort alert description" }, + { "classification", T_OBJECT_EX, offsetof ( PyAlert, classification ), 0, "Snort alert classification" }, + { "timestamp", T_INT, offsetof ( PyAlert, timestamp ), 0, "Snort alert timestamp" }, + { "from", T_OBJECT_EX, offsetof ( PyAlert, ip_src_addr ), 0, "Source IP address" }, + { "to", T_OBJECT_EX, offsetof ( PyAlert, ip_dst_addr ), 0, "Destination IP address" }, + { "from_port", T_INT, offsetof ( PyAlert, tcp_src_port ), 0, "Source port" }, + { "to_port", T_INT, offsetof ( PyAlert, tcp_dst_port ), 0, "Destination port" }, + { "latitude", T_DOUBLE, offsetof ( PyAlert, latitude ), 0, "Geographical latitude, if available" }, + { "longitude", T_DOUBLE, offsetof ( PyAlert, longitude ), 0, "Geographical longitude, if available" }, + { "alert_count", T_INT, offsetof ( PyAlert, clusteredAlertsCount ), 0, "Number of alerts clustered in this object" }, + { NULL } +}; + +/** Methods inside of the Python binded alert object */ +static PyMethodDef alert_methods[] = {{ NULL }}; + +/** Module methods */ +static PyMethodDef module_methods[] = { + { "alerts", (PyCFunction) Py_alerts, METH_VARARGS, "Return the list of SnortAI alerts" }, + { NULL, NULL, 0, NULL } +}; + +/** Definition of the Python binded alert object */ +static PyTypeObject alert_type = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size*/ + "snortai.__alert", /* tp_name*/ + sizeof(PyAlert), /* tp_basicsize*/ + 0, /* tp_itemsize*/ + (destructor)PyAlerts_free, /* tp_dealloc*/ + 0, /* tp_print*/ + 0, /* tp_getattr*/ + 0, /* tp_setattr*/ + 0, /* tp_compare*/ + 0, /* tp_repr*/ + 0, /* tp_as_number*/ + 0, /* tp_as_sequence*/ + 0, /* tp_as_mapping*/ + 0, /* tp_hash */ + 0, /* tp_call*/ + 0, /* tp_str*/ + 0, /* tp_getattro*/ + 0, /* tp_setattro*/ + 0, /* tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags*/ + "SnortAI alert type", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + alert_methods, /* tp_methods */ + alert_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)PyAlert_init, /* tp_init */ + 0, /* tp_alloc */ + PyAlert_new, /* tp_new */ +}; + +static PyObject* +Py_alerts ( PyObject *module, PyObject *args ) +{ + int i, sd, len = 0, xml_offset = -1; + char addr[INET_ADDRSTRLEN] = { 0 }; + char *response = NULL; + unsigned int response_len = 1; + unsigned int n_alerts = 0; + FILE *fsock = NULL; + struct sockaddr_in server; + struct hostent *host; + PyAlert *alerts = NULL, *cur_alert = NULL, *prev_alert = NULL; + PyObject *self = NULL; + + BOOL xmlFlags[ALERT_TAG_NUM] = { false }; + xmlTextReaderPtr xml; + const xmlChar *tagname; + + /* Connect to the web server and get the XML containing the alerts */ + if ( !( host = gethostbyname ( webserv ))) + { + PyErr_SetString ( exception, "Could not resolve web server name" ); + return NULL; + } + + if ( !( host->h_addr )) + { + PyErr_SetString ( exception, "Could not resolve web server name" ); + return NULL; + } + + snprintf ( addr, sizeof ( addr ), "%u.%u.%u.%u", + (unsigned char) host->h_addr[0], + (unsigned char) host->h_addr[1], + (unsigned char) host->h_addr[2], + (unsigned char) host->h_addr[3] ); + + if (( sd = socket ( AF_INET, SOCK_STREAM, IPPROTO_IP )) < 0 ) + { + PyErr_SetString ( exception, "Could not initialize the socket" ); + return NULL; + } + + server.sin_family = AF_INET; + server.sin_port = htons ( webport ); + server.sin_addr.s_addr = inet_addr ( addr ); + + if ( connect ( sd, ( struct sockaddr* ) &server, sizeof ( struct sockaddr )) < 0 ) + { + PyErr_SetString ( exception, "Could not connect to the web server" ); + close ( sd ); + return NULL; + } + + if ( !( fsock = fdopen ( sd, "r+" ))) + { + PyErr_SetString ( exception, "Could not open the socket" ); + close ( sd ); + return NULL; + } + + fprintf ( fsock, + "GET %s HTTP/1.1\r\n" + "Host: %s\r\n" + "User-Agent: Python SnortAI request\r\n" + "Connection: close\r\n\r\n", + resource, webserv ); + + while ( !feof ( fsock )) + { + if ( !( response = (char*) realloc ( response, ++response_len ))) + { + PyErr_SetString ( exception, "Dynamic memory allocation error" ); + fclose ( fsock ); + close ( sd ); + return NULL; + } + + response[ response_len - 2 ] = fgetc ( fsock ); + } + + response[ (--response_len) - 1 ] = 0; + fclose ( fsock ); + close ( sd ); + /*****************************/ + + /* Remove the HTTP headers from the response */ + if (( xml_offset = (int) strstr ( response, "\n\n" ) - (int) response ) >= 0 ) {} + else if (( xml_offset = (int) strstr ( response, "\r\n\r\n" ) - (int) response ) >= 0 ) {} + else { + PyErr_SetString ( exception, "The HTTP response provided by the server has no valid HTTP header" ); + free ( response ); + return NULL; + } + + len = strlen ( response ); + + for ( i=0; i < len - xml_offset; i++ ) + { + response[i] = response [i + xml_offset]; + } + + response [len - xml_offset] = 0; + + for ( xml_offset=0; response[xml_offset] != '<'; xml_offset++ ); + for ( i=0; i < len - xml_offset; i++ ) + { + response[i] = response[i + xml_offset]; + } + + response [len - xml_offset] = 0; + /*****************************/ + + /* Parse the XML document */ + LIBXML_TEST_VERSION + + if ( !( xml = xmlReaderForMemory ( response, strlen ( response ), NULL, NULL, 0 ))) + { + PyErr_SetString ( exception, "Could not initialize the XML reader object" ); + free ( response ); + return NULL; + } + + while ( xmlTextReaderRead ( xml )) + { + if ( !( tagname = xmlTextReaderConstName ( xml ))) + continue; + + if ( xmlTextReaderNodeType ( xml ) == XML_READER_TYPE_ELEMENT ) + { + if ( !strcasecmp ((const char*) tagname, "alerts" )) + { + if ( xmlFlags[inAlerts] ) + { + PyErr_SetString ( exception, "Parse error in received XML: 'alerts' tag opened twice" ); + free ( response ); + return NULL; + } else { + xmlFlags[inAlerts] = true; + } + } else if ( !strcasecmp ((const char*) tagname, "alert" )) + { + if ( xmlFlags[inAlert] ) + { + PyErr_SetString ( exception, "Parse error in received XML: 'alert' tag opened inside of another 'alert' tag" ); + free ( response ); + return NULL; + } else { + xmlFlags[inAlert] = true; + + /* Fill the PyObject */ + if ( !( cur_alert = (PyAlert*) alert_type.tp_alloc ( &alert_type, 0 ))) + { + PyErr_SetString ( exception, "Could not initialize the PyAlert object" ); + free ( response ); + return NULL; + } + + if ( !alerts ) + { + alerts = cur_alert; + } + + if ( prev_alert ) + { + prev_alert->next = cur_alert; + } + + n_alerts++; + cur_alert->next = NULL; + + cur_alert->id = ( xmlTextReaderGetAttribute ( xml, (const xmlChar*) "id" )) ? + (unsigned int) strtol ((const char*) xmlTextReaderGetAttribute ( xml, (const xmlChar*) "id" ), NULL, 10 ) : 0; + cur_alert->gid = ( xmlTextReaderGetAttribute ( xml, (const xmlChar*) "gid" )) ? + (unsigned int) strtol ((const char*) xmlTextReaderGetAttribute ( xml, (const xmlChar*) "gid" ), NULL, 10 ) : 0; + cur_alert->sid = ( xmlTextReaderGetAttribute ( xml, (const xmlChar*) "sid" )) ? + (unsigned int) strtol ((const char*) xmlTextReaderGetAttribute ( xml, (const xmlChar*) "sid" ), NULL, 10 ) : 0; + cur_alert->rev = ( xmlTextReaderGetAttribute ( xml, (const xmlChar*) "rev" )) ? + (unsigned int) strtol ((const char*) xmlTextReaderGetAttribute ( xml, (const xmlChar*) "rev" ), NULL, 10 ) : 0; + cur_alert->priority = ( xmlTextReaderGetAttribute ( xml, (const xmlChar*) "priority" )) ? + (unsigned int) strtol ((const char*) xmlTextReaderGetAttribute ( xml, (const xmlChar*) "priority" ), NULL, 10 ) : 0; + cur_alert->timestamp = ( xmlTextReaderGetAttribute ( xml, (const xmlChar*) "timestamp" )) ? + (time_t) strtol ((const char*) xmlTextReaderGetAttribute ( xml, (const xmlChar*) "timestamp" ), NULL, 10 ) : (time_t) 0; + cur_alert->tcp_src_port = ( xmlTextReaderGetAttribute ( xml, (const xmlChar*) "from_port" )) ? + (unsigned short) strtol ((const char*) xmlTextReaderGetAttribute ( xml, (const xmlChar*) "from_port" ), NULL, 10 ) : 0; + cur_alert->tcp_dst_port = ( xmlTextReaderGetAttribute ( xml, (const xmlChar*) "to_port" )) ? + (unsigned short) strtol ((const char*) xmlTextReaderGetAttribute ( xml, (const xmlChar*) "to_port" ), NULL, 10 ) : 0; + cur_alert->latitude = ( xmlTextReaderGetAttribute ( xml, (const xmlChar*) "latitude" )) ? + strtod ((const char*) xmlTextReaderGetAttribute ( xml, (const xmlChar*) "latitude" ), NULL ) : 0.0; + cur_alert->longitude = ( xmlTextReaderGetAttribute ( xml, (const xmlChar*) "longitude" )) ? + strtod ((const char*) xmlTextReaderGetAttribute ( xml, (const xmlChar*) "longitude" ), NULL ) : 0.0; + + if ( !( cur_alert->desc = + xmlTextReaderGetAttribute ( xml, (const xmlChar*) "label" ) ? + PyString_FromString ((char*) ( xmlTextReaderGetAttribute ( xml, (const xmlChar*) "label" ))) : Py_None )) + { + PyErr_SetString ( exception, "Could not initialize a field in PyAlert object" ); + free ( response ); + return NULL; + } + + if ( !( cur_alert->classification = + xmlTextReaderGetAttribute ( xml, (const xmlChar*) "classification" ) ? + PyString_FromString ((char*) ( xmlTextReaderGetAttribute ( xml, (const xmlChar*) "classification" ))) : Py_None )) + { + PyErr_SetString ( exception, "Could not initialize a field in PyAlert object" ); + free ( response ); + return NULL; + } + + if ( !( cur_alert->ip_src_addr = + xmlTextReaderGetAttribute ( xml, (const xmlChar*) "from" ) ? + PyString_FromString ((char*) ( xmlTextReaderGetAttribute ( xml, (const xmlChar*) "from" ))) : Py_None )) + { + PyErr_SetString ( exception, "Could not initialize a field in PyAlert object" ); + free ( response ); + return NULL; + } + + if ( !( cur_alert->ip_dst_addr = + xmlTextReaderGetAttribute ( xml, (const xmlChar*) "to" ) ? + PyString_FromString ((char*) ( xmlTextReaderGetAttribute ( xml, (const xmlChar*) "to" ))) : Py_None )) + { + PyErr_SetString ( exception, "Could not initialize a field in PyAlert object" ); + free ( response ); + return NULL; + } + + prev_alert = cur_alert; + } + } else { + PyErr_SetString ( exception, "Unrecognized XML tag in received response" ); + free ( response ); + return NULL; + } + } else if ( xmlTextReaderNodeType ( xml ) == XML_READER_TYPE_END_ELEMENT ) { + if ( !strcasecmp ((const char*) tagname, "alerts" )) + { + if ( !xmlFlags[inAlerts] ) + { + PyErr_SetString ( exception, "'alerts' tag closed, but never opened" ); + free ( response ); + return NULL; + } else { + xmlFlags[inAlerts] = false; + } + } else if ( !strcasecmp ((const char*) tagname, "alert" )) { + if ( !xmlFlags[inAlert] ) + { + PyErr_SetString ( exception, "'alert' tag closed, but never opened" ); + free ( response ); + return NULL; + } else { + xmlFlags[inAlert] = false; + } + } + } + } + + xmlFreeTextReader ( xml ); + xmlCleanupParser(); + free ( response ); + /*****************************/ + + /* Build the alerts tuple */ + if ( n_alerts > 0 ) + { + if ( !( self = PyTuple_New ( n_alerts ))) + { + PyErr_SetString ( exception, "Could not initialize the vector of alerts" ); + return NULL; + } + + for ( i=0, cur_alert = alerts; cur_alert; cur_alert = cur_alert->next, i++ ) + { + PyTuple_SetItem ( self, i, Py_BuildValue ( "O", cur_alert )); + Py_INCREF ((PyObject*) cur_alert ); + } + } else { + Py_RETURN_NONE; + } + + return self; +} + +static int +PyAlert_init ( PyAlert *self, PyObject *args, PyObject *kwds ) +{ + static char *kwlist[] = { + "gid", "sid", "rev", + "priority", "description", "classifcation", + "timestamp", "from", "to", "from_port", "to_port", + "latitude", "longitude", "alert_count", NULL + }; + + if ( !( PyArg_ParseTupleAndKeywords ( args, kwds, "|iiiiOOiOOiiddi", kwlist, + &self->gid, &self->sid, &self->rev, &self->priority, &self->desc, + &self->classification, &self->timestamp, + &self->ip_src_addr, &self->ip_dst_addr, &self->tcp_src_port, &self->tcp_dst_port, + &self->latitude, &self->longitude, &self->clusteredAlertsCount ))) + { + PyErr_SetString ( exception, "Could not initialize a PyAlert object" ); + return -1; + } + + return 0; +} + +static PyObject* +PyAlert_new ( PyTypeObject *type, PyObject *args, PyObject *kwds ) +{ + PyAlert *self = NULL; + + if ( !( self = (PyAlert*) type->tp_alloc ( type, 0 ))) + { + PyErr_SetString ( exception, "Could not allocate a PyAlert object" ); + return NULL; + } + + return (PyObject*) self; +} + +static void +PyAlerts_free ( PyAlert *self ) +{ + Py_XDECREF ( self->classification ); + Py_XDECREF ( self->desc ); + self->ob_type->tp_free (( PyObject* ) self ); +} + +PyMODINIT_FUNC +initsnortai ( void ) +{ + PyObject *m = NULL; + + if ( PyType_Ready ( &alert_type ) < 0 ) + { + return; + } + + if ( !( m = Py_InitModule ( "snortai", module_methods ))) + { + return; + } + + if ( !exception ) + { + exception = PyErr_NewException ( "snortai.error", NULL, NULL ); + Py_INCREF ( exception ); + } + + Py_INCREF ( &alert_type ); + PyModule_AddObject ( m, "resource", PyString_FromString ( resource )); + PyModule_AddObject ( m, "webserv", PyString_FromString ( webserv )); + PyModule_AddObject ( m, "webport", PyInt_FromLong ((long int) webport )); + /* PyModule_AddObject ( m, "alerts", (PyObject*) &alert_type ); */ +} + diff --git a/spp_ai.h b/spp_ai.h index dc5b32c..7b1f166 100644 --- a/spp_ai.h +++ b/spp_ai.h @@ -543,7 +543,60 @@ typedef struct { UT_hash_handle hh; } AI_alert_type_pair; /*****************************************************************/ +#ifdef HAVE_LIBPYTHON2_6 +/*******************************************/ +/* Avoid conflicts with Snort header files */ +#ifdef _POSIX_C_SOURCE +#undef _POSIX_C_SOURCE +#endif + +#ifdef _XOPEN_C_SOURCE +#undef _XOPEN_C_SOURCE +#endif + +#ifdef _XOPEN_SOURCE +#undef _XOPEN_SOURCE +#endif +/*******************************************/ + +#include + +/** Definition of the alert type for Python binding */ +typedef struct _PyAlert +{ + PyObject_HEAD + + unsigned int id; + + /* Identifiers of the alert */ + unsigned int gid; + unsigned int sid; + unsigned int rev; + + /* Snort priority, description, + * classification and timestamp + * of the alert */ + unsigned short priority; + PyObject* desc; + PyObject* classification; + time_t timestamp; + + PyObject* ip_src_addr; + PyObject* ip_dst_addr; + unsigned short tcp_src_port; + unsigned short tcp_dst_port; + + double latitude; + double longitude; + + unsigned int clusteredAlertsCount; + + struct _PyAlert *next; +} PyAlert; + +#endif +/*****************************************************************/ /** Enumeration for describing the table in the output database */ @@ -585,7 +638,7 @@ void AI_pkt_enqueue ( SFSnortPacket* ); void AI_set_stream_observed ( struct pkt_key key ); void AI_hierarchies_build ( hierarchy_node**, int ); void AI_free_alerts ( AI_snort_alert *node ); -void AI_init_corr_modules (); +void AI_init_corr_modules ( void ); struct pkt_info* AI_get_stream_by_key ( struct pkt_key ); AI_snort_alert* AI_get_alerts ( void ); @@ -594,32 +647,40 @@ AI_snort_alert* AI_get_clustered_alerts ( void ); void AI_serialize_alerts ( AI_snort_alert**, unsigned int ); void AI_serializer ( AI_snort_alert* ); -void* AI_deserialize_alerts (); +void* AI_deserialize_alerts ( void ); void* AI_alerts_pool_thread ( void* ); void* AI_neural_thread ( void* ); void* AI_manual_correlations_parsing_thread ( void* ); void* AI_neural_clustering_thread ( void* ); const AI_alert_event* AI_get_alert_events_by_key ( AI_alert_event_key ); -unsigned int AI_get_history_alert_number (); +unsigned int AI_get_history_alert_number ( void ); double AI_alert_bayesian_correlation ( const AI_snort_alert*, const AI_snort_alert* ); double AI_alert_neural_som_correlation ( const AI_snort_alert*, const AI_snort_alert* ); double AI_kb_correlation_coefficient ( const AI_snort_alert*, const AI_snort_alert* ); -double AI_neural_correlation_weight (); -double AI_bayesian_correlation_weight (); +double AI_neural_correlation_weight ( void ); +double AI_bayesian_correlation_weight ( void ); int AI_geoinfobyaddr ( const char*, double** ); -void AI_outdb_mutex_initialize (); +void AI_outdb_mutex_initialize ( void ); void AI_store_alert_to_db ( AI_snort_alert* ); void AI_store_cluster_to_db ( AI_alerts_couple* ); void AI_store_correlation_to_db ( AI_alert_correlation* ); void AI_kb_index_init ( AI_snort_alert* ); -AI_alerts_per_neuron* AI_get_alerts_per_neuron (); +AI_alerts_per_neuron* AI_get_alerts_per_neuron ( void ); double(**AI_get_corr_functions ( size_t* ))(const AI_snort_alert*, const AI_snort_alert*); -double(**AI_get_corr_weights ( size_t* ))(); +double(**AI_get_corr_weights ( size_t* ))( void ); + +#ifdef HAVE_LIBPYTHON2_6 + +PyObject** AI_get_py_functions ( size_t* ); +PyObject** AI_get_py_weights ( size_t* ); +PyAlert* AI_alert_to_pyalert ( AI_snort_alert* ); + +#endif /** Function pointer to the function used for getting the alert list (from log file, db, ...) */ extern AI_snort_alert* (*get_alerts)(void); diff --git a/webserv.c b/webserv.c index dd0d731..588342e 100644 --- a/webserv.c +++ b/webserv.c @@ -385,6 +385,8 @@ __AI_webservlet_thread ( void *arg ) strncpy ( content_type, "application/x-javascript", sizeof ( content_type )); } else if ( !strcasecmp ( extension, "json" )) { strncpy ( content_type, "application/json", sizeof ( content_type )); + } else if ( !strcasecmp ( extension, "xml" )) { + strncpy ( content_type, "application/xml", sizeof ( content_type )); } else if ( !strcasecmp ( extension, "jpg" ) || !strcasecmp ( extension, "jpeg" )) { strncpy ( content_type, "image/jpeg", sizeof ( content_type )); } else if ( !strcasecmp ( extension, "cgi" )) {