CVE-2020-26273 (published 2020-12-16T02:15Z)

osquery is a SQL powered operating system instrumentation, monitoring, and analytics framework. In osquery before version 4.6.0, by using sqlite's ATTACH verb, someone with administrative access to osquery can cause reads and writes to arbitrary sqlite databases on disk. This _does_ allow arbitrary files to be created, but they will be sqlite databases. It does not appear to allow existing non-sqlite files to be overwritten. This has been patched in osquery 4.6.0. There are several mitigating factors and possible workarounds. In some deployments, the people with access to these interfaces may be considered administrators. In some deployments, configuration is managed by a central tool. This tool can filter for the `ATTACH` keyword. osquery can be run as non-root user. Because this also limits the desired access levels, this requires deployment specific testing and configuration.

Severity: LOW
CVSS v2 base score: 3.6 (AV:L/AC:L/Au:N/C:P/I:P/A:N)
CVSS v3 base score: 5.2, MEDIUM (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N)

Weakness information:
cwe_id: CWE-77
cwe_name: Improper Neutralization of Special Elements used in a Command ('Command Injection')
description: The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.
extended_description: 
            Many protocols and products have their own custom command language. While OS or shell command strings are frequently discovered and targeted, developers may not realize that these other command languages might also be vulnerable to attacks.
         
         

Fix information:
repo_url: https://github.com/osquery/osquery
commit_details: repo_url: https://github.com/osquery/osquery
author: Zach Wasserman
author_date: 2020-12-14 20:41:57+00:00
msg: Merge pull request from GHSA-4g56-2482-x7q8

* Proposed fix for attach tables vulnerability

* Add authorizer to ATC tables and cleanups

- Add unit test for authorizer function
repository: repo_url: https://github.com/osquery/osquery
repo_name: osquery/osquery
description: SQL powered operating system instrumentation, monitoring, and analytics.
repo_language: C++
owner: osquery
file_changes: filename: sqlite_util.cpp
change_type: ModificationType.MODIFY
diff: @@ -16,6 +16,7 @@
 
 #include <osquery/core/core.h>
 #include <osquery/core/flags.h>
+#include <osquery/core/shutdown.h>
 #include <osquery/logger/logger.h>
 #include <osquery/registry/registry_factory.h>
 #include <osquery/sql/sql.h>
@@ -270,6 +271,23 @@ SQLiteDBInstance::SQLiteDBInstance(sqlite3*& db, Mutex& mtx)
   }
 }
 
+// This function is called by SQLite when a statement is prepared and we use
+// it to allowlist specific actions.
+int sqliteAuthorizer(void* userData,
+                     int code,
+                     const char* arg3,
+                     const char* arg4,
+                     const char* arg5,
+                     const char* arg6) {
+  if (kAllowedSQLiteActionCodes.count(code) > 0) {
+    return SQLITE_OK;
+  }
+  LOG(ERROR) << "Authorizer denied action " << code << " "
+             << (arg3 ? arg3 : "null") << " " << (arg4 ? arg4 : "null") << " "
+             << (arg5 ? arg5 : "null") << " " << (arg6 ? arg6 : "null");
+  return SQLITE_DENY;
+}
+
 static inline void openOptimized(sqlite3*& db) {
   sqlite3_open(":memory:", &db);
 
@@ -290,6 +308,12 @@ static inline void openOptimized(sqlite3*& db) {
   registerFilesystemExtensions(db);
   registerHashingExtensions(db);
   registerEncodingExtensions(db);
+
+  auto rc = sqlite3_set_authorizer(db, &sqliteAuthorizer, nullptr);
+  if (rc != SQLITE_OK) {
+    LOG(ERROR) << "Failed to set sqlite authorizer: " << sqlite3_errmsg(db);
+    requestShutdown(rc);
+  }
 }
 
 void SQLiteDBInstance::init() {
@@ -458,6 +482,7 @@ SQLiteDBInstanceRef SQLiteDBManager::getConnection(bool primary) {
   if (!instance->isPrimary()) {
     attachVirtualTables(instance);
   }
+
   return instance;
 }
 

programming_language: C++
filename: sqlite_util.h
change_type: ModificationType.MODIFY
diff: @@ -29,6 +29,47 @@
 
 namespace osquery {
 
+int sqliteAuthorizer(void* userData,
+                     int code,
+                     const char* arg3,
+                     const char* arg4,
+                     const char* arg5,
+                     const char* arg6);
+
+// Allowlist of SQLite actions. Any action not in this set is denied. See
+// possible values in https://sqlite.org/c3ref/c_alter_table.html.
+// ** Never allow SQLITE_ATTACH ** as it can be used to write arbitrary files.
+const std::unordered_set<int> kAllowedSQLiteActionCodes = {
+    // Enable basic functionality
+    SQLITE_READ,
+    SQLITE_SELECT,
+
+    // Some extensions implement writeable tables
+    SQLITE_INSERT,
+    SQLITE_UPDATE,
+    SQLITE_DELETE,
+
+    // Allow virtual tables to be attached
+    SQLITE_CREATE_VTABLE,
+    SQLITE_DROP_VTABLE,
+
+    // Users may sometimes want to create tables and views
+    SQLITE_CREATE_VIEW,
+    SQLITE_DROP_VIEW,
+    SQLITE_CREATE_TABLE,
+    SQLITE_DROP_TABLE,
+    SQLITE_CREATE_TEMP_TABLE,
+    SQLITE_DROP_TEMP_TABLE,
+    SQLITE_CREATE_TEMP_VIEW,
+    SQLITE_DROP_TEMP_VIEW,
+
+    // Required to allow calling functions in SQL
+    SQLITE_FUNCTION,
+
+    // Required for recursive queries
+    SQLITE_RECURSIVE,
+};
+
 class SQLiteDBManager;
 
 /**

programming_language: C++
filename: sqlite_util_tests.cpp
change_type: ModificationType.MODIFY
diff: @@ -36,9 +36,10 @@ class SQLiteUtilTests : public testing::Test {
 
 std::shared_ptr<SQLiteDBInstance> getTestDBC() {
   auto dbc = SQLiteDBManager::getUnique();
+
   char* err = nullptr;
   std::vector<std::string> queries = {
-      "CREATE TABLE test_table (username varchar(30) primary key, age int)",
+      "CREATE TABLE test_table (username varchar(30), age int)",
       "INSERT INTO test_table VALUES (\"mike\", 23)",
       "INSERT INTO test_table VALUES (\"matt\", 24)"};
 
@@ -434,7 +435,7 @@ TEST_F(SQLiteUtilTests, test_column_type_determination) {
   // if they happen to have integer value.  And also test multi-statement
   // queries.
   testTypesExpected(
-      "CREATE TABLE test_types_table (username varchar(30) primary key, age "
+      "CREATE TABLE test_types_table (username varchar(30), age "
       "double);INSERT INTO test_types_table VALUES (\"mike\", 23); SELECT age "
       "from test_types_table",
       TypeMap({{"age", DOUBLE_TYPE}}));
@@ -450,4 +451,17 @@ TEST_F(SQLiteUtilTests, test_enable) {
   ASSERT_TRUE(SQLiteDBManager::isDisabled("fake_table"));
 }
 
+TEST_F(SQLiteUtilTests, test_sqlite_authorizer) {
+  auto rc = sqliteAuthorizer(
+      nullptr, SQLITE_ATTACH, nullptr, nullptr, nullptr, nullptr);
+  EXPECT_EQ(SQLITE_DENY, rc);
+
+  rc = sqliteAuthorizer(nullptr, 534, nullptr, nullptr, nullptr, nullptr);
+  EXPECT_EQ(SQLITE_DENY, rc);
+
+  rc = sqliteAuthorizer(
+      nullptr, SQLITE_SELECT, nullptr, nullptr, nullptr, nullptr);
+  EXPECT_EQ(SQLITE_OK, rc);
+}
+
 } // namespace osquery

programming_language: C++
filename: virtual_sqlite_table.cpp
change_type: ModificationType.MODIFY
diff: @@ -96,6 +96,14 @@ Status genTableRowsForSqliteTable(const fs::path& sqlite_db,
     return Status(1, "Could not open database");
   }
 
+  rc = sqlite3_set_authorizer(db, &sqliteAuthorizer, nullptr);
+  if (rc != SQLITE_OK) {
+    sqlite3_close(db);
+    auto errMsg =
+        std::string("Failed to set sqlite authorizer: ") + sqlite3_errmsg(db);
+    return Status(1, errMsg);
+  }
+
   sqlite3_stmt* stmt = nullptr;
   rc = sqlite3_prepare_v2(db, sqlite_query.c_str(), -1, &stmt, nullptr);
   if (rc != SQLITE_OK) {

programming_language: C++