1. 引言:
本文主要聊一些web项目的安全问题,由于编程语言的差异性,本文更多的会讲一些思路性的上的问题和解决方法,另外提供一些spring方面的一些安全控件。
2. 事例及解决方法:
自我工作以来发生在我身上的项目安全问题一共有2起:
(1) 数据库攻击:
当时公司要求在公网上部署一套测试用的环境,我就把我当时的项目直接部署在了一个没有进行安全加固的服务器上,还没有改mysql的端口号,用的密码口令较弱,在部署好的第二天上班的时候发现都访问不了了,看了项目报错是mysql连接失败,在排查的时候发现数据被删除了,只留下一个勒索的数据库,由于是测试环境,所以当时也就解决比较轻松。
(2) 项目漏洞攻击:
公司在开发项目的时候,公司另外一个团队写的项目开发临近尾声,客户要求要看一下项目的进度,提前试用一下,公司直接把内网的测试服务器通过一条公网宽带给挂了出去,虽然做了一些保护,但是在用了一段时间后,客户要求进行一次安全测试,然后请合作公司进行了一次安全测试,然后给了一份测试报告,第二天公司服务器就被攻击了,经排查是远程文件通过目录穿透进行下载,将服务器的用户配置文件下载,从而进行连接服务器,造成了数据库被删的局面。
由于此项目是另外一个团队主导的,我也没有进行过多的干预,提交了漏洞报告,另外一个团队的负责人也没有在意,至此以后服务器隔几天就被工具一次,后来本人为了保护测试环境的数据库,给数据库做了主从,只要检测到主数据库有非正常操作被删除的情况就通过定时检测脚本将备数据库关掉,后来我也就离职了。
针对这两个问题,我们确实该重视,在我们部署的时候一定要注意数据库的密码的复杂程度,必要的时候我们可以关闭root用户的远程权限,用普通用户进行操作,给普通用户添加一些安全规则,如果有安全人员提供的专业的安全测试报告,一定要引起注意,及时复现并修复,网络攻击离我们很近,我们程序员要提高警惕。
3. 安全问题:
从大的方面来看项目安全从大的方面分为4大类,每一类出现问题都可能造成很严重的事故:操作系统安全性、容器安全性、项目安全性、中间件安全性、等。。。
今天我们着重聊项目安全问题(攻击原理)及解决方式,防护只能增加项目的安全等级,不能说绝对安全
我们常见的漏洞:
(1) 代码质量问题
项目建立较为完善的用户、角色、权限体系,大部分接口请求时一定要加token,增加安全等级
推荐使用:shiro框架和spring security+spring oauth2框架
Springbok+shiro框架:https://blog.csdn.net/qq_41015193/article/details/115072278
(2) 弱口令攻击:
攻击者会通过一些常用的密码,使用暴力破解工具,进行大规模尝试,直到找到正确的为止
解决:
- 前端再给后台发送的密码可以进行简单混淆(MD5加盐)
- 添加用户时,后台要将前端提供的密码进行二次混淆添加到数据库
- 给登录的接口添加验证码,降低被破解的可能性
验证码种类:
1.数字验证码(安全效率低)
2.滑块验证(建议使用)
3.手机验证码(需要短信服务器) - 添加同一用户验证错误超过N次,将用户锁定N分钟等安全规则
可以通过redis实现,将错误的用户信息保存到redis,再给reids添加过期时间 - 添加登录IP规则限制,某个ip错误登录几次,禁用一段时间
说明:
4,5有利有弊,根据实际情况慎重使用,正常用户也有可能误操作被禁用
(3) 明文传输攻击
攻击者通过拦截我们的请求包,篡改请求包中的内容,进行二次转发进行攻击
解决:
- 一般的解决方式就是采用https协议进行加密保护传输数据
Springboot配置https:https://blog.csdn.net/qq_41015193/article/details/114674278
(4) 信息泄露
- 我们上传到git公开的代码一定要注意配置文件中的内容,避免泄露重要的信息
- 我们常用的接口调试框架swagger,在生产环境一定要将其关闭
swagger详细配置关注:https://blog.csdn.net/qq_41015193/article/details/115914819
(5) CSRF攻击
攻击者盗用了你的身份,以你的名义发送恶意请求。
解决:
- Spring+security’框架中提供了防止crlf攻击的配置
- 重要的请求接口添加token
(6) 跨脚本攻击(xss攻击)
可通过滥用网站内的动态内容以执行外部代码实现,常见的可以在web端添加js语句进行执行
解决:
- 添加xss补丁(过滤请求中的特殊字符)
Java解决:通过拦截器进行过滤
添加依赖
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.9.2</version>
</dependency>
XssConfig
package com.byyj.exorcist.config;
import com.byyj.exorcist.xssFile.XssFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName: XssConfig
* @Auther: wh
* @Date: 2019/8/19 09:15
* @Description:
*/
@Configuration
public class XssConfig {
@Bean
public FilterRegistrationBean xssFilterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new XssFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.setEnabled(true);
filterRegistrationBean.addUrlPatterns("/*");
Map<String, String> initParameters = new HashMap();
initParameters.put("excludes", "/favicon.ico,/img/*,/js/*,/css/*");
initParameters.put("isIncludeRichText", "true");
filterRegistrationBean.setInitParameters(initParameters);
return filterRegistrationBean;
}
}
XssFilter
package com.byyj.exorcist.xssFile;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @ClassName: XssFilter
* @Auther: wh
* @Date: 2019/8/19 09:21
* @Description:
*/
public class XssFilter implements Filter {
private static boolean IS_INCLUDE_RICH_TEXT = false;
private List<String> excludes = new ArrayList<String>();
private String[] excludedUris;//不需要过滤的路径
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String isIncludeRichText = filterConfig.getInitParameter("isIncludeRichText");
if (StringUtils.isNotBlank(isIncludeRichText)) {
IS_INCLUDE_RICH_TEXT = BooleanUtils.toBoolean(isIncludeRichText);
}
String temp = filterConfig.getInitParameter("excludes");
if (null != temp) {
String[] url = temp.split(",");
for (int i = 0; url != null && i < url.length; i++) {
excludes.add(url[i]);
}
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (handleExcludeURL(request, response)) {
filterChain.doFilter(request, response);
return;
}
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request, IS_INCLUDE_RICH_TEXT);
String url = xssRequest.getServletPath();
if (isExcludedUri(url)) {
filterChain.doFilter(request, response);
} else {
filterChain.doFilter(xssRequest, response);
}
}
private boolean isExcludedUri(String uri) {
if (excludedUris == null || excludedUris.length <= 0) {
return false;
}
for (String ex : excludedUris) {
uri = uri.trim();
ex = ex.trim();
if (uri.toLowerCase().matches(ex.toLowerCase().replace("*", ".*"))) {
return true;
}
}
return false;
}
private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {
if (excludes == null || excludes.isEmpty()) {
return false;
}
String url = request.getServletPath();
for (String pattern : excludes) {
Pattern p = Pattern.compile("^" + pattern);
Matcher m = p.matcher(url);
if (m.find()) {
return true;
}
}
return false;
}
@Override
public void destroy() {
}
}
XssHttpServletRequestWrapper
package com.byyj.exorcist.xssFile;
import com.byyj.exorcist.util.JsoupUtil;
import org.apache.commons.lang.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
* @ClassName: XssHttpServletRequestWrapper
* @Auther: wh
* @Date: 2019/8/19 09:21
* @Description:
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
HttpServletRequest orgRequest = null;
private boolean isIncludeRichText = false;
public XssHttpServletRequestWrapper(HttpServletRequest request, boolean isIncludeRichText) {
super(request);
orgRequest = request;
this.isIncludeRichText = isIncludeRichText;
}
@Override
public String getParameter(String name) {
boolean flag = ("content".equals(name) || name.equals("WithHtml"));
if (flag && !isIncludeRichText) {
return super.getParameter(name);
}
name = JsoupUtil.clean(name);
String value = super.getParameter(name);
if (StringUtils.isNotBlank(value)) {
value = JsoupUtil.clean(value);
}
return value;
}
@Override
public String[] getParameterValues(String name) {
String[] arr = super.getParameterValues(name);
if (arr != null) {
for (int i = 0; i < arr.length; i++) {
arr[i] = JsoupUtil.clean(arr[i]);
}
}
return arr;
}
@Override
public String getHeader(String name) {
name = JsoupUtil.clean(name);
String value = super.getHeader(name);
if (StringUtils.isNotBlank(value)) {
JsoupUtil.clean(value);
}
return value;
}
public HttpServletRequest getOrgRequest() {
return orgRequest;
}
public static HttpServletRequest getOrgRequest(HttpServletRequest request) {
if (request instanceof XssHttpServletRequestWrapper) {
return ((XssHttpServletRequestWrapper) request).getOrgRequest();
}
return request;
}
}
JsoupUtil
package com.byyj.exorcist.util;
import org.apache.commons.lang.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;
/**
* @ClassName: JsoupUtil
* @Auther: wh
* @Date: 2019/8/19 09:36
* @Description:
*/
public class JsoupUtil {
private static final Whitelist whitelist = Whitelist.basicWithImages();
private static final Document.OutputSettings outputSettings = new Document.OutputSettings().prettyPrint(false);
static {
whitelist.addAttributes(":all", "style");
}
public static String clean(String content) {
if (StringUtils.isNotBlank(content)) {
content = content.trim();
}
return Jsoup.clean(content, "", whitelist, outputSettings);
}
}
(7) SQL注入攻击(比较常见)
sql注入只对sql语句的准备(编译)过程有破坏作用
解决:
- 多检查自己的代码,提高可阅读性和安全性(不懂得可以先看看mybait ${}和#{}的区别)
(8) 目录穿透攻击(文件下载漏洞):
攻击者可以对服务请求从而通过…/进行穿透目录,访问服务器上的文件,从而进行下载
解决:
- 将项目放到容器中进行执行
- 添加文件访问路径判断
(9) Shell反弹:
反弹shell(reverse shell),就是控制端监听在某TCP/UDP端口,被控端发起请求到该端口,并将其命令行的输入输出转到控制端
解决:
- 正确配置服务器的防火墙策略,如果是云服务器可以先将服务器进行简单的安全加固,然后配置相应的安全策略组,制定出入站规则
(10) webshell漏洞(文件上传漏洞):
攻击者通过上传一些脚本文件进行攻击
解决:
- 对上传的文件进行后缀名判断
- 对上传的文件进行魔数判断(速度慢,安全等级高)
还有很多攻击漏洞的方式,就不一一介绍的,上面的顺序没有轻重,希望大家都能写出健康高效的代码
4. 建议
建议大家关注一些网络安全知识的论坛,关注一些框架和中间件的安全漏洞,使用时避开有安全风险的版本,从而提高项目的安全等级
Nessus:系统漏洞扫描与分析软件
Nmap:黑客会利用nmap来搜集目标电脑的网络设定,从而计划攻击的方法。
SqlMap:sql注入攻击工具
AWVS:web安全漏洞扫描工具
以上的工具大家有兴趣的可以自行安装研究(建议kali系统安装,比较简单),有什么问题可以私信我,交流学习