response.setHeader("Set-Cookie","cookiename=cookievalue; path=/; Domain=domainvaule; Max-age=seconds; HttpOnly");
Cookie属性
Name:Cookie名
Value:Cookie值
Domain:Cookie的域。如果设成.test.com,那么子域名a.test.com和b.test.com,都可以使用.test.com的Cookie。
Path:Cookie的路径。如果设成/path/,则只有路径为/path/的页面可以访问该Cookie。如果设为/,则本域名下的所有页面都可以访问该Cookie。
Expires / Max-Age:Cookie的超时时间。若设置其值为一个时间,那么当到达此时间后,此Cookie失效。不设置的话默认值是Session,意思是Cookie会和Session一起失效。当浏览器关闭(不是浏览器标签页,而是整个浏览器)后,此Cookie失效。
Size:Cookie大小。
HttpOnly:若此属性为true,则只有在http请求头中会带有此Cookie的信息,而不能通过document.cookie来访问此Cookie。
Secure:设置是否只能通过https来传递此条Cookie。
SameSite:用来防止 CSRF 攻击和用户追踪。可以设置三个值:Strict、Lax 和 None。
Strict:Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。
Lax:Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
None:关闭SameSite属性,提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。Priority:优先级。定义了三种优先级,Low/Medium/High,当Cookie数量超出时,低优先级的Cookie会被优先清除。
高危:反射型XSS
反射型XSS是指应用程序通过Web请求获取不可信赖的数据,并在未检验数据是否存在恶意代码的情况下,将其发送给用户。反射型XSS一般可以由攻击者构造带有恶意代码参数的URL来实现,在构造的URL地址被打开后,其中包含的恶意代码参数被浏览器解析和执行。这种攻击的特点是非持久化,必须用户点击包含恶意代码参数的链接时才会触发。
**例如**:下面JSP代码片段的功能是从HTTP请求中读取输入的用户名(username)并显示到页面。
<%String name= request.getParameter("username"); %>
姓名: <%= name%>
修复建议
如果name里有包含恶意代码,那么Web浏览器就会像显示HTTP响应那样执行该代码,应用程序将受到反射型XSS攻击。
为了避免反射型XSS攻击,建议采用以下方式进行防御:
1.对用户的输入进行合理验证(如年龄只能是数字),对特殊字符(如<、>、'、"
以及<script>、javascript
等进行过滤。
2.根据数据将要置于HTML上下文中的不同位置(HTML标签、HTML属性、JavaScript脚本、CSS、URL),对所有不可信数据进行恰当的输出编码。
**例如**:采用OWASP ESAPI对数据输出HTML上下文中不同位置,编码方法如下。
//HTML encode
ESAPI.encoder().encodeForHTML(inputData);
//HTML attribute encode
ESAPI.encoder().encodeForHTMLAttribute(inputData);
//JavaScript encode
ESAPI.encoder().encodeForJavaScript(inputData);
//CSS encode
ESAPI.encoder().encodeForCSS(inputData);
//URL encode
ESAPI.encoder().encodeForURL(inputData);
3.设置HttpOnly属性,避免攻击者利用跨站脚本漏洞进行Cookie劫持攻击。在Java EE中,给Cookie添加HttpOnly的代码如下:
response.setHeader("Set-Cookie","cookiename=cookievalue; path=/; Domain=domainvaule; Max-age=seconds; HttpOnly");
Cookie属性
Name:Cookie名
Value:Cookie值
Domain:Cookie的域。如果设成.test.com,那么子域名a.test.com和b.test.com,都可以使用.test.com的Cookie。
Path:Cookie的路径。如果设成/path/,则只有路径为/path/的页面可以访问该Cookie。如果设为/,则本域名下的所有页面都可以访问该Cookie。
Expires / Max-Age:Cookie的超时时间。若设置其值为一个时间,那么当到达此时间后,此Cookie失效。不设置的话默认值是Session,意思是Cookie会和Session一起失效。当浏览器关闭(不是浏览器标签页,而是整个浏览器)后,此Cookie失效。
Size:Cookie大小。
HttpOnly:若此属性为true,则只有在http请求头中会带有此Cookie的信息,而不能通过document.cookie来访问此Cookie。
Secure:设置是否只能通过https来传递此条Cookie。
SameSite:用来防止 CSRF 攻击和用户追踪。可以设置三个值:Strict、Lax 和 None。
Strict:Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。
Lax:Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
None:关闭SameSite属性,提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。Priority:优先级。定义了三种优先级,Low/Medium/High,当Cookie数量超出时,低优先级的Cookie会被优先清除。
输入验证
高危:路径遍历
应用程序对用户可控制的输入未经合理校验,就传送给一个文件API。攻击者可能会使用一些特殊的字符(如..
和/
)摆脱受保护的限制,访问一些受保护的文件或目录。
**例如**:下面代码片段通过验证输入路径是否以/safe\_dir/
为开头,来判断是否进行创建、删除操作。
String path = getInputPath();
if (path.startsWith("/safe_dir/")){
File f = new File(path);
f.delete()
}
攻击者可能提供类似下面的输入:
/safe\_dir/../important.dat
程序假定路径是有效的,因为它是以/safe\_dir/
开头的,但是../
将导致程序删除important.dat
文件的父目录。
修复建议
预防路径遍历的威胁,有以下三种方法:
-
程序对非受信的用户输入做过滤和验证,对网站用户提交的文件路径进行硬编码或统一编码,过滤非法字符。
-
对文件后缀进行白名单控制,拒绝包含了恶意的符号或空字节。
-
合理配置Web服务器的目录访问权限。
高危:基于DOM的XSS
应用程序的客户端代码从
document.location
request.url
document.URL
document.referrer
或其他任何攻击者可以修改的浏览器对象获取数据,如果未验证数据是否存在恶意代码的情况下,就将其动态更新到页面的DOM节点,应用程序将易于受到基于DOM的XSS攻击。
**例如**:下面的JavaScript代码片段可从URL中读取msg信息,并将其显示给用户。
var url=document.URL;
document.write(url.substring(url.indexOf("msg=")+4,url.length);
该段脚本解析URL,读取msg参数的值,并将其写入页面。如果攻击者设计一个恶意的URL,并以JavaScript代码作为msg参数,那么Web浏览器就会像显示HTTP响应那样执行该代码,应用程序将受到基于DOM的XSS攻击。
修复建议
基于DOM的XSS是将用户可控的JavaScript数据输出到HTML页面中而产生的漏洞,为了避免基于DOM的XSS攻击,避免将用户控制的数据直接输出到DOM或脚本中执行。如果不能避免,则应进行严格的过滤。
高危:重定向
应用程序允许未验证的用户输入控制重定向中的URL,可能会导致攻击者发动钓鱼攻击。
**例1**:以下JavaScript代码从用户输入表单的dest参数中读取目的URL,然后在新窗口中打开。
dsturl = myForm.dsturl.value;
window.open(dsturl,"newwin");
假如攻击者可以控制这个表单,那么用户就有可能打开一个恶意站点。
**例2**:以下是Node.js可能出现的。
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.redirect(req.url);
});
与例1一样假如攻击者控制了这个url,那么就会导致用户可能打开恶意站点。
修复建议
js和Node.js都要避免采用不可信数据源的数据来构造重定向的URL,如果业务逻辑需要涉及到用户输入,那么就创建一份合法URL列表,用户只能从中进行选择,进行重定向操作。
**例如**:以下代码中,用户只能输入数字来选择URL。
dst = myForm.dst.value;
if(dst == "1"){
dsturl = "http://www.1.com"
}else if(dst == "2"){
dsturl = "http://www.2.com"
}else{
dsturl = "http://www.3.com"
}
window.open(dsturl,"newwin");
中危:拒绝服务:正则表达式
正则表达式引擎分成两类:一类称为DFA(确定性有限状态自动机),另一类称为NFA(非确定性有限状态自动机)。Java使用的是NFA正则引擎,使用正则式和文本比较,每碰到一个字符,就把它跟正则式比较,匹配就记下来,然后接着往下比较。一旦不匹配,就会后退直到回到上一次匹配的地方。而不可信赖数据被传递至应用程序并作为正则表达式使用,可能导致线程过度使用 CPU 资源,从而导致拒绝服务攻击。
**例如**:
(e+)+
([a-zA-Z]+)*
(a|aa)+
(a|a?)+
^(a+)
+$
以^(a+)+$
为例,该正则表达式对aaaax进行匹配时需要经历24次尝试失败才会确定这个字符串不符合要求,对aaaaaaaaax进行匹配时则需要经历210次尝试,随着长度的增加,尝试次数并不是线性增长而是指数型的增长,当长度达到20、30时就会大量消耗cpu导致拒绝服务。目前已知的正则表达式实现方法均无法避免这种漏洞,所有平台和语言都容易受到这种攻击。
Java对于正则的支持:
String类中,很多方法是专门用来支持正则:
- split()
- replaceAll()
- replaceFirst()
- matches()
- ……
java.util.regex包有两个类:Pattren类、Matcher类
修复建议:
不要将不可信赖数据作为正则表达式使用。
中危:访问权限修饰符控制
AccessibleObject类是Field、Method和Constructor对象的基类,能够允许反射对象修改访问权限修饰符,绕过由Java访问修饰符提供的访问控制检查。它让程序员能够更改私有字段或调用私有方法,这在通常情况下是不允许的。
**例如**:以下代码片段中,将Field将accessible
标记设置为true。
Class clazz = User.class;
Field field = clazz.getField("name");
field.setAccessible(true);
修复建议:
通过有权限的类更改访问权限修饰符,并确保修改的访问权限修饰符参数不能被攻击者控制。
中危:直接绑定敏感字段
目前大部分WEB框架支持将HTTP请求参数与类的属性相匹配的而生成一个对象。因此,攻击者能够将值放入HTTP请求参数中从而绑定系统对象。
**例如**:在以下代码片段中, Spring MVC可以将 HTTP请求参数绑定到 User属性:
@RequestMapping("/login" )
public String login(User user) {
}
其中,User 类定义为:
public class User {
private String username;
private String address;
private int age;
private boolean admin;
}
当Spring MVC未配置为禁止绑定敏感属性,则攻击者可能会通过发送以下使普通用户变为管理员。
name=张三&address=北京&age=22&admin=true
修复建议:
当程序将非将HTTP请求参数直接绑定给对象时,应该要控制绑定到对象的属性,防止暴露敏感属性。
**例1**:在以下代码片段中,在 Spring MVC(3.0版本至最新)禁止绑定敏感属性。
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields(new String[]{"admin"});
}
@RequestMapping("/login" )
public String login(User user) {
}
**例2**:在 Spring MVC(2.X版本)禁止绑定敏感属性。
@Override
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
binder.setDisallowedFields(new String[]{"admin"});
}
在使用@RequestBody
注释参数的 Spring MVC应用程序中,绑定过程由HttpMessageConverter
进行处理,这些实例使用Jackson和JAXB等库将 HTTP请求参数转换为Java对象。这些库提供了注释来控制应允许或禁止的字段。例如对于Jackson,可以使用@JsonIgnore
注释禁止将某个字段绑定到请求。
**例3**:在以下代码片段中,Jackson禁止绑定敏感属性。
@RequestMapping(value="/add/user", method=RequestMethod.POST, consumes="text/html")
public void addEmployee(@RequestBody User user){
}
public class User {
private String username;
private String address;
@JsonIgnore
private boolean admin;
private int age;
}
同理,Jackson还可以使用@JsonIgnoreProperties、@JsonIgnoreTyp和 @JsonInclude
等注解告诉框架忽略这些属性,使用JAXB使用@XmlAccessorType、@XmlAttribute、@XmlElement和 @XmlTransient
等注解告诉框架忽略这些属性,然后使用@XmlAttribute和@XmlElement
等注解选择应绑定的字段。
**例4**:在以下代码片段中,Jackson使用@XmlAttribute
选择要绑定的字段。
@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
public class User {
private String username;
private String address;
@JsonIgnore
private boolean admin;
private int age;
@XmlAttribute
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@XmlAttribute
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
private boolean isAdmin() {
return admin;
}
private void setAdmin(boolean admin) {
this.admin = admin;
}
}
**例5**:在以下代码片段中,在Struts可以将某个属性的setter
方法设置为私有从而禁止绑定敏感属性。
private boolean admin;
private void setAdmin(boolean admin) {
this.admin = admin;
}
还有另一种方法是使用将 HTTP请求参数绑定到仅含有 Web表单或 API中定义的属性DTO对象中,再将其映射到User中,防止敏感字段暴露。
低危:拒绝服务:解析Double类型数据
程序调用Double的解析方法时,可能导致线程被挂起。java.lang.Double.parseDouble()
方法解析位于2(-1022) - 2(-1075)到2(-1022) - 2(-1076)范围内的任何数字时可能导致线程被挂起,攻击者可以故意触发该漏洞执行拒绝服务攻击。该漏洞在java6 update24或更高版本中进行了修复。
**例如**:下面代码片段中,使用了易受攻击的方法。
Double d = Double.parseDouble(request.getParameter("d"));
攻击者可发送d参数值位于该范围(例如0.0222507385850720119e-00306
)内的请求,致使程序在处理该请求时被挂起。
修复建议:
修复该缺陷的方式如下:
-
验证传递给parseDouble数据的合法性。
-
升级JDK版本到6 Update 24或更高版本。
低危:有风险的资源使用
拒绝服务是攻击者通过消耗应用资源,以致程序崩溃使得其他用户无法继续正常使用的一种攻击方式。
**例1**:下面代码片段中,解压文件前,未检查文件大小,攻击者可以通过提供一个超大文件来占用系统的计算资源从而实施DOS攻击。
static final int SIZE= 512;
public static void unZip(BufferedInputStream bin){
BufferedOutputStream bop= null;
ZipInputStream zi = new ZipInputStream(bin);
ZipEntry zentry;
while ((zentry= zi.getNextEntry()) != null) {
int count;
byte data[] = new byte[SIZE];
FileOutputStream fos = new FileOutputStream(zentry.getName());
bop= new BufferedOutputStream(fos, SIZE);
while ((count = zi.read(data, 0, SIZE)) != -1) {
bop.write(data, 0, count);
}
bop.flush();
bop.close();
}
zi.close();
}
**例2**:下面使用了waitFor
方法,意味着直到该进程结束才能继续执行后续代码,不正确的处理输入输出流有可能发生死锁,导致程序持续浪费资源甚至崩溃。
process.waitFor();
修复建议:
拒绝服务攻击是一种滥用资源性的攻击。从代码角度来考虑,对于涉及到需要占用系统资源的外部数据而言,代码逻辑中应该包含严格校验,防止无限制的输入。另外,谨慎使用线程阻塞的API,防止浪费系统资源或发生系统崩溃。
**例如**:下面代码片段中,对解压文件进行验证,超过50M,将抛出异常。
static final int MAX= 0x3200000; // 50MB
// write the files to the disk, but only if file is not insanely big
if (entry.getSize() > MAX) {
throw new IllegalStateException("File to be unzipped is huge.");
}
if (entry.getSize() == -1) {
throw new IllegalStateException("File to be unzipped might be huge.");
}
FileOutputStream fos = new FileOutputStream(entry.getName());
bop = new BufferedOutputStream(fos, SIZE);
while ((count = zis.read(data, 0, SIZE)) != -1) {
bop.write(data, 0, count);
}
低危:数据跨越信任边界
数据跨越信任边界是指,数据从一个不可信赖域存储到一个可信赖域导致程序错误信赖未验证的数据。
**例如**:下面代码片段中将用户输入的数据name
存储到Http Session中。
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("userName");
HttpSession sess = req.getSession();
sess.setAttribute("user", name);
}
修复建议:
数据跨越信任边界时需要进行合理的验证,保证信赖域中数据是安全的。
低危:文件上传
允许用户上传文件可能导致危险的文件或代码被注入,并在服务器执行。
**例如**:
<input type="file">
修复建议:
-
如果应用程序不需要文件上传功能,应该禁用文件上传功能。
-
如果应用程序需要文件上传功能,可以参考以下建议:
文件上传的目录设置为不可执行。
采用白名单方式判断文件类型。在判断文件类型时,可采用MIME Type、 后缀检查等方式。
使用随机数改写文件名和文件路径。 如果应用程序采用随机数改写了文件名和路径,可以很大程度上增加攻击的成本。
对于图片的处理,可以采用压缩函数或者resize函数,在处理图片的同时,破坏图片中可能包含的脚本代码。
代码注入
中危:HTTP响应截断
程序从一个不可信赖的数据源获取数据,未进行验证就置于HTTP头文件中发给用户,可能会导致HTTP响应截断攻击。
**例如**:下列代码片段中,程序从HTTP请求中获取author
的值,并将其设置到HTTP响应文件的cookie中。
String author = request.getParameter(AUTHOR_PARAM);
Cookie cookie = new Cookie("author", author);
cookie.setMaxAge(cookieExpiration);
response.addCookie(cookie);
如果请求中提交的是一个Jane Smith
字符串,那么包含该cookie的HTTP响应可能表现为以下形式:
HTTP/1.1 200 OK
…
Set-Cookie: author=Jane Smith
那么如果攻击者提交的是一个恶意字符串,比如
Wiley Hacker\r\nHTTP/1.1 200 OK\r\n...
那么HTTP响应就会被分割成以下形式的两个响应:
HTTP/1.1 200 OK
…
Set-Cookie: author=Wiley Hacker
HTTP/1.1 200 OK
这样第二个响应已完全由攻击者控制,攻击者可以用所需的头文件和正文内容构建该响应实施攻击。
修复建议
防止HTTP响应截断攻击的最安全的方法是创建一份安全字符白名单,只接受完全由这些受认可的字符组成的输入出现在HTTP响应头文件中。
**例如**:以下代码片段中,验证了author
的值是否由标准的字母数字字符组成。
String author = request.getParameter(AUTHOR_PARAM);
if (Pattern.matches("[0-9A-Za-z]+", author)) {
Cookie cookie = new Cookie("author", author);
cookie.setMaxAge(cookieExpiration);
response.addCookie(cookie);
}
中危:有风险的SQL查询:MyBatis
SQL注入是一种数据库攻击手段。攻击者通过向应用程序提交恶意代码来改变原SQL语句的含义,进而执行任意SQL命令,达到入侵数据库乃至操作系统的目的。在Mybatis Mapper Xml中,#
变量名称创建参数化查询SQL语句,不会导致SQL注入。而$
变量名称直接使用SQL指令,而$
变量名称直接使用SQL指令,将会存在一定风险,当SQL指令所需的数据来源于不可信赖的数据源时,可能会导致SQL注入。
**例如**:以下代码片段采用$
变量名称动态地构造并执行了SQL查询。
<!--select user information by name-->
<select id="queryByUserName" resultMap="userResultMap" parameterType="String">
select * from db_user where user_name=${username}
</select>
如果攻击者能够替代username中的任意字符串,它们可以使用下面的关于username的字符串进行SQL注入。
validuser' OR '1'='1
当其注入到命令时,命令就会变成:
select \* from db\_user where user\_name ='validuser' OR '1'='1'
即使所输入字符串不是来源于不可信赖的数据源,程序仍然存在着一定风险。
修复建议:
造成SQL注入攻击的根本原因在于攻击者可以改变SQL查询的上下文,使程序员原本要作为数据解析的数值,被篡改为命令了。防止SQL注入的方法如下:
-
正确使用参数化API进行SQL查询。
-
如果构造SQL指令时需要动态加入约束条件,可以通过创建一份合法字符串列表,使其对应于可能要加入到SQL指令中的不同元素,来避免SQL注入攻击。
**例如**:以下代码片段采用#
变量名称,创建参数化查询SQL语句。
<!--select user information by name-->
<select id="queryByUserName" resultMap="userResultMap" parameterType="String">
select * from db_user where user_name=#{username}
</select>
中危:公式注入
微软公司的Excel或 Apache OpenOffice的Calc等电子表格都支持公式运算,如果攻击者控制了这类表格的数据,系统可能会导致任意命令执行或泄漏敏感信息。
**例如**:下面代码片段中,使用未经检查的数据来生成 CSV,并通过Spring控制器响应。
@RequestMapping(value = "/download/count.csv")
public ResponseEntity<String> service(String username) {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", "application/csv; charset=utf-8");
responseHeaders.add("Content-Disposition", "attachment;filename=count.csv");
String data = getCSVDataForUserName(username);
return new ResponseEntity<>(data, responseHeaders, HttpStatus.OK);
}
当data中被注入:=cmd|'/C calc.exe'!Z0
时,当用户打开此电子表格, Windows中的计算器将在其系统上运行。
修复建议
防止公式注入的方法如下:
-
程序对非受信的用户输入数据进行净化,删除不安全的字符。
-
创建一份安全字符串列表,限制用户只能输入该列表中的数据。
中危:资源注入
使用用户输入控制资源标识符,借此攻击者可以访问或修改其他受保护的系统资源。当满足以下两个条件时,就会发生资源注入:
-
攻击者可以指定已使用的标识符来访问系统资源。例如:攻击者能够指定用来连接到网络资源的端口号。
-
攻击者可以通过指定特定资源来获取某种权限,而这种权限在一般情况下是不可能获得的。例如:程序可能会允许攻击者把敏感信息传输到第三方服务器。
**例1**:下面的代码片段从HTTP请求获取端口号,并使用此端口号创建一个套接字,而不进行任何验证。使用代理的用户可以修改此端口并获得与服务器的直接连接。
String port = request.getParameter("port");
ServerSocket serverSocket = new ServerSocket(port);
Socket socket = serverSocket.accept();
这种利用用户输入影响的资源可能存在风险。
**例2**:下面的代码利用WebView的File域协议读取任意可读文件或受害应用的私有文件。
WebView webView=(WebView) findViewById(R.id.webView);
String url= getIntent().getData().toString();
webView.loadUrl(url);
查看hosts文件:
adb shell am start -n com.mytest/.MainActivity -d file:///system/etc/hosts
查看应用私有文件:
adb shell am start -n /data/data/com.cn.test/databases/user.db
修复建议:
为了避免资源注入漏洞攻击,可以采用黑名单或白名单策略。黑名单会有选择地拒绝或避免潜在的危险字符。但是,任何这样一份黑名单都不可能是完整的,而且将随着时间的推移而过时。比较好的方法是创建白名单,允许其中的字符出现在资源名称中,且只接受完全由这些被认可的字符所组成的输入。
中危:HTTP响应截断
程序从一个不可信赖的数据源获取数据,未进行验证就置于HTTP头中发给用户,可能会导致HTTP响应截断攻击。
**例如**:下列代码片段中,程序从HTTP请求中获取author的值,并将其设置到HTTP请求头的cookie中。
insecureInfo = document.URL;
document.cookie = "author=" + insecureInfo + ";expires="+ cookieExpiration;
author是来自用户输入,未经任何处理就存入了cookie中,使应用有可能受到cookie篡改的攻击。如果服务端解析了cookie,则可以造成其他攻击。
修复建议:
创建一份安全字符白名单,只接受完全由这些受认可的字符组成的输入出现在HTTP头中。
**例如**:以下代码片段中,验证了author的值是否由标准的字母数字字符组成。
insecureInfo = document.URL;
var exp = /[0-9A-Za-z]/;
var objExp = new RegExp(exp);
if (objExp.test(insecureInfo)==true) {
document.cookie = "author=" + insecureInfo + ";expires="+ cookieExpiration;
}
低危:有风险的反序列化
某些协议如RMI和JMX会在传输层后台使用Java序列化。当远程调用这些方法时,应用程序会在服务器上对参数进行反序列化,此时攻击者可以注入恶意对象。
**例1**:下列代码是public的RMI接口的示例,其包含的方法具有一个或多个参数。
public interface MyWebService extends Remote {
public Object doSomething (Object arg0) throws RemoteException;
}
**例2**:JMX MBeans也使用 Java序列化传输调用参数。在下面的示例中:MyManagedBean中的方法将会暴露给客户端。
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("com.example:type=MyManagedBean");
MyManagedBean myManagedBean = new MyManagedBean();
mBeanServer.registerMBean(myManagedBean, name);
而一些类如RedisTemplate使用默认的序列化器存在着不足,攻击者可以注入恶意对象,从而使反序列化产生非预期的对象,非预期的对象有可能带来意想不到的结果。
**例3**:下列代码是直接返回RedisTemplate,未对RedisTemplate设置序列化器。
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
修复建议:
检查程序逻辑,确定是否需要使用RMI和JMX等存在反序列化问题的协议,对于一些序列化器要设定可选择策略,以避免产生Java反序列化漏洞。
**例如**:以下代码对RedisTemplate设置了序列化器Jackson2JsonRedisSerializer
。
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
template.setValueSerializer(jacksonSeial);
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jacksonSeial);
template.afterPropertiesSet();
return template;
}
低危:JavaScript劫持:易受攻击的框架
使用JavaScript传送敏感数据的应用程序可能会存在JavaScript劫持的漏洞,该漏洞允许未经授权的攻击者从一个易受攻击的应用程序中读取机密数据。
JavaScript劫持可以简单的理解为模拟授权的用户,窃取用户在服务器上的信息。Web浏览器使用同源策略(Same Origin Policy),以保护用户免受恶意网站的攻击。同源策略规定:如果要使用JavaScript来访问某个网页的内容的话,则JavaScript和网页必须都来源于相同的域。若不采取同源策略,恶意网站便可以使用受害者的客户端凭证来运行 JavaScript,从其他网站加载的敏感信息,并对这些信息进行处理,然后将其返回给攻击者。
使用JSON传输数据的JavaScript应用更容易受到JavaScript劫持攻击。由于JSON使用JavaScript语法的子集表示对象、数组、简单值,JSON本身可以被当做JavaScript执行,且使用*eval*()函数对JSON数据结构求值早被认为是存在风险的,其可能执行恶意代码。
应该注意的是,某些过时的第三方Javascript框架也可能存在上述的问题,使用它们将导致Javascript劫持。
修复建议:
不使用过时的有风险的Javascript框架。
API误用
中危:不安全的框架绑定
目前大部分WEB框架支持将HTTP请求参数与类的属性相匹配的而生成一个对象。因此,攻击者能够将值放入HTTP请求参数中从而绑定系统对象。
**例如**:在以下代码片段中, Spring MVC可以将 HTTP请求参数绑定到User所有属性。
@RequestMapping("/login" )
public String login(User user) {
}
其中,User 类定义为:
public class User {
private String username;
private String address;
private boolean admin;
private int age;
}
修复建议:
当程序将非将HTTP请求参数直接绑定给对象时,应该要控制绑定到对象的属性,防止暴露所有属性。
在Spring MVC中,可以配置绑定器使用setAllowedFields
和setDisallowedFields
方法控制属性绑定过程以控制应绑定的属性。
**例1**:在以下代码片段中,在 Spring MVC(3.0版本至最新)通过setDisallowedFields
方法禁止绑定敏感属性。
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields(new String[]{"admin"});
}
@RequestMapping("/login" )
public String login(User user) {
}
**例2**:在 Spring MVC(2.X版本)通过setDisallowedFields
方法禁止绑定敏感属性。
@Override
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
binder.setDisallowedFields(new String[]{"admin"});
}
而在使用 @RequestBody
注释参数的 Spring MVC应用程序中,绑定过程由HttpMessageConverter进行处理,这些实例使用Jackson和JAXB等库将 HTTP请求参数转换为Java对象。这些库提供了注释来控制应允许或禁止的字段。例如对于Jackson,可以使用@JsonIgnore
注释禁止将某个字段绑定到请求。
**例3**:在以下代码片段中,Jackson禁止绑定敏感属性。
@RequestMapping(value="/add/user", method=RequestMethod.POST, consumes="text/html")
public void addEmployee(@RequestBody User user){
}
public class User {
private String username;
private String address;
@JsonIgnore
private boolean admin;
private int age;
}
同理,Jackson还可以使用@JsonIgnoreProperties、@JsonIgnoreType和 @JsonInclude
等注解告诉框架忽略这些属性,使用JAXB使用@XmlAccessorType、@XmlAttribute、@XmlElement和 @XmlTransient
等注解告诉框架忽略这些属性,然后使用@XmlAttribute和@XmlElement
等注解选择应绑定的字段。
**例4**:在以下代码片段中,Jackson使用@XmlAttribute
选择要绑定的字段。
@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
public class User {
private String username;
private String address;
@JsonIgnore
private boolean admin;
private int age;
@XmlAttribute
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@XmlAttribute
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
private boolean isAdmin() {
return admin;
}
private void setAdmin(boolean admin) {
this.admin = admin;
}
}
在Struts 1和 2 中,如果某个属性不应绑定到请求,则应将其 setter
方法设置为私有即可。
**例5**:在以下代码片段中,在Struts可以将某个属性的setter
方法设置为私有从而禁止绑定敏感属性。
private boolean admin;
private void setAdmin(boolean admin) {
this.admin = admin;
}
还有另一种方法是使用将 HTTP请求参数绑定到仅含有Web表单或API中定义的属性DTO对象中,再将其映射到User中,防止敏感字段暴露。
低危:忽略返回值
一般在开发过程中,程序员凭借经验可以断言某些用户事件或条件,例如某个函数永远不会执行失败。但是,攻击者往往会故意触发一些异常情况,导致冲破了程序员的断言,给系统带来了不稳定性,利用不正确的行为触发出发程序的漏洞。**例如**:程序调用删除权限函数,但不检查返回值判断是否成功删除权限,则程序将继续以更高权限运行。
**例如**:在下面的代码片段中,程序没有对read()
返回值做判断。
int bytesToRead = 1024;
byte[] byteArray = new byte[bytesToRead];
streamFileInput = new FileInputStream("C:\\file.txt");
streamFileInput.read(byteArray);
修复建议:
程序应检查方法的返回值,确保方法返回的是期望的数据,再进行下一步操作。
低危:HTTP响应完成后继续操作输出流
转发HttpServletRequest
、重定向HttpServletResponse
或刷新servlet的输出流缓冲区会导致提交相关的数据流,程序后续再执行到缓冲区重置或数据流提交,将会抛出IllegalStateException
异常。
此外,servlets允许使用ServletOutputStream
或PrintWriter
将数据写入响应数据流。如果在调用getOutputStream()
之后再调用getWriter()
或者反向调用,会导致抛出IllegalStateException
异常,使其中断响应。
**例如**:在下面代码片段中,会在servlet的输出流缓冲区刷新之后进行重定向,会抛出IllegalStateException
异常。
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
OutputStream os = response.getOutputStream();
os.flush();
os.close();
response.sendRedirect("http://www.codesafe.cn");
}
修复建议:
避免HTTP响应完成后继续操作输出流,应注意:
-
提交servlet的输出流之后,不要重置数据流缓冲区或执行重新提交该数据流。
-
避免在调用
getOutputStream()
之后调用getWriter()
,或者在调用getWriter()
后调用getOutputStream()
。
低危:缺少对方法返回值的null检查
程序没有对有可能返回null的方法返回值进行检查,可能会导致NullPointException。
**例如**:下列代码片段中,未对getenv()
方法的返回值data进行null检查,可能会抛出NullPointException。
String data = System.getenv("ADD");
if (data.equalsIgnoreCase("XXX") ){
...
}
修复建议:
程序应对可能返回null的方法的返回值进行检查,避免产生NullPointException。
密码管理
中危:不安全的随机数
Java API中提供了java.util.Random
类实现PRNG()
,该PRNG是可移植和可重复的,如果两个java.util.Random
类的实例使用相同的种子,会在所有Java实现中生成相同的数值序列。
**例如**:下面代码片段中,使用了java.util.Random
类,该类对每一个指定的种子值生成同一个序列。
import java.util.Random;
public static void main (String args[]) {
for (int i = 0; i < 10; i++) {
Random random = new Random(123456);
int number = random.nextInt(21);
}
}
修复建议:
在安全性要求较高的应用中,应使用更安全的随机数生成器,如java.security.SecureRandom
类。
**例如**:下面代码片段中,使用java.security.SecureRandom
来生成更安全的随机数。
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
public static void main (String args[]) {
try {
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
for (int i = 0; i < 10; i++) {
int number = random.nextInt(21);
}
} catch (NoSuchAlgorithmException nsae) {
}
}
中危:空密码
程序中使用了空的密码值,系统安全性将会受到威胁。
**例如**:下列代码中采用了空的密码。
public Connection getConnection(){
String url = "localhost";
String name = "admin";
String password = "";
}
修复建议:
程序中不应使用空的密码值,程序所需密码应从配置文件中获取加密的密码值。
**例如**:下列代码中从配置文件中获取经过加密的密码值并解密使用。
public Connection getConnection(){
String url = EncryptUtil.decrypt(PropertiesUtil.get("connection.url"));
String name = EncryptUtil.decrypt(PropertiesUtil.get("connection.username"));
String password = EncryptUtil.decrypt(PropertiesUtil.get("connection.password"));
}
中危:硬编码密码
程序中采用硬编码方式处理密码,一方面会降低系统安全性,另一方面不易于程序维护。
**例如**:下列代码中采用硬编码方式处理密码。
public class ConnectionConfig{
String url = "localhost";
String name = "admin";
String password = "123456";
}
修复建议:
程序中不应对密码进行硬编码,可以使用配置文件或数据库存储的方式来存储系统所需的数据;并且录入数据时,还可以在对敏感数据做加密处理之后再进行数据的录入。
**例如**:下列代码中从配置文件中获取经过加密的密码值并解密使用。
public class ConnectionConfig{
String url = EncryptUtil.decrypt(PropertiesUtil.get("connection.url"));
String name = EncryptUtil.decrypt(PropertiesUtil.get("connection.username"));
String password = EncryptUtil.decrypt(PropertiesUtil.get("connection.password"));
}
中危:弱加密
在安全性要求较高的系统中,使用不安全的加密算法(如DES、RC4、RC5等),将无法保证敏感数据的保密性。
**例如**:下面代码片段中,采用DES对数据进行加密。
BufferedReader bufread2 = null;
InputStreamReader inread2 = null;
try {
inread2 = new InputStreamReader(System.in);
bufread2 = new BufferedReader(inread2);
String str = bufread2.readLine();
/* FLAW: Insecure cryptographic algorithm (DES) */
Cipher des = Cipher.getInstance("DES");
SecretKey key = KeyGenerator.getInstance("DES").generateKey();
des.init(Cipher.ENCRYPT_MODE, key);
byte[] enc_str = des.doFinal(str.getBytes());
IO.writeLine(IO.toHex(enc_str));
} catch(IOException e) {
log_bsnk.warning("Error reading from console");
} finally{
}
修复建议:
在安全性要求较高的系统中,建议应使用安全的加密算法(如AES、RSA)对敏感数据进行加密。
**例如**:下面代码片段中,使用AES取代DES保证数据完整性。
BufferedReader bufread2 = null;
InputStreamReader inread2 = null;
try {
inread2 = new InputStreamReader(System.in);
bufread2 = new BufferedReader(inread2);
String str = bufread2.readLine();
/* FIX: Secure cryptographic algorithm (AES) */
Cipher aes = Cipher.getInstance("AES");
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(128);
SecretKey key = kg.generateKey();
aes.init(Cipher.ENCRYPT_MODE, key);
byte[] enc_str = aes.doFinal(str.getBytes());
IO.writeLine(IO.toHex(enc_str));
} catch(IOException e) {
log_gsnk.warning("Error reading from console");
} finally{
}
中危:配置文件中的明文密码
配置文件中采用明文存储密码,所有能够访问该文件的人都能访问该密码,将会降低系统安全性。
**例如**:下列配置文件中采用明文存储密码。
jdbc.username=user
jdbc.password=123456
修复建议:
即使不能阻止应用程序被那些可以访问配置文件的攻击者入侵,也可以通过加密密码提升攻击者入侵难度,故配置文件中的密码应进行加密存储。
**例如**:下列配置文件中采用jasypt加密的密码存储密码。
jdbc.username=user
jdbc.password=ENC(AaDVxaWVcgnN4lZswvK46QQkaxCfD7Xa)
中危:硬编码加密密钥
当程序中使用硬编码加密密钥时,所有项目开发人员都可以查看该密钥,甚至如果攻击者可以获取到程序class文件,可以通过反编译得到密钥,硬编码加密密钥会大大降低系统安全性。
**例如**:下列代码使用硬编码加密密钥执行AES加密。
private static String encryptionKey = "dfashsdsdfsdgagascv";
byte[] keyBytes = encryptionKey.getBytes();
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher encryptCipher = Cipher.getInstance("AES");
encryptCipher.init(Cipher.ENCRYPT_MODE, key);
修复建议:
程序应采用不小于8个字节的随机生成的字符串作为密钥。
**例如**:以下代码使用KeyGenerator来生成密钥。
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128, new SecureRandom(password.getBytes()));
SecretKey secretKey = kgen.generateKey();
byte[] keyBytes = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher encryptCipher = Cipher.getInstance("AES");
encryptCipher.init(Cipher.ENCRYPT_MODE, key);
低危:注释中的密码
应用程序注释中保留密码等敏感信息,将使敏感信息对任何能够获取到该文件的人员可见。
修复建议:
应用程序注释中不应保留密码等敏感信息。
低危:不安全的哈希算法
在安全性要求较高的系统中,不应使用被业界公认的不安全的哈希算法(如MD2、MD4、MD5、SHA、SHA1等)来保证数据的完整性。
**例如**:下面代码片段中,采用MD5算法来保证数据的完整性。
byte[] b = str.getBytes();
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
md.update(b);
}catch (NoSuchAlgorithmException e){
}
修复建议:
在安全性要求较高的系统中,应采用散列值>=224比特的SHA系列算法(如SHA-224、SHA-256、SHA-384和SHA-512)来保证敏感数据的完整性。
**例如**:下面代码片段中,使用SHA-256算法取代MD5算法保证数据完整性。
byte[] b = str.getBytes();
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
md.update(b);
} catch (NoSuchAlgorithmException e) {
...
}
低危:弱加密:不安全的块密码加密模式
块密码又称为分组加密,一次加密明文中的一个块。将明文按一定的位长分组,明文组经过加密运算得到密文组,密文组经过解密运算(加密运算的逆运算),还原成明文组。这种加密算法共有四种操作模式用于描述如何重复地应用密码的单块操作来安全的转换大于块的数据量,分别是电子代码(ECB)、密码块链(CBC)、密码反馈(CFB)以及输出反馈(OFB)。其中ECB模式下相同的明文块总是会得到相同的密文,故不能抵挡回放攻击,而CBC模式则没有这个缺陷。
**例如**:以下代码将AES密码用于ECB模式。
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, createSecretKey(seed));
修复建议:
加密大于块的数据时,应该避免使用ECB模式,因为ECB模式下相同的明文块总是会得到相同的密文,有回放攻击的风险。CBC模式可以避免回放攻击,但是其效率较低,并且在和SSL一起使用时会造成严重风险。故可以改用CCM(Counter with CBC-MAC)
模式,如果更注重性能,在可用的情况下则使用GCM(Galois/Counter)
模式。
**例如**:以下代码将AES密码用于GCM模式。
GCMParameterSpec s = new GCMParameterSpec(tLen, src);
Cipher cipher = Cipher.getInstance("AES/OFB8/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, createSecretKey(seed), s);
低危:不安全的随机数
JavaScript的Math.random()
实现PRNG(伪随机数序列发生器),该PRNG是可移植和可重复的,因此如果两个
Math.random()
使用相同的种子,会生成相同的数值序列。
**例如**:下面的代码使用
Math.random()
创建的Token很容易被猜到。
function getToken (){
var token = Math.random();
return token;
}
修复建议:
JavaScript在安全性要求较高的环境中生成随机数,常规的建议是使用 Mozilla API 中的window.crypto.getRandomValues()
函数,但这种方法受限于浏览器的兼容性,只在较新的版本(Chrome>=11.0, Firefox>=21, Edge, IE>=11, Safari>=3.1)可以使用。如果考虑到旧版本浏览器,则应在javascript之外处理PRNG功能。
**例如**:缺陷描述中的例子可以改写为。
function getToken (){
var array = new Uint32Array(1);
window.crypto.getRandomValues(array);
var token = array[0];
return token;
}
低危:注释中的密码
应用程序注释中保留密码等敏感信息,将使敏感信息对任何能够获取到该文件的人员可见。
**例如**:
//password:123
修复建议:
应用程序注释中不应保留密码等敏感信息。
低危:不安全的提交
使用HTTP GET的方式提交敏感信息如密码,可能造成密码被显示,记录等。
**例如**:以下示例使用HTTP GET的方式提交表单中的密码。
<form method="get">
密码: <input type="password" >
<input type="submit" name="" value="提交" >
</form>
修复建议:
避免使用HTTP GET的方式提交敏感数据,应使用HTTP POST传输敏感数据。
**例1**:以下示例通过HTTP POST的方式提交用户的密码。
<form method="post">
密码: <input type="password" >
<input type="submit" name="" value="提交" >
</form>
HTML5新增了一项功能,可以将formmethod属性作为 submit 和 image 输入标签一部分的功能,并且该属性值会覆盖相应form标签中 method 属性值。
**例2**:以下示例用户使用HTTP POST的方式提交密码,是由submit输入标签 formmethod 的属性值所指定。
<form method="get">
密码: <input type="password" >
<input type="submit" name="" value="提交" formmethod="post">
</form>
注意,如果将formmethod的值设为get,form的method的无论为什么,都会通过HTTP GET的方式提交表单。
资源管理
中危:数据库访问控制
程序未进行恰当的访问控制,执行了一个包含用户控制主键的SQL语句,可能会导致攻击者访问未经授权的记录。
**例如**:下面代码片段中的SQL语句用于查询与指定标识符相匹配的清单。
id = Integer.decode(request.getParameter("invoiceID"));
String query = "SELECT * FROM invoices WHERE id = ?";
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setInt(1, id);
ResultSet results = stmt.execute();
在上面代码中,攻击者可以通过为invoiceID
设置不同的值,获取所需的任何清单信息。
修复建议:
任何情况下都不允许用户在没有取得相应权限的情况下获取或修改数据库中的记录。可以通过把当前被授权的用户名作为查询语句的一部分来实现。
**例如**:下面代码片段中,通过把当前被授权的用户名作为查询语句的一部分来限制用户对清单的访问。
userName = ctx.getAuthenticatedUserName();
id = Integer.decode(request.getParameter("invoiceID"));
String query = "SELECT * FROM invoices WHERE id = ? AND user = ?";
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setString(1, id);
stmt.setString(2, userName);
ResultSet results = stmt.execute();
中危:资源未释放:流
程序创建或分配流资源后,不进行合理释放,将会降低系统性能。攻击者可能会通过耗尽资源池的方式发起拒绝服务攻击。
**例如**:在下面Java方法中,创建I/O流对象后未进行合理释放,程序依靠Java虚拟机的垃圾回收机制释放I/O流资源,事实上,程序不能确定何时调用虚拟机的finalize()
方法。在繁忙的程序环境下,可能导致Java虚拟机不能有效的使用I/O对象。
public void processFile(String filePath){
try {
FileInputStream fis = new FileInputStream(filePath);
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
String line="";
while((line=br.readLine())!=null){
processLine(line);
}
} catch (FileNotFoundException e) {
log(e);
} catch (IOException e){
log(e);
}
}
程序不应依赖于Java虚拟机的finalize()
方法来自动回收流资源,而需要手动在finally代码块中进行流资源的释放。
**例如**:下面代码片段中,在finally代码块中对流资源进行了合理的释放。
public void processFile(String filePath){
FileInputStream fis = null;
InputStreamReader isr = null;
BufferedReader br = null;
try {
fis = new FileInputStream(filePath);
isr = new InputStreamReader(fis);
br = new BufferedReader(isr);
String line="";
while((line=br.readLine())!=null){
//processLine(line);
}
} catch (FileNotFoundException e) {
//log(e);
} catch (IOException e){
//log(e);
}finally{
if(br!=null){
try {
br.close();
} catch (IOException e) {
//log(e);
}
}
if(isr!=null){
try {
isr.close();
} catch (IOException e) {
//log(e);
}
}
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
//log(e);
}
}
}
}
中危:使用不安全的target blank
在 <a>
标签中使用target属性,值设置为\_blank
攻击者会针对window.opener
API进行恶意行为的攻击,有可能导致钓鱼安全漏洞问题。
**例如**:以下示例使用的targer
属性,但是没有设置rel
属性。
<a href="www.example.com" target="_blank"/>
修复建议:
建议使用target="\_blank"
时,配合使用rel="noopenner noreferrer"
。
**例如**:配合使用rel
属性。
<a href="www.example.com" target="_blank" rel="noopenner noreferrer"/>
代码质量
中危:使用==或!=比较基本数据类型的包装类
不能直接使用==
或!=
操作符来比较的两个基本数据类型的包装类型的值,因为这些操作符比较的是对象的引用而不是对象的值。
不过由于Java的缓存机制,所以如果基本类型的包装类是一个整数且在-128和127之间,或是布尔类型true或false,或者是’\u0000’和’\u007f’之间的字符文本,可以使用==
或!=
进行比较。也就是说,如果使用了基本类型的包装类型(除去Boolean或Byte),则会缓存或记住一个值区间。对于值区间内的值,使用==
或!=
会返回正确的值,而对于值区间外的值,将返回对象地址的比较结果。
**例如**:在这个不符合规范的代码示例中:使用==
操作符来比较两个Integer对象的值。然而,这个==
操作比较的是对象的引用,而不是对象的值。
public class Wrapper {
public static void main(String[] args){
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 1000;
Integer i4 = 1000;
System.out.println(i1 == i2);
System.out.println(i1 != i2);
System.out.println(i3 == i4);
System.out.println(i3 != i4);
}
}
Integer类只能保证缓存介入-128~127的整型数值,当使用相等操作符的时候,这会导致在这个范围之外的等价数值的比较是不相等的。比如,在那些不缓存任何值的JVM虚拟机中,运行程序会产生以下结果:
true
false
false
true
修复建议:
不能直接使用==
或!=
操作符来比较的两个基本数据类型的包装类的值,因为这些操作符比较的是对象的引用而不是对象的值。
符合规范的方案使用equals()
而不是==
操作符来比较两个对象的值。这个程序在所有的平台运行时都会打印true、false、true、false的结果,这符合预期。
**例如**:以下代码使用equals()
方法比较两个Integer对象的值。
public class Wrapper {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 1000;
Integer i4 = 1000;
System.out.println(i1.equals(i2));
System.out.println(!i1.equals(i2));
System.out.println(i3.equals(i4));
System.out.println(!i3.equals(i4));
}
}
中危:比较Locale相关的数据未指定适当的Locale
在比较数据时,如果可能与Locale设置相关,则应指定相应的Locale。
**例1**:下面代码示例中:使用了Locale相关的String.toUpperCase()
将字符转换为大写字符。在英文Locale中,会将title
转换为TITLE
;在土耳其Locale中,会将title
转换为T?TLE
,其中的?
是拉丁字母的I
。
"title".toUpperCase();
"TITLE".toLowerCase();
修复建议:
在比较数据时,如果可能与Locale设置相关,则应指定相应的Locale。
**例1**:下面代码示例将Locale设置为英文,从而避免产生意外的问题。
"title".toUpperCase(Locale.ENGLISH);
"TITLE".toLowerCase(Locale.ENGLISH);
**例2**:可以在对字符串处理前,将默认的Locale设置为English。
Locale.setDefault(Locale.ENGLISH);
"title".toUpperCase();
"TITLE".toLowerCase();
中危:null引用
程序间接引用了可能为null的变量,从而引发空指针异常。
**例如**:下面代码片段中,在使用变量data之前没有判断它是否为null。
Data data = null
data.setId(id);
修复建议:
程序在间接引用可能为null的对象之前应对其进行判断。
**例如**:下面代码片段中,程序data设置为null,使用之前进行判断是否为null。
Data data = null
if(data != null){
data.setId(id);
}
中危:系统信息泄露:Session传递
在localStorage和sessionStorage之间传输值会不知不觉地暴露敏感信息。
实现了Web Storage的HTML5浏览器提供了localStorage和sessionStorage两个对象用于存储数据。 localStorage和sessionStorage的区别在于存储的作用域和有效期不同。两者的作用域都限定在文档源级别,sessionStorage还被限定在窗口中。localStorage存储的数据是永久性的,除非Web应用刻意删除这些存储的数据,或者用户通过浏览器配置来删除,否则数据将一直存储在用户的电脑上。sessionStorage在当前页面实例和当前浏览器会话期间为页面提供存储,一旦窗口或者标签页被永久关闭,所有通过sessionStorage存储的数据会被删除。将数据在localStorage和sessionStorage之间传输,会将浏览器生命周期以内的数据和本地永久数据互相暴露。
**例如**:以下代码中,为了免重复输入,开发人员将用户的手机号存储在sessionStorage对象中。但是,在存储用户信息到localStorage时,又错误的将手机号码一并存了进去。这样将导致敏感信息被存储到localStorage。
sessionStorage.setItem("phone", phone_number);
var userInfo = {};
var userInfo["phone"] = sessionStorage.getItem("phone");
localStorage.setItem("phone", userInfo);
修复建议:
不要将敏感数据存储在localStorage和sessionStorage中。将数据从一种存储格式迁移至另一种存储格式时,需要考虑迁移对于数据的隐私性和依赖该数据的业务逻辑的影响。
低危:JavaEE程序:直接使用线程
JAVA EE标准禁止在某些环境下使用Web应用程序中的线程管理,因为此时使用线程管理非常容易出错。线程管理起来很困难,可能还会以不可预知的方式干扰应用程序容器。即使容器没有受到干扰,线程管理通常还会导致各种难以发现的错误,如死锁、竞争条件及其他同步错误等。
**例如**:下面代码片段中,在Servlet的doGet()
方法中,直接创建了一个线程对象。
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// Perform servlet tasks.
// Create a new thread to handle background processing.
Runnable r = new Runnable() {
public void run() {
// Process and store request statistics.
...
}
};
new Thread(r).start();
}
修复建议:
建议使用框架(如EJB、Spring等)提供的线程管理功能来替代直接使用操作线程。
低危:JavaEE程序:遗留的调试代码
应用程序中的测试代码会建立一些意想不到的入口,这些入口可能会被攻击者作为“后门”进行利用
**例如**:JAVA EE程序中的main()
方法。
public static void main(String[] args) {
...
}
修复建议:
系统在发布之前应删除测试代码。
低危:日志记录:使用系统输出流
使用System.out
或System.err
进行程序日志输出,会导致程序的运行状况难以监控。
**例如**:下列代码中使用System.out
进行程序日志输出
System.out.println(log);
修复建议:
建议使用日志记录工具代替System.out
或System.err
记录程序日志。
**例如**:下列代码中使用Log4j工具类进行程序日志输出
logger.info(log);
低危:使用==或!=比较字符串
程序中使用==
或!=
比较两个字符串是否相等,其实比较的是两个对象在内存中的地址值,而不是字符串的值。
**例如**:下面代码片段中,将request中获取的参数值与"admin"使用==
做比较,dosomething()
方法将永远不会被执行。
String username = (String)request.getAttribute("usernamae");
if(username=="admin"){
dosomething();
}
修复建议:
程序中应采用equals()
方法来对字符串进行比较,而不是通过==或!=运算符的方式来操作。
低危:硬编码文件分隔符
路径分割符号问题,不同的操作系统不同。在程序中不要硬性编码与平台相关的任何常量,比如行分隔符,文件分隔符,路径分隔符等等。例如文件分隔符,Windows系统使用"“或”/“,而UNIX系统则使用”/"。应用程序需要在不同的平台上运行时,使用硬编码文件分隔符会导致应用程序逻辑执行错误,并有可能导致拒绝服务。
**例如**:以下代码使用硬编码文件分隔符来打开文件:
File file = new File(dirName + "\\" + fileName);
修复建议:
不应使用硬编码文件分隔符,而应使用语言库提供的独立于平台的API,如java.io.File.separator
,也可以通过使用java.lang.System.getProperty("file.separator")
来获取。
**例如**:针对示例代码的修改方法是:
File file = new File(dirName + File.separator + fileName);
低危:使用浮点数进行精确计算
Java中浮点数采用的是IEEE 754标准,所以在精确计算中使用浮点类型会发生精度缺失从而产生不正确的数值。
**例如**:下面代码输出为false和0.060000000000000005
public static void main(String[] args){
double a = 0.05 + 0.01;
System.out.println(a == 0.06);
System.out.println(a);
}
修复建议:
程序中避免使用浮点数进行精确计算,可以考虑采用整数类型或用于精确表达小数的BigDecimal类型替代。
低危:系统信息泄露:内部
通过系统输出流(非标准错误流)打印或日志功能将系统数据或调试信息输出到本地文件或屏幕时,发生内部信息泄露。
**例如**:下面代码片段中,通过日志对象输出异常的堆栈信息,攻击者可能会利用这些堆栈信息制定相应的攻击计划。
try{
...
}catch(Exception e){
logger.error("程序发生异常:", e);
}
修复建议:
程序不应通过系统输出流或程序日志将系统数据或调试信息输出显示。
低危:试图重写静态方法
静态方法无法覆盖,在子类中覆盖父类的静态方法,这样容易产生混淆,从而导致错误。
**例如**:下面代码示例中:子类不能覆盖父类的方法,choose("user")
和choose("admin")
都调用了父类中的方法。
class GrantAccess {
public static void displayAccountStatus() {
System.out.println("Account details for admin: XX");
}
}
class GrantUserAccess extends GrantAccess {
public static void displayAccountStatus() {
System.out.println("Account details for user: XX");
}
}
public class StatMethod {
public static void choose(String username) {
GrantAccess admin = new GrantAccess();
GrantAccess user = new GrantUserAccess();
if (username.equals("admin")) {
admin.displayAccountStatus();
} else {
user.displayAccountStatus();
}
}
public static void main(String[] args) {
choose("user"); // Account details for admin: XX
choose("admin"); // Account details for admin: XX
}
}
修复建议:
应该尽量避免静态方法的命名与超类中的命名相同,以避免产生混淆。在开发中,可以将静态方法放入单独的final类中, 因为final类不会创建子类,还可以创建私有的构造函数,以防止针对类的实例而非类来调用静态方法。
**例如**:下面代码示例中:去掉了static关键字,将displayAccountStatus()
方法声明为实例方法,choose("user")
和choose("admin")
调用实现了预期的程序逻辑。
class GrantAccess {
public void displayAccountStatus() {
System.out.println("Account details for admin: XX");
}
}
class GrantUserAccess extends GrantAccess {
@Override
public void displayAccountStatus() {
System.out.println("Account details for user: XX");
}
}
public class StatMethod {
public static void choose(String username) {
GrantAccess admin = new GrantAccess();
GrantAccess user = new GrantUserAccess();
if (username.equals("admin")) {
admin.displayAccountStatus();
} else {
user.displayAccountStatus();
}
}
public static void main(String[] args) {
choose("user"); // Account details for user: XX
choose("admin"); // Account details for admin: XX
}
}
低危:系统信息泄露:标准错误流
通过系统输出流(标准错误流)打印或日志功能将系统数据或调试信息输出到本地文件或屏幕时,在一些类似Eclipse的程序,为了让错误信息更加显眼,会将错误信息以红色文本的形式通过System.err输出到控制台上,更容易发生内部信息泄露。
**例如**:下面代码片段中,printStackTrace()
方法内部使用了标准错误流输出异常的堆栈信息,攻击者可能会利用这些堆栈信息制定相应的攻击计划。
try{
...
}catch(Exception e){
e.printStackTrace();
}
修复建议:
程序不应通过系统输出流或程序日志将系统数据或调试信息输出程序。
低危:泛化的捕获异常
使用一个catch块捕获泛化的异常类(如Exception),可能会混淆那些需要特殊处理的异常,或是捕获了不应在该程序点捕获的异常。捕获范围过大的异常与“Java的异常处理机制”是相违背的。
**例如**:下面的代码片段中,程序捕获了一个泛化的Exception异常。
try {
doExchange();
} catch(Exception e){
logger.error("doExchange failed", e);
}
修复建议:
不应捕获范围过大的异常类,比如Exception、SystemException或ApplicationException,除非是级别非常高的程序或线程。
低危:泛化的抛出异常
Java语言通过面向对象的异常处理机制来解决运行期间的错误,可以预防错误的程序代码或系统错误所造成的不可预期的结果发生。减少编程人员的工作,增加了程序的灵活性,增加程序的可读性和健壮性。如果程序只是抛出一个泛化的过于笼统的异常,不仅违反了该系统还会使调用者很难处理和修复发生的异常。
**例如**:下面代码片段中,程序抛出了一个Exception异常,而不是具体的FileNotFoundException子类型异常,这样使得调用者很难理解和处理可能发生的异常。
public void doExchange() throws Exception {
FileInputStream fis = new FileInputStream("filename.txt");
}
修复建议:
不应泛化的抛出Exception或Throwable类型的异常。
低危:表达式永远为false
表达式的计算结果永远为false,表明基于该表达式的代码快将永远不会被执行,是程序中的死代码或用于调试的代码,这些死代码会增加代码的阅读、理解和维护难度,甚至该段代码可能是调试过程中用来发现程序错误的,这样该代码块可能被攻击者利用。
**例如**:下列代码中,if的条件表达式为false,if代码块永远都不会被执行。
if (false){
//doSomething
}
修复建议:
程序异常中断了预期中的程序流程,应恰当地进行异常处理,要么从异常中恢复,将异常重新抛出,由下一个和try语句对应的catch段来处理,要么根据catch程序段的上下文抛出另一个合适的异常。
低危:表达式永远为true
表达式的计算结果永远为true,表明程序中基于该表达式的段代码永远被执行,因此没有必要进行条件判断。
**例如**:下列代码中,if的条件表达式为true,if代码块将永远被执行。
if (true){
//doSomething
}
修复建议:
检查程序逻辑,修改表达式判断条件。
低危:未使用的字段
未使用的变量在程序中没有起到任何作用,是程序中的死代码或者是被注释掉的调试代码。这些死代码会增加代码的阅读、理解和维护难度。
**例如**:如下代码中MyClass类中的字段userName
从未使用过
public class MyClass {
private String userName="admin";
public static void main(String[] args) {
System.out.println("UnusedField");
}
}
修复建议:
检查程序逻辑,如果确定程序中的死代码没有作用,应该将其删除。
低危:未使用的方法
未使用的方法在程序中没有起到任何作用,是程序中的死代码或者是被注释掉的调试代码。这些死代码会增加代码的阅读、理解和维护难度。
**例如**:如下代码中MyClass类中的unusedMethod()
方法从未使用过
public class MyClass {
private String unusedMethod(){
return "myclass";
}
public static void main(String[] args) {
System.out.println("UnusedMethod");
}
}
修复建议:
检查程序逻辑,如果确定程序中的死代码没有作用,应该将其删除。
低危:冗余的初始化
程序未使用变量的初始值,变量初始化后,被重新赋值或者转向作用域之外。
**例如**:下列程序声明一个变量total
并赋值,后续并未使用所赋的值,再次赋值。
double total = getTotal();
total = getOtherTotal();
修复建议:
为了使代码易于理解和维护,删除不必要的赋值。
低危:未使用的值
变量赋值后,程序未使用该变量。
**例如**:下列程序声明一个字符串并赋值,后续一直未使用
String username = "张三";
修复建议:
为了使代码易于理解和维护,删除不必要的赋值。
低危:冗余的null检查
程序对一个引用的变量进行了null检查,但是在这之前,程序中已经对该变量进行了null检查,或是对该变量进行了一些可能会引发null异常的操作(取值、转换)。或者程序对变量进行了null检查但是并未返回或者抛出异常,后续使用该变量仍然可能引发null异常。
**例1**:如下代码先调用字符串string的length()
方法,此时如果string如果为null就已经会抛出空指针的异常,所以之后对string是否为null的判断完全是多余的。
System.out.println(string.length());
## 最后
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**
**深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
**因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。**
![img](https://img-blog.csdnimg.cn/img_convert/b47b64500cd7a155938717e8fe58a222.png)
![img](https://img-blog.csdnimg.cn/img_convert/f3d90912b64e2715f5e4e67beeef7e67.png)
![img](https://img-blog.csdnimg.cn/img_convert/340ca55d99acd38dcd7884fd9ca20bdc.png)
![img](https://img-blog.csdnimg.cn/img_convert/5d087613f8cb7be5600d6ddc1f051145.png)
![img](https://img-blog.csdnimg.cn/img_convert/2e91cf3148bcba4fee502029ca210c3b.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点!真正的体系化!**
[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618653875)
**由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**
至该段代码可能是调试过程中用来发现程序错误的,这样该代码块可能被攻击者利用。
\*\*例如\*\*:下列代码中,if的条件表达式为false,if代码块永远都不会被执行。
if (false){
//doSomething
}
修复建议:
程序异常中断了预期中的程序流程,应恰当地进行异常处理,要么从异常中恢复,将异常重新抛出,由下一个和try语句对应的catch段来处理,要么根据catch程序段的上下文抛出另一个合适的异常。
### 低危:表达式永远为true
表达式的计算结果永远为true,表明程序中基于该表达式的段代码永远被执行,因此没有必要进行条件判断。
\*\*例如\*\*:下列代码中,if的条件表达式为true,if代码块将永远被执行。
if (true){
//doSomething
}
修复建议:
检查程序逻辑,修改表达式判断条件。
### 低危:未使用的字段
未使用的变量在程序中没有起到任何作用,是程序中的死代码或者是被注释掉的调试代码。这些死代码会增加代码的阅读、理解和维护难度。
\*\*例如\*\*:如下代码中MyClass类中的字段`userName`从未使用过
public class MyClass {
private String userName=“admin”;
public static void main(String[] args) {
System.out.println(“UnusedField”);
}
}
修复建议:
检查程序逻辑,如果确定程序中的死代码没有作用,应该将其删除。
### 低危:未使用的方法
未使用的方法在程序中没有起到任何作用,是程序中的死代码或者是被注释掉的调试代码。这些死代码会增加代码的阅读、理解和维护难度。
\*\*例如\*\*:如下代码中MyClass类中的`unusedMethod()`方法从未使用过
public class MyClass {
private String unusedMethod(){
return “myclass”;
}
public static void main(String[] args) {
System.out.println("UnusedMethod");
}
}
修复建议:
检查程序逻辑,如果确定程序中的死代码没有作用,应该将其删除。
### 低危:冗余的初始化
程序未使用变量的初始值,变量初始化后,被重新赋值或者转向作用域之外。
\*\*例如\*\*:下列程序声明一个变量`total`并赋值,后续并未使用所赋的值,再次赋值。
double total = getTotal();
total = getOtherTotal();
修复建议:
为了使代码易于理解和维护,删除不必要的赋值。
### 低危:未使用的值
变量赋值后,程序未使用该变量。
\*\*例如\*\*:下列程序声明一个字符串并赋值,后续一直未使用
String username = “张三”;
修复建议:
为了使代码易于理解和维护,删除不必要的赋值。
### 低危:冗余的null检查
程序对一个引用的变量进行了null检查,但是在这之前,程序中已经对该变量进行了null检查,或是对该变量进行了一些可能会引发null异常的操作(取值、转换)。或者程序对变量进行了null检查但是并未返回或者抛出异常,后续使用该变量仍然可能引发null异常。
\*\*例1\*\*:如下代码先调用字符串string的`length()`方法,此时如果string如果为null就已经会抛出空指针的异常,所以之后对string是否为null的判断完全是多余的。
System.out.println(string.length());
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
[外链图片转存中…(img-xRVOqBcz-1715881670315)]
[外链图片转存中…(img-teCOoP7m-1715881670316)]
[外链图片转存中…(img-wj2T04DE-1715881670316)]
[外链图片转存中…(img-147lJ3T5-1715881670316)]
[外链图片转存中…(img-zrAMhHaK-1715881670316)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点!真正的体系化!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!