概述: 组策略-密码策略说明

0x01 密码策略如下图所示:

密码策略

0x02 详细说明

1 放宽最小密码长度限制

2 密码必须符合复杂性要求

3 密码长度最小值

4 密码最短使用期限

5 密码最长使用期限

注册表:

6 强制密码历史

7 用户可还原的加密来存储密码

8 最小密码长度审核

相关API

用NetUser函数修改密码策略

可以使用 NetUserModalsGet 来查询密码策略,最后需要使用 NetApiBufferFree 来释放 查询到的数据空间。还可以使用 NetUserModalsSet 可以对密码策略进行设定。

NET_API_STATUS NetUserModalsGet(
  _In_opt_ LPCWSTR servername,
  _In_     DWORD   level,
  _Out_    LPBYTE  *bufptr
); // 查询密码策略
NET_API_STATUS NetApiBufferFree(
  _In_ LPVOID Buffer
); // 释放查询的数据空间
NET_API_STATUS NetUserModalsSet(
  _In_  LPCWSTR servername,
  _In_  DWORD   level,
  _In_  LPBYTE  buf,
  _Out_ LPDWORD parm_err
); // 设定密码策略

这两个函数中 level 参数决定了要处理数据的类型,可以取值的内容为

ValueMeaningStructure
0Specifies global password parameters.USER_MODALS_INFO_0
1Specifies logon server and domain controller information.USER_MODALS_INFO_1
2Specifies the domain name and identifier.USER_MODALS_INFO_2
3Specifies lockout information.USER_MODALS_INFO_3
1001Specifies the minimum allowable password length.USER_MODALS_INFO_1001
1002Specifies the maximum allowable password age.USER_MODALS_INFO_1002
1003Specifies the minimum allowable password age.USER_MODALS_INFO_1003
1004Specifies forced logoff information.USER_MODALS_INFO_1004
1005Specifies the length of the password history.USER_MODALS_INFO_1005
1006Specifies the role of the logon server.USER_MODALS_INFO_1006
1007Specifies domain controller information.USER_MODALS_INFO_1007

如下是使用 NetUserModalsGet 查询时,我们关注的结构体信息

typedef struct _USER_MODALS_INFO_0 {
  DWORD usrmod0_min_passwd_len; // 密码长度最小值
  DWORD usrmod0_max_passwd_age; // 密码最长使用期限(秒)
  DWORD usrmod0_min_passwd_age; // 密码最短使用期限(秒)
  DWORD usrmod0_force_logoff; // 过期后强制注销的期限(秒)
  DWORD usrmod0_password_hist_len; // 强制密码历史个数
} USER_MODALS_INFO_0, *PUSER_MODALS_INFO_0, *LPUSER_MODALS_INFO_0;
typedef struct _USER_MODALS_INFO_3 {
  DWORD usrmod3_lockout_duration; // 账户锁定时间(秒)
  DWORD usrmod3_lockout_observation_window; // 重置账户锁定计数器(秒)
  DWORD usrmod3_lockout_threshold; // 账户锁定阈值
} USER_MODALS_INFO_3, *PUSER_MODALS_INFO_3, *LPUSER_MODALS_INFO_3;

使用 NetUserModalsSet 除了以上结构体进行整体设置外,还可以对部分参数进行单独设置

typedef struct _USER_MODALS_INFO_1001 {
  DWORD usrmod1001_min_passwd_len; // 密码长度最小值
} USER_MODALS_INFO_1001, *PUSER_MODALS_INFO_1001, *LPUSER_MODALS_INFO_1001;
typedef struct _USER_MODALS_INFO_1002 {
  DWORD usrmod1002_max_passwd_age; // 密码最长使用期限(秒)
} USER_MODALS_INFO_1002, *PUSER_MODALS_INFO_1002, *LPUSER_MODALS_INFO_1002;
typedef struct _USER_MODALS_INFO_1003 {
  DWORD usrmod1003_min_passwd_age; // 密码最短使用期限(秒)
} USER_MODALS_INFO_1003, *PUSER_MODALS_INFO_1003, *LPUSER_MODALS_INFO_1003;
typedef struct _USER_MODALS_INFO_1004 {
  DWORD usrmod1004_force_logoff; // 过期后强制注销的期限(秒)
} USER_MODALS_INFO_1004, *PUSER_MODALS_INFO_1004, *LPUSER_MODALS_INFO_1004;
typedef struct _USER_MODALS_INFO_1005 {
  DWORD usrmod1005_password_hist_len; // 强制密码历史个数
} USER_MODALS_INFO_1005, *PUSER_MODALS_INFO_1005, *LPUSER_MODALS_INFO_1005;

密码复杂度的处理

看到这里是不是发现一个问题,那就是 密码必须符合复杂性要求 这项配置,查询不到。 我们可以通过直接读取 SAM 注册表的信息,来判断密码复杂度是否启用

BOOL CheckPwdComplexPolicy()
{
    HKEY hKey = NULL;
    // 打开密码策略注册表
    LONG lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
        "SAM\\SAM\\Domains\\Account", 0, KEY_ALL_ACCESS, &hKey);
    if (lResult != ERROR_SUCCESS) return FALSE;
    // 读取密码策略注册表信息
    DWORD dwLen = 1024;
    BYTE pBuf[1024] = { 0 };
    lResult = RegQueryValueEx(hKey, "F", NULL, NULL, pBuf, &dwLen);
    RegCloseKey(hKey);
    if (lResult != ERROR_SUCCESS) return FALSE;
    // 检查密码复杂度是否启用
    if (pBuf[76] != 1) return FALSE; // 复杂度(0未启用)(1已启用)
    // if (pBuf[80] < 8) return FALSE; // 最小长度
    return TRUE;
}

普通情况下 SAM 注册表是不允许访问的,就需要我们首先修改一下访问 SAM 的权限

BOOL ModifySamRegPrivilege()
{
    PACL pOldDacl = NULL;
    PSECURITY_DESCRIPTOR pSID = NULL;
    // 获取SAM主键的DACL
    DWORD dRet = GetNamedSecurityInfo("MACHINE\\SAM\\SAM",
        SE_REGISTRY_KEY, DACL_SECURITY_INFORMATION, NULL, NULL, &pOldDacl, NULL, &pSID);
    if (dRet != ERROR_SUCCESS)
    {
        LocalFree(pSID);
        return FALSE;
    }
    // 创建一个ACE,允许Administrators组成员完全控制对象,并允许子对象继承此权限
    EXPLICIT_ACCESS_A eia = { 0 };
    BuildExplicitAccessWithName(&eia, "Administrators",
        KEY_ALL_ACCESS, SET_ACCESS, SUB_CONTAINERS_AND_OBJECTS_INHERIT);
    // 将新的ACE加入DACL
    PACL pNewDacl = NULL;
    dRet = SetEntriesInAcl(1, &eia, pOldDacl, &pNewDacl);
    if (dRet != ERROR_SUCCESS)
    {
        LocalFree(pSID);
        return FALSE;
    }
    // 更新SAM主键的DACL   
    dRet = SetNamedSecurityInfo("MACHINE\\SAM\\SAM",
        SE_REGISTRY_KEY, DACL_SECURITY_INFORMATION, NULL, NULL, pNewDacl, NULL);
    if (dRet != ERROR_SUCCESS)
    {
        LocalFree(pNewDacl);
        LocalFree(pSID);
        return FALSE;
    }
    return TRUE;
}