Windows.Sys.AllUsers

List User accounts. We combine two data sources - the output from the NetUserEnum API (termed local users) and the list of SIDs in the registry (termed remote users).

In this artifact, ‘remote’ means that user profile was cached in the registry, but the user does not appear in the output of the NetUserEnum API - this normally happens for users remotely logging into the system using domain credentials.

On Domain Controllers the NetUserEnum API will return the contents of the entire ActiveDirectory as a list of ’local’ users, however this does not mean that the users have logged into the DC locally. In this artifact we limit the number of users to 1000. If you need to obtain the full list from the AD, customize this artifact.


name: Windows.Sys.AllUsers
description: |
  List User accounts. We combine two data sources - the output from
  the `NetUserEnum` API (termed `local` users) and the list of SIDs in
  the registry (termed `remote` users).

  In this artifact, 'remote' means that user profile was cached in the
  registry, but the user does not appear in the output of the
  `NetUserEnum` API - this normally happens for users remotely logging
  into the system using domain credentials.

  On Domain Controllers the `NetUserEnum` API will return the contents
  of the entire ActiveDirectory as a list of 'local' users, however
  this does not mean that the users have logged into the DC
  locally. In this artifact we limit the number of users to 1000. If
  you need to obtain the full list from the AD, customize this
  artifact.

parameters:
  - name: remoteRegKey
    default: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*

export: |
  -- Cache function for lookupSID
  LET LookupSIDCache(SID) = cache(name="SID", key=SID,
     func=lookupSID(sid=SID) ||

     -- resolve usernames via registry if lookupSID is not available
     -- or yields no results

          pathspec(parse=stat(accessor="registry",
               filename="HKEY_LOCAL_MACHINE/Software/Microsoft/Windows NT/CurrentVersion/ProfileList/" +
                      SID + "/ProfileImagePath").Data.value).Basename || "")

sources:
  - precondition:
      SELECT OS From info() where OS = 'windows'
    query: |
        LET GetTimestamp(High, Low) = if(condition=High,
                then=timestamp(winfiletime=High * 4294967296 + Low))

        -- lookupSID() may not be available on deaddisk analysis
        LET roaming_users <=
          SELECT
             split(string=Key.OSPath.Basename, sep="-")[-1] as Uid,
             "" AS Gid,
             LookupSIDCache(SID=Key.OSPath.Basename || "") AS Name,
             Key.OSPath as Description,
             ProfileImagePath as Directory,
             Key.OSPath.Basename as UUID,
             Key.Mtime as Mtime,
             {
                SELECT Mtime
                FROM stat(filename=expand(path=ProfileImagePath))
             } AS HomedirMtime,
             dict(ProfileLoadTime=GetTimestamp(
                  High=LocalProfileLoadTimeHigh, Low=LocalProfileLoadTimeLow),
                  ProfileUnloadTime=GetTimestamp(
                  High=LocalProfileUnloadTimeHigh, Low=LocalProfileUnloadTimeLow),
                  ProfileImagePath=ProfileImagePath,
                  ProfileKey=Key.OSPath
             ) AS Data
           FROM read_reg_key(globs=remoteRegKey, accessor="registry")


        LET roaming_users_lookup <= memoize(query=roaming_users, key="UUID")

        -- On a DC the NetUserEnum API will return the entire domain!
        LET local_users <= select User_id as Uid,
           Primary_group_id as Gid, Name,
           Comment as Description,
           get(item=roaming_users_lookup, field=User_sid) AS  RoamingData,
           User_sid as UUID
        FROM users()
        LIMIT 1000

        LET local_users_lookup <= memoize(query={
            SELECT UUID FROM local_users
        }, key="UUID")

        -- Populate the mtime from the user's home directory.
        LET local_users_with_mtime = SELECT Uid, Gid, Name, Description,
            RoamingData.Directory AS Directory,
            UUID,
            RoamingData.Mtime As Mtime,
            RoamingData.HomedirMtime AS HomedirMtime,
            RoamingData.Data || dict() AS Data
        FROM local_users

        SELECT * from chain(
           q1=local_users_with_mtime,
           q2={
             SELECT * FROM roaming_users
             -- Only show records who were not shown before
             WHERE NOT get(item=local_users_lookup, field=UUID)
           })
         --ORDER BY Gid DESC
    notebook:
      - type: VQL
        template: |
          /*
          # Users Hunt

          Enumerating all the users on all endpoints can reveal machines
          which had an unexpected login activity. For example, if a user
          from an unrelated department is logging into an endpoint by
          virtue of domain credentials, this could mean their account is
          compromised and the attackers are laterally moving through the
          network.
          */
          LET s = scope()
          SELECT Name, UUID, s.Fqdn AS Fqdn, HomedirMtime as LastMod, Data FROM source()
          WHERE NOT UUID =~ "(-5..$|S-1-5-18|S-1-5-19|S-1-5-20)"