很多企业对代码的安全性有着很高的要求,目前使用的较为多的扫描代码安全漏洞的工具为Fortify 5.1。以下为一些漏洞的解决方案,欢迎指正。
1.Password Management: Empty Password
1)简介:为密码变量指定空字符串绝非一个好方法。如果使用 empty password 成功通过其他系统的验证,那么相应帐户的安全性很可能会被减弱,原因是其接受了 empty password。如果在为变量指定一个合法的值之前,empty password 仅仅是一个占位符,那么它将给任何不熟悉代码的人造成困惑,而且还可能导致出现意外控制流路径方面的问题。
例 1: 以下代码尝试使用empty password 连接数据库。
...
DriverManager.getConnection(url, ""scott"", """");
...
如果例 1 中的代码成功执行,则表明数据库用户帐户“scott”配置了一个 empty password,攻击者可以轻松地猜测到这一点。更危险的是,程序一旦发布,更新帐户以使用非 empty password 时,需要对代码进行更改。
例 2: 以下代码可将密码变量初始化为空字符串,并尝试在存储的值中读取密码,且将其与用户提供的值进行比较。
...
String storedPassword = """";
String temp;
if ((temp = readPassword()) != null) {
storedPassword = temp;
}
if(storedPassword.equals(userPassword))
// Access protected resources
...
}
...
如果 readPassword() 因数据库错误或其他问题而未能取得存储的密码,攻击者只需向 userPassword 提供一个空字符串,就能轻松绕过密码检查。
在移动世界中,由于设备丢失的几率较高,因此密码管理是一个非常棘手的问题。
例 3:以下代码可将用户名和密码变量初始化为空字符串,如果服务器之前未拒绝这些变量当前提出的请求,代码就可从 Android WebView 存储读取凭证,并使用用户名和密码设置身份验证,从而查看受保护页面。
...
webview.setWebViewClient(new WebViewClient() {
public void onReceivedHttpAuthRequest(WebView view,
HttpAuthHandler handler, String host, String realm) {
String username = """";
String password = """";
if (handler.useHttpAuthUsernamePassword()) {
String[] credentials = view.getHttpAuthUsernamePassword(host, realm);
username = credentials[0];
password = credentials[1];
}
handler.proceed(username, password);
}
});
...
与例 2 相似,如果 useHttpAuthUsernamePassword() 返回 false,攻击者就可以通过提供 empty password 查看受保护页面。
2)解决方案:由于该漏洞是根据password的关键字扫描出的结果,所以规避该类问题,最好的办法就是不使用password当变量。
修改前:
String password = "";
try {
password = AESUtils.encryptForJS(rawPassword);
} catch (Exception e1) {
logger.error(e1);
}
修改后:
String pwd= "";
try {
pwd= AESUtils.encryptForJS(rawPassword);
} catch (Exception e1) {
logger.error(e1);
}
2.Unreleased Resource: Sockets
1)简介:程序可能无法释放某个套接字。资源泄露至少有两种常见的原因:
- 错误状况及其他异常情况。
- 未明确程序的哪一部份负责释放资源。
大部分 Unreleased Resource 问题只会导致一般的软件可靠性问题,但如果攻击者能够故意触发资源泄漏,该攻击者就有可能通过耗尽资源池的方式发起 denial of service 攻击。
例 1:下面的方法绝不会关闭它所打开的套接字。在繁忙的环境中,这会导致 JVM 用尽它所有的套接字。
private void echoSocket(String host, int port) throws UnknownHostException, SocketException, IOException
{
Socket sock = new Socket(host, port);
BufferedReader reader = new BufferedReader(new InputStreamReader(sock.getInputStream()));
while ((String socketData = reader.readLine()) != null) { System.out.println(socketData); } }
例 2:正常情况下,以下修复代码会正常关闭套接字以及任何相关联的数据流。但如果在读取输入或将数据输出到屏幕时出现异常,则不会关闭套接字对象。如果这种情况经常出现,系统将会耗尽所有套接字,无法处理更多连接。
private void echoSocket(String host, int port) throws UnknownHostException, SocketException, IOException
{
Socket sock = new Socket(host, port);
BufferedReader reader = new BufferedReader(new InputStreamReader(sock.getInputStream()));
while ((String socketData = reader.readLine()) != null) { System.out.println(socketData); } sock.close(); }
2)解决方案:释放资源。
修改前:
private ClientHandle getClientHandle() throws IOException,
InterruptedException {
Socket socket = null;
for (int retry = 4; retry-- != 0;)
try {
socket = new Socket(ip, port);
break;
} catch (Exception exception) {
Thread.sleep(10L);
}
if (socket == null) {
throw new InterruptedException("������æ!");
} else {
ClientHandle handle = new ClientHandle(socket);
return handle;
}
}
修改后:
private ClientHandle getClientHandle() throws IOException,
InterruptedException {
Socket socket = null;
for (int retry = 4; retry-- != 0;)
try {
socket = new Socket(ip, port);
break;
} catch (Exception exception) {
Thread.sleep(10L);
}
if (socket == null) {
throw new InterruptedException("������æ!");
} else {
ClientHandle handle = null;
try {
handle = new ClientHandle(socket);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(socket != null) {
socket.close();
}
}
return handle;
}
}
3.Null Dereference
1)简介:当违反程序员的一个或多个假设时,通常会出现 null 指针异常。如果程序明确将对象设置为 null,但稍后却间接引用该对象,则将出现 dereference-after-store 错误。此错误通常是因为程序员在声明变量时将变量初始化为 null。
大部分空指针问题只会引起一般的软件可靠性问题,但如果攻击者能够故意触发空指针间接引用,攻击者就有可能利用引发的异常绕过安全逻辑,或致使应用程序泄漏调试信息,这些信息对于规划随后的攻击十分有用。
示例:在下列代码中,程序员将变量 foo 明确设置为 null。稍后,程序员间接引用 foo,而未检查对象是否为 null 值。
Foo foo = null;
...
foo.setBar(val);
...
}
2)解决方案:使用对象前,需要进行判空。
修改前:
if (uploadFolder.startsWith(File.separator))
uploadFolder.replaceFirst(File.separator, "");
修改后:
if (uploadFolder!=null&&uploadFolder.startsWith(File.separator))
uploadFolder.replaceFirst(File.separator, "");
4.Insecure Randomness
1)简介:在对安全性要求较高的环境中,使用一个能产生可预测数值的函数作为随机数据源,会产生 Insecure Randomness 错误。
电脑是一种具有确定性的机器,因此不可能产生真正的随机性。伪随机数生成器 (PRNG) 近似于随机算法,始于一个能计算后续数值的种子。
PRNG 包括两种类型:统计学的 PRNG 和密码学的 PRNG。统计学的 PRNG 可提供有用的统计资料,但其输出结果很容易预测,因此数据流容易复制。若安全性取决于生成数值的不可预测性,则此类型不适用。密码学的 PRNG 通过可产生较难预测的输出结果来应对这一问题。为了使加密数值更为安全,必须使攻击者根本无法、或极不可能将它与真实的随机数加以区分。通常情况下,如果并未声明 PRNG 算法带有加密保护,那么它有可能就是一个统计学的 PRNG,不应在对安全性要求较高的环境中使用,其中随着它的使用可能会导致严重的漏洞(如易于猜测的密码、可预测的加密密钥、会话劫持攻击和 DNS 欺骗)。
示例: 下面的代码可利用统计学的 PRNG 为购买产品后仍在有效期内的收据创建一个 URL。
String GenerateReceiptURL(String baseUrl) {
Random ranGen = new Random();
ranGen.setSeed((new Date()).getTime());
return (baseUrl + ranGen.nextInt(400000000) + "".html"");
}
这段代码使用 Random.nextInt() 函数为它所生成的收据页面生成独特的标识符。因为 Random.nextInt() 是一个统计学的 PRNG,攻击者很容易猜到由它所生成的字符串。尽管收据系统的底层设计也存在错误,但如果使用了一个不生成可预测收据标识符的随机数生成器(如密码学的 PRNG),会更安全一些。
2)解决方案:修改获取随机数的方式。
修改前:
Random r = new Random();
i = r.nextInt(15);
修改后:
SecureRandom s = new SecureRandom();
i= s.nextInt(15);
5.XML External Entity Injection
1)简介:XML External Entities 攻击可利用能够在处理时动态构建文档的 XML 功能。XML 实体可动态包含来自给定资源的数据。外部实体允许 XML 文档包含来自外部 URI 的数据。除非另行配置,否则外部实体会迫使 XML 解析器访问由 URI 指定的资源,例如位于本地计算机或远程系统上的某个文件。这一行为会将应用程序暴露给 XML External Entity (XXE) 攻击,从而用于拒绝本地系统的服务,获取对本地计算机上文件未经授权的访问权限,扫描远程计算机,并拒绝远程系统的服务。
下面的 XML 文档介绍了 XXE 攻击的示例。
]>
如果 XML 解析器尝试使用 /dev/random 文件中的内容来替代实体,则此示例会使服务器(使用 UNIX 系统)崩溃。
2)解决方案:应对 XML 解析器进行安全配置,使它不允许将外部实体包含在传入的 XML 文档中。
为了避免 XXE injections,应为 XML 代理、解析器或读取器设置下面的属性:
factory.setFeature(""http://xml.org/sax/features/external-general-entities"", false);
factory.setFeature(""http://xml.org/sax/features/external-parameter-entities"", false);
如果不需要 inline DOCTYPE 声明,可使用以下属性将其完全禁用:
factory.setFeature(""http://apache.org/xml/features/disallow-doctype-decl"", true);
要保护 TransformerFactory,应设置下列属性:
TransformerFactory transFact = TransformerFactory.newInstance();
transFact.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, """");
Transformer trans = transFact.newTransformer(xsltSource);
trans.transform(xmlSource, result);
修改前:
TransformerFactory tFac = TransformerFactory.newInstance();
Transformer t = tFac.newTransformer(xslSource);
修改后:
TransformerFactory tFac = TransformerFactory.newInstance();
tFac.setFeature("http://xml.org/sax/features/external-general-entities", false);
tFac.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
Transformer t = tFac.newTransformer(xslSource);
6.Unreleased Resource: Streams
1)简介:"程序可能无法成功释放某一项系统资源。资源泄露至少有两种常见的原因:
- 错误状况及其他异常情况。
- 未明确程序的哪一部份负责释放资源。
大部分 Unreleased Resource 问题只会导致一般的软件可靠性问题,但如果攻击者能够故意触发资源泄漏,该攻击者就有可能通过耗尽资源池的方式发起 denial of service 攻击。
示例:下面的方法绝不会关闭它所打开的文件句柄。FileInputStream 中的 finalize() 方法最终会调用 close(),但是不能确定何时会调用 finalize() 方法。在繁忙的环境中,这会导致 JVM 用尽它所有的文件句柄。
private void processFile(String fName) throws FileNotFoundException, IOException {
FileInputStream fis = new FileInputStream(fName);
int sz;
byte[] byteArray = new byte[BLOCK_SIZE];
while ((sz = fis.read(byteArray)) != -1) {
processBytes(byteArray, sz);
}
}
2)解决方案:及时释放资源。
修改前:
OutputStream out = null;
try {
out = new FileOutputStream(outputZipFileName);
out.write(b);
} catch (Exception e) {
log.error(e);
}
修改后:
OutputStream out = null;
try {
out = new FileOutputStream(outputZipFileName);
out.write(b);
} catch (Exception e) {
log.error(e);
} finally {
if(out != null) {
//out.flush();
out.close();
}
}
8.Portability Flaw: Locale Dependent Comparison
1)简介:对可能与区域设置相关的数据进行比较时,应指定相应的区域设置。
示例 1:以下示例尝试执行验证,以确定用户输入是否包含