From 4f21090fb8a71b8e8e400fcf7b9125e2e35ef9ec Mon Sep 17 00:00:00 2001 From: Javier Marcos Date: Mon, 27 Apr 2015 17:41:58 -0700 Subject: [PATCH] Adding new table to display iptables filters, chains and rules Patching headers to avoid void pointers Adding test for parsing ipt_ip entries --- osquery/tables/CMakeLists.txt | 1 + osquery/tables/networking/linux/iptables.cpp | 159 ++++++++++++++++++ .../networking/linux/tests/iptables_tests.cpp | 66 ++++++++ osquery/tables/specs/linux/iptables.table | 21 +++ tools/provision/centos.sh | 3 + tools/provision/lib.sh | 32 ++++ tools/provision/rhel.sh | 3 + tools/provision/ubuntu.sh | 3 + 8 files changed, 288 insertions(+) create mode 100644 osquery/tables/networking/linux/iptables.cpp create mode 100644 osquery/tables/networking/linux/tests/iptables_tests.cpp create mode 100644 osquery/tables/specs/linux/iptables.table diff --git a/osquery/tables/CMakeLists.txt b/osquery/tables/CMakeLists.txt index b63767e9..66fd29ca 100644 --- a/osquery/tables/CMakeLists.txt +++ b/osquery/tables/CMakeLists.txt @@ -57,6 +57,7 @@ else() ADD_OSQUERY_LINK(FALSE "cryptsetup") ADD_OSQUERY_LINK(FALSE "udev") ADD_OSQUERY_LINK(FALSE "uuid") + ADD_OSQUERY_LINK(FALSE "ip4tc") endif() file(GLOB OSQUERY_CROSS_TABLES "[!ue]*/*.cpp") diff --git a/osquery/tables/networking/linux/iptables.cpp b/osquery/tables/networking/linux/iptables.cpp new file mode 100644 index 00000000..64b0faae --- /dev/null +++ b/osquery/tables/networking/linux/iptables.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2014, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#include + +#include +#include + +#include +#include + +#include +#include +#include + +namespace osquery { +namespace tables { + +const std::string kLinuxIpTablesNames = "/proc/net/ip_tables_names"; +const char MAP[] = {'0','1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; +const int HIGH_BITS = 4; +const int LOW_BITS = 15; + +void parseIpEntry(ipt_ip *ip, Row &r) { + r["protocol"] = INTEGER(ip->proto); + if (strlen(ip->iniface)) { + r["iniface"] = TEXT(ip->iniface); + } else { + r["iniface"] = "all"; + } + if (strlen(ip->outiface)) { + r["outiface"] = TEXT(ip->outiface); + } else { + r["outiface"] = "all"; + } + char src_ip_string[INET6_ADDRSTRLEN] = {0}; + if (inet_ntop(AF_INET, (struct in_addr *)&ip->src, src_ip_string, INET6_ADDRSTRLEN) != NULL) { + r["src_ip"] = TEXT(src_ip_string); + } + char dst_ip_string[INET6_ADDRSTRLEN] = {0}; + if (inet_ntop(AF_INET, (struct in_addr *)&ip->dst, dst_ip_string, INET6_ADDRSTRLEN) != NULL) { + r["dst_ip"] = TEXT(dst_ip_string); + } + char src_ip_mask[INET6_ADDRSTRLEN] = {0}; + if (inet_ntop(AF_INET, (struct in_addr *)&ip->smsk, src_ip_mask, INET6_ADDRSTRLEN) != NULL) { + r["src_mask"] = TEXT(src_ip_mask); + } + char dst_ip_mask[INET6_ADDRSTRLEN] = {0}; + if (inet_ntop(AF_INET, (struct in_addr *)&ip->dmsk, dst_ip_mask, INET6_ADDRSTRLEN) != NULL) { + r["dst_mask"] = TEXT(dst_ip_mask); + } + + char aux_char[2]; + std::string iniface_mask = ""; + for (int i = 0; ip->iniface_mask[i] != 0x00 && iiniface_mask[i] >> HIGH_BITS]; + aux_char[1] = MAP[(int) ip->iniface_mask[i] & LOW_BITS]; + iniface_mask += aux_char[0]; + iniface_mask += aux_char[1]; + } + + r["iniface_mask"] = TEXT(iniface_mask); + std::string outiface_mask = ""; + for (int i = 0; ip->outiface_mask[i] != 0x00 && ioutiface_mask[i] >> HIGH_BITS]; + aux_char[1] = MAP[(int) ip->outiface_mask[i] & LOW_BITS]; + outiface_mask += aux_char[0]; + outiface_mask += aux_char[1]; + } + r["outiface_mask"] = TEXT(outiface_mask); +} + +QueryData getIptablesRules(const std::string& content) { + QueryData results; + + for (auto& line : split(content, "\n")) { + if (line.size() == 0) { + continue; + } + + // Inline trim each line. + boost::trim(line); + + Row r; + + r["filter_name"] = TEXT(line); + + // Initialize the access to iptc + auto handle = (struct iptc_handle*) iptc_init(line.c_str()); + + if (handle) { + // Iterate through chains + for (auto chain = iptc_first_chain((struct iptc_handle*)handle); chain; chain = iptc_next_chain((struct iptc_handle*)handle)) { + r["chain"] = TEXT(chain); + + struct ipt_counters counters; + const char* policy; + + if ((policy = iptc_get_policy(chain, &counters, (struct iptc_handle*)handle))) { + r["policy"] = TEXT(policy); + r["packets"] = INTEGER(counters.pcnt); + r["bytes"] = INTEGER(counters.bcnt); + } + + struct ipt_entry *prev_rule; + + // Iterating through all the rules per chain + for (auto chain_rule = iptc_first_rule(chain, (struct iptc_handle*)handle); chain_rule; chain_rule = iptc_next_rule(prev_rule, (struct iptc_handle*)handle)) { + prev_rule = (struct ipt_entry*)chain_rule; + + auto target = iptc_get_target(chain_rule, (struct iptc_handle*)handle); + if (target) { + r["target"] = TEXT(target); + } + + if (chain_rule->target_offset) { + r["match"] = "yes"; + } else { + r["match"] = "no"; + } + + struct ipt_ip *ip = (struct ipt_ip*)&chain_rule->ip; + parseIpEntry(ip, r); + + results.push_back(r); + } // Rule iteration + results.push_back(r); + } // Chain iteration + + iptc_free((struct iptc_handle*) handle); + + } + } // Filter table iteration + + return results; +} + +QueryData genIptables(QueryContext& context) { + std::string content; + QueryData results; + + auto s = osquery::readFile(kLinuxIpTablesNames, content); + + if (s.ok()) { + return getIptablesRules(content); + } else { + LOG(ERROR) << "Error reading " << kLinuxIpTablesNames << " : " << s.toString(); + return {}; + } +} +} +} diff --git a/osquery/tables/networking/linux/tests/iptables_tests.cpp b/osquery/tables/networking/linux/tests/iptables_tests.cpp new file mode 100644 index 00000000..3d5eb07b --- /dev/null +++ b/osquery/tables/networking/linux/tests/iptables_tests.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2014, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#include + +#include +#include + +#include +#include + +#include "osquery/core/test_util.h" + +namespace osquery { +namespace tables { + +void parseIpEntry(ipt_ip *ip, Row &row); + +ipt_ip* getIpEntryContent() { + static ipt_ip ip_entry; + + ip_entry.proto = 6; + memset(ip_entry.iniface, 0, IFNAMSIZ); + strcpy(ip_entry.outiface, "eth0"); + inet_aton("123.123.123.123", &ip_entry.src); + inet_aton("45.45.45.45", &ip_entry.dst); + inet_aton("250.251.252.253", &ip_entry.smsk); + inet_aton("253.252.251.250", &ip_entry.dmsk); + memset(ip_entry.iniface_mask, 0xfe, IFNAMSIZ ); + memset(ip_entry.outiface_mask, 0xfa, IFNAMSIZ ); + + return &ip_entry; +} + +Row getIpEntryExpectedResults() { + Row row; + + row["protocol"] = "6"; + row["iniface"] = "all"; + row["outiface"] = "eth0"; + row["src_ip"] = "123.123.123.123"; + row["dst_ip"] = "45.45.45.45"; + row["src_mask"] = "250.251.252.253"; + row["dst_mask"] = "253.252.251.250"; + row["iniface_mask"] = "FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFE"; + row["outiface_mask"] = "FAFAFAFAFAFAFAFAFAFAFAFAFAFAFAFA"; + + return row; +} + +class IptablesTests : public testing::Test {}; + +TEST_F(IptablesTests, test_iptables_ip_entry) { + Row row; + parseIpEntry(getIpEntryContent(), row); + EXPECT_EQ(row, getIpEntryExpectedResults()); +} +} +} diff --git a/osquery/tables/specs/linux/iptables.table b/osquery/tables/specs/linux/iptables.table new file mode 100644 index 00000000..f88216a3 --- /dev/null +++ b/osquery/tables/specs/linux/iptables.table @@ -0,0 +1,21 @@ +table_name("iptables") +description("Linux IP packet filtering and NAT tool.") +schema([ + Column("filter_name", TEXT, "Packet matching filter table name."), + Column("chain", TEXT, "Size of module content."), + Column("policy", TEXT, "Policy that applies for this rule."), + Column("target", TEXT, "Target that applies for this rule."), + Column("protocol", INTEGER, "Protocol number identification."), + Column("src_ip", TEXT, "Source IP address."), + Column("src_mask", TEXT, "Source IP address mask."), + Column("iniface", TEXT, "Input interface for the rule."), + Column("iniface_mask", TEXT, "Input interface mask for the rule."), + Column("dst_ip", TEXT, "Destination IP address."), + Column("dst_mask", TEXT, "Destination IP address mask."), + Column("outiface", TEXT, "Output interface for the rule."), + Column("outiface_mask", TEXT, "Output interface mask for the rule."), + Column("match", TEXT, "Matching rule that applies."), + Column("packets", INTEGER, "Number of matching packets for this rule."), + Column("bytes", INTEGER, "Number of matching bytes for this rule."), +]) +implementation("iptables@genIptables") diff --git a/tools/provision/centos.sh b/tools/provision/centos.sh index 1db0e8a5..92fc9959 100755 --- a/tools/provision/centos.sh +++ b/tools/provision/centos.sh @@ -62,6 +62,9 @@ function main_centos() { package rpm-devel package rpm-build package libblkid-devel + package iptables + package iptables-devel + patch_iptables_headers install_cmake diff --git a/tools/provision/lib.sh b/tools/provision/lib.sh index f8894df3..ef25019e 100755 --- a/tools/provision/lib.sh +++ b/tools/provision/lib.sh @@ -151,6 +151,38 @@ function install_boost() { fi } +function patch_iptables_headers() { + IPV4FILE="/usr/include/linux/netfilter_ipv4/ip_tables.h" + IPV6FILE="/usr/include/linux/netfilter_ipv6/ip6_tables.h" + CODE_TO_PATCH="return (void \*)e + e->target_offset;" + if [[ -f "$IPV4FILE" ]]; then + if [[ -n `grep "$CODE_TO_PATCH" "$IPV4FILE"` ]]; then + log "IPv4 code to patch found, backing up first" + sudo cp "$IPV4FILE" "$IPV4FILE.osquery" + PATCH="return (struct ipt_entry_target *)((char *)e + e->target_offset);" + cat "$IPV4FILE" | sudo bash -c "sed \"s/$CODE_TO_PATCH/$PATCH/g\" > \"$IPV4FILE\"" + log "IPv4 headers patched succesfully" + else + log "IPv4 code to patch not found, skipping." + fi + else + log "IPv4 iptables headers not found, skipping." + fi + if [[ -f "$IPV6FILE" ]]; then + if [[ -n `grep "$CODE_TO_PATCH" "$IPV6FILE"` ]]; then + log "IPv6 code to patch found, backing up first" + sudo cp "$IPV6FILE" "$IPV6FILE.osquery" + PATCH="return (struct ip6t_entry_target *)((char *)e + e->target_offset);" + cat "$IPV6FILE" | sudo bash -c "sed \"s/$CODE_TO_PATCH/$PATCH/g\" > \"$IPV6FILE\"" + log "IPv6 headers patched succesfully" + else + log "IPv6 code to patch not found, skipping." + fi + else + log "IPv6 iptables headers not found, skipping." + fi +} + function install_gflags() { if [[ ! -f /usr/local/lib/libgflags.a ]]; then if [[ ! -f v2.1.1.tar.gz ]]; then diff --git a/tools/provision/rhel.sh b/tools/provision/rhel.sh index c0461f94..ca793eda 100755 --- a/tools/provision/rhel.sh +++ b/tools/provision/rhel.sh @@ -41,6 +41,9 @@ function main_rhel() { package xz package xz-devel package subscription-manager + package iptables + package iptables-devel + patch_iptables_headers if [[ -z `rpm -qa epel-release` ]]; then if [[ $DISTRO = "rhel6" ]]; then diff --git a/tools/provision/ubuntu.sh b/tools/provision/ubuntu.sh index 84c0552b..d4a408c5 100755 --- a/tools/provision/ubuntu.sh +++ b/tools/provision/ubuntu.sh @@ -32,6 +32,9 @@ function main_ubuntu() { package libbz2-dev package devscripts package debhelper + package iptables + package iptables-dev + patch_iptables_headers if [[ $DISTRO = "precise" ]]; then package clang-3.4