高级java开发工程师也会犯得错误,你中枪了吗。

高级java开发工程师也会犯得错误,你中枪了吗。

写在前面的话:记录一下平常开发造成项目安全问题的漏洞,以免自己今后再犯错误。

1.SQL注入

SQL注入攻击即用户通过输入非法字符串,篡改原SQL语句的意图。SQL语句由用户输入条件动态组装,就存在此风险。
例如1. 屏蔽查询条件:

 String userName = ctx.getAuthenticatedUserName();
 String itemName = request.getParameter("itemName");
 String query = "SELECT * FROM items WHERE owner = '" 
    + userName + "' AND itemname = '"  
    + itemName + "'";
 ResultSet rs = stmt.execute(query);

如果输入的条件中为恒等式,就可能导致信息泄露,例如如果用户输入的userName参数值为“Juice’ OR ‘a’=‘a”,那么该查询语句的WHERE字句就失去了条件过滤的功能。
例如2. 篡改原SQL语句意图:
上面的例子中如果输入的userName参数值为“name’; DELETE FROM items; --”,那么原查询语句就会变成一条查询语句与一条删除操作语句,将导致在该查询动作之后,执行delete,删除items表中所有数据。

为避免SQL注入攻击,我们一般可以通过两种方式避免:

  1. 避免SQL语句的组装,使用“?”占位符的预编译SQL语句;
    …//使用jdbc时
    Connection conn = getConn();//获得连接
    String sql = “select id, username, password, role from user where id=?”; //执行sql前会预编译号该条语句,无论id输入什么字符都会被当做文本来处理
    PreparedStatement pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, id);
    ResultSet rs=pstmt.executeUpdate();

    …//使用mybatis框架时

    select id, username, password, role from user where id=#{}

  2. 如果无法避免SQL语句的组装,则应该对用户输入参数进行特殊字符处理。例如:
    String id = request.getParameter(“id”);
    id=id.replaceAll(“’”,”’’”)//将id中的单引号’替换成双引号’’
    id=id.replaceAll(“;”,””)//将id中的分号;去除
    String query = "SELECT * FROM user WHERE id =”+id

2. 资源未及时释放

系统中的一些关键资源是非常有限的,必须有效管理和使用才能保证系统的性能和稳定性,例如文件资源,网络连接资源,数据库连接资源,同步锁资源等。
资源释放需要彻底,否则可能出现内存泄露或资源无法回收的情况,例如在执行数据库操作时,我们首先获得数据库的Connection对象,然后基于此对象创建如Statement对象等数据库操作对象,那么在释放资源时,应该按创建对象的逆序关闭Statement对象和Connection对象,确保资源完全释放。
下图所示为出现该风险的问题代码:
在这里插入图片描述
inS为InputStream, 在finnally中对InputStream进行close时,本身可能会触发IOExceptions。 而一旦触发IOExceiponts,则之后的out(out为FileOutputStream)流将无法被成功释放资源。因此Foritfy报Unreleased Resource:Streams风险,提示可能无法正常释放FileOutputStream资源。
解决办法:
在这里插入图片描述
在这里插入图片描述
以上方案使用了一个助手函数safeClose,用以记录在尝试关闭fis流时可能发生的异常,catch住关闭流时可能引起的异常,也使后续的out流能够不会因为前面关闭流时出现异常而无法被执行到,导致后续的out流无法释放资源。

3. 空指针检查

空指针一般出现在程序员对对象的一个或多个假设前提下。如果程序明确将对象设置为 null,但稍后却间接引用该对象,则将出现空指针异常。
大部分空指针问题只会引起一般的软件可靠性问题,但如果攻击者能够故意触发空指针间接引用,攻击者就有可能利用引发的异常绕过安全逻辑,或致使应用程序泄漏调试信息,这些信息对于规划随后的攻击十分有用。所以在使用对象前,应该做非空检查,避免出现空指针异常。
问题举例:
Foo foo = null;
………………
foo=new Foo(parameters)
foo.setBar(var);
…….
以上代码中,new方法有可能失败,程序员间接引用foo的时候,foo有可能为null。故需要对foo进行检查。
解决办法:
Foo foo = null;
………………
if (foo != null){
foo.setBar(var);
}
…….
在所有初始化赋值为NULL的地方,正式引用前都需要做非空检查。

4. 文件分隔符

不同的操作系统使用不同的字符作为文件分隔符。 例如,Microsoft Windows 系统使用 “”,而 UNIX 系统则使用 “/”。 应用程序需要在不同的平台上运行时,使用硬编码文件分隔符会导致应用程序逻辑执行错误。
解决办法:
建议使用“File.separator”获得文件分隔符,避免硬编码。

5.跨站脚本攻击

产生场景:
1外部输入的数据未验证而直接页面输入
2数据库读取的数据未验证直接页面输入
例如,以下代码可从 HTTP servlet 请求中读取雇员 ID eid,并在 servlet 响应中将值显示给该用户。

String eid = request.getParameter("eid");
ServletOutputStream out = response.getOutputStream();
out.print("Employee ID: " + eid);
out.close();

如果 eid 只包含标准的字母或数字文本,这个例子中的代码就能正确运行。如果 eid 里有包含元字符或源代码中的值,那么 Web 浏览器就会像显示 HTTP 响应那样执行代码。
起初,这个例子似乎是不会轻易遭受攻击的。毕竟,有谁会输入导致恶意代码的 URL,并且还在自己的电脑上运行呢?真正的危险在于攻击者会创建恶意的 URL,然后采用电子邮件或者社会工程的欺骗手段诱使受害者访问此 URL 的链接。当受害者单击这个链接时,他们不知不觉地通过易受攻击的网络应用程序,将恶意内容带到了自己的电脑中。这种对易受攻击的 Web 应用程序进行盗取的机制通常被称为反射式 XSS。
针对漏洞产生的原因,解决方案如下:

  1. 在数据流出应用程序(输出验证)的前一刻对其进行验证。
  2. 加强一个应用程序现有的输入验证机制。
  3. 最安全的验证方式:创建一份安全字符白名单,允许其中的字符出现在HTTP内容中,只接受完全由这些经认可的字符组成的输入。
  4. 最灵活的验证方式:黑名单方法(对于web浏览器具有特殊含义的字符集,如“<”、“&”、“>”、“;”、“/”、“:”=”、“+”、“?”、“%”、“”等)这种方法在进行输入之前就选择性地拒绝或者避免了潜在的危险字符。
    例如:
    例:提供公共方法,结合实际业务需求,对输入源和输出源调用该方法进行特殊字符的过滤,主要是浏览器脚本可能包含的一些特殊字符。如图
    在这里插入图片描述
    在spring框架中,平台的所有controller都继承自基类BaseController,该类通过@InitBinder对返回值对特殊字符进行处理,从而避免XSS攻击。
/**
	 * 初始化数据绑定
	 * 1. 将所有传递进来的String进行HTML编码,防止XSS攻击
	 * 2. 将字段中Date类型转换为String类型
	 */
	@InitBinder
	protected void initBinder(WebDataBinder binder) {
		// String类型转换,将所有传递进来的String进行HTML编码,防止XSS攻击
		binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				setValue(text == null ? null : StringEscapeUtils.escapeHtml4(text.trim()));
			}
			@Override
			public String getAsText() {
				Object value = getValue();
				return value != null ? value.toString() : "";
			}
		});
		// Date 类型转换
		binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				setValue(DateUtils.parseDate(text));
			}
		});
	}

6.伪造日志攻击

日志是对系统行为的历史记录,作为系统排错和自动报警等的基础信息,如果如果日志输出内容包括用户输入信息,则存在伪造日志攻击的风险。
例如1:
攻击者提交字符串 “twenty-one%0a%0aINFO:+User+logged+out%3dbadguy”,并且该字符串被作为日志信息直接写到日志中,则日志中会记录以下条目:
INFO: Failed to parse val=twenty-one
INFO: User logged out=badguy
日志信息中就多了一条“INFO: User logged out=badguy”的日志。

通过这种方式,用户通过控制输入信息,伪造日志信息。如果系统的输出日志,将被自动处理、自动信息报警等,恶意用户可以通过该方式破坏日志文件,或者提供误报信息等;用户甚至可以篡改日志,隐藏恶意操作的痕迹,或者插入恶意代码,在日志文件被解析时执行。
解决办法:
对用户输入信息都需要进行特殊字符过滤再写入日志文件中,如’%0d’,’\r’,’\n’,’0a’

7.XML实体注入

XML注入攻击即如果攻击者能够写入原始 XML,则可以更改 XML 文档和消息的语义。 危害最轻的情况下,攻击者可能插入无关的标签,导致 XML 解析器抛出异常。更为严重的情况下,攻击者可以添加 XML 元素,更改 authentication 凭证或修改 XML 电子商务数据库中的价格。 还有些情况,XML injection 甚至可以导致 Cross-Site Scripting 或 Dynamic Code Evaluation。
例如1:
假设攻击者能够控制下列 XML 中的 shoes。

<order>
   <price>100.00</price>
   <item>shoes</item>
</order>

如果攻击者能够写入原始 XML,将XML文件内容修改为:

<order>
	<price>100.00</price>
	<item>shoes</item>
	<price>1.00</price>
	<item>shoes</item>
</order>

在标签中用户通过输入重复的price标签值和item标签值,覆盖前面的值,从而篡改了商品价格。
对xml实体进行解析式,使用安全配置的实例:
1.如果使用XML Factory时,必须配置如下属性:

factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

2.如果不需要内嵌文本形式的xml,可以使用如下属性:

factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

3.如果使用的是SAX解析,可以使用如下配置:

xmlInputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", false);
xmlInputFactory.setProperty("javax.xml.stream.supportDTD", false);

8.路径操纵攻击(Path Manipulation)

路径操纵即用户通过控制文件访问路径而进行非法访问或越权访问文件。例如:
String rName = request.getParameter(“reportName”);
File rFile = new File("/usr/local/apfr/reports/" + rName);

rFile.delete();
本段代码是期望用户在指定的目录下面选择删除自己希望删除的文件,并且系统对该操作时允许的。不过如果输入参数并不是系统期望的文件名,而是类似这样的输入:“…/…/tomcat/conf/server.xml”,从而导致应用程序删除它自己的配置文件。
解决办法:
1) 黑名单检查:检查路径中不能包含“…”以及“:”
2) 白名单检查:如检查文件类型,只允许特定后缀的文件名。

9.HTTP报文头控制攻击

HTTP 响应头文件中包含当前请求的响应状态,响应内容等,如果其中包含用户输入的未经验证的数据,那么可能会引发 cache-poisoning、cross-site scripting、cross-user defacement、page hijacking、cookie manipulation 或 open redirect风险。
例如:
Cookie cookie = new Cookie(“author”, author);
cookie.setMaxAge(cookieExpiration);
response.addCookie(cookie);
如果攻击者提交的是一个恶意字符串,比如 “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

实际请求响应头会被用户输入的响应头覆盖,达到欺骗客户端浏览器的作用,从而导致浏览器作出一些错误的有利于攻击者的操作,例如重定向等。
解决办法:
对所有涉及到用户输入的报文头参数都需要进行特殊字符过滤,如’\r’,’\n’

10.伪随机码

标准的java.util下的伪随机数生成器不能抵挡各种加密攻击。
在对安全性要求较高的环境中,使用一个能产生可预测数值的函数作为随机数据源,会产生不安全的随机数错误。
修改办法:
建议使用java.security.SecureRandom提供的方法来替代伪随机数生成方法。

11.隐私信息管理

系统应该对用户隐私信息做有效的管理和保护,不被盗取和破坏。如果对各种隐私信息处理不当,如客户密码或重要凭证号码等,会危及到用户的个人隐私,这是一种非法行为。例如:
………
UserInfo userInfo = new UserInfo();
String password=userInfo.getPassword();
log.info(“密码”+password)
………

为了有效管理用户的隐私信息,系统应遵循以下规则:

  1. 不必要的用户私人信息不要进入程序;
  2. 不要将用户的隐私数据写到外部介质,例如控制台、日志 ,不能以明文的形式传输用户的隐私信息。

12.命令注入攻击

命令注入攻击一般分两种途径:

  1. 通过修改程序所用的环境变量值,将待执行的命令指向另一个位置的同名命令;
  2. 控制待执行的命令,例如Java系统函数System.Runtime.getRuntime().exec(cmd)执行的字符串指令“cmd”中可以通过“&&”分割多条指令,并依次被执行。如果这个指令字符串有用户输入部分,则用户可以通过“&&”加入其他命令。
    例如1:

    String home = System.getProperty(“APPHOME”);
    String cmd = home + INITCMD;
    java.lang.Runtime.getRuntime().exec(cmd);

    本段代码根据系统属性APPHOME来决定命令所在目录,如果该系统属性能被攻击者控制,那么被执行的命令也可能不是期望的命令。
    例如2:

    String btype = request.getParameter(“backuptype”);
    String cmd = new String(“cmd.exe /K “c:\util\rmanDB.bat “+btype+”&&c:\utl\cleanup.bat””)
    System.Runtime.getRuntime().exec(cmd);

    本段代码接受用户输入的参数“backuptype”,但是没有做任何检查。命令中通过“&&”告诉“cmd.exe”顺序执行rmanDB.bat和cleanup.bat命令,如果用户输入的参数中包括以“&&”分割的别的命令,例如“&& del c:\dbms\.”,那么这个删除命令也会被执行。所以,为了避免命令注入攻击,我们应该对待执行命令的有效性做检查,包括命令路径和命令文件是否合法,以及执行的命令参数是否合法。
    修改办法:
    对输入的命令进行校验,如果输入的命令中含有命令分隔符,则定性为不安全的命令,则不予执行。比如命令中含有 “&&” “ ||” “ ;”

13.密码明文存储或硬编码

将明文密码直接写在配置文中会降低系统的安全性。例如将数据库帐号密码直接未经加密填写在properties文件:
jdbc.username=root
jdbc.password=root
修改办法:
将明文密码通过可逆算法加密后写入配置文件,如异或,base64算法,例如base64加密:
jdbc.username=root
jdbc.password=cm9vdA==
程序读密码后再进行解密使用,bases64需要依赖sun公司提供的jar包sun.misc.BASE64Encoder

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章