/***** * * Copyright (C) 2002 Vincent Glaume , Baptiste Malguy * All Rights Reserved * * This file is part of the Prelude program. * * 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; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * *****/ #include #include #include #include #include #include #include #include "counter-measure.h" /* * Here should appear the management of the information between * the various cm plugin types : when an idmef[msg/alert] is received * and want to be examined to know if cm is needed, it should be done * by calling functions defined here, which will then do what they need * using our plugins. */ /* * For now, used to know which hosts a white host wants to keep in touch with */ typedef struct { HOST_LIST_GENERIC; } known_host_t; typedef struct { /* * interval or simple port => interval has second field != 0 */ uint16_t low; uint16_t high; struct list_head list; } port_interval_t; typedef struct { HOST_LIST_GENERIC; uint32_t cm_threshold; uint8_t proto; struct list_head *known_hosts; struct list_head *ports; uint8_t cm_type; } cm_white_host_t; typedef struct { HOST_LIST_GENERIC; uint8_t cm_type; } cm_black_host_t; struct list_head *cm_whitelist[256]; struct list_head *cm_attackers[256]; struct list_head *cm_targets[256]; /* should have the same form as whitelist and the rest above */ static LIST_HEAD(cm_hosts_blacklist); static uint32_t cm_msg_id = 0; /* * Managing addr, nets and subnets */ static int is_valid_mask(struct in_addr netmask) { uint32_t mask = ntohl(netmask.s_addr); while ( mask & 0x80000000 ) mask = mask << 1; if ( mask ) return 0; return 1; } static int addr_belongs_to_net(struct in_addr addr, struct in_addr mask) { return ( addr.s_addr & ~mask.s_addr ) ? 0 : 1; } /* * Is addr1/mask1 a subnet of addr2/mask2 */ int counter_measure_is_subnet(struct in_addr netaddr1, struct in_addr netmask1, struct in_addr netaddr2, struct in_addr netmask2) { uint32_t ad1, ad2, m1, m2; if ( !is_valid_mask(netmask1) ) return 0; if ( !is_valid_mask(netmask2) ) return 0; if ( !addr_belongs_to_net(netaddr1, netmask1) ) return 0; if ( !addr_belongs_to_net(netaddr2, netmask2) ) return 0; m1 = ntohl(netmask1.s_addr); m2 = ntohl(netmask2.s_addr); ad1 = ntohl(netaddr1.s_addr) & m1; ad2 = ntohl(netaddr2.s_addr) & m2; return (( ad2 <= ad1 ) && ( ad1 + ~m1 <= ad2 + ~m2)) ? 1 : 0; } void cm_request_destroy(cm_request_t *req) { if ( ! req ) return; if (req->msg) free(req->msg); if(req->alternative) free(req->alternative); free(req); } /* * When an attack comes from an host/net we keep it in mind; if it occurs again * we'll remember and increment its dangerousness level * attacks from net rather than host will be found by correlation work */ /* * Will see if there's a specialized plugin to protect the defined net */ static int cm_specialized_plugin_net_protection(struct in_addr net, struct in_addr mask) { /* * FIXME : code me please ! */ return -1; } /* * The 2 following ones look like one another, but we need the manipulated type so we keep * 2 functions instead of doing one with tests * Search if addr/mask is an already registered attacker */ cm_attack_host_t *counter_measure_search_attacker(struct in_addr addr, struct in_addr mask) { struct list_head *subnet_list; cm_attack_host_t *ahost; if ( (subnet_list = cm_attackers[addr.s_addr & 0xff]) ) { struct list_head *tmp; list_for_each(tmp, subnet_list) { ahost = list_entry(tmp, cm_attack_host_t, list); if ( counter_measure_is_subnet(addr, mask, ahost->addr, ahost->mask) ) return ahost; } } return NULL; } /* * Search if addr/mask is an already registered target */ cm_target_host_t *counter_measure_search_target(struct in_addr addr, struct in_addr mask) { struct list_head *subnet_list; cm_target_host_t *thost; if ( (subnet_list = cm_targets[addr.s_addr & 0xff]) ) { struct list_head *tmp; list_for_each(tmp, subnet_list) { thost = list_entry(tmp, cm_target_host_t, list); if ( counter_measure_is_subnet(addr, mask, thost->addr, thost->mask) ) return thost; } } return NULL; } /* * Create an attacker structure */ cm_attack_host_t *counter_measure_attacker_new(struct in_addr addr, struct in_addr mask) { cm_attack_host_t *ahost = (cm_attack_host_t *) malloc(sizeof(cm_attack_host_t)); if ( !ahost ) return NULL; ahost->addr = addr; ahost->mask = mask; ahost->score = 0; ahost->attacks = 0; return ahost; } /* * Create an attacker structure */ cm_target_host_t *counter_measure_target_new(struct in_addr addr, struct in_addr mask, int sensibility) { cm_target_host_t *thost = (cm_target_host_t *) malloc(sizeof(cm_target_host_t)); if ( !thost ) return NULL; thost->addr = addr; thost->mask = mask; if ( sensibility ) thost->sensibility = sensibility; else thost->sensibility = DEFAULT_SENSIBILITY; return thost; } /* * If we call this, it means there is no net in the attacker list containing ahost * So, we must add it at the end of the appropriate list */ int counter_measure_add_attacker(cm_attack_host_t *ahost) { struct list_head *list; if ( !ahost ) return -1; if ( !(list = cm_attackers[ahost->addr.s_addr & 0xff]) ) { list = (struct list_head *)malloc(sizeof(struct list_head)); INIT_LIST_HEAD(list); cm_attackers[ahost->addr.s_addr & 0xff] = list; } list_add_tail( &ahost->list, list); return 0; } int counter_measure_add_target(cm_target_host_t *thost) { struct list_head *list; if ( !thost ) return -1; if ( !(list = cm_targets[thost->addr.s_addr & 0xff]) ) { list = (struct list_head *)malloc(sizeof(struct list_head)); INIT_LIST_HEAD(list); cm_targets[thost->addr.s_addr & 0xff] = list; } list_add_tail( &thost->list, list); return 0; } /* * Check if our CM order does not offend our black list or our white list */ /* * ports may be NULL => check in the fction ! * returns 0 if port is not in ports */ static int search_port_list( struct list_head *ports, uint16_t port) { /* * FIXME : code me please ! */ return 0; } /* * Returns 0 if the intersection of the net defined by addr/host with the nets of whost->known_hosts * is empty, else -1, which means addr/mask is part of the hosts whost should keep contact with. * whost must be != NULL, proto must be != 0 (check before calling please) */ static int isolate_whitehost(cm_white_host_t *whost, uint8_t proto, uint16_t port, struct in_addr addr, struct in_addr mask) { struct list_head *ktmp; known_host_t *khost; list_for_each(ktmp, whost->known_hosts) { khost = list_entry(ktmp, known_host_t, list); if ( !counter_measure_is_subnet(addr, mask, khost->addr, khost->mask) ) if ( !counter_measure_is_subnet(khost->addr, khost->mask, addr, mask) ) continue; if ( proto & CM_ICMP ) return -1; if ( proto & ( CM_TCP | CM_UDP) ) if ( search_port_list(whost->ports, port) ) return -1; } return 0; } /* * We look for an host or a (sub)net in our whitelist which is, contains or is contained by * the host or net defined by addr/mask. We want to return the most fitting one if several match, * which means : this host itself, or the smallest net containing it, or the bigger it contains. */ static int search_white_list(struct in_addr addr, struct in_addr mask, uint16_t port, uint8_t proto, int threshold, struct in_addr rem_addr, struct in_addr rem_mask) { struct list_head *subnet_list; if ( (subnet_list = cm_whitelist[addr.s_addr & 0xff]) ) { struct list_head *tmp; cm_white_host_t *whost; list_for_each(tmp, subnet_list) { whost = list_entry(tmp, cm_white_host_t, list); if ( counter_measure_is_subnet(addr, mask, whost->addr, whost->mask) || counter_measure_is_subnet(whost->addr, whost->mask, addr, mask) ) if ( threshold < whost->cm_threshold ) { if ( proto &= whost->proto ) if ( isolate_whitehost(whost, proto, port, rem_addr, rem_mask) < 0 ) return -1; } } } return 0; } static int allow_blackhost(cm_black_host_t *bhost, struct in_addr addr1, struct in_addr mask1, struct in_addr addr2, struct in_addr mask2) { /* struct list_head *ktmp; known_host_t *khost; if ( counter_measure_is_subnet(bhost->addr, bhost->mask, addr1, mask1) || counter_measure_is_subnet(addr1, mask1, bhost->addr, bhost->mask) ) { log(LOG_INFO, "Subnet relationship between CM rule address and a black host\n"); if ( bhost->cm_type == PREVENT_TOTAL_REACHABILITY ) return -1; list_for_each(ktmp, &known_hosts_list) { khost = list_entry(ktmp, known_host_t, list); if ( counter_measure_is_subnet(khost->addr, khost->mask, addr2, mask2) ) return -1; if ( counter_measure_is_subnet(addr2, mask2, khost->addr, khost->mask) ) return -1; } } */ return 0; } /* * Look for a host in our whitelist structure - it should be built considering that if a host and a subnet * containing it are in the list, the host is found first. (--> cf parser etc...) */ static int check_firewall_black_white_list(cm_firewall_msg_t *msg, int threshold) { if ( !msg ) return -1; /* * We must not prevent our white hosts from communicating: * The From or To fields must not contain or be subnets of our white hosts if it is a drop or reject order */ if ( ( msg->rule.rule_cmd & CM_FW_RULE_DROP ) || ( msg->rule.rule_cmd & CM_FW_RULE_REJECT ) ) { if ( search_white_list(msg->rule.saddr, msg->rule.smask, msg->rule.sport[0], msg->rule.rule_proto, threshold, msg->rule.daddr, msg->rule.dmask) < 0 ) return -1; if ( search_white_list(msg->rule.daddr, msg->rule.dmask, msg->rule.dport[0], msg->rule.rule_proto, threshold, msg->rule.saddr, msg->rule.smask) < 0 ) return -1; return 0; } if ( msg->rule.rule_cmd & CM_FW_RULE_ACCEPT ) { struct list_head *tmp; cm_black_host_t *bhost; list_for_each(tmp, &cm_hosts_blacklist) { bhost = list_entry(tmp, cm_black_host_t, list); if ( allow_blackhost(bhost, msg->rule.saddr, msg->rule.smask, msg->rule.daddr, msg->rule.dmask) < 0 ) return -1; if ( allow_blackhost(bhost, msg->rule.daddr, msg->rule.dmask, msg->rule.saddr, msg->rule.smask) < 0 ) return -1; } } return 0; } static int check_throttle_black_white_list(cm_throttle_msg_t *msg, int threshold) { return 0; } static int check_island_black_white_list(cm_island_msg_t *msg, int threshold) { return 0; } static int check_imsg_black_white_list(cm_internal_msg_t *msg, int threshold) { switch ( msg->cm_msg_type ) { case PRELUDE_MSG_CM_FIREWALL: return check_firewall_black_white_list(msg->cm_msg.fw, threshold); case PRELUDE_MSG_CM_THROTTLE: return check_throttle_black_white_list(msg->cm_msg.thr, threshold); case PRELUDE_MSG_CM_ISLAND: return check_island_black_white_list(msg->cm_msg.isl, threshold); default: log(LOG_INFO, "Unknown counter-measure message type : %d\n"); return -1; } } static int check_request_black_white_list(cm_request_t *req) { int ret; if ( ! req->msg ) { req->msg = req->alternative; req->alternative = NULL; return check_imsg_black_white_list(req->msg, req->cm_needed); } ret = check_imsg_black_white_list(req->msg, req->cm_needed); if ( ret < 0 ) { free(req->msg); req->msg = req->alternative; req->alternative = NULL; return check_imsg_black_white_list(req->msg, req->cm_needed); } if ( ! req->alternative ) return ret; if ( check_imsg_black_white_list(req->alternative, req->cm_needed) < 0 ) { free(req->alternative); req->alternative = NULL; } return ret; } /* * Here we should be able to know the various agents, to decide later which has to * receive the CM order */ int counter_measure_launch(idmef_alert_t *msg) { int ret = 0; if ( cm_trigger_plugin_available() < 0) { log(LOG_ERR, "Could not launch the counter-measure process: no triggering plugin available\n"); return -1; } ret = cm_trigger_plugin_run(msg); /* * We get a CM request, we should find which agent is best suited to act: a specialized or the generic one. * This will be decided depending on the net/mask which is supposed to be protected by a given agent. * The specialized agents depend on the conf. whereas the generic contact the manager, and are known by our * generic comm. plugin only: we first try our specialized agents, and if no suited agent is available, the * cm msg is given to the communication plugin. * But first we should see if we are trying to ban a host from our whitelist */ return ret; } /* * The CM request is passed to this function to decide if it may be sent (white list check) * and which agent should deal with it. It must be called from a triggering plugin */ int counter_measure_send(cm_request_t *req) { int ret = 0; if ( ! req ) { log(LOG_ERR, "Empty CM request.\n"); return -1; } if ( ! req->cm_needed ) return -1;; /* perform the check at the beginning to avoid useless expensive operations */ if ( cm_comm_plugins_available() < 0) { log(LOG_ERR, "Could not launch the counter-measure process: no generic communication plugin available\n"); return -1; } /* * If the host whitelist is not empty, be careful of it. */ if ( check_request_black_white_list(req) < 0) return -1; /* * Should return a pointer to the appropriate specialized plugin... */ if ( cm_specialized_plugin_net_protection(req->net, req->mask) > 0 ) { /* * pass the request to the plugin which will send the message * if the return status is < 0, we try with our generic comm plugin * else we return */ } req->cm_msg_id = cm_msg_id++; ret = cm_comm_plugins_run(req); return ret; }