1-所有的.dll和.exe都要有数字签名
意义:
对应用程序进行代码签名可确保用户自上次签名以来未对其进行修改。形象点的解释:程序可以通过数字签名证明自己从出生到现在没被修改过。
市面上基本所有的应用程序都有数字签名。
实现:
用证书对dll和exe进行签名,已签名的dll和exe会在属性窗口上,多出一个tab:“数字签名“ 来显示签名信息。
这里给一个使用msbuild+signtool(微软官方的命令行签名工具)实现的例子:
<Target Name="Sign" DependsOnTargets="CompileApp" Condition="'$(BuildLocation)' == 'buildserver'"> <CreateItem Include="$(TargetDir)***.exe"> <Output TaskParameter="Include" ItemName="BuildedExeToSign"/> </CreateItem> <CreateItem Include="$(TargetDir)***.dll" Exclude="$(TargetDir)**System*.dll;$(TargetDir)**DevExpress*.dll"> <Output TaskParameter="Include" ItemName="BuildedDllToSign"/> </CreateItem> <CreateItem Include="$(TargetDir)**DevExpress*.dll"> <Output TaskParameter="Include" ItemName="DevExpressToSign"/> </CreateItem> <Exec Command="$(Signtool) sign /v /sm /n "TEST" /fd sha256 /tr http://xxx.xxx.com /as @(BuildedExeToSign->'"%(FullPath)"', ' ')" /> <Exec Command="$(Signtool) sign /v /sm /n "TEST" /fd sha256 /tr http://xxx.xxx.com /as @(BuildedDllToSign->'"%(FullPath)"', ' ')" /> <Exec Command="$(Signtool) sign /v /sm /n "TEST" /fd sha256 /tr http://xxx.xxx.com /as @(DevExpressToSign->'"%(FullPath)"', ' ')" /> </Target>
$(Signtool)为Signtool的路径。 之所以要给exe和dll分开(并且dll也按照种类分开)是为了避免因为签名指令太长,msbuild忽略个别字符的情况。尤其程序目录里有子文件夹的情况,需要按文件夹拆分成多条sign命令。
参考:
https://docs.microsoft.com/zh-cn/previous-versions/dotnet/netframework-2.0/8s9b9yaz(v=vs.80)?redirectedfrom=MSDN
https://docs.microsoft.com/en-gb/visualstudio/ide/managing-assembly-and-manifest-signing
https://docs.microsoft.com/en-us/visualstudio/ide/how-to-sign-application-and-deployment-manifests
2-所有需要写文件的功能都需要保证对写入目录有write权限。
意义:
一个windows app的最终宿命是被安装/复制到C盘或D盘的某个位置。为了安全起见,尤其一些工业级app,都会要求安装/复制到C:/Program Files(x86) 文件夹。同时只有Administrator用户对C:/Program Files(x86)有Write权限。
普通用户只有Read权限。当app中发生写文件的操作时,很多程序会直接写入到程序当前所在文件夹。尤其是修改config文件(或者ini文件等等),因为config文件本身就在程序目录下。如果写入时发现没有权限,那就会引起异常,最终操作失败。
所以在开发时,就需要考虑到文件夹权限问题。
实现:
---对于配置文件(config,ini,xml...)
配置文件是一开始就和程序并存的。所以在程序运行之初就需要进行处理。
判断当前路径是否有写入权限,若没有 则将配置文件拷贝到C:/ProgramData(或其他所有用户都有写入权限的文件夹)。
之后对配置文件的读写操作都通过这个路径完成。
下面代码可以获取到ProgramData的路径。
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
当然也可以不加判断,直接就指定C:/ProgramData。
---对于其他写文件操作(log文件,导出文件...)
可以将这些文件路径放在UI上进行配置,类似微信、qq的聊天记录文件的存放位置。导出文件很多时候是让用户自己选择路径。
参考:
https://docs.microsoft.com/en-us/uwp/api/windows.storage.applicationdata
3-异常提示窗口的不能显示堆栈信息和其他敏感信息。
意义:
对于终端用户,堆栈信息是毫无意义的。对于攻击者来说,堆栈信息提供了很有价值的参考。所以堆栈信息不应该出现在提示窗口上。
实现:
这个原则有些苛刻,不显示堆栈可以,但是对开发者来说,必须有一个记录异常堆栈的地方,log文件在上个原则中已经被放到了一个有写权限的路径中,攻击者当然也能获取。
这里给出的办法是将异常的详细信息写到Windows Event Log中。
参考:
https://cwe.mitre.org/data/definitions/209.html
4-敏感信息需要加密
意义:
多数app都需要用户名密码登录,登录完成后,这些信息会留在内存中。攻击者可以获取这些用户名密码信息。
实现:
理论上,我们应该将不再需要的用户信息等从内存中清除。这可以通过不将数据存储在实例变量中而仅将它们作为方法参数传递来完成。
这样,数据就被保存在经常被刷新的存储器堆栈中。
C#中 可以通过SecureString来存储敏感信息。
SecureString To string
public static string ConvertToString(SecureString aSecureText) { // Declare variables var valuePtr = IntPtr.Zero; try { // Convert into string valuePtr = Marshal.SecureStringToGlobalAllocUnicode(aSecureText); return Marshal.PtrToStringUni(valuePtr); } finally { // Release resources Marshal.ZeroFreeGlobalAllocUnicode(valuePtr); } }
string to SecureString
public static SecureString ConvertToSecureString(string aText) { // Declare variables var secure = new SecureString(); // Iterate text by chars and append to secure string foreach (var chr in aText) secure.AppendChar(chr); // Return result return secure; }
使用的时候,先把用户输入的密码转成SecureString,再把SecureString转成string传给logon方法。
这里有个疑问,既然最终都要转成string,那么这个转换的意义何在?攻击者依然可以获取string。
我查了一些文章,目前的结论是:从securestring转出来的string使用后会立即被销毁,阅后即焚的感觉。所以留给攻击者的时间很短。
参考:
https://docs.microsoft.com/en-us/dotnet/api/system.security.securestring?view=netframework-4.7.2
https://cwe.mitre.org/data/definitions/226.html
*CWE是个好东西!! https://cwe.mitre.org/data/definitions/1200.html