Windows API和C Runtime对安全性的影响
CopyMemory
安全性评价
第一个参数 Destination 必须足以容纳 count 个字节的 Source
组合大小,否则就可能发生缓冲区溢出。这样,当发生违规访问时,应用程序就可能会遭到拒绝服务攻击,或者更坏,可能会使攻击者将可执行代码注入到您的进程中。如果
Destination
是基于堆栈的缓冲区,则尤为如此。要注意的是,最后一个参数 Length
是要复制到 Destination 的字节数,而不是 Destination
的大小。
以下代码示例演示了安全使用 CopyMemory() 的方法:
void test(char *pbData, unsigned int cbData) {
char buf[BUFFER_SIZE];
CopyMemory(buf, pbData, min(cbData,BUFFER_SIZE));
}
CreateProcess、CreateProcessAsUser、CreateProcessWithLogonW
安全性评价
第一个参数 lpApplicationName 可以为 NULL。在这种情况下,可执行程序的名称必须是
lpCommandLine
中第一个用空格分隔的字符串。但是,如果可执行程序的名称或路径名中有空格,则存在一定的风险,因为如果空格处理不当,就可能会运行恶意的可执行程序。以下示例是危险的,因为该进程将试图运行“Program.exe”(如果该程序存在),而不是“foo.exe”。
CreateProcess(NULL, "C:\Program Files\foo", ...)
如果恶意用户要在系统中创建名为“Program.exe”的特洛伊程序,那么任何使用“Program
Files”目录不正确地调用 CreateProcess
的程序都将启动特洛伊程序,而不是要调用的应用程序。
注意不要为 lpApplicationName 传递 NULL,以避免函数根据其运行时参数来分析并确定可执行文件路径名。如果
lpApplicationName 一定要为 NULL,则用引号将 lpCommandLine
中的可执行路径引起,如下例所示。
CreateProcess(NULL, "\"C:\Program Files\foo.exe\" -L -S", ...)
模拟函数
安全性评价
如果对模拟函数的调用因任何原因而失败,则不会对客户端进行模拟,客户端请求将在进行调用的进程所在的安全环境中进行。如果进程作为高度特权化的帐户(如
LocalSystem)来运行,或作为管理组的成员来运行,则用户可能可以执行在其他情况下不允许进行的操作。所以,您务必要始终检查调用的返回值,如果该值未报出错误,则不要继续执行客户端请求。以下是一些示例:
RpcImpersonateClient
ImpersonateNamedPipeClient
SetThreatToken
ImpersonateSelf
CoImpersonateClient
ImpersonateDdeClientWindow
ImpersonateSecurityContext
ImpersonateLoggedOnUser
SetSecurityDescriptorDacl
安全性评价
最好不要创建具有 NULL DACL 的安全描述符(即:pDacl 为 NULL),因为这样的
DACL 无法为对象提供安全性。实际上,攻击者可在对象上设置一个
Everyone (Deny All Access) ACE,从而拒绝每个人(包括管理员)访问该对象。NULL
DACL 没有为对象提供任何免受攻击的保护。
memcpy
安全性评价
第一个参数 dest 必须足以容纳 count 个字节的 src
组合大小,否则就可能发生缓冲区溢出。这样,当发生违规访问时,应用程序就可能会遭到拒绝服务攻击,或者更坏,可能会使攻击者将可执行代码注入到您的进程中。如果
dest
是基于堆栈的缓冲区,则尤为如此。要注意的是,最后一个参数 count
是要复制到 dest 的字节数,而不是 dest 的大小。
以下代码示例演示了安全使用 memcpy() 的方法:
void test(char *pbData, unsigned int cbData) {
char buf[BUFFER_SIZE];
memcpy(buf, pbData, min(cbData,BUFFER_SIZE));
}
sprintf、swprintf
安全性评价
第一个参数 buffer 必须足以容纳 format
的格式化版本和末尾的 NULL ('\0')
字符,否则就可能发生缓冲区溢出。这样,当发生违规访问时,应用程序就可能会遭到拒绝服务攻击,或者更坏,可能会使攻击者将可执行代码注入到您的进程中。如果
buffer 是基于堆栈的缓冲区,则尤为如此。
另外,应注意用户或应用程序将 format
提供为变量的危险。下例是危险的,因为攻击者可能会将 szTemplate
设置为“%90s%10s”,这样会创建一个 100 字节的字符串:
void test(char *szTemplate,char *szData1, char *szData2) {
char buf[BUFFER_SIZE];
sprintf(buf,szTemplate,szData1,szData2);
}
应考虑使用 _snprintf(英文)或 _snwprintf 来代替。
strcat、wcscat、_mbscat
安全性评价
第一个参数 strDestination 必须足以容纳当前的 strDestination
和 strSource 组合以及一个末尾 '\0',否则就可能发生缓冲区溢出。这样,当发生违规访问时,应用程序就可能会遭到拒绝服务攻击,或者更坏,可能会使攻击者将可执行代码注入到您的进程中。如果
strDestination 是基于堆栈的缓冲区,则尤为如此。应考虑使用strncat(英文)、wcsncat
或 _mbsncat。
strcpy、wcscpy、_mbscpy
安全性评价
第一个参数 strDestination 必须足以容纳 strSource 和末尾的
'\0',否则就可能发生缓冲区溢出。这样,当发生违规访问时,应用程序就可能会遭到拒绝服务攻击,或者更坏,可能会使攻击者将可执行代码注入到您的进程中。如果
strDestination 是基于堆栈的缓冲区,则尤为如此。应考虑使用strncpy(英文)、wcsncpy
或 _mbsncpy。
strncat、wcsncat、_mbsncat
安全性评价
第一个参数 strDestination 必须足以容纳当前 strDestination
和 strSource 组合以及一个末尾 NULL ('\0'),否则就可能发生缓冲区溢出。这样,当发生违规访问时,应用程序就可能会遭到拒绝服务攻击,或者更坏,可能会使攻击者将可执行代码注入到您的进程中。如果
strDestination
是基于堆栈的缓冲区,则尤为如此。要注意的是,最后一个参数 count
是要复制到 strDestination 的字节数,而不是 strDestination
的大小。
还要注意,如果缓冲区 strDestination 中有剩余的空间,则 strncat
仅添加末尾 NULL。
以下代码示例演示了安全使用 strncat 的方法:
void test(char *szWords1, char *szWords2) {
char buf[BUFFER_SIZE];
strncpy(buf,szWords1,sizeof buf - 1);
buf[BUFFER_SIZE - 1] = '\0';
unsigned int cRemaining = (sizeof buf - strlen(buf)) - 1;
strncat(buf,szWords2,cRemaining);
}
WinExec
安全性评价
可执行名称被视为 lpCmdLine 中的第一个用空格分隔的字符串。
但是,如果可执行程序的名称或路径名中有空格,则存在一定的风险,因为如果空格处理不当,就可能会运行恶意的可执行程序。以下示例是危险的,因为该进程将试图运行“Program.exe”(如果该程序存在),而不是“foo.exe”。
WinExec("C:\Program Files\foo", ...)
如果恶意用户要在系统中创建名为“Program.exe”的特洛伊程序,那么任何使用“Program
Files”目录不正确地调用 WinExec
的程序都将启动特洛伊程序,而不是要调用的应用程序。
就安全性而言,我们强烈建议您使用 CreateProcess 而不是 WinExec。但是,如果您由于遗留问题而必须使用
WinExec,则务必要将应用程序名用引号引起来,如下例所示:
WinExec("\"C:\Program Files\foo.exe\" -L -S", ...)
|