36、Windows API 用户、认证和对象安全

本文涉及的产品
访问控制,不限时长
简介:     Windows系统具有很完善的安全和认证机制,称作访问控制机制。程序的执行主体(线程)在访问对象(文件、事件等)时,系统会根据线程的“权限”和线程需要访问的对象所具有的访问控制列表(ACL)中的“安全描述符”是否匹配来进行认证,决定一个线程是否可以操作一个对象。

    Windows系统具有很完善的安全和认证机制,称作访问控制机制。程序的执行主体(线程)在访问对象(文件、事件等)时,系统会根据线程的“权限”和线程需要访问的对象所具有的访问控制列表(ACL)中的“安全描述符”是否匹配来进行认证,决定一个线程是否可以操作一个对象。

一、基本概念

1A需要访问(Access)BA就是访问的主体,B就是访问的客体。A的“访问令牌”和B的安全描述符共同决定了A是否可以访问B

    访问的主体是进程。在系统中,线程才是程序执行的流程,因此只有线程才能操作对象。每个线程都是属于一个进程的,线程并没有属于自己的权限,而是来源于线程所属于的进程。一个进程中的所有线程都具有同样的权限。一个线程能访问哪些对象,能进行哪此操作,是由线程权限决定的。

访问的客体是安全对象,所有被访问的对象都具有安全描述符,包括了文件、注册表、事件(Event)、互斥(Mutex)、管道等。

2、进程的权限继承自创建进程用户和用户所属的用户组。用户有专用数据结构来表示权限—访问令牌(Access Token)访问令牌包括两个部分:一个是令牌所表示的用户,包括用户标识符(SID),用户所属的用户组等;另一部分是“权限”(Privilege)

    在进程访问安全对象时,会用到SID。每个安全对象都有访问控制列表(ACL)ACL说明了哪些用户( SID)能访问本对象,哪些不能,以及能进行哪种访问等。而“权限”在访问某个具体的安全对象时并没有作用,“权限”是表示进程是否能够进行特定的系统操作,如关闭系统、修改系统时间、加载设备驱动等。

创建进程的API函数是CreateProcessCreateProcess函数所创建的进程使用的访问令牌是当前登录用户的访问令牌。

    可以指定进程的用户。通过CreateProcessAsUserCreateProcessWithTokenWAPI函数,在创建前需要先得到用户的令牌,可以使用LogonUser登录用户(是否可以同时登录多个用户受操作系统版本限制),LogonUser函数用返回用户的令牌。

如果需要得到进程和线程的访问令牌,可以使用OpenProcessTokenOpenThreadToken等函数。获取令牌中的信息可以使用API函数GetTokenInformation。如果需要修改权限,可以使用AdjustTokenPrivileges等函数。

3、进程的系统操作权限

    进程的权限特指进程是否能够进行各种系统操作,例如是否可以关闭系统,是否能够修改系统时间,是否能够加载设备驱动等。权限是一个列表,每种权限是列表中的一项。权限列表存在于进程的访问令牌中。

权限有很多种,每一种表示了一个特定的操作是否能够进行,如果进程的访问令牌中的权限列表中有这个权限,则表示进程可以进行这种操作,比如SE_LOAD_DRIVER_ NAME表示进程可以加载驱动。

4、安全对象

    Windows系统几乎所有的对象都有安全属性,包括文件、文件夹、注册表、线程同步对象、进程间通信对象、网络共享等,进程和线程也可以是其他进程的操作对象,所以进程和线程也是安全对象。

在创建对象时都可以指定对象的安全属性,比如CreateFileCreatePipeCreateProcessRegCreateKeyExRegSaveKeyEx等,SECURITY_ATTRIBUTES结构用于指定对象的安全属性。

GetNamedSecurityInfoGetSecurityInfoSetSecurityInfoSetKernelObjectSecuritySetNamedSecurityInfoAPI函数可以获取和设置对象的安全属性。

对象的安全属性是以安全描述符(Security Descriptor)的形式存在的,安全描述符中包括了访问控制列表。

5、访问控制列表(ACL)

每个安全对象都有访问控制列表。访问控制列表有两种,一种是选择访问控制列表(discretionary access control listDACL),另一种是系统访问控制列表(system access controllistSACL)DACL决定了用户或用户组是否能访问这个对象,SACL控制了尝试访问安全对象的检测信息的继承关系。

DACL是访问控制的关键。DACL中包括一个访问控制入口(Access Control

EntriesACE)列表。ACE表明了用户(通过用户SID或用户组SID)是否能进行操作以及能进行哪种操作。在进行访问控制检测时,会依次检测DACL中的ACE,直到被允许或被拒绝。

wps_clip_image-2539

wps_clip_image-30142

ACE内容及线程访问令牌中的SIDDACL中的ACE的认证过程

二、示例代码

1、列举进程访问令牌内容和权限

显示进程的访问令牌内容

SDK安装目录下,Samples\Security\Authorization\MyToken

显示SID

获取SID可以通过LookupAccountName API函数。

Samples\Security\Authorization\TextSid

列举安全对象的安全描述符

Samples\Security\Authorization\Check_SD

修改安全描述符通过SetFileSecurity API

三、用户

增加

NetUserAdd API函数在系统中创建用户[4]

NetGroupAddUser

NetUserDel

NetUserEnumNetLocalGroupEnum API函数分别用于列举指定主机中当前的所有用户和用户组。

示例用户操作

img_1c53668bcee393edac0d7b3b3daff1ae.gif img_405b18b4b6584ae338e0f6ecaf736533.gif 用户、用户组
 
   
/* UNICODE */
#ifndef UNICODE
#define UNICODE
#endif
/* 头文件 */
#include
< stdio.h >
#include
< assert.h >
#include
< windows.h >
#include
< lm.h >
#pragma comment(lib,"Netapi32.lib")
/* ************************************
* AddUser
* 功能 增加用户
* 参数 szServerName,主机名,如果为本机增加用户,设置为NULL
* szUserName,用户名
* szPassword,密码
*************************************
*/
int AddUser(LPWSTR szServerName,
LPWSTR szUserName,
LPWSTR szPassword)
{
USER_INFO_1 ui;
DWORD dwLevel
= 1 ; // 使用 USER_INFO_1 作为参数
DWORD dwError = 0 ;
NET_API_STATUS nStatus;
// 填充 USER_INFO_1
ui.usri1_name = szUserName; // 用户名
ui.usri1_password = szPassword; // 密码
ui.usri1_priv = USER_PRIV_USER; // privilege
ui.usri1_home_dir = NULL;
ui.usri1_comment
= NULL;
ui.usri1_flags
= UF_SCRIPT;
ui.usri1_script_path
= NULL;
// 调用 NetUserAdd 增加用户
nStatus = NetUserAdd(szServerName,
dwLevel,
(LPBYTE)
& ui,
& dwError);

// 判断结果
if (nStatus == NERR_Success)
{
wprintf((
const wchar_t * )stderr, L " User %s has been successfully added on %s\n " ,
szUserName, szServerName);
}
else
{
fprintf(stderr,
" A system error has occurred: %d\n " , nStatus);
}
return 0 ;
}

/* ************************************
* AddUserToGroup
* 功能 为用户组增加用户
* 参数 szServerName,主机名,如果为本机,设置为NULL
* szUserName,用户名
* szGroup,用户组名
*************************************
*/
int AddUserToGroup(LPWSTR szServerName,
LPWSTR szUserName,
LPWSTR szGroup)
{
NET_API_STATUS nStatus;
// 调用 NetGroupAddUser
nStatus = NetGroupAddUser(
szServerName,
szGroup,
szUserName
);

// 判断结果
if (nStatus == NERR_Success)
fwprintf(stderr, L
" User %s has been successfully added on %s\n " ,
szUserName, szServerName);

else
fprintf(stderr,
" NetGroupAddUser A system error has occurred: %d\n " , nStatus);
return 0 ;
}

/* ************************************
* DelUser
* 功能 删除用户
* 参数 szServerName,主机名,如果为本机,设置为NULL
* szUserName,用户名
*************************************
*/
int DelUser(LPWSTR szServerName, LPWSTR szUserName)
{
DWORD dwError
= 0 ;
NET_API_STATUS nStatus;

// 调用 NetUserDel 删除用户
nStatus = NetUserDel(szServerName, szUserName);
// 判断并显示结果
if (nStatus == NERR_Success)
fwprintf(stderr, L
" User %s has been successfully deleted on %s\n " ,
szUserName, szServerName);
else
fprintf(stderr,
" A system error has occurred: %d\n " , nStatus);

return 0 ;

}

/* ************************************
* int ListUsers(LPWSTR pszServerName)
* 功能 列举用户
* 参数 szServerName,主机名,如果为本机,设置为NULL
*************************************
*/
int ListUsers(LPWSTR pszServerName)
{
LPUSER_INFO_0 pBuf
= NULL;
LPUSER_INFO_0 pTmpBuf;
DWORD dwLevel
= 0 ;
DWORD dwPrefMaxLen
= MAX_PREFERRED_LENGTH;
DWORD dwEntriesRead
= 0 ;
DWORD dwTotalEntries
= 0 ;
DWORD dwResumeHandle
= 0 ;
DWORD i;
DWORD dwTotalCount
= 0 ;
NET_API_STATUS nStatus;

// 循环,直到可以成功调用 NetUserEnum
do
{
// 调用NetUserEnum函数
nStatus = NetUserEnum(pszServerName,
dwLevel,
// 这里设置为0,使用 LPUSER_INFO_0 返回结果
FILTER_NORMAL_ACCOUNT, // 只列举“正常”类型的用户
(LPBYTE * ) & pBuf, // LPUSER_INFO_0 保存返回结果
// MAX_PREFERRED_LENGTH,内存由API分配,需要在之后调用NetApiBufferFree释放
dwPrefMaxLen,
& dwEntriesRead, // 读了的 Entries
& dwTotalEntries, // 一共的 Entries
& dwResumeHandle);
// 判断是否成功
if ((nStatus == NERR_Success) || (nStatus == ERROR_MORE_DATA))
{
if ((pTmpBuf = pBuf) != NULL)
{
// 循环读取用户信息
for (i = 0 ; (i < dwEntriesRead); i ++ )
{
assert(pTmpBuf
!= NULL);

if (pTmpBuf == NULL)
{
fprintf(stderr,
" An access violation has occurred\n " );
break ;
}
// 输出
wprintf(L " \t-- %s\n " , pTmpBuf -> usri0_name);
// 下一个
pTmpBuf ++ ;
dwTotalCount
++ ;
}
}
}
else
fprintf(stderr,
" A system error has occurred: %d\n " , nStatus);
// 释放内存
if (pBuf != NULL)
{
NetApiBufferFree(pBuf);
pBuf
= NULL;
}
}
while (nStatus == ERROR_MORE_DATA); // end do

// 释放内存
if (pBuf != NULL)
NetApiBufferFree(pBuf);

fprintf(stderr,
" Total of %d users\n\n " , dwTotalCount);

return 0 ;
}

/* ************************************
* int ListGroup(LPWSTR pszServerName)
* 功能 列举用户组
* 参数 szServerName,主机名,如果为本机,设置为NULL
*************************************
*/
int ListGroup(LPWSTR pszServerName)
{

DWORD dwLevel
= 0 ;
DWORD dwPrefMaxLen
= MAX_PREFERRED_LENGTH;
DWORD dwEntriesRead
= 0 ;
DWORD dwTotalEntries
= 0 ;
DWORD dwResumeHandle
= 0 ;
DWORD i;
DWORD dwTotalCount
= 0 ;
NET_API_STATUS nStatus;


LPLOCALGROUP_INFO_0 pBuf
= NULL;
LPLOCALGROUP_INFO_0 pTmpBuf;

do // begin do
{
// 调用NetLocalGroupEnum 参数设置与NetLocalGroup类似
nStatus = NetLocalGroupEnum(
pszServerName,
0 ,
(LPBYTE
* ) & pBuf,
dwPrefMaxLen,
& dwEntriesRead,
& dwTotalEntries,
& dwResumeHandle);
// 判断结果
if ((nStatus == NERR_Success) || (nStatus == ERROR_MORE_DATA))
{
if ((pTmpBuf = pBuf) != NULL)
{
// 循环输出
for (i = 0 ; (i < dwEntriesRead); i ++ )
{
assert(pTmpBuf
!= NULL);

if (pTmpBuf == NULL)
{
fprintf(stderr,
" An access violation has occurred\n " );
break ;
}

wprintf(L
" \t-- %s\n " , pTmpBuf -> lgrpi0_name);
pTmpBuf
++ ;
dwTotalCount
++ ;
}
}
}

else
fprintf(stderr,
" A system error has occurred: %d\n " , nStatus);
// 释放内存
if (pBuf != NULL)
{
NetApiBufferFree(pBuf);
pBuf
= NULL;
}
}

while (nStatus == ERROR_MORE_DATA); // end do

if (pBuf != NULL)
NetApiBufferFree(pBuf);

fprintf(stderr,
" Total of %d groups\n\n " , dwTotalCount);

return 0 ;
}

/* ************************************
* ShowUsersInfo
* 功能 显示指定用户的信息
* 参数 szServerName,主机名,如果为本机,设置为NULL
* pszUserName,用户名
*************************************
*/
int ShowUsersInfo(LPWSTR pszServerName,LPWSTR pszUserName)
{

DWORD dwLevel
= 4 ; // 使用 LPUSER_INFO_4 返回结果
LPUSER_INFO_4 pBuf = NULL;
NET_API_STATUS nStatus;

nStatus
= NetUserGetInfo(pszServerName,
pszUserName,
dwLevel,
// pBuf参数类型
(LPBYTE * ) & pBuf);

// 判断并输出结果
if (nStatus == NERR_Success)
{
if (pBuf != NULL)
{
wprintf(L
" \n\tAccount: %s\n " , pBuf -> usri4_name);
wprintf(L
" \tComment: %s\n " , pBuf -> usri4_comment);
wprintf(L
" \tUser comment: %s\n " , pBuf -> usri4_usr_comment);
wprintf(L
" \tFull name: %s\n " , pBuf -> usri4_full_name);
wprintf(L
" \tpriv: %d\n " , pBuf -> usri4_priv);
}
}

else
fprintf(stderr,
" A system error has occurred: %d\n " , nStatus);
// 释放内存
if (pBuf != NULL)
NetApiBufferFree(pBuf);
return 0 ;

}

/* ************************************
* wmain
* 功能 入口函数,根据参数判断需要调用的功能函数
* 参数 参见usage输出
*************************************
*/
int __cdecl wmain( int ac, wchar_t * av[])
{

if (ac == 4 && lstrcmpW( av[ 1 ], L " -a " ) == 0 )
{
AddUser(NULL, av[
2 ], av[ 3 ]);
}

else if (ac == 4 && lstrcmpW( av[ 1 ], L " -g " ) == 0 )
{
AddUserToGroup(NULL, av[
2 ], av[ 3 ]);
}
else if (ac == 3 && lstrcmpW( av[ 1 ], L " -i " ) == 0 )
{
ShowUsersInfo(NULL, av[
2 ]);
}
else if (ac == 2 && lstrcmpW( av[ 1 ], L " -i " ) == 0 )
{
ListUsers(NULL);
ListGroup(NULL);
}
else if (ac == 3 && lstrcmpW( av[ 1 ], L " -d " ) == 0 )
{
DelUser(NULL, av[
2 ]);
}
else
{
printf(
" usage: \n "
" \t %ws -a <username> <password> to add a user\n "
" \t %ws -g <username> <group> add a user to a group "
" \t %ws -i <username> to show user info\n "
" \t %ws -d <username> to del a user\n " ,
av[
0 ], av[ 0 ], av[ 0 ], av[ 0 ]);
}
return 0 ;
}

参考

[1] 精通Windows API 函数、接口、编程实例

[2] http://www.cnblogs.com/mydomain/archive/2010/11/24/1887138.html

[3] http://msdn.microsoft.com/en-us/library/aa378184%28VS.85%29.aspx

[4] http://msdn.microsoft.com/en-us/library/aa370672%28VS.85%29.aspx

相关实践学习
消息队列+Serverless+Tablestore:实现高弹性的电商订单系统
基于消息队列以及函数计算,快速部署一个高弹性的商品订单系统,能够应对抢购场景下的高并发情况。
云安全基础课 - 访问控制概述
课程大纲 课程目标和内容介绍视频时长 访问控制概述视频时长 身份标识和认证技术视频时长 授权机制视频时长 访问控制的常见攻击视频时长
目录
相关文章
|
22天前
|
开发框架 .NET API
Windows Forms应用程序中集成一个ASP.NET API服务
Windows Forms应用程序中集成一个ASP.NET API服务
77 9
|
17天前
|
Kubernetes 安全 Cloud Native
云上攻防-云原生篇&K8s安全-Kubelet未授权访问、API Server未授权访问
本文介绍了云原生环境下Kubernetes集群的安全问题及攻击方法。首先概述了云环境下的新型攻击路径,如通过虚拟机攻击云管理平台、容器逃逸控制宿主机等。接着详细解释了Kubernetes集群架构,并列举了常见组件的默认端口及其安全隐患。文章通过具体案例演示了API Server 8080和6443端口未授权访问的攻击过程,以及Kubelet 10250端口未授权访问的利用方法,展示了如何通过这些漏洞实现权限提升和横向渗透。
云上攻防-云原生篇&K8s安全-Kubelet未授权访问、API Server未授权访问
|
26天前
|
缓存 安全 网络协议
Windows 安全基础——NetBIOS篇
Windows 安全基础——NetBIOS篇
30 4
|
27天前
|
API Python 容器
再探泛型 API,感受 Python 对象的设计哲学
再探泛型 API,感受 Python 对象的设计哲学
18 2
|
2月前
|
人工智能 安全 API
API应用安全风险倍增,F5助企业赢得关键安全挑战
API应用安全风险倍增,F5助企业赢得关键安全挑战
47 11
|
2月前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
60 11
|
2月前
|
安全 Java API
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
86 11
|
2月前
|
JavaScript 前端开发 API
什么是ES6的Proxy对象和Reactive API
【9月更文挑战第3天】什么是ES6的Proxy对象和Reactive API
24 8
|
2月前
|
网络协议 API Windows
MASM32编程调用 API函数RtlIpv6AddressToString,windows 10 容易,Windows 7 折腾
MASM32编程调用 API函数RtlIpv6AddressToString,windows 10 容易,Windows 7 折腾
|
3月前
|
安全 API 数据处理
后端开发中的API设计哲学:简洁、高效与安全
【8月更文挑战第29天】 在后端开发的广阔天地中,API(应用程序编程接口)的设计如同编织一张无形的网,连接着数据的海洋与应用的大陆。本文将深入探讨如何打造一个既简洁又高效的API,同时不忘筑牢安全的防线。我们将从API设计的基本原则出发,逐步剖析如何通过合理的结构设计、有效的数据处理和严格的安全措施来提升API的性能和用户体验。无论你是初学者还是资深开发者,这篇文章都将为你提供宝贵的视角和实用的技巧,帮助你构建出更优秀的后端服务。