《程序员》9月文章
申明。文章仅代表个人观点,与所在公司无任何联系。
-
概述
在前面的安全编码实践的文章里,我们讨论了GS编译选项,<wbr>数据执行保护</wbr>DEP功能,以及静态代码分析工具Prefast。<wbr>这里,我们讨论在</wbr>C/C++代码中禁用危险的API,<wbr>其主要目的是为了减少代码中引入安全漏洞的可能性。</wbr>
-
那些是危险的API
在微软产品的安全漏洞中,有很大一部分是由于不正确的使用<wbr>C</wbr>动态库(C Runtime Library) 的函数,特别是有关字符串处理的函数导致的。<wbr>表一给出了微软若干由于不当使用</wbr>C动态库函数而导致的安全漏洞【<wbr>1</wbr>,p242】。
微软安全公告 |
涉及产品 |
涉及的函数 |
MS02-039 |
Microsoft SQL Server 2000 |
sprint |
MS05-010 |
Microsoft License Server |
lstrcpy |
MS04-011 |
Microsoft Windows (DCPromo) |
wvsprintf |
MS04-011 |
Microsoft Windows (MSGina) |
lstrcpy |
MS04-031 |
Microsoft Windows (NetDDE) |
wcscat |
MS03-045 |
Microsoft Windows (USER) |
wcscpy |
表1:不当使用C动态库函数而导致的安全漏洞
不当使用C动态库函数容易引入安全漏洞,这一点并不奇怪。<wbr>C</wbr>动态库函数的设计大约是30年前的事情了。当时,<wbr>安全方面的考虑并不是设计上需要太多注意的地方。</wbr>
有关完整的危险API的禁用列表,大家可以参见http:<wbr>//msdn.microsoft.com/en-us/<wbr>library/bb288454.aspx</wbr></wbr>.
在这里我们列出其中的一部分,以便大家对那些API被禁用<wbr>有所体会。</wbr>
禁用的API
|
替代的StrSafe函数
|
替代的Safe CRT函数
|
有关字符串拷贝的API
|
strcpy, wcscpy, _tcscpy, _mbscpy, StrCpy, StrCpyA, StrCpyW, lstrcpy, lstrcpyA, lstrcpyW, strcpyA, strcpyW, _tccpy, _mbccpy |
StringCchCopy, StringCbCopy,
StringCchCopyEx, StringCbCopyEx
|
strcpy_s |
有关字符串合并的API
|
strcat, wcscat, _tcscat, _mbscat, StrCat, StrCatA, StrCatW, lstrcat, lstrcatA, lstrcatW, StrCatBuffW, StrCatBuff, StrCatBuffA, StrCatChainW, strcatA, strcatW, _tccat, _mbccat |
StringCchCat, StringCbCat,
StringCchCatEx, StringCbCatEx
|
strcat_s |
有关sprintf的API
|
wnsprintf, wnsprintfA, wnsprintfW, sprintfW, sprintfA, wsprintf, wsprintfW, wsprintfA, sprintf, swprintf, _stprintf |
StringCchPrintf, StringCbPrintf,
StringCchPrintfEx, StringCbPrintfEx
|
_snprintf_s
_snwprintf_s
|
表2:禁用API的列表(部分)
其它被禁用的API还有scanf, strtok, gets, itoa等等。 ”n”系列的字符串处理函数,例如strncpy等,<wbr>也在被禁用之列。</wbr>
-
如何替代被禁用的危险API
从上面的介绍可以看出绝大多数C动态库中的字符串处理函数<wbr>都被禁用。那么,如何在代码中替代这些危险的</wbr>API呢?在表2里<wbr>,我们看到有两种替代方案:</wbr>
后面我们会讨论这两种方案的不同之处。<wbr>这里我们先说它们的共同点:提供更安全的字符串处理功能。<wbr>特别在以下几个方面:</wbr></wbr>
-
目标缓存区的大小被显式指明。
-
动态校验。
-
返回代码。
以StringCchCopy举例。它的定义如下:
HRESULT StringCchCopy(
LPTSTR pszDest,
size_t cchDest,
LPCTSTR pszSrc
);
cchDest指明目标缓存区pszDest最多能容纳字<wbr>符的数目,其值必须在</wbr>1和STRSAFE_MAX_CCH之间。<wbr>StringCchCopy</wbr>总是确保pszDest被拷贝的字符<wbr>串是以</wbr>NULL结尾。并且提供以下的返回代码: S_OK,STRSAFE_E_INVALID_<wbr>PARAMETER</wbr>,和STRSAFE_E_<wbr>INSUFFICIENT_BUFFER</wbr>。这样,采用Strin<wbr>gCchCopy</wbr>来替代被禁用的strcpy的话,<wbr>就可以有效降低由于误用字符串拷贝而导致缓存溢出的可能。</wbr>
使用StrSafe非常简单。在C/C++代码中加入以下<wbr>的头文件即可。</wbr>
#include "strsafe.h"
StrSafe.h包含在Windows Platform SDK中。用户可以通过在微软的网站直接下载。
下面给出一个使用StrSafe的代码示例【2】。
不安全的代码:
void UnsafeFunc(LPTSTR szPath,DWORD cchPath) {
TCHAR szCWD[MAX_PATH];
GetCurrentDirectory(ARRAYSIZE(<wbr>szCWD), szCWD);</wbr>
strncpy(szPath, szCWD, cchPath);
strncat(szPath, TEXT("\\"), cchPath);
strncat(szPath, TEXT("desktop.ini"),cchPath);
}
在以上代码里存在着几个问题:首先,没有错误代码的校验。<wbr>更严重的是,在</wbr>strncat中,cchPath是目标缓存区可<wbr>以存放字符的最大数目,<wbr>而正确传递的参数应该是目标缓存区剩余的字符数目。</wbr></wbr>
使用StrSafe后的代码是
bool SaferFunc(LPTSTR szPath,DWORD cchPath) {
TCHAR szCWD[MAX_PATH];
if (GetCurrentDirectory(<wbr>ARRAYSIZE(szCWD), szCWD) &&</wbr>
SUCCEEDED(StringCchCopy(<wbr>szPath, cchPath, szCWD)) &&</wbr>
SUCCEEDED(StringCchCat(szPath, cchPath, TEXT("\\"))) &&
SUCCEEDED(StringCchCat(szPath, cchPath, TEXT("desktop.ini")))) {
return true;
}
return false;
}
SafeCRT自Visual Studio 2005起开始支持。当代码中使用了禁用的危险的CRT函数,V<wbr>isual Studio 2005</wbr>编译时会报告相应警告信息,<wbr>以提醒开发人员考虑将其替代为</wbr>Safe CRT中更为安全的函数。
下面给出一个使用Safe CRT的代码示例【3】。
不安全的代码:
void UnsafeFunc (const wchar_t * src)
{
// Original
wchar_t dest[20];
wcscpy(dest, src); // 编译警告
wcscat(dest, L"..."); // 编译警告
}
以上这段代码里存在着明显缓存溢出的问题。
使用Safe CRT后的代码是
errno_t SaferFunc(const wchar_t * src)
{
wchar_t dest[20];
errno_t err = wcscpy_s(dest, _countof(dest), src);
if (!err)
return err;
return wcscat_s(dest, _countof(dest), L"...");
}
我们看到,StrSafe和Safe CRT存在功能重叠的地方。那么什么时候使用StrSafe,<wbr>什么时候使用</wbr>SafeCRT呢?
下面的表格【1,p246】里列出了两者之间的差异。<wbr>采用何种方式应该根据具体情况而定。<wbr>有时候也许只能采取其中一种方式:例如如果你的开发系统是</wbr></wbr>Vis<wbr>ual Studio 2003</wbr>的话,就只能使用StrSafe。或者你的代码中有许多<wbr>itoa</wbr>的话,就考虑使用Safe CRT,因为StrSafe中没有提供简单的替代方式。<wbr>有时候也许两者都可以。这种情况下,我个人是更喜欢采用</wbr>StrS<wbr>afe</wbr>这种方式,因为它不依赖具体的动态库支持。如果是编写Wi<wbr>n32</wbr>上的程序的话,StrSafe的HRESULT的返回代码<wbr>,也和</wbr>Win32 API的代码类似,这样代码的整体风格可能会更加一致。
|
StrSafe |
Safe CRT |
发布方式 |
Web |
Microsoft Visual Studio 2005 |
头文件 |
一个 (StrSafe.h)
|
多个 (不同的 C runtime 头文件)
|
是否提供链接库的版本 |
是 |
是 |
是否提供内嵌(Inline)版本
|
是 |
否 |
是否是业界标准 |
否 |
正在评估过程 |
Kernel Mode支持
|
是 |
否 |
返回类型 |
HRESULT (user mode)
NTSTATUS (kernel mode)
|
随函数变化,errno_t
|
是否需要修改代码 |
是 |
是 |
主要针对 |
缓存溢出 |
缓存溢出,和其它安全方面的考虑 |
表3:StrSafe和Safe CRT对比
-
争论
在开发过程中,代码中全面禁用危险的API的编码实践,<wbr>存在着一定的争议性。其中最具有代表性的观点可以参见</wbr>Danny Kalev的Visual C++ 8.0 Hijacks the C++ Standard一文【4】。争论主要集中在以下几点。
以StrSafe举例,由于增加了更多的动态校验,<wbr>其速度较</wbr>C动态库的函数相比,是有所下降的。在【2】一文中,<wbr>给出了对</wbr>StrSafe速度方面的测试数据如下:
测试例子:1千万次字符串合并调用。结果:
C动态库:7.3秒
StrSafe:8.3秒
我们看到,<wbr>如果开发的系统不是完全以字符串处理为工作核心的话,使用</wbr>Str<wbr>Safe</wbr>对系统性能的影响是可以控制的。
首先,同意如果代码中正确使用危险API的话,<wbr>也是可以避免安全漏洞的引入。但是,在具体的开发实践中,<wbr>存在着以下问题:</wbr></wbr>
-
开发人员的素质和培训
-
有时候即使执行严格的代码复查,仍然可能由于使用危险的API而<wbr>引入安全漏洞。</wbr>
第二点尤其关键。大家看到这里可能会有疑问,使用危险的<wbr>API</wbr>有这么容易出问题吗?即便代码复查(code review)也没能看出来?【5】<wbr>中给出了一个微软安全漏洞的具体实例。</wbr>
微软 05-047 Plug-n-Play RPC:即插即用中的漏洞,允许远程执行代码和特权提升。<wbr>经过身份验证的攻击者可以通过创建特制的网络消息并将该消息发送<wbr>到受影响的系统来尝试利用此漏洞。<wbr>导致这个严重的安全漏洞的代码如下:</wbr></wbr></wbr>
#define MAX_CM_PATH<wbr> 360</wbr>
GetInstanceList(
IN LPCWSTR pszDevice, IN OUT LPWSTR *pBuffer, IN OUT PULONG pulLength)
{
WCHAR RegStr[MAX_CM_PATH], szInstance[MAX_DEVICE_ID_LEN];
...
// Validate that passed in pszDevice is an actual registry entry
// If lookup for the key fails, reject call and cleanup.
// ghEnumKey points to HKLM\System\CurrentControlSet\<wbr>Enum </wbr>
if (RegOpenKeyEx(ghEnumKey, pszDevice, 0,
KEY_ENUMERATE_SUB_KEYS, &hKey) != ERROR_SUCCESS) {
Status = CR_REGISTRY_ERROR;
goto Clean0;
}
...
ulLen = MAX_DEVICE_ID_LEN; // size in chars
...
// Query szInstance from registry
RegStatus = RegEnumKeyEx(hKey, ulIndex, szInstance, &ulLen, ...);
if (RegStatus == ERROR_SUCCESS) {
// Build lookup string given a valid registry root key and valid instance ID
wsprintf(RegStr, TEXT("%s\\%s"), pszDevice, szInstance);}
复查这段代码时,我们看到,虽然使用了危险的API:w<wbr>sprintf</wbr>,但应该是不会发生缓存溢出的问题。这是因为根据<wbr>MSDN</wbr>,
图1:注册表字符数目的限制
于是:
-
pszDevice 应该少于255 characters
-
pszDevice 是一个 有效的 key 在HKLM\System\<wbr>CurrentControlSet\Enum </wbr>
-
szInstance 是一个有效的subkey在pszDevice下
-
RegStr is 360 characters
-
攻击者并不能控制注册表内容
但实际上,wspringf还是导致了缓存溢出的安全漏<wbr>洞。到底是怎么回事?我们来看一下攻击代码:</wbr>
errno_t SaferFunc(const wchar_t * src)
int main()
{
PWCHAR pszFilter = (PWCHAR)malloc(sizeof(WCHAR)*<wbr>1000);</wbr>
PWCHAR Buffer = (PWCHAR)malloc(86);
wsprintf(pszFilter,L"<wbr>ISAPNP\\ReadDataPort\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\<wbr>\\\\\\\\\\0");</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>
CM_Get_Device_ID_List((<wbr>PCWSTR)pszFilter,Buffer,86,1);</wbr>
return 0;
}
攻击代码之所以有效,是因为:
-
pszFilter(除去末位的0) 作为pszDevice 传递给GetInstanceList
-
RegOpenKeyEx 接收了这个长字符串,忽略那些“\”,返回 ERROR_SUCCESS。
通过这个例子我们看出,在开发复杂的系统中,<wbr>即便是有经验的开发人员,加上严格的代码复查过程,<wbr>还是有可能由于使用危险的</wbr></wbr>API而导致安全漏洞的引入。<wbr>这也是微软在</wbr>Windows Vista的开发过程中全面禁用危险API的原因。
这一点的考虑是非常值得重视的。不管是StrSafe,<wbr>还是</wbr>Safe CRT,都不是工业界标准。因此,<wbr>如果开发的系统需要移植到其它平台的话,采用</wbr>Safe CRT是肯定不合适的。StrSafe的Inline方式,<wbr>因为不依赖特定库,对可移植性的影响相对较小。</wbr>
-
总结
在C/C++程序中禁用危险的API,<wbr>可以有效降低在代码中引入安全漏洞的可能。<wbr>在考虑了性能和可移植性的因素下,强烈建议在开发过程中,<wbr>使用StrSafe或Safe CRT中对应的安全函数来替代被禁用的危险的API调用。</wbr></wbr></wbr>
-
参考文献
-
The Security Development Lifecycle: SDL, Michael Howard; Steve Lipner, Microsoft
-
Strsafe.h: Safer String Handling in C, http://msdn.microsoft.com/en-<wbr>us/library/ms995353.aspx</wbr>, Michael Howard, Microsoft
-
Repel Attacks on Your Code with the Visual Studio 2005 Safe C and C++ Libraries, http://msdn.microsoft.com/en-<wbr>us/magazine/cc163794.aspx</wbr>, Martyn Lovell, Microsoft
-
Visual C++ 8.0 Hijacks the C++ Standard, http://www.informit.com/<wbr>guides/content.aspx?g=<wbr>cplusplus&seqNum=259</wbr></wbr>, Danny Kalev
- School of hard knocks: things you can learn from working with MSRC, PhNeutral 0x7d7, Damian Hasse, Microsoft
分享到:
相关推荐
描述中提到的"c‑***"暗示这是一个为C++开发者提供的编码规范文件,可能由百度公司维护。百度作为一个搜索和人工智能技术公司,很可能有自己的编程标准和实践,该编码规范可能与之相关。 从可读的片段中,我们可以...
在IT行业中,尤其是在软件开发领域,理解如何与浏览器交互,特别是获取和操作Cookie,是至关重要的。本篇文章将深入探讨如何...通过实践和学习,你可以在C++中成功实现这一功能,从而实现与浏览器的更高级别的交互。
libcurl提供了丰富的API接口,使得开发者可以轻松地在C/C++代码中集成网络请求功能。使用libcurl连接HTTPS服务器,主要涉及以下几个步骤: 1. **安装libcurl**:首先,你需要在开发环境中安装libcurl库。这通常包括...
这份规范不仅适用于C++和C语言,还覆盖了Python、Java等多种编程语言。它旨在提升代码的可读性、可维护性和团队合作效率。以下是针对C++和C编程规范的一些关键知识点: 1. **命名规则**: - 变量、函数和常量:...
要开发Xvid编码器,首先需要安装一个支持C/C++编译的环境,如GCC或Visual Studio。然后,从Xvid官方网站获取源代码,并按照提供的编译指南配置编译环境,包括设置路径、链接库等。记得安装必要的开发工具,如...
它包含了多种组件,如音视频采集、编码、传输和解码,以及网络适配等功能,使得开发者可以轻松地在网页应用中集成音视频通讯功能。 在编译WebRTC的过程中,开发者可能会遇到各种问题,尤其是对于初次尝试的人来说。...
《Google C++编程风格指南》是中国开发者非常推崇的一份C++编程规范,它详细阐述了Google公司内部对于C++编程的约定与最佳实践。这份指南旨在提高代码的可读性、可维护性和团队协作效率。以下是对其中主要知识点的...
若遇到此问题,可以在调用GDAL函数前设置`CPLSetConfigOption("GDAL_FILENAME_IS_UTF8","NO")`来禁用UTF-8编码。 #### 二、Prj.4库的编译与引用 **2.1 Prj.4简介** Prj.4是一个开源的坐标变换工具,用于实现各种...
X264是世界上最先进的H.264/AVC视频编码库,广泛应用于高清视频编码、流媒体传输以及各种视频处理工具中。它通过优化的算法实现了高效的视频压缩,能够在保持画质的同时大大减小视频文件的大小。这篇资料将深入探讨X...
6. **编写Java接口**:在Android应用中,通过JNI(Java Native Interface)调用FFmpeg的C/C++函数,创建相应的Java层接口。 7. **调用FFmpeg功能**:通过Java接口,开发者可以实现如视频录制、视频转码、视频裁剪等...
Android NDK(Native Development Kit)允许开发者在Android应用中使用原生C/C++代码,为FFmpeg移植提供了基础。 2. **交叉编译**: 由于Android系统与Linux有所不同,需要通过交叉编译来生成适用于Android架构的...
- 应在调试版本中启用,在发布版本中禁用。 5. **关键字`static`的作用** - 在函数体内修饰局部变量:使变量具有静态生存期,即使函数多次调用,变量仅初始化一次,保持上次调用结束时的值。 - 在类内部修饰...
在Windows操作系统中,"禁用网卡"或"网卡禁用"通常指的是通过软件方式关闭计算机上的网络连接功能,这可能是出于安全、测试或维护目的。这种操作可以手动在设备管理器中完成,也可以通过编程接口(API)来实现,比如...
首先,"jni"目录是Android原生开发的关键部分,它允许我们使用C/C++代码来实现性能敏感的部分或利用已有的C/C++库,如FFmpeg。在这个项目中,jni目录下包含的"ffmpeg-0.6"源代码是FFmpeg的一个特定版本,开发者已经...
### Android安全指南:关键知识点概览 ...通过上述分析可以看出,《Android安全指南》不仅提供了实用的安全实践指南,还为研究人员指明了未来发展的方向,对于提高Android生态系统的整体安全性具有重要意义。
它针对那些希望通过广东电信短信接入API来实现短信功能的技术人员,特别是设计人员和编码人员。 **1.2 背景说明** 手册基于广东电信SMGP协议(V3.0.2版),这是一种专门为接入广东电信短信网关而设计的标准协议。...