Skip to content

Commit

Permalink
Table users on linux by default to return only users in `/etc/passw…
Browse files Browse the repository at this point in the history
…d` (osquery#8342)

osquery#8337

For manual testing I used [this](https://www.analogous.dev/blog/sssd-without-tls/) guide to setup a local OpenLDAP directory server and a Ubuntu VM that uses such server for authentication.

The Ubuntu VM has 51 local users and 2 "remote" users (joe,uid:1005 and julie,uid:1006) in an LDAP directory.
```sqlite
SELECT uid,username FROM users; // returns 51 local users as expected
+-------+---------------------+
| uid   | username            |
+-------+---------------------+
| 0     | root                |
| 1     | daemon              |
| 2     | bin                 |
| 3     | sys                 |
| 4     | sync                |
| 5     | games               |
| 6     | man                 |
| 7     | lp                  |
| 8     | mail                |
| 9     | news                |
| 10    | uucp                |
| 13    | proxy               |
| 33    | www-data            |
| 34    | backup              |
| 38    | list                |
| 39    | irc                 |
| 41    | gnats               |
| 65534 | nobody              |
| 100   | systemd-network     |
| 101   | systemd-resolve     |
| 102   | messagebus          |
| 103   | systemd-timesync    |
| 104   | syslog              |
| 105   | _apt                |
| 106   | tss                 |
| 107   | uuidd               |
| 108   | systemd-oom         |
| 109   | tcpdump             |
| 110   | avahi-autoipd       |
| 111   | usbmux              |
| 112   | dnsmasq             |
| 113   | kernoops            |
| 114   | avahi               |
| 115   | cups-pk-helper      |
| 116   | rtkit               |
| 117   | whoopsie            |
| 118   | sssd                |
| 119   | speech-dispatcher   |
| 120   | fwupd-refresh       |
| 121   | nm-openvpn          |
| 122   | saned               |
| 123   | colord              |
| 124   | geoclue             |
| 125   | pulse               |
| 126   | gnome-initial-setup |
| 127   | hplip               |
| 128   | gdm                 |
| 1000  | luk                 |
| 1002  | citrixlog           |
| 129   | openldap            |
| 1003  | zoo                 |
+-------+---------------------+

SELECT uid,username FROM users WHERE include_remote=1; // returns 53 users as expected
+-------+---------------------+
| uid   | username            |
+-------+---------------------+
| 0     | root                |
| 1     | daemon              |
| 2     | bin                 |
| 3     | sys                 |
| 4     | sync                |
| 5     | games               |
| 6     | man                 |
| 7     | lp                  |
| 8     | mail                |
| 9     | news                |
| 10    | uucp                |
| 13    | proxy               |
| 33    | www-data            |
| 34    | backup              |
| 38    | list                |
| 39    | irc                 |
| 41    | gnats               |
| 65534 | nobody              |
| 100   | systemd-network     |
| 101   | systemd-resolve     |
| 102   | messagebus          |
| 103   | systemd-timesync    |
| 104   | syslog              |
| 105   | _apt                |
| 106   | tss                 |
| 107   | uuidd               |
| 108   | systemd-oom         |
| 109   | tcpdump             |
| 110   | avahi-autoipd       |
| 111   | usbmux              |
| 112   | dnsmasq             |
| 113   | kernoops            |
| 114   | avahi               |
| 115   | cups-pk-helper      |
| 116   | rtkit               |
| 117   | whoopsie            |
| 118   | sssd                |
| 119   | speech-dispatcher   |
| 120   | fwupd-refresh       |
| 121   | nm-openvpn          |
| 122   | saned               |
| 123   | colord              |
| 124   | geoclue             |
| 125   | pulse               |
| 126   | gnome-initial-setup |
| 127   | hplip               |
| 128   | gdm                 |
| 1000  | luk                 |
| 1002  | citrixlog           |
| 129   | openldap            |
| 1003  | zoo                 |
| 1005  | joe                 |
| 1006  | julie               |
+-------+---------------------+

SELECT * FROM users where uid = 1000; // returns a local user luk as expected
+------+------+------------+------------+----------+-------------+-----------+-----------+------+
| uid  | gid  | uid_signed | gid_signed | username | description | directory | shell     | uuid |
+------+------+------------+------------+----------+-------------+-----------+-----------+------+
| 1000 | 1000 | 1000       | 1000       | luk      | Lucas,,,    | /home/luk | /bin/bash |      |
+------+------+------------+------------+----------+-------------+-----------+-----------+------+

SELECT * FROM users where username = 'luk'; // returns a local user luk as expected
+------+------+------------+------------+----------+-------------+-----------+-----------+------+
| uid  | gid  | uid_signed | gid_signed | username | description | directory | shell     | uuid |
+------+------+------------+------------+----------+-------------+-----------+-----------+------+
| 1000 | 1000 | 1000       | 1000       | luk      | Lucas,,,    | /home/luk | /bin/bash |      |
+------+------+------------+------------+----------+-------------+-----------+-----------+------+

SELECT * FROM users where username = 'luk' OR uid < 10; // returns a local user luk + other local users as expected
+------+-------+------------+------------+----------+-------------+-----------------+-------------------+------+
| uid  | gid   | uid_signed | gid_signed | username | description | directory       | shell             | uuid |
+------+-------+------------+------------+----------+-------------+-----------------+-------------------+------+
| 1000 | 1000  | 1000       | 1000       | luk      | Lucas,,,    | /home/luk       | /bin/bash         |      |
| 0    | 0     | 0          | 0          | root     | root        | /root           | /bin/bash         |      |
| 1    | 1     | 1          | 1          | daemon   | daemon      | /usr/sbin       | /usr/sbin/nologin |      |
| 2    | 2     | 2          | 2          | bin      | bin         | /bin            | /usr/sbin/nologin |      |
| 3    | 3     | 3          | 3          | sys      | sys         | /dev            | /usr/sbin/nologin |      |
| 4    | 65534 | 4          | 65534      | sync     | sync        | /bin            | /bin/sync         |      |
| 5    | 60    | 5          | 60         | games    | games       | /usr/games      | /usr/sbin/nologin |      |
| 6    | 12    | 6          | 12         | man      | man         | /var/cache/man  | /usr/sbin/nologin |      |
| 7    | 7     | 7          | 7          | lp       | lp          | /var/spool/lpd  | /usr/sbin/nologin |      |
| 8    | 8     | 8          | 8          | mail     | mail        | /var/mail       | /usr/sbin/nologin |      |
| 9    | 9     | 9          | 9          | news     | news        | /var/spool/news | /usr/sbin/nologin |      |
+------+-------+------------+------------+----------+-------------+-----------------+-------------------+------+

SELECT * FROM users where (username = 'luk' OR uid < 10) AND include_remote=1; // returns a local user luk + other local users as expected
+------+-------+------------+------------+----------+-------------+-----------------+-------------------+------+
| uid  | gid   | uid_signed | gid_signed | username | description | directory       | shell             | uuid |
+------+-------+------------+------------+----------+-------------+-----------------+-------------------+------+
| 0    | 0     | 0          | 0          | root     | root        | /root           | /bin/bash         |      |
| 1    | 1     | 1          | 1          | daemon   | daemon      | /usr/sbin       | /usr/sbin/nologin |      |
| 2    | 2     | 2          | 2          | bin      | bin         | /bin            | /usr/sbin/nologin |      |
| 3    | 3     | 3          | 3          | sys      | sys         | /dev            | /usr/sbin/nologin |      |
| 4    | 65534 | 4          | 65534      | sync     | sync        | /bin            | /bin/sync         |      |
| 5    | 60    | 5          | 60         | games    | games       | /usr/games      | /usr/sbin/nologin |      |
| 6    | 12    | 6          | 12         | man      | man         | /var/cache/man  | /usr/sbin/nologin |      |
| 7    | 7     | 7          | 7          | lp       | lp          | /var/spool/lpd  | /usr/sbin/nologin |      |
| 8    | 8     | 8          | 8          | mail     | mail        | /var/mail       | /usr/sbin/nologin |      |
| 9    | 9     | 9          | 9          | news     | news        | /var/spool/news | /usr/sbin/nologin |      |
| 1000 | 1000  | 1000       | 1000       | luk      | Lucas,,,    | /home/luk       | /bin/bash         |      |
+------+-------+------------+------------+----------+-------------+-----------------+-------------------+------+

SELECT * FROM users where (username = 'julie' OR uid = 1005) AND include_remote=1; // returns the two remote users as expected
+------+-----+------------+------------+----------+-------------+-------------+---------+------+
| uid  | gid | uid_signed | gid_signed | username | description | directory   | shell   | uuid |
+------+-----+------------+------------+----------+-------------+-------------+---------+------+
| 1005 | 600 | 1005       | 600        | joe      | joe         | /home/joe   | /bin/sh |      |
| 1006 | 600 | 1006       | 600        | julie    | julie       | /home/julie | /bin/sh |      |
+------+-----+------------+------------+----------+-------------+-------------+---------+------+

SELECT * FROM users where (username = 'julie' OR uid = 1005); // returns empty as expected
```
  • Loading branch information
lucasmrod authored Jun 27, 2024
1 parent 989f376 commit a56ce1e
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 9 deletions.
91 changes: 86 additions & 5 deletions osquery/tables/system/linux/users.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only)
*/

#include <errno.h>
#include <pwd.h>

#include <osquery/core/core.h>
Expand All @@ -18,7 +19,9 @@
namespace osquery {
namespace tables {

void genUser(const struct passwd* pwd, QueryData& results) {
void genUser(const struct passwd* pwd,
QueryData& results,
const std::string& include_remote) {
Row r;
r["uid"] = BIGINT(pwd->pw_uid);
r["gid"] = BIGINT(pwd->pw_gid);
Expand All @@ -41,10 +44,11 @@ void genUser(const struct passwd* pwd, QueryData& results) {
r["shell"] = SQL_TEXT(pwd->pw_shell);
}
r["pid_with_namespace"] = "0";
r["include_remote"] = include_remote;
results.push_back(r);
}

QueryData genUsersImpl(QueryContext& context, Logger& logger) {
QueryData genUsersImplIncludeRemote(QueryContext& context, Logger& logger) {
QueryData results;
struct passwd pwd;
struct passwd* pwd_results{nullptr};
Expand All @@ -62,7 +66,7 @@ QueryData genUsersImpl(QueryContext& context, Logger& logger) {
if (auid_exp.isValue()) {
getpwuid_r(auid_exp.get(), &pwd, buf.get(), bufsize, &pwd_results);
if (pwd_results != nullptr) {
genUser(pwd_results, results);
genUser(pwd_results, results, "1");
}
}
}
Expand All @@ -71,7 +75,7 @@ QueryData genUsersImpl(QueryContext& context, Logger& logger) {
for (const auto& username : usernames) {
getpwnam_r(username.c_str(), &pwd, buf.get(), bufsize, &pwd_results);
if (pwd_results != nullptr) {
genUser(pwd_results, results);
genUser(pwd_results, results, "1");
}
}
} else {
Expand All @@ -81,14 +85,90 @@ QueryData genUsersImpl(QueryContext& context, Logger& logger) {
if (pwd_results == nullptr) {
break;
}
genUser(pwd_results, results);
genUser(pwd_results, results, "1");
}
endpwent();
}

return results;
}

QueryData genUsersImplLocal(QueryContext& context, Logger& logger) {
//
// Either "username" or "uid" is set on the constraints, not both.
//
const auto usernames = context.constraints["username"].getAll(EQUALS);
const auto uids = [&context]() -> std::set<uid_t> {
std::set<uid_t> uids;
const auto uid_constraints = context.constraints["uid"].getAll(EQUALS);
for (const auto& uid_constraint : uid_constraints) {
auto const auid_exp = tryTo<long>(uid_constraint, 10);
if (auid_exp.isValue()) {
uids.insert(auid_exp.get());
}
}
return uids;
}();

//
// We are avoiding the use of setpwent, getpwent_r, endpwent, getpwnam_r and
// getpwuid_r to prevent osquery sending requests to LDAP directories on
// hosts that have LDAP configured for authentication.
// (See https://github.com/osquery/osquery/issues/8337.)
//
QueryData results;
FILE* passwd_file = fopen("/etc/passwd", "r");
if (passwd_file == nullptr) {
LOG(ERROR) << "could not open /etc/passwd file: " << std::strerror(errno);
return results;
}

size_t bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
if (bufsize > 16384) { // value was indeterminate
bufsize = 16384; // should be more than enough
}
auto buf = std::make_unique<char[]>(bufsize);

struct passwd pwd;
struct passwd* result{nullptr};
int ret;
while (1) {
ret = fgetpwent_r(passwd_file, &pwd, buf.get(), bufsize, &result);
if (ret != 0 || result == nullptr) {
break;
}
if (!usernames.empty()) {
if (usernames.find(result->pw_name) == usernames.end()) {
continue;
}
} else if (!uids.empty()) {
if (uids.find(result->pw_uid) == uids.end()) {
continue;
}
}
genUser(result, results, "0");
}

if (ret != 0 && ret != ENOENT) {
LOG(ERROR) << "failed to iterate /etc/passwd file: "
<< std::strerror(errno);
}
fclose(passwd_file);

return results;
}

QueryData genUsersImpl(QueryContext& context, Logger& logger) {
auto include_remote = 0;
if (context.hasConstraint("include_remote", EQUALS)) {
include_remote = context.constraints["include_remote"].matches<int>(1);
}
if (include_remote) {
return genUsersImplIncludeRemote(context, logger);
}
return genUsersImplLocal(context, logger);
}

QueryData genUsers(QueryContext& context) {
if (hasNamespaceConstraint(context)) {
return generateInNamespace(context, "users", genUsersImpl);
Expand All @@ -97,5 +177,6 @@ QueryData genUsers(QueryContext& context) {
return genUsersImpl(context, logger);
}
}

} // namespace tables
} // namespace osquery
3 changes: 3 additions & 0 deletions specs/users.table
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ extended_schema(DARWIN, [
extended_schema(LINUX, [
Column("pid_with_namespace", INTEGER, "Pids that contain a namespace", additional=True, hidden=True),
])
extended_schema(LINUX, [
Column("include_remote", INTEGER, "1 to include remote (LDAP/AD) accounts (default 0). Warning: without any uid/username filtering it may list whole LDAP directories", additional=True, hidden=True),
])
implementation("users@genUsers")
examples([
"select * from users where uid = 1000",
Expand Down
45 changes: 41 additions & 4 deletions tests/integration/tables/users.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,54 @@ TEST_F(UsersTest, test_sanity) {
row_map.emplace("uuid", NormalType);
}

//
// The returned user might be "root" or a test user created as
// part of the Github CI action (see .github/workflows/hosted_runners.yml
// and .github/workflows/self_hosted_runners.yml).
//

// select * case
auto const rows = execute_query("select * from users");
ASSERT_GE(rows.size(), 1ul);
validate_rows(rows, row_map);

// select with a specified uid
auto test_uid = rows.front().at("uid");
auto const rows_one =
auto test_username = rows.front().at("username");

// select with a specified uid
auto const rows_uid =
execute_query(std::string("select * from users where uid=") + test_uid);
ASSERT_GE(rows_one.size(), 1ul);
validate_rows(rows_one, row_map);
ASSERT_GE(rows_uid.size(), 1ul);
validate_rows(rows_uid, row_map);

// select with a specified username
auto const rows_username =
execute_query(std::string("select * from users where username='") +
test_username + "'");
ASSERT_GE(rows_username.size(), 1ul);
validate_rows(rows_username, row_map);

#ifdef OSQUERY_LINUX
// select with include_remote flag set
auto const rows_include_remote =
execute_query(std::string("select * from users where include_remote=1"));
ASSERT_GE(rows_include_remote.size(), 1ul);
validate_rows(rows_include_remote, row_map);

// select with a specified uid and include_remote flag set
auto const rows_uid_include_remote = execute_query(
std::string("select * from users where include_remote=1 and uid=") +
test_uid);
ASSERT_GE(rows_uid_include_remote.size(), 1ul);
validate_rows(rows_uid_include_remote, row_map);

// select with a specified username and include_remote flag set
auto const rows_username_include_remote = execute_query(
std::string("select * from users where include_remote=1 and username='") +
test_username + "'");
ASSERT_GE(rows_username_include_remote.size(), 1ul);
validate_rows(rows_username_include_remote, row_map);
#endif
}

} // namespace table_tests
Expand Down

0 comments on commit a56ce1e

Please sign in to comment.