原代码审计笔记-安全缺陷
URL重定向:
不安全的SSL(过于宽泛的证书信托):
反射型跨站脚本:
存储型跨站脚本:
路径操纵:
类的操纵:
拒绝服务(StringBuilder):
拒绝服务(正则表达式问题):
拒绝服务(整数溢出):
拒绝服务(解析double类型):
J2EE错误配置(过度会话超时):
代码正确性:字节数组到字符串转换:
系统信息泄露:
SQL注入:
JSON注入:
命令注入:
资源注入:
XML解析器注入、XML外部实体注入、XML注入:
跨站请求伪造:
服务端请求伪造:
不安全的随机数:
密码安全(硬编码密码):
密码管理(配置文件存密码):
不安全的反序列化:
Cookie安全(路径范围过大):
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String location = req.getParameter("url");
resp.sendRedirect(location); // Noncompliant
}
通过重定向,Web 应用程序能够引导用户访问同一应用程序内的不同网页或访问外部站点。应用程序利用重定向来帮助进行站点导航,有时还跟踪用户退出站点的方式。当 Web 应用程序将客户端重定向到攻击者可以控制的任意 URL 时,就会发生 Open redirect 漏洞:
攻击者可以利用 Open redirect 漏洞诱骗用户访问某个可信赖站点的 URL,并将他们重定向到恶意站点。攻击者通过对 URL 进行编码,使最终用户很难注意到重定向的恶意目标,即使将这一目标作为 URL 参数传递给可信赖的站点时也会发生这种情况。因此,Open redirect 常被作为钓鱼手段的一种而滥用,攻击者通过这种方式来获取最终用户的敏感数据。
如果允许未验证的输入控制重定向机制所使用的 URL,可能会有利于攻击者发动钓鱼攻击。
建议:过滤即将重定向的路径,或者增设黑白名单实现安全访问限制。
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
过于信任证书,没有对证书进行校验和约束,或是校验不完善。
公钥基础架构 (PKI) 基于受信任的证书颁发机构 (CA),但盗用的证书颁发机构的数量不断增加,这意味着其根证书无法再受到信任,因此由该 CA 签名的证书也不再受到信任。拥有这些盗用证书的攻击者将能够拦截所有信任这些 CA 的 SSL/TLS 信息流。
证书颁发机构的签名对于浏览器等通用网络通信工具而言是必不可少的,这些工具连接到任意网络端点,并且事先并不知道哪些 CA 对这些端点的 SSL/TLS 证书进行了签名。对于连接到有限的后端服务器的移动应用程序,如果它们知道这些服务器在对其证书进行签名时可能会使用的若干受信任 CA,则这些应用程序可从中获益,并在应用程序中“固定”这些证书/公钥,以便仅信任应用程序需要的证书。
SSL/TLS 连接使用默认的预加载系统证书颁发机构 (CA) 创建,这可能会使攻击者利用由盗用的根 CA 签名的证书执行中间人 (MiTM) 攻击,从而拦截加密通信。
建议:CA签名的证书执行中间人(MiTM)攻击,建议对证书有效性有完善的校验、使用时间短、2048位加密长度的SSL证书。次则如果使用默认的URLConnection建立SSL/TLS连接,建议使用HttpsURLConnection进行替代,并对证书进行判断和处理。
public void risk(HttpServletRequest request,
HttpServletResponse response ,org.apache.log4j.Logger logger) {
String text = request.getParameter("text");
try {
response.getWriter().print(text);
} catch (IOException e) {
logger.warn(“Exception”, e);
}
}
Cross-Site Scripting (XSS) 漏洞在以下情况下发生:
1.数据通过一个不可信赖的数据源进入 Web 应用程序。对于 Reflected XSS,不可信赖的源通常为 Web 请求,而对于 Persisted(也称为 Stored)XSS,该源通常为数据库或其他后端数据存储。
2. 未检验包含在动态内容中的数据,便将其传送给了 Web 用户。
传送到 Web 浏览器的恶意内容通常采用 JavaScript 代码片段的形式,但也可能会包含一些 HTML、Flash 或者其他任意一种可以被浏览器执行的代码。基于 XSS 的攻击手段花样百出,几乎是无穷无尽的,但通常它们都会包含传输给攻击者的私人数据(如 Cookie 或者其他会话信息)。在攻击者的控制下,指引受害者进入恶意的网络内容;或者利用易受攻击的站点,对用户的机器进行其他恶意操作。
向一个 Web 浏览器发送未经验证的数据会导致该浏览器执行恶意代码。
建议:反射型跨站,来自用户输入的字符串被直接输出到客户端。
1、使用跨站修复函数处理输出到客户端的数据字符串。
例如:
1.使用struts自带的跨站修复函数方式:
String filtedText = org.apache.struts.util.ResponseUtils.filter(text);
response.getWriter().print(filtedText);
2.未使用struts的系统,使用自定义的跨站修复函数方式
String encodeText = encodeHtml(text);
response.getWriter().print(encodeText);
3.使用Apache的commons-lang.jar提供系统库函数StringEscapeUtils.escapeHtml(str):
String encodeText = StringEscapeUtils.escapeHtml(text);
response.getWriter().print(encodeText);
2、设置cookie时使用HttpOnly参数,限制cookie作为DOM对象存取,避免攻击者利用跨站脚本漏洞进行Cookie劫持攻击。
3、次则需要设置过滤库或者黑白名单,确保浏览器不会执行恶意代码。
protected void printComment(Connection conn, ServletOutputStream out, String user) throws SQLException, IOException {
PreparedStatement pr = conn.prepareStatement("SELECT * FROM comms WHERE user = ?");
pr.setString(0, user);
String comment = pr.executeQuery().getString("comment");
out.println("Comments: " + comment);
}
Cross-Site Scripting (XSS) 漏洞在以下情况下发生:
1.数据通过一个不可信赖的数据源进入 Web 应用程序。对于 Reflected XSS,不可信赖的源通常为 Web 请求,而对于 Persisted(也称为 Stored)XSS,该源通常为数据库或其他后端数据存储。
2. 未检验包含在动态内容中的数据,便将其传送给了 Web 用户。
传送到 Web 浏览器的恶意内容通常采用 JavaScript 代码片段的形式,但也可能会包含一些 HTML、Flash 或者其他任意一种可以被浏览器执行的代码。基于 XSS 的攻击手段花样百出,几乎是无穷无尽的,但通常它们都会包含传输给攻击者的私人数据(如 Cookie 或者其他会话信息)。在攻击者的控制下,指引受害者进入恶意的网络内容;或者利用易受攻击的站点,对用户的机器进行其他恶意操作。
向一个 Web 浏览器发送未经验证的数据会导致该浏览器执行恶意代码。
建议:存储型跨站,来自用户输入的字符串被直接输出到客户端。
1、使用esapi处理输出到客户端的数据字符串,例如:org.owasp.encoder.Encode.forHtml(name);。
2、次则需要设置过滤库或者黑白名单,确保浏览器不会执行恶意代码。
public boolean authenticate(javax.servlet.http.HttpServletRequest request) {
String user = request.getParameter("user");
// If the special value "../bin" is passed as user, authentication is bypassed
// Indeed, if it passed as a user, the path becomes:
// /bin
// which exists on most Linux / BSD / Mac OS distributions
return Files.exists(Paths.get("/home/", user)); // Noncompliant
}
当满足以下两个条件时,就会产生 path manipulation 错误:
1. 攻击者可以指定某一文件系统操作中所使用的路径。
2. 攻击者可以通过指定特定资源来获取某种权限,而这种权限在一般情况下是不可能获得的。
例如,在某一程序中,攻击者可以获得特定的权限,以重写指定的文件或是在其控制的配置环境下运行程序。 例 1: 下面的代码使用来自于 HTTP 请求的输入来创建一个文件名。程序员没有考虑到攻击者可能使用像“ ../../tomcat/conf/server.xml”一样的文件名,从而导致应用程序删除它自己的配置文件。
String rName = request.getParameter("reportName");
File rFile = new File("/usr/local/apfr/reports/" + rName);
...
rFile.delete();
允许用户输入控制文件系统操作所用的路径会导致攻击者能够访问或修改其他受保护的系统资源。
建议:对访问文件路径有过滤限制或者增设黑白名单,限制访问权限,确保访问是安全的、允许访问的。
String prop = request.getParameter('prop');
String value = request.getParameter('value');
HashMap properties = new HashMap();
properties.put(prop, value);
BeanUtils.populate(user, properties);
Bean 属性的名称和值在填充任何 bean 之前都需要进行验证。Bean 填充功能允许设置 bean 属性或嵌套属性。攻击者可以利用此功能访问特殊的 bean 属性,例如 class.classLoader,此属性将允许攻击者覆盖系统属性并可能会执行任何代码。
攻击者可以设置可能会危及系统完整性的任意 bean 属性。
建议:使用白名单操作固定的bean属性。
...
StringBuilder sb = new StringBuilder();
sb.append(request.getParameter("foo"));
...
将用户控制的数据附加到使用默认支持字符数组大小 (16) 进行初始化的 StringBuilder 实例,会导致应用程序在调整基础数组的大小以适应用户数据时占用大量堆内存。每次新数据附加到 StringBuilder 实例时,它都会尝试使数据适应其支持字符数组。如果数据不合适,将会创建新的数组,大小为之前的两倍,而旧数组在进行回收之前,将继续留在堆中。此缺陷可被攻击者用于执行拒绝服务 (DoS) 攻击。
将不受信任的数据附加到使用默认支持数组大小进行初始化的 StringBuilder 实例会导致 JVM 过度使用堆内存空间。
建议:对于外界获取的数据进行内容过滤和长度校验,有效的使用JVM堆内存空间,防止资源浪费或溢出。
public boolean validate(javax.servlet.http.HttpServletRequest request) {
String regex = request.getParameter("regex");
String input = request.getParameter("input");
// Enables attackers to force the web server to evaluate
// regex such as "(a+)+" on inputs such as "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"
input.matches(regex); // Noncompliant
}
实施正则表达式评估程序及相关方法时存在漏洞,该漏洞会导致评估线程在处理嵌套和重复的正则表达式组的重复和交替重叠时挂起。此缺陷可被攻击者用于执行拒绝服务 (DoS) 攻击。
不可信赖数据被传递至应用程序并作为正则表达式使用。这会导致线程过度使用 CPU 资源。
建议:使用安全的正则表达式,不要直接使用外部输入作为正则表达式。
例如:参数A.matches(参数B);
public static boolean checkIfModifiedSince(HttpServletRequest request, HttpServletResponse response,
long lastModified) {
long ifModifiedSince = request.getDateHeader("If-Modified-Since");
if ((ifModifiedSince != -1) && (lastModified < ifModifiedSince + 1000)) {
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return false;
}
return true;
}
可以将被感染的数据追溯到算术运算时,会出现该错误。
整数溢出可能导致某些重要的安全性检查失败,这可能导致特权升级,或者导致拒绝服务 (DoS) 攻击。例如,如果请求大量对象,则循环计数器可能导致无限循环或非常长的循环,或者导致过度的内存分配。不仅仅是加法和乘法,所有操作都可能产生问题。
确保所处理的任何 int 值都在正确的范围中,而如果它们是大小值,则务必进行检查以保证这些值不是负值,然后再次检查允许的上限。
建议:第一,先决条件检查,对范围进行检查判断; 第二,向上类型转换,比如换成范围更大的类型; 第三使用BigInteger,java提供的BigInteger是用来处理高精度问题的,推荐使用BigInteger;防止DoS攻击。
java.lang.Double.parseDouble();
//及相关方法时存在漏洞,可能导致在解析位于 [2^(-1022) - 2^(-1075) :2^(-1022) - 2^(- 1076)]
在实施 java.lang.Double.parseDouble() 及相关方法时出现漏洞,会导致在解析 [2^(-1022) - 2^(-1075) : 2^(-1022) - 2^(-1076)] 范围内的任意数字时挂起线程。此缺陷可被攻击者用于执行拒绝服务 (DoS) 攻击。
程序会调用解析 double 类型的方法,这会导致线程被挂起。
建议:
1:如果可能,请应用由 Oracle 发布的修补程序。尽可能为类似 Apache Tomcat 的易受攻击的其他产品安装修补程序。如果不可能,请务必将您的 HPE Security Fortify Real-Time Analyzer(HPE Security Fortify 实时分析器)安装配置为防范此攻击。
2:提升jdk版本至1.7以上。
3:转型Float测试,可以预防因Double转型导致的拒绝服务,例如:
public static double parseDouble(String value)
String normalString = normalizeDoubleString(value);
int offset = normalString.indexOf('E');
BigDecimal base;
int exponent;
if (offset == -1) {
base = new BigDecimal(value);
exponent = 0;
} else {
base = new BigDecimal(normalString.substring(0, offset));
exponent = Integer.parseInt(normalString.charAt(offset + 1) == '+' ?
normalString.substring(offset + 2)
normalString.substring(offset + 1));
}
return base.scaleByPowerOfTen(exponent).doubleValue();
}
-1
会话持续时间越长,攻击者危害用户帐户的机会就越大。当会话处于活动状态时,攻击者可能会强力攻击用户的密码、破解用户的无线加密密钥或者通过打开的浏览器强占会话。如果创建大量的会话,较长的会话超时时间还会阻止系统释放内存,并最终导致 denial of service。
如果会话超时时间过长,攻击者就会有更多时间危害用户帐户。
建议:将会话超时间隔设置为 30 分钟或更少,既能使用户在一段时间内与应用程序互动,又提供了一个限制窗口攻击的合理范围。
public static String desDecryptFromBase64(String input, byte[] keyBytes) {
byte[] decryptResult = des(EncodeUtils.decodeBase64(input), keyBytes, Cipher.DECRYPT_MODE);
return new String(decryptResult);
}
在将字节数组的数据转换为 String 后,没有说明适用字符集外的数据会发生何种变化。这会导致数据丢失,或者在需要二进制数据来确保执行正确的安全措施时,安全级别降低。
将字节数组转换为 String 会导致数据丢失。
建议:不要将可能包含非字符数据的字节数组转换为 String 对象,因为这会破坏该代码的功能,且在某些情况下会造成更大的安全问题。事实上,很多情况下都不需要将字节数组转换为字符串,如果基于特定原因可以从二进制数据创建 String 对象,则必须先对其进行编码,使其适合于默认字符集。
如:new String(Base64.encode(text));
new String(Hex.encode(text));
try {
...
} catch (Exception e) {
e.printStackTrace();
}
当系统数据或调试信息通过输出流或者日志功能流出程序时,就会发生信息泄漏。
建议:编写错误消息时,始终要牢记安全性。在编码的过程中,尽量避免使用繁复的消息,提倡使用简短的错误消息。限制生成与存储繁复的输出数据将有助于管理员和程序员诊断问题的所在。此外,还要留意有关调试的跟踪信息,有时它可能出现在不明显的位置(例如嵌入在错误页 HTML 代码的注释行中)。
即便是并未揭示栈踪迹或数据库转储的简短错误消息,也有可能帮助攻击者发起攻击。例如,“Access Denied”(拒绝访问)消息可以揭示系统中存在一个文件或用户。
public void fix(HttpServletRequest request, Connection c, org.apache.log4j.Logger logger) {
String text = request.getParameter("text");
String sql = "select * from tableName where columnName = ?";
try {
PreparedStatement s = c.prepareStatement(sql);
s.setString(1, text);
s.executeQuery();
} catch (SQLException e) {
logger.warn("Exception", e);
}
}
SQL injection 错误在以下情况下发生:
1. 数据从一个不可信赖的数据源进入程序。
在这种情况下, Static Code Analyzer( 静态代码分析器)无法确定数据源是否可信赖。
2. 数据用于动态地构造一个 SQL 查询。
通过不可信来源的输入构建动态 SQL 指令,攻击者就能够修改指令的含义或者执行任意 SQL 命令。
建议:
1.对参数正则过滤,起到SQL防爆效果。
2.SQL语句中存在用户输入的字符串,且未使用安全的查询方法进行执行SQL语句。
使用带占位符的预编译执行方式的SQL语句,未经滤所有非程序自身的数据都不参与SQL语句的构成。
InputStream responseBodyAsStream = null;
responseString = EntityUtils.toString(httpResponse.getEntity(),"UTF-8");
Map map = mapper.readValue(responseString, Map.class);
JSON injection 会在以下情况中出现:
1. 数据从一个不可信赖的数据源进入程序。
2. 将数据写入到 JSON 流。
应用程序通常使用 JSON 来存储数据或发送消息。用于存储数据时,JSON 通常会像缓存数据那样处理,而且可能会包含敏感信息。用于发送消息时,JSON 通常与 RESTful 服务一起使用,并且可以用于传输敏感信息,例如身份验证凭据。
如果应用程序利用未经验证的输入构造 JSON,则可以更改 JSON 文档和消息的语义。在相对理想的情况下,攻击者可能会插入无关的元素,导致应用程序在解析 JSON 文档或请求时抛出异常。在更为严重的情况下,例如涉及 JSON Injection,攻击者可能会插入无关的元素,从而允许对 JSON 文档或请求中对业务非常关键的值执行可预见操作。还有一些情况,JSON Injection 可以导致 Cross-Site Scripting 或 Dynamic Code Evaluation。
该方法会将未经验证的输入写入 JSON。攻击者可以利用此调用将任意元素或属性注入 JSON 实体。
建议:
1.InputStream responseBodyAsStream = null;
responseString = EntityUtils.toString(httpResponse.getEntity(),"UTF-8");
String wellFormedJson = com.google.json.JsonSanitizer.sanitize(responseString);
Map map = mapper.readValue(wellFormedJson, Map.class);
2.com.fasterxml.jackson.core.ioJsonStringEncoder下的quoteAsString或者quoteAsUTF8方法。
或者org.apache.commons.lang3. StringEscapeUtils. escapeJson;
3.或者编写正则过滤,对其内容限制,防止JSON注入
public void run(javax.servlet.http.HttpServletRequest request) throws IOException {
String binary = request.getParameter("binary");
// If the value "/sbin/shutdown" is passed as binary and the web server is running as root,
// then the machine running the web server will be shut down and become unavailable for future requests
Runtime.getRuntime().exec(binary); // Noncompliant
}
命令注入漏洞,用户通过浏览器提交执行命令,由于服务器端没有针对执行函数做过滤,导致在没有指定绝对路径的情况下就执行命令,可能会允许攻击者通过改变 $PATH 或程序执行环境的其他方面来执行一个恶意构造的代码。 将未检查的用户输入用于全部或部分由应用程序执行的操作系统命令时,会检测到该错误。
通常,在应用程序之内进行外部命令的创建或执行进程时,需要考虑安全因素。如果在用于执行的命令字符串的任意部分中使用用户输入,则存在严重的漏洞。攻击者可以注入额外的命令,并且在应用程序服务器中执行这些命令,从而发动进程或命令注入攻击。如果攻击者能够运行任意命令,就可以导致拒绝服务 (DoS)、数据损坏、数据安全性违规以及其他风险。
建议:使用校验过滤,避免用户输入的字符串被直接作为系统命令在命令行执行。
// 与Resource相关函数均涉及此漏洞
public InputStream risk(HttpServletRequest request) {
String path = request.getParameter("path");
HttpSession session = request.getSession();
ServletContext context = session.getServletContext();
return context.getResourceAsStream(path);
}
当满足以下两个条件时,就会发生 resource injection:
1. 攻击者可以指定已使用的标识符来访问系统资源。
例如,攻击者可能可以指定用来连接到网络资源的端口号。
2. 攻击者可以通过指定特定资源来获取某种权限,而这种权限在一般情况下是不可能获得的。
例如,程序可能会允许攻击者把敏感信息传输到第三方服务器。
注意:如果资源注入涉及存储在文件系统中的资源,则可以将其报告为名为路径篡改的不同类别。有关这一漏洞的详细信息,请参见 path manipulation 的描述。
建议:资源注入,与应用程序资源获取和使用相关的路径中含有用户输入的字符串。
主要修复策略为对用作资源路径的字符串作白名单控制或与资源加载和使用相关的路径仅由程序控制。
//资源注入
]>
&lol9;
//外部实体
/* Load XML stream and display content */
String maliciousSample = "xxe.xml";
XMLInputFactory factory = XMLInputFactory.newInstance();
try (FileInputStream fis = new FileInputStream(malicousSample)) {
// Load XML stream
XMLStreamReader xmlStreamReader = factory.createXMLStreamReader(fis); // Noncompliant; reader is vulnerable
XML Entity Expansion Injection 也称为 XML Bombs,属于 Denial of Service (DoS) 攻击,利用格式工整的有效 XML 块,它们在耗尽服务器分配的资源之前不断呈指数式扩张。XML 允许定义充当字符串替代宏的自定义实体。通过嵌套复发性实体解析,攻击者可以轻松使服务器资源崩溃。
建议:
1:内部资源尽量不要过滤,限制在安全范围内,可提升解析效率;禁用外部实体;
2:开启外部实体,需要对其做出限制,资源注入,与应用程序资源获取和使用相关的路径中含有用户输入的字符串。主要修复策略为对用作资源路径的字符串作白名单控制或与资源加载和使用相关的路径仅由程序控制。对其注入资源内容过滤,防止文件扩展达到服务器承受范围之外,一般超过3G即会引起服务器崩溃。
“http://x.com/del.php?id=1”> 再把这个页面发送给管理员,只要管理员打开这个页面,同时浏览器也会利用当前登陆的这个管理账号权限发出:http://x.com/del.php?1d=1 这个请求,从而劫持此请求,利用管理员账户执行了一些操作。
跨站点伪装请求 (CSRF) 漏洞会在以下情况下发生:
1. Web 应用程序使用会话 cookie。
2. 应用程序未验证请求是否经过用户同意便处理 HTTP 请求。
Nonce 是随消息一起发送的加密随机值,可防止 replay 攻击。如果该请求未包含证明其来源的 nonce,则处理该请求的代码将易受到 CSRF 攻击(除非它并未更改应用程序的状态)。这意味着使用会话 cookie 的 Web 应用程序必须采取特殊的预防措施,确保攻击者无法诱骗用户提交伪请求。假设有一个 Web 应用程序,它允许管理员创建新帐户,如下所示:
RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, "/new_user");
body = addToPost(body, new_username);
body = addToPost(body, new_passwd);
rb.sendRequest(body, new NewAccountCallback(callback));
攻击者可以设置一个包含以下代码的恶意网站。
RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, "http://www.example.com/new_user");
body = addToPost(body, "attacker";
body = addToPost(body, "haha");
rb.sendRequest(body, new NewAccountCallback(callback));
如果 example.com 的管理员在网站上具有活动会话时访问了恶意页面,则会在毫不知情的情况下为攻击者创建一个帐户。这就是 CSRF 攻击。正是由于该应用程序无法确定请求的来源,才有可能受到 CSRF 攻击。任何请求都有可能是用户选定的合法操作,也有可能是攻击者设置的伪操作。攻击者无法查看伪请求生成的网页,因此,这种攻击技术仅适用于篡改应用程序状态的请求。
如果应用程序通过 URL 传递会话标识符(而不是 cookie),则不会出现 CSRF 问题,因为攻击者无法访问会话标识符,也无法在伪请求中包含会话标识符。
CSRF 在 2007 OWASP Top 10 排行榜上名列第 5。
建议:
1:Token实在页面或者cookie中插入一个不可预测的字符串,服务器验证token是否是上次留下的即可判断是不是可信请求;
2:验证码没token那么实用,用户体验较差,所以这一种方式只能用在敏感操作的页面,利用登录页面等。
//示例:在下列中,攻击者将能够控制服务器连接至的 URL。
String url = request.getParameter("url");
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse response1 = httpclient.execute(httpGet);
//攻击者劫持网络连接的能力取决于他可以控制的 URI 的特定部分以及用于建立连接的库。例如,控制 URI //方案将使攻击者可以使用不同于
//http 或
// https 的协议,类似于如下:
//- up://
//- ldap://
//- jar://
//- gopher://
//- mailto://
//- ssh2://
//- telnet://
//- expect://
当攻击者可以影响应用程序服务器建立的网络连接时,将会发生 Server-Side Request Forgery。网络连接源自于应用程序服务器内部 IP,因此攻击者将可以使用此连接来避开网络控制,并扫描或攻击没有以其他方式暴露的内部资源。
应用程序将使用用户控制的数据启动与第三方系统的连接,以创建资源 URI。
建议:请勿基于用户控制的数据建立网络连接,并要确保请求发送给预期的目的地。如果需要提供用户数据来构建目的地 URI,请采用间接方法:例如创建一份合法资源名的列表,并且规定用户只能选择其中的文件名。通过这种方法,用户就不能直接由自己来指定资源的名称了。
但在某些情况下,这种方法并不可行,因为这样一份合法资源名的列表过于庞大、难以跟踪。因此,程序员通常在这种情况下采用黑名单的办法。在输入之前,黑名单会有选择地拒绝或避免潜在的危险字符。但是,任何这样一份黑名单都不可能是完整的,而且将随着时间的推移而过时。更好的方法是创建一份白名单,允许其中的字符出现在资源名称中,且只接受完全由这些被认可的字符组成的输入。
Random sr = new Random();
int v = sr.next(32);
SecureRandom sr1 = new SecureRandom();
sr1.setSeed(123456L); // Noncompliant
int v1 = sr1.next(32);
在对安全性要求较高的环境中,使用能够生成可预测值的函数作为随机数据源,会产生 Insecure Randomness 错误。
电脑是一种具有确定性的机器,因此不可能产生真正的随机性。伪随机数生成器 (PRNG) 近似于随机算法,始于一个能计算后续数值的种子。
PRNG 包括两种类型:统计学的 PRNG 和密码学的 PRNG。统计学的 PRNG 提供很多有用的统计属性,但其输出结果很容易预测,因此容易复制数值流。在安全性所依赖的生成值不可预测的情况下,这种类型并不适用。密码学的 PRNG 生成的输出结果较难预测,可解决这一问题。为保证值的加密安全性,必须使攻击者根本无法、或几乎不可能鉴别生成的随机值和真正的随机值。通常情况下,如果并未声明 PRNG 算法带有加密保护,那么它很可能就是统计学的 PRNG,因此不应在对安全性要求较高的环境中使用,否则会导致严重的漏洞(如易于猜测的密码、可预测的加密密钥、Session Hijacking 和 DNS Spoofing)。标准的伪随机数值生成器不能抵挡各种加密攻击。
当不可预测性至关重要时,如大多数对安全性要求较高的环境都采用随机性,这时可以使用密码学的 PRNG。不管选择了哪一种 PRNG,都要始终使用带有充足熵的数值作为该算法的种子。(诸如当前时间之类的数值只提供很小的熵,因此不应该使用。) 。
建议:Java 语言在 java.security.SecureRandom 中提供了一个加密 PRNG。就像 java.security 中其他以算法为基础的类那样, SecureRandom 提供了与某个特定算法集合相关的包,该包可以独立实现。当使用 SecureRandom.getInstance() 请求一个 SecureRandom 实例时,您可以申请实现某个特定的算法。如果算法可行,那么您可以将它作为 SecureRandom 的对象使用。如果算法不可行,或者您没有为算法明确特定的实现方法,那么会由系统为您选择 SecureRandom 的实现方法。
Sun 在名为 SHA1PRNG 的 Java 版本中提供了一种单独实现 SecureRandom 的方式,Sun 将其描述为计算:
“SHA-1 可以计算一个真实的随机种子参数的散列值,同时,该种子参数带有一个 64 比特的计算器,会在每一次操作后加 1。在 160 比特的 SHA-1 输出中,只能使用 64 比特的输出 [1]。”
然而,文档中有关 Sun 的 SHA1PRNG 算法实现细节的相关记录很少,人们无法了解算法实现中使用的熵的来源,因此也并不清楚输出中到底存在多少真实的随机数值。尽管有关 Sun 的实现方法网络上有各种各样的猜测,但是有一点无庸置疑,即算法具有很强的加密性,可以在对安全性极为敏感的各种内容中安全地使用。
软件随机数均为伪随机,多使用强伪随机对象java.security.SecureRandom和增强随机库、随机数范围来降低被加密攻击的命中性。
public String bad()
{
String password = "Password";
return password;
}
使用硬编码方式处理密码绝非好方法。这不仅是因为所有项目开发人员都可以使用通过硬编码方式处理的密码,而且还会使解决这一问题变得极其困难。一旦代码投入使用,除非对软件进行修补,否则您再也不能改变密码了。如果帐户中的密码保护减弱,系统所有者将被迫在安全性和可行性之间做出选择。
Hardcoded password 可能会危及系统安全性,并且无法轻易修正出现的安全问题。
建议:数据源解密获取密码。
在配置文件中存储明文密码会使所有能够访问该文件的人都能访问那些用密码保护的资源。程序员有时候认为,他们不可能阻止应用程序被那些能够访问配置文件的攻击者入侵,但是这种想法会导致攻击者发动攻击变得更加容易。健全的 password management 方针从来不会允许以明文形式存储密码。
在配置文件中存储明文密码,可能会危及系统安全。
建议:将配置文件抽取到property中来,以数据源注入的方式注入到对应的属性。
扩展在继承org.springframework.beans.factory.config.PropertyPlaceholderConfigurer类的解密方法convertProperty,在convertProperty中实现业务处理过程。
例如:
p:fileEncoding="utf-8" />
p:driverClassName="${driverClassName}"
p:url="${url}"
p:username="${userName}"
p:password="${password}"/>
public class RequestProcessor {
protected void processRequest(HttpServletRequest request) {
ServletInputStream sis = request.getInputStream();
ObjectInputStream ois = new ObjectInputStream(sis);
Object obj = ois.readObject(); // Noncompliant
}
}
Java 序列化会将对象图转换为字节流(包含对象本身和必要的元数据),以便通过字节流进行重构。开发人员可以创建自定义代码,以协助 Java 对象反序列化过程,在此期间,他们可以使用其他对象或代理替代反序列化对象。在对象重构过程中,并在对象返回至应用程序并转换为预期的类型之前,会执行自定义反序列化过程。到开发人员尝试强制执行预期的类型时,代码可能已被执行。
在必须存在于运行时类路径中且无法由攻击者注入的可序列化类中,会自定义反序列化例程,所以这些攻击的可利用性取决于应用程序环境中的可用类。令人遗憾的是,常用的第三方类,甚至 JDK 类都可以被滥用,导致 JVM 资源耗尽、部署恶意文件或运行任意代码。
在运行时对用户控制的对象流进行反序列化,会让攻击者有机会在服务器上执行任意代码、滥用应用程序逻辑和/或导致 Denial of Service。
建议:继承ObjectInputStream,并实现resolveClass,在内部设置白名单机制,只允许序列化已知的类。
public class SecureObjectInputStream extends ObjectInputStream {
// Constructor here
@Override
protected Class resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException {
// Only deserialize instances of AllowedClass
if (!osc.getName().equals(AllowedClass.class.getName())) {
throw new InvalidClassException("Unauthorized deserialization", osc.getName());
}
return super.resolveClass(osc);
}
}
[...]
Cookie cookie = new Cookie("email", email);
cookie.setMaxAge(60*60*24*365);
[...]
大多数 Web 编程环境默认设置为创建非永久性的 cookie。这些 cookie 仅驻留在浏览器内存中(不写入磁盘),并在浏览器关闭时丢失。程序员可以指定在浏览器会话中保留这些 cookie,直到将来某个日期为止。这样的 cookie 将被写入磁盘,在浏览器会话结束以及计算机重启后仍然存在。
如果私人信息存储在永久性 cookie 中,那么攻击者就有足够的时间窃取这些数据 — 尤其是因为通常将永久性 cookie 设置为在不久的将来到期。永久性 cookie 通常用于在用户与某个站点交互时对其进行标识。根据此跟踪数据的用途,有可能利用永久性 cookie 违反用户隐私。
将敏感数据存储在永久性的 cookie 中可能导致违反保密性或危及帐户安全。
建议:不要将敏感数据存储在永久性 cookie 中。
应确保在合理的时间内清除与在服务器端存储的永久性 cookie 关联的所有数据。
[...]
Cookie cookie = new Cookie("email", email);
cookie.setMaxAge(60*60*24*365);
[...]
开发人员通常将 cookie 设置为可从根上下文路径(“ /”)访问它。这样做会使 cookie 暴露在域中的所有 Web 应用程序下。由于 cookie 通常包含敏感信息(如会话标识符),因此,在应用程序之间共享 cookie 可能在一个应用程序中导致漏洞,从而危及其他应用程序安全。
可通过相同域中的其他应用程序访问路径范围过大的 cookie。
建议:将Cookie路径设置为具有较高的限制性。例如黑白名单限制。