Virtual tables for Apple's application level firewall

This commit is contained in:
mike@arpaia.co 2014-08-13 13:44:28 -07:00
parent 826f9d9905
commit 1a381e0feb
12 changed files with 653 additions and 0 deletions

View File

@ -9,6 +9,10 @@ build:
python tools/gentable.py osquery/tables/specs/processes.table
python tools/gentable.py osquery/tables/specs/nvram.table
python tools/gentable.py osquery/tables/specs/osx_version.table
python tools/gentable.py osquery/tables/specs/alf.table
python tools/gentable.py osquery/tables/specs/alf_exceptions.table
python tools/gentable.py osquery/tables/specs/alf_explicit_auths.table
python tools/gentable.py osquery/tables/specs/alf_services.table
mkdir -p build
cd build && cmake .. && make -j5

View File

@ -8,6 +8,7 @@ TARGET_LINK_LIBRARIES(osquery_core gflags)
TARGET_LINK_LIBRARIES(osquery_core glog)
TARGET_LINK_LIBRARIES(osquery_core gtest)
TARGET_LINK_LIBRARIES(osquery_core osquery_database)
TARGET_LINK_LIBRARIES(osquery_core osquery_filesystem)
TARGET_LINK_LIBRARIES(osquery_core osquery_sqlite)
TARGET_LINK_LIBRARIES(osquery_core osquery_tables)

View File

@ -10,6 +10,7 @@
#include <glog/logging.h>
#include "osquery/core/sqlite_util.h"
#include "osquery/filesystem.h"
using namespace osquery::db;
namespace pt = boost::property_tree;
@ -311,4 +312,172 @@ std::vector<SplitStringTestData> generateSplitStringTestData() {
return {s1, s2, s3};
}
std::string getALFContent() {
std::string content = R"(
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>allowsignedenabled</key>
<integer>1</integer>
<key>applications</key>
<array/>
<key>exceptions</key>
<array>
<dict>
<key>path</key>
<string>/usr/libexec/configd</string>
<key>state</key>
<integer>3</integer>
</dict>
<dict>
<key>path</key>
<string>/usr/sbin/mDNSResponder</string>
<key>state</key>
<integer>3</integer>
</dict>
<dict>
<key>path</key>
<string>/usr/sbin/racoon</string>
<key>state</key>
<integer>3</integer>
</dict>
<dict>
<key>path</key>
<string>/usr/bin/nmblookup</string>
<key>state</key>
<integer>3</integer>
</dict>
<dict>
<key>path</key>
<string>/System/Library/PrivateFrameworks/Admin.framework/Versions/A/Resources/readconfig</string>
<key>state</key>
<integer>3</integer>
</dict>
</array>
<key>explicitauths</key>
<array>
<dict>
<key>id</key>
<string>org.python.python.app</string>
</dict>
<dict>
<key>id</key>
<string>com.apple.ruby</string>
</dict>
<dict>
<key>id</key>
<string>com.apple.a2p</string>
</dict>
<dict>
<key>id</key>
<string>com.apple.javajdk16.cmd</string>
</dict>
<dict>
<key>id</key>
<string>com.apple.php</string>
</dict>
<dict>
<key>id</key>
<string>com.apple.nc</string>
</dict>
<dict>
<key>id</key>
<string>com.apple.ksh</string>
</dict>
</array>
<key>firewall</key>
<dict>
<key>Apple Remote Desktop</key>
<dict>
<key>proc</key>
<string>AppleVNCServer</string>
<key>state</key>
<integer>0</integer>
</dict>
<key>FTP Access</key>
<dict>
<key>proc</key>
<string>ftpd</string>
<key>state</key>
<integer>0</integer>
</dict>
<key>ODSAgent</key>
<dict>
<key>proc</key>
<string>ODSAgent</string>
<key>servicebundleid</key>
<string>com.apple.ODSAgent</string>
<key>state</key>
<integer>0</integer>
</dict>
<key>Personal File Sharing</key>
<dict>
<key>proc</key>
<string>AppleFileServer</string>
<key>state</key>
<integer>0</integer>
</dict>
<key>Personal Web Sharing</key>
<dict>
<key>proc</key>
<string>httpd</string>
<key>state</key>
<integer>0</integer>
</dict>
<key>Printer Sharing</key>
<dict>
<key>proc</key>
<string>cupsd</string>
<key>state</key>
<integer>0</integer>
</dict>
<key>Remote Apple Events</key>
<dict>
<key>proc</key>
<string>AEServer</string>
<key>state</key>
<integer>0</integer>
</dict>
<key>Remote Login - SSH</key>
<dict>
<key>proc</key>
<string>sshd-keygen-wrapper</string>
<key>state</key>
<integer>0</integer>
</dict>
<key>Samba Sharing</key>
<dict>
<key>proc</key>
<string>smbd</string>
<key>state</key>
<integer>0</integer>
</dict>
</dict>
<key>firewallunload</key>
<integer>0</integer>
<key>globalstate</key>
<integer>0</integer>
<key>loggingenabled</key>
<integer>0</integer>
<key>loggingoption</key>
<integer>0</integer>
<key>stealthenabled</key>
<integer>0</integer>
<key>version</key>
<string>1.0a25</string>
</dict>
</plist>
)";
return content;
}
pt::ptree getALFTree() {
auto content = getALFContent();
pt::ptree tree;
fs::parsePlistContent(content, tree);
return tree;
}
}}

View File

@ -97,6 +97,12 @@ std::vector<SplitStringTestData> generateSplitStringTestData();
// generate test content of a property list
std::string getPlistContent();
// generate test content of com.apple.alf
std::string getALFcontent();
// generate a test ptree of the content returned by getALFContent
boost::property_tree::ptree getALFTree();
}}
#endif /* OSQUERY_CORE_TEST_UTIL_H */

View File

@ -13,6 +13,7 @@ ADD_LIBRARY(osquery_tables
system/nvram.cpp
system/NSProcessInfo+PECocoaBackports.mm
system/osx_version.mm
system/firewall.cpp
)
TARGET_LINK_LIBRARIES(osquery_tables boost_filesystem)
TARGET_LINK_LIBRARIES(osquery_tables glog)
@ -28,3 +29,11 @@ TARGET_LINK_LIBRARIES(etc_hosts_tests osquery_core)
TARGET_LINK_LIBRARIES(etc_hosts_tests osquery_database)
TARGET_LINK_LIBRARIES(etc_hosts_tests osquery_filesystem)
TARGET_LINK_LIBRARIES(etc_hosts_tests osquery_tables)
ADD_EXECUTABLE(firewall_tests system/firewall_tests.cpp)
TARGET_LINK_LIBRARIES(firewall_tests gtest)
TARGET_LINK_LIBRARIES(firewall_tests glog)
TARGET_LINK_LIBRARIES(firewall_tests osquery_core)
TARGET_LINK_LIBRARIES(firewall_tests osquery_database)
TARGET_LINK_LIBRARIES(firewall_tests osquery_filesystem)
TARGET_LINK_LIBRARIES(firewall_tests osquery_tables)

View File

@ -0,0 +1,11 @@
table_name("alf")
schema([
Column(name="allow_signed_enabled", type="int"),
Column(name="firewall_unload", type="int"),
Column(name="global_state", type="int"),
Column(name="logging_enabled", type="int"),
Column(name="logging_option", type="int"),
Column(name="stealth_enabled", type="int"),
Column(name="version", type="std::string"),
])
implementation("osquery/tables/system/firewall@genALF")

View File

@ -0,0 +1,6 @@
table_name("alf_exceptions")
schema([
Column(name="path", type="std::string"),
Column(name="state", type="int"),
])
implementation("osquery/tables/system/firewall@genALFExceptions")

View File

@ -0,0 +1,5 @@
table_name("alf_explicit_auths")
schema([
Column(name="processes", type="std::string"),
])
implementation("osquery/tables/system/firewall@genALFExplicitAuths")

View File

@ -0,0 +1,7 @@
table_name("alf_services")
schema([
Column(name="service", type="std::string"),
Column(name="process", type="std::string"),
Column(name="state", type="int"),
])
implementation("osquery/tables/system/firewall@genALFServices")

View File

@ -0,0 +1,218 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "osquery/tables/system/firewall.h"
#include <glog/logging.h>
#include <boost/lexical_cast.hpp>
#include "osquery/database.h"
#include "osquery/filesystem.h"
#include "osquery/status.h"
using namespace osquery::db;
using osquery::Status;
namespace pt = boost::property_tree;
namespace osquery { namespace tables {
const std::string kALFPlistPath = "/Library/Preferences/com.apple.alf.plist";
// it.first represents the key that is used in com.apple.alf.plist to identify
// the data in question. it.second represents the value of the "service" column
// in the alf_services table.
const std::map<std::string, std::string> kFirewallTreeKeys = {
{"Apple Remote Desktop", "Apple Remote Desktop"},
{"FTP Access", "FTP"},
{"ODSAgent", "ODSAgent"},
{"Personal File Sharing", "File Sharing"},
{"Personal Web Sharing", "Web Sharing"},
{"Printer Sharing", "Printer Sharing"},
{"Remote Apple Events", "Remote Apple Events"},
{"Remote Login - SSH", "SSH"},
{"Samba Sharing", "Samba Sharing"},
};
// it.first represents the top level keys in com.apple.alf.plist to identify
// the data in question. it.second represents the names of the columns that
// each sample of data can be found under in the alf table.
const std::map<std::string, std::string> kTopLevelIntKeys = {
{"allowsignedenabled", "allow_signed_enabled"},
{"firewallunload", "firewall_unload"},
{"globalstate", "global_state"},
{"loggingenabled", "logging_enabled"},
{"loggingoption", "logging_option"},
{"stealthenabled", "stealth_enabled"},
};
// it.first represents the top level keys in com.apple.alf.plist to identify
// the data in question. it.second represents the names of the columns that
// each sample of data can be found under in the alf table.
const std::map<std::string, std::string> kTopLevelStringKeys = {
{"version", "version"},
};
Status genALFTreeFromFilesystem(pt::ptree& tree) {
try {
Status s = osquery::fs::parsePlist(kALFPlistPath, tree);
if (!s.ok()) {
LOG(ERROR) << "Error parsing " << kALFPlistPath << ": " << s.toString();
return s;
}
} catch(const std::exception& e) {
return Status(1, e.what());
}
return Status(0, "OK");
}
QueryData parseALFTree(const pt::ptree& tree) {
Row r;
for (const auto& it : kTopLevelIntKeys) {
try {
int val = tree.get<int>(it.first);
r[it.second] = boost::lexical_cast<std::string>(val);
} catch (const pt::ptree_error& e) {
LOG(ERROR) << "Error retreiving " << it.second << " from com.apple.alf: "
<< e.what();
}
}
for (const auto& it : kTopLevelStringKeys) {
try {
std::string val = tree.get<std::string>(it.second);
r[it.first] = val;
} catch (const pt::ptree_error& e) {
LOG(ERROR) << "Error retreiving " << it.second << " from com.apple.alf: "
<< e.what();
}
}
return {r};
}
QueryData genALF() {
pt::ptree tree;
auto s = genALFTreeFromFilesystem(tree);
if (!s.ok()) {
return {};
}
return parseALFTree(tree);
}
QueryData parseALFExceptionsTree(const pt::ptree& tree) {
QueryData results;
pt::ptree exceptions_tree;
try {
exceptions_tree = tree.get_child("exceptions");
} catch (const pt::ptree_error& e) {
LOG(ERROR) << "Error retrieving exceptions key: " << e.what();
return {};
}
for (const auto& it : exceptions_tree) {
std::string path;
int state;
try {
path = it.second.get<std::string>("path");
state = it.second.get<int>("state");
Row r;
r["path"] = path;
r["state"] = boost::lexical_cast<std::string>(state);
results.push_back(r);
} catch (const pt::ptree_error& e) {
LOG(ERROR) << "Error retrieving firewall exception keys: " << e.what();
} catch (const boost::bad_lexical_cast& e) {
LOG(ERROR) << "Error casting state (" << state << "): " << e.what();
}
}
return results;
}
QueryData genALFExceptions() {
pt::ptree tree;
auto s = genALFTreeFromFilesystem(tree);
if (!s.ok()) {
return {};
}
return parseALFExceptionsTree(tree);
}
QueryData parseALFExplicitAuthsTree(const pt::ptree& tree) {
QueryData results;
pt::ptree auths_tree;
try {
auths_tree = tree.get_child("explicitauths");
} catch (const pt::ptree_error& e) {
LOG(ERROR) << "Error retrieving explicitauths key: " << e.what();
}
for (const auto& it : auths_tree) {
std::string process;
try {
process = it.second.get<std::string>("id");
Row r;
r["process"] = process;
results.push_back(r);
} catch (const pt::ptree_error& e) {
LOG(ERROR) << "Error retrieving firewall exception keys: " << e.what();
}
}
return results;
}
QueryData genALFExplicitAuths() {
pt::ptree tree;
auto s = genALFTreeFromFilesystem(tree);
if (!s.ok()) {
return {};
}
return parseALFExplicitAuthsTree(tree);
}
QueryData parseALFServicesTree(const pt::ptree& tree) {
QueryData results;
pt::ptree firewall_tree;
try {
firewall_tree = tree.get_child("firewall");
} catch (const pt::ptree_error& e) {
LOG(ERROR) << "Error retrieving firewall key: " << e.what();
}
for (const auto& it : kFirewallTreeKeys) {
std::string proc;
int state;
pt::ptree subtree;
try {
subtree = firewall_tree.get_child(it.first);
proc = subtree.get<std::string>("proc");
state = subtree.get<int>("state");
Row r;
r["service"] = it.second;
r["process"] = proc;
r["state"] = boost::lexical_cast<std::string>(state);
results.push_back(r);
} catch (const pt::ptree_error& e) {
LOG(ERROR) << "Error retrieving " << it.first << " keys: " << e.what();
} catch (const boost::bad_lexical_cast& e) {
LOG(ERROR) << "Error casting state (" << state << "): " << e.what();
}
}
return results;
}
QueryData genALFServices() {
pt::ptree tree;
auto s = genALFTreeFromFilesystem(tree);
if (!s.ok()) {
return {};
}
return parseALFServicesTree(tree);
}
}}

View File

@ -0,0 +1,52 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#ifndef OSQUERY_TABLES_SYSTEM_FIREWALL_H
#define OSQUERY_TABLES_SYSTEM_FIREWALL_H
#include <map>
#include <string>
#include <boost/property_tree/ptree.hpp>
#include "osquery/database/results.h"
namespace osquery { namespace tables {
// Given a property tree of the parsed content of com.apple.alf.plist,
// parseALFExceptionsTree parses out the "exceptions" key
osquery::db::QueryData parseALFExceptionsTree(
const boost::property_tree::ptree& tree);
// Given a property tree of the parsed content of com.apple.alf.plist,
// parseALFExplicitAuthsTree parses out the "explicitauth" key
osquery::db::QueryData parseALFExplicitAuthsTree(
const boost::property_tree::ptree& tree);
// Given a property tree of the parsed content of com.apple.alf.plist,
// parseALFServicesTree parses out the services which exist under the
// "firewall" key
osquery::db::QueryData parseALFServicesTree(
const boost::property_tree::ptree& tree);
// Given a property tree of the parsed content of com.apple.alf.plist,
// parseALFTree parses out the top level string and int keys
osquery::db::QueryData parseALFTree(const boost::property_tree::ptree& tree);
// kALFPlistPath is the path of the com.apple.alf.plist path
extern const std::string kALFPlistPath;
// kFirewallTreeKeys is a map of keys and columns which are used while parsing
// in the function parseALFServicesTree
extern const std::map<std::string, std::string> kFirewallTreeKeys;
// kTopLevelIntKeys is a map of keys and columns which are used while parsing
// in the function parseALFTree
extern const std::map<std::string, std::string> kTopLevelIntKeys;
// kTopLevelStringKeys is a map of keys and columns which are used while
// parsing in the function parseALFTree
extern const std::map<std::string, std::string> kTopLevelStringKeys;
}}
#endif /* OSQUERY_TABLES_SYSTEM_FIREWALL_H */

View File

@ -0,0 +1,165 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include <gtest/gtest.h>
#include <glog/logging.h>
#include "osquery/core/test_util.h"
#include "osquery/database.h"
#include "osquery/filesystem.h"
#include "osquery/tables/system/firewall.h"
using namespace osquery::core;
namespace pt = boost::property_tree;
namespace osquery { namespace tables {
class FirewallTests : public testing::Test {};
TEST_F(FirewallTests, test_parse_alf_tree) {
pt::ptree tree = getALFTree();
auto results = parseALFTree(tree);
osquery::db::QueryData expected = {
{
{"allow_signed_enabled", "1"},
{"firewall_unload", "0"},
{"global_state", "0"},
{"logging_enabled", "0"},
{"logging_option", "0"},
{"stealth_enabled", "0"},
{"version", "1.0a25"},
},
};
EXPECT_EQ(results, expected);
}
TEST_F(FirewallTests, test_parse_alf_exceptions_tree) {
pt::ptree tree = getALFTree();
auto results = parseALFExceptionsTree(tree);
osquery::db::QueryData expected = {
{
{"path", "/usr/libexec/configd"},
{"state", "3"}
},
{
{"path", "/usr/sbin/mDNSResponder"},
{"state", "3"}
},
{
{"path", "/usr/sbin/racoon"},
{"state", "3"}
},
{
{"path", "/usr/bin/nmblookup"},
{"state", "3"}
},
{
{"path", "/System/Library/PrivateFrameworks/Admin.framework/Versions/A/Resources/readconfig"},
{"state", "3"}
},
};
EXPECT_EQ(results, expected);
}
TEST_F(FirewallTests, test_parse_alf_explicit_auths_tree) {
pt::ptree tree = getALFTree();
auto results = parseALFExplicitAuthsTree(tree);
osquery::db::QueryData expected = {
{{"process", "org.python.python.app"}},
{{"process", "com.apple.ruby"}},
{{"process", "com.apple.a2p"}},
{{"process", "com.apple.javajdk16.cmd"}},
{{"process", "com.apple.php"}},
{{"process", "com.apple.nc"}},
{{"process", "com.apple.ksh"}},
};
EXPECT_EQ(results, expected);
}
TEST_F(FirewallTests, test_parse_alf_services_tree) {
pt::ptree tree = getALFTree();
auto results = parseALFServicesTree(tree);
osquery::db::QueryData expected = {
{
{"service", "Apple Remote Desktop"},
{"process", "AppleVNCServer"},
{"state", "0"},
},
{
{"service", "FTP"},
{"process", "ftpd"},
{"state", "0"},
},
{
{"service", "ODSAgent"},
{"process", "ODSAgent"},
{"state", "0"},
},
{
{"service", "File Sharing"},
{"process", "AppleFileServer"},
{"state", "0"},
},
{
{"service", "Web Sharing"},
{"process", "httpd"},
{"state", "0"},
},
{
{"service", "Printer Sharing"},
{"process", "cupsd"},
{"state", "0"},
},
{
{"service", "Remote Apple Events"},
{"process", "AEServer"},
{"state", "0"},
},
{
{"service", "SSH"},
{"process", "sshd-keygen-wrapper"},
{"state", "0"},
},
{
{"service", "Samba Sharing"},
{"process", "smbd"},
{"state", "0"},
},
};
EXPECT_EQ(results, expected);
}
TEST_F(FirewallTests, test_errors) {
pt::ptree tree = getALFTree();
auto results = parseALFTree(tree);
ASSERT_THROW(tree.get<int>("foo"), pt::ptree_error);
ASSERT_THROW(tree.get<int>("version"), pt::ptree_error);
ASSERT_THROW(tree.get<int>("version"), pt::ptree_bad_data);
ASSERT_THROW(tree.get_child("foo"), pt::ptree_error);
ASSERT_THROW(tree.get_child("foo"), pt::ptree_bad_path);
}
TEST_F(FirewallTests, test_on_disk_format) {
pt::ptree tree;
auto s = osquery::fs::parsePlist(kALFPlistPath, tree);
EXPECT_TRUE(s.ok());
EXPECT_EQ(s.toString(), "OK");
for (const auto& it : kTopLevelIntKeys) {
EXPECT_NO_THROW(tree.get<int>(it.first));
}
for (const auto& it : kTopLevelStringKeys) {
EXPECT_NO_THROW(tree.get<std::string>(it.first));
}
EXPECT_NO_THROW(tree.get_child("firewall"));
auto firewall = tree.get_child("firewall");
for (const auto& it : kFirewallTreeKeys) {
EXPECT_NO_THROW(firewall.get_child(it.first));
}
}
}}
int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv);
google::InitGoogleLogging(argv[0]);
return RUN_ALL_TESTS();
}