New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

darwin preferences table not reading values enforced by profiles #3083

Closed
groob opened this Issue Mar 17, 2017 · 8 comments

Comments

Projects
None yet
3 participants
@groob
Contributor

groob commented Mar 17, 2017

I am using the preferences table to check for certain keys to be set on user machines. Unfortunately, I've discovered that when the preference is managed with a profile (.mobileconfig), the key is no longer accurately reported through the preferences.

On a new mac(when the preference is not managed):

sudo osqueryi --line 'select * from preferences where domain = "com.apple.security.firewall"';

I install this profile:

Which enforces the following keys:

			<key>BlockAllIncoming</key>
			<true/>
			<key>EnableFirewall</key>
			<true/>
			<key>EnableStealthMode</key>
			<true/>

Still no output from osquery even though the key is now managed:

sudo osqueryi --line 'select * from preferences where domain = "com.apple.security.firewall"';

I use defaults to set the EnableFirewall key to false:
sudo defaults write com.apple.security.firewall EnableFirewall -bool false

sudo osqueryi --line 'select * from preferences where domain = "com.apple.security.firewall"';
domain = com.apple.security.firewall
   key = EnableFirewall
subkey =
 value = false
forced = 1
  path =

Using defaults should not have an effect since the profile is still enforcing the actual preferences. Worse though, it's now reporting an incorrect value.

@headmin has a related issue open to create a profiles table -#2990. The issue is categorized as a feature request, but IMO this is a bug.

@gregneagle

This comment has been minimized.

Show comment
Hide comment
@gregneagle

gregneagle Mar 17, 2017

You can easily read a specific preference value in such a way that you'd get the managed value for the preference, but it's really difficult to enumerate all the currently set preferences keys for a given domain in a way that also gets you managed preferences.

gregneagle commented Mar 17, 2017

You can easily read a specific preference value in such a way that you'd get the managed value for the preference, but it's really difficult to enumerate all the currently set preferences keys for a given domain in a way that also gets you managed preferences.

@theopolis

This comment has been minimized.

Show comment
Hide comment
@theopolis

theopolis Mar 17, 2017

Contributor

Is there a way to get the accurate data without osquery?

Contributor

theopolis commented Mar 17, 2017

Is there a way to get the accurate data without osquery?

@groob

This comment has been minimized.

Show comment
Hide comment
@groob

groob Mar 17, 2017

Contributor

@theopolis it appears that I can only reliably get the value, but not to enumerate.

Example when I only use the above profile and
sudo defaults delete com.apple.security.firewall

from Foundation import CFPreferencesCopyAppValue
from Foundation import CFPreferencesCopyKeyList
from Foundation import kCFPreferencesAnyUser
from Foundation import kCFPreferencesCurrentUser
from Foundation import kCFPreferencesAnyHost

BUNDLE_ID = 'com.apple.security.firewall'
current_keys = CFPreferencesCopyKeyList(
    BUNDLE_ID, kCFPreferencesCurrentUser, kCFPreferencesAnyHost)
all_keys = CFPreferencesCopyKeyList(
    BUNDLE_ID, kCFPreferencesAnyUser, kCFPreferencesAnyHost)

key = "EnableFirewall"
value = CFPreferencesCopyAppValue(key, BUNDLE_ID)
print(current_keys) #prints `None`
print(all_keys) #prints None
print(value) #prints True
sudo python script.py
None
None
True
Contributor

groob commented Mar 17, 2017

@theopolis it appears that I can only reliably get the value, but not to enumerate.

Example when I only use the above profile and
sudo defaults delete com.apple.security.firewall

from Foundation import CFPreferencesCopyAppValue
from Foundation import CFPreferencesCopyKeyList
from Foundation import kCFPreferencesAnyUser
from Foundation import kCFPreferencesCurrentUser
from Foundation import kCFPreferencesAnyHost

BUNDLE_ID = 'com.apple.security.firewall'
current_keys = CFPreferencesCopyKeyList(
    BUNDLE_ID, kCFPreferencesCurrentUser, kCFPreferencesAnyHost)
all_keys = CFPreferencesCopyKeyList(
    BUNDLE_ID, kCFPreferencesAnyUser, kCFPreferencesAnyHost)

key = "EnableFirewall"
value = CFPreferencesCopyAppValue(key, BUNDLE_ID)
print(current_keys) #prints `None`
print(all_keys) #prints None
print(value) #prints True
sudo python script.py
None
None
True
@theopolis

This comment has been minimized.

Show comment
Hide comment
@theopolis

theopolis Mar 26, 2017

Contributor

Is this an issue of running osquery as the user with the profile versus root?

I added:

diff --git a/osquery/tables/system/darwin/preferences.cpp b/osquery/tables/system/darwin/preferences.cpp
index 2635fe9..a8409d9 100644
--- a/osquery/tables/system/darwin/preferences.cpp
+++ b/osquery/tables/system/darwin/preferences.cpp
@@ -145,10 +145,19 @@ void genOSXDomainPrefs(const CFStringRef& domain, QueryData& results) {
     }
 
     // Check if the preference key is managed by a profile.
-    r["forced"] = (CFPreferencesAppValueIsForced(key, domain)) ? "1" : "0";
-
-    // Check the key and key type (which may be any CF type).
-    CFTypeRef value = (CFTypeRef)CFDictionaryGetValue(values, key);
+    auto forced = CFPreferencesAppValueIsForced(key, domain);
+    r["forced"] = (forced) ? "1" : "0";
+
+    CFTypeRef value = nullptr;
+    if (forced) {
+      value = (CFTypeRef)CFPreferencesCopyAppValue(key, domain);
+      printf("forced value was returned (%s) (%s)\n",
+             r["key"].c_str(),
+             r["domain"].c_str());
+    } else {
+      // Check the key and key type (which may be any CF type).
+      value = (CFTypeRef)CFDictionaryGetValue(values, key);
+    }
     genOSXPrefValues(value, r, results, 0);
   }

But with/without the change the results are the same:

$ ./build/darwin/osquery/osqueryi "select * from preferences" --json | grep -i askForPassword
forced value was returned (askForPassword) (com.apple.screensaver)
forced value was returned (askForPasswordDelay) (com.apple.screensaver)
  {"domain":"com.apple.screensaver","forced":"1","key":"askForPassword","path":"","subkey":"","value":"1"},
  {"domain":"com.apple.screensaver","forced":"1","key":"askForPasswordDelay","path":"","subkey":"","value":"0"},
  {"domain":"com.apple.MCX","forced":"0","key":"com.apple.screensaver","path":"","subkey":"askForPasswordDelay","value":""},
  {"domain":"com.apple.MCX","forced":"0","key":"com.apple.screensaver","path":"","subkey":"askForPassword","value":""},
$ sudo ./build/darwin/osquery/osqueryi "select * from preferences" --json | grep -i askForPassword

(no results)

Contributor

theopolis commented Mar 26, 2017

Is this an issue of running osquery as the user with the profile versus root?

I added:

diff --git a/osquery/tables/system/darwin/preferences.cpp b/osquery/tables/system/darwin/preferences.cpp
index 2635fe9..a8409d9 100644
--- a/osquery/tables/system/darwin/preferences.cpp
+++ b/osquery/tables/system/darwin/preferences.cpp
@@ -145,10 +145,19 @@ void genOSXDomainPrefs(const CFStringRef& domain, QueryData& results) {
     }
 
     // Check if the preference key is managed by a profile.
-    r["forced"] = (CFPreferencesAppValueIsForced(key, domain)) ? "1" : "0";
-
-    // Check the key and key type (which may be any CF type).
-    CFTypeRef value = (CFTypeRef)CFDictionaryGetValue(values, key);
+    auto forced = CFPreferencesAppValueIsForced(key, domain);
+    r["forced"] = (forced) ? "1" : "0";
+
+    CFTypeRef value = nullptr;
+    if (forced) {
+      value = (CFTypeRef)CFPreferencesCopyAppValue(key, domain);
+      printf("forced value was returned (%s) (%s)\n",
+             r["key"].c_str(),
+             r["domain"].c_str());
+    } else {
+      // Check the key and key type (which may be any CF type).
+      value = (CFTypeRef)CFDictionaryGetValue(values, key);
+    }
     genOSXPrefValues(value, r, results, 0);
   }

But with/without the change the results are the same:

$ ./build/darwin/osquery/osqueryi "select * from preferences" --json | grep -i askForPassword
forced value was returned (askForPassword) (com.apple.screensaver)
forced value was returned (askForPasswordDelay) (com.apple.screensaver)
  {"domain":"com.apple.screensaver","forced":"1","key":"askForPassword","path":"","subkey":"","value":"1"},
  {"domain":"com.apple.screensaver","forced":"1","key":"askForPasswordDelay","path":"","subkey":"","value":"0"},
  {"domain":"com.apple.MCX","forced":"0","key":"com.apple.screensaver","path":"","subkey":"askForPasswordDelay","value":""},
  {"domain":"com.apple.MCX","forced":"0","key":"com.apple.screensaver","path":"","subkey":"askForPassword","value":""},
$ sudo ./build/darwin/osquery/osqueryi "select * from preferences" --json | grep -i askForPassword

(no results)

@theopolis

This comment has been minimized.

Show comment
Hide comment
@theopolis

theopolis Mar 26, 2017

Contributor

Right, the answer is, yes... but..., when I use any-user in my local build versus current-user in stable:

$ osqueryi "select * from preferences" --json | wc -l                                      
   12399
$ sudo osqueryi "select * from preferences" --json | wc -l
    1074
$ ./build/darwin/osquery/osqueryi "select * from preferences" --json | wc -l     
    5490
$ sudo ./build/darwin/osquery/osqueryi "select * from preferences" --json | wc -l                 
    5661

Seems like we need to prefer kCFPreferencesAnyUser when run as root, and then figure out how to report user preferences set by profiles when run as root. Not sure about that last one.

Contributor

theopolis commented Mar 26, 2017

Right, the answer is, yes... but..., when I use any-user in my local build versus current-user in stable:

$ osqueryi "select * from preferences" --json | wc -l                                      
   12399
$ sudo osqueryi "select * from preferences" --json | wc -l
    1074
$ ./build/darwin/osquery/osqueryi "select * from preferences" --json | wc -l     
    5490
$ sudo ./build/darwin/osquery/osqueryi "select * from preferences" --json | wc -l                 
    5661

Seems like we need to prefer kCFPreferencesAnyUser when run as root, and then figure out how to report user preferences set by profiles when run as root. Not sure about that last one.

@theopolis

This comment has been minimized.

Show comment
Hide comment
@theopolis

theopolis Mar 26, 2017

Contributor

Ok, if I set my user explicitly then both sudo and me return the profile-forced values for my account, but miss the "any user" profile values. I can add username to the table and use that to do something similar to shell_history. If you set where user = 'reed' then that will ask for a specific person's defaults. That's an explosion of data. :(

Contributor

theopolis commented Mar 26, 2017

Ok, if I set my user explicitly then both sudo and me return the profile-forced values for my account, but miss the "any user" profile values. I can add username to the table and use that to do something similar to shell_history. If you set where user = 'reed' then that will ask for a specific person's defaults. That's an explosion of data. :(

@theopolis

This comment has been minimized.

Show comment
Hide comment
@theopolis

theopolis Mar 26, 2017

Contributor

I can reduce the complications here by separating out the defaults-concepts and plist parsing by introducing a new plist table. This new table uses the existing path input to parse the requested plist file.

Then preferences gets a username column that can, as root, take another username and return most of their defaults along with their profiles.

theopolis@9945462

Thoughts?

Contributor

theopolis commented Mar 26, 2017

I can reduce the complications here by separating out the defaults-concepts and plist parsing by introducing a new plist table. This new table uses the existing path input to parse the requested plist file.

Then preferences gets a username column that can, as root, take another username and return most of their defaults along with their profiles.

theopolis@9945462

Thoughts?

@groob

This comment has been minimized.

Show comment
Hide comment
@groob

groob Mar 29, 2017

Contributor

@theopolis I feel like splitting the tables the way you're describing would make sense, at least for my needs of instrumenting macOS endpoints.

Obviously that's a big change, so it would be good to get more feedback. I've brought up the issue several times in #osx on slack, and there's been some discussion there.

Contributor

groob commented Mar 29, 2017

@theopolis I feel like splitting the tables the way you're describing would make sense, at least for my needs of instrumenting macOS endpoints.

Obviously that's a big change, so it would be good to get more feedback. I've brought up the issue several times in #osx on slack, and there's been some discussion there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment