WEB应用安全
开发 WEB 应用的同学应该不少,不知道大家平时开发中,有没有关注过应用开发的「安全问题」。
所谓安全问题,就是开发中不要留下漏洞,给入侵者破坏者机会。
比如,我们常挂在嘴边的安全问题有 「SQL 注入」,为了防止出现问题,一般都会使用预编译的 SQL,而不是拼接SQL,以此来保证安全。再比如,我们一个允许用户输入的文本框中,会禁用代码的渲染,比如像
<script> alert(“hello”);</script> 这种会被做为纯文本对待。
类似需要注意的问题有很多,如果我们不留意,就会容易造成安全问题。
不过有些「洞」,是你我开发中不曾留意的。或者说你只有先意识到有这一类问题需要注意,开发的时候才会把它堵上。
比如最近开发的一个WEB 应用,公司做安全的同事帮忙看了看,就给报了几个问题。
这里简单总结和各位一起分享下,欢迎留言讨论。
同事给指出的问题主要有两个:
- 不安全的 TRACE、OPTIONS 等方法未禁用
- 用户提交的信息里,可以包含第三方链接,容易出现钓鱼问题。
问题一:
通过TRACE、OPTIONS 方法,可以暴露不少关键信息。
我们知道,HTTP 请求里可以使用不同的 Method 来实现不同的请求功能, 比如常见的 GET/POST/PUT/DELETE 。除此之外还有一些,比如 TRACE/OPTIONS 等,每种请求简单描述如下。详细描述,可以查看RFC2616(https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html)
GET 请求指定的页面信息,并返回实体主体。
HEAD 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
PUT 从客户端向服务器传送的数据取代指定的文档的内容。
DELETE 请求服务器删除指定的页面。
CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
OPTIONS 允许客户端查看服务器的可用内容。
TRACE 回显服务器收到的请求,主要用于测试或诊断
从上面内容可以看出,TRACE 方法,OPTIONS方法,一般都会返回大量和实际业务处理无关的内容,如果开放会暴露许多服务器的关键信息,可能引出安全问题。
处理方式:
对于传统的web项目, 直接在web.xml 里增加安全限制「security-constraint」即可。
对于 Spring Boot应用,如果是使用 Tomcat做为容器,可以通过声明独立的配置,实现类似web.xml的效果:
web.xml的配置
<security-constraint>
<web-resource-collection>
<web-resource-name>restricted methods</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>HEAD</http-method>
<http-method>OPTIONS</http-method>
<http-method>TRACE</http-method>
</web-resource-collection>
<auth-constraint/>
</security-constraint>
Spring Boot 应用中的配置
@Configuration
public class TomcatCustomizer implements EmbeddedServletContainerCustomizer {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container;
tomcat.setSessionTimeout(8, TimeUnit.HOURS);
tomcat.addContextCustomizers(new ContextSecurityCustomizer());
}
private static class ContextSecurityCustomizer implements TomcatContextCustomizer {
@Override
public void customize(Context context) {
SecurityConstraint constraint = new SecurityConstraint();
SecurityCollection securityCollection = new SecurityCollection();
securityCollection.setName("restricted_methods");
securityCollection.addPattern("/*");
securityCollection.addOmittedMethod(HttpMethod.POST.toString());
securityCollection.addOmittedMethod(HttpMethod.GET.toString());
constraint.addCollection(securityCollection);
constraint.setAuthConstraint(true);
context.addConstraint(constraint);
}
}
}
如果使用 Jetty,配置方式也类似。
当然,如果想要做到不与容器依赖紧耦合,还可以自己定义Filter,然后在请求中判断请求头里的 Method 如果是不允许的方法,可以直接返回错误。
问题二:
具体是这样,我们的应用里允许用户上传图片。上传的图片会保存到对象存储中,然后返回一个URL,最后用户完整的信息里,是直接存储了一个URL。但是,我们看到,整个上传到提交的过程,并不是一步完成的,所以,用户可以自行构造这个提交的请求,并将图片的URL,替换成任意链接。
这个时候,在页面再次渲染时,是会请求图片URL来展示图片的。如果是一个恶意的第三方钓鱼链接,容易造成盗取用户信息等安全问题。
处理方式:
这个问题,一般在后端对提交的内容进行检验,如果有「不符合规定」的域名,就报错。一般是维护一个白名单,判断URL信息在不在白名单中。
判断一个URL的后缀,你一般用什么方式呢?正则表达式?字符串截取?
这里有个方式供参考,可以直接取出我们预期的域名后缀:
String host = "";
URL fullUrl = null;
try {
fullUrl = new URL("http://blog.tomcat8080.com/abc=def");
} catch (MalformedURLException e) {
e.printStackTrace();
}
host = fullUrl.getHost();
InternetDomainName domainName = InternetDomainName.from(host);
String top = domainName.topPrivateDomain().toString();
这些安全技术,仿佛是和WEB开发平行的一个领域,属于另一个工种。这些不属于架构,与设计模式无关,也不在数据库设计、虚拟机优化的范围内,但是,也是我们不得不去认真了解和重视的。可能不需要安全工程师那么深入,但一些常见的安全问题要了解,类似于一个 「CheatSheet」避免自己开发的时候暴露,保证我们开发的应用安全,不被攻击和泄露数据。对于WEB安全,我还是 too young了,欢迎各位留言述说自己的经验。