开发安全问题
1.XSS攻击
1.1什么是XSS攻击手段
XSS攻击使用Javascript脚本注入进行攻击
例如在提交表单后,展示到另一个页面,可能会受到XSS脚本注入,读取本地cookie远程 发送给黑客服务器端。
alert('sss')
window.location.href='http://www.itmayiedu.com';
对应html源代码: <script>alert('sss')</script>
1.2.如何防御XSS攻击
将脚本特殊字符,转换成html源代码进行展示。
步骤:编写过滤器拦截所有getParameter参数,重写httpservletwrapp方法
将参数特殊字符转换成html源代码保存.
// 重写HttpServletRequestWrapper
防止XSS攻击
publicclassXssHttpServletRequestWrapperextendsHttpServletRequestWrapper {
privateHttpServletRequest request;
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
this.request = request;
}
@Override
publicString getParameter(String name) {
// 过滤getParameter参数 检查是否有特殊字符
Stringvalue =super.getParameter(name);
System.out.println("value:" + value);
if(!StringUtils.isEmpty(value)) {
// 将中文转换为字符编码格式,将特殊字符变为html源代码保存
//这是common-lang依赖库的工具类
value= StringEscapeUtils.escapeHtml(value);
System.out.println("newValue:" + value);
}
returnvalue;
}
}
SpringBoot启动加上@ServletComponentScan
@SpringBootApplication
@ServletComponentScan
publicclassApp {
publicstaticvoidmain(String[] args) {
SpringApplication.run(App.class, args);
}
}
2.sql注入攻击
2.1什么是SQL注入
SQL注入:利用现有应用程序,将(恶意)的SQL命令注入到后台数据库执行一些恶意的工作。造成SQL注入的原因是因为程序没有有效过滤用户的输入,使攻击者成功的向服务器提交恶意的SQL查询代码,程序在接收后错误的将攻击者的输入作为查询语句的一部分执行,导致原始的查询逻辑被改变,额外的执行了攻击者精心构造的恶意代码
2.2SQL注入防攻击手段
不要使用拼接SQL语句方式、最好使用预编译方式,在mybatis编写sql语句的时候,最好使用#传参数方式,不要使用$传参数,因为$传参数方式,可能会受到sql语句攻击。
演示案例:
http://127.0.0.1:8080/login?userName='liusi'&password='123'
http://127.0.0.1:8080/login?userName='liusi'&password='123' or 1=1
整体意思为mybatis框架中配置文件mapper中一般采用#{}方式因为这种代表采用预编译方式,不管调用者传怎么样的值都会只当成内容
3.Http请求防盗链
3.1什么是防盗链
比如A网站有一张图片,被B网站直接通过img标签属性引入,直接盗用A网站图片展示。
防盗链
3.2如何实现防盗链(底层原理)
判断http请求头Referer域中的记录来源的值,如果和当前访问的域名不一致的情况下,说明该图片可能被其他服务器盗用。
3.3使用过滤器判断请求头Referer记录请求来源
@WebFilter(filterName = "imgFilter", urlPatterns = "/imgs/*")
publicclassImgFilterimplementsFilter {
@Value("${domain.name}")//获取域名
privateString domainName;
publicvoidinit(FilterConfig filterConfig)throwsServletException {
}
publicvoiddoFilter(ServletRequest request, ServletResponse response,
FilterChain chain)throwsIOException, ServletException {
HttpServletRequestreq = (HttpServletRequest) request;
Stringreferer = req.getHeader("Referer");
if(StringUtils.isEmpty(referer)) {//为空
request.getRequestDispatcher("/imgs/error.png").forward(request, response);
return;
}
Stringdomain = getDomain(referer);
if(!domain.equals(domainName)) {//和传过来的进行对比
request.getRequestDispatcher("/imgs/error.png").forward(request, response);
return;
}
chain.doFilter(request, response);//放行
}
/**
*获取url对应的域名
*
*@paramurl
*@return
*/
publicString getDomain(String url) {//只要域名,不要http以及双斜杠那些所以截取
Stringresult = "";
intj = 0, startIndex = 0, endIndex = 0;
for(inti = 0; i < url.length(); i++) {
if(url.charAt(i) == '/') {
j++;
if(j == 2)
startIndex = i;
elseif(j == 3)
endIndex = i;
}
}
result = url.substring(startIndex + 1, endIndex);
returnresult;
}
}
对于springboot项目记得启动类加上@ServletComponentScan
否则识别不了拦截器注解
现在比较高级的一般采用黑名单,白名单方式来设定是否可以访问资源(不仅图片)
黑名单,白名单
4.CSRF攻击
4.1CSRF攻击产生的原因
(Cross
SiteRequest Forgery, 跨站域请求伪造)是一种网络的攻击方式,它在 2007 年曾被列为互联网 20
大安全隐患之一,也被称为“OneClick Attack”或者Session
Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用也就是人们所知道的钓鱼网站。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,并且攻击方式几乎相左。XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。
简单说明即多次重复性发送请求
4.2如何解决CSRF以及防止伪造token
可以通过验证码,短信校验,以及人脸识别等技术,进而判断是真人操作,而不是机器人在模拟发送,nginx限流,配置黑名单,白名单,当然没有百分百能够完全保证没有问题
5.忘记密码漏洞
黑客使用抓包工具分析Http请求,在忘记密码找回时,需要发送一套短信验证码,如果验证码数字比较短的话,很容易使用暴力破解方式攻击破。
防御手段:
忘记密码验证码最好在6-8位。
一旦频繁调用接口验证时,应该使用图形验证码拦截,防止机器模拟。
使用黑名单和白名单机制,防御攻击。
如何防止密码被破解
6.上传文件漏洞
6.1漏洞描述
上传漏洞这个顾名思义,就是攻击者通过上传木马文件,直接得到WEBSHELL,危害等级超级高,现在的入侵中上传漏洞也是常见的漏洞。
导致该漏洞的原因在于代码作者没有对访客提交的数据进行检验或者过滤不严,可以直接提交修改过的数据绕过扩展名的检验。
6.2 漏洞危害
1)可以得到WEBSHELL
2)上传木马文件,可以导致系统瘫痪
原理和方式
例如上图,我知道别人项目上传的资源在upload下,那我上传一个jsp或者可以操作项目的文件,比如一个jsp,里面是删除资源的代码,我就可以在上传之后,直接访问upload里面上传的jsp就可以执行jsp操作完成里面的功能,甚至还有一些病毒等等
6.3修复方案
1)对文件格式限制,只允许某些格式上传
2)对文件格式进行校验,前端跟服务器都要进行校验(前端校验扩展名,服务器校验扩展名、Content_Type等)例如例如常见的class文件,exe文件,js文件,jsp文件等等都要进行限制上传,或者验证
3)将上传目录防止到项目工程目录之外,当做静态资源文件路径,并且对文件的权限进行设定,禁止文件下的执行权限。
另外如图也要注意
限制
6.4限定为图片,以后可以模拟以及百度限制为其他方案
/**
*文件上传
*/
protectedvoiddoPost(HttpServletRequest request,
HttpServletResponse response) {
Stringroot = request.getServletContext().getRealPath("/upload");
DiskFileItemFactoryfactory =newDiskFileItemFactory();
ServletFileUploadupload =newServletFileUpload(factory);
try{
Listlist = upload.parseRequest(request);
for(FileItem it : list) {
// 如果是file文件类型
if(!it.isFormField())
{
FileTypefileType=getFileType(it.getInputStream());
if(fileType ==null) {
// 非图片格式
response.getWriter().write("fail");
return;
}
StringimgValue = fileType.getValue();
System.out.println("imgValue:" + imgValue);
// 是图片格式
it.write(newFile(root + "/" + it.getName()));
response.getWriter().write("success");
}
}
}catch(Exception e) {
try{
response.getWriter().write("exception");
}catch(IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}
// 判断文件是图片格式
publicstaticFileType getFileType(InputStream is)throwsIOException {
byte[] src =newbyte[28];
is.read(src, 0, 28);
StringBuilderstringBuilder =newStringBuilder("");
if(src ==null|| src.length <= 0)
{
returnnull;
}
for(inti = 0; i < src.length; i++) {
intv = src[i] & 0xFF;
Stringhv= Integer.toHexString(v).toUpperCase();
if(hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
FileType[]fileTypes= FileType.values();
for(FileType fileType : fileTypes) {
if(stringBuilder.toString().startsWith(fileType.getValue()))
{
returnfileType;
}
}
returnnull;
}
7.API接口幂等性设计
7.1什么是幂等:保证数据的唯一性,不允许有重复
7.2出现场景:例如表单重复性提交,网络延迟重复提交
7.3解决方案
Token机制,防止页面重复提交
业务要求:页面的数据只能被点击提交一次
发生原因:由于重复点击或者网络重发,或者 nginx 重发等情况会导致数据被重复提交
解决办法:
集群环境:采用 token 加 redis(redis 单线程的,处理需要排队)
单 JVM 环境:采用 token 加 redis 或token 加 jvm 内存
处理流程:
数据提交前要向服务的申请token,token 放到 redis 或 jvm 内存,token 有效时间
提交后后台校验 token,同时删除 token,生成新的 token 返回
token 特点:要申请,一次有效性,可以限流
基于Token方式防止API接口幂等
客户端每次在调用接口的时候,需要在请求头中,传递令牌参数,每次令牌只能用一次。一旦使用之后,就会被删除,这样可以有效防止重复提交。
简单代码,可以自己封装
BaseRedisService封装Redis
@Component
publicclassBaseRedisService{
@Autowired
privateStringRedisTemplate stringRedisTemplate;
publicvoidsetString(String key, Object data, Long timeout) {
if(datainstanceofString) {
Stringvalue = (String) data;
stringRedisTemplate.opsForValue().set(key, value);
}
if(timeout !=null) {
stringRedisTemplate.expire(key, timeout,
TimeUnit.SECONDS);
}
}
publicObject getString(String key) {
returnstringRedisTemplate.opsForValue().get(key);
}
publicvoiddelKey(String key) {
stringRedisTemplate.delete(key);
}
}
RedisTokenUtils工具类
@Component
publicclassRedisTokenUtils {
privatelongtimeout = 60 * 60;
@Autowired
privateBaseRedisService baseRedisService;
// 将token存入在redis
publicStringgetToken() {
Stringtoken = "token"+ System.currentTimeMillis();
baseRedisService.setString(token, token, timeout);
returntoken;
}
publicbooleanfindToken(String tokenKey) {
Stringtoken = (String) baseRedisService.getString(tokenKey);
if(StringUtils.isEmpty(token)) {
returnfalse;
}
// token
获取成功后 删除对应tokenMapstoken
baseRedisService.delKey(token);
returntrue;
}
}
可以在controller中调用工具类验证token,如果成功则进行表单提交,提交完就删除当前token,当网络延迟或者重复提交表单时由于之前已经删除了token再去判断此时的token值则不存在了所以就不会再次提交,这样就保证了不会重复性提交(重点:redis保证了安全性,单线程机制,如果网络延迟还没有执行到删除token值,他会等待上一个提交结束后在完成下一个提交,形成队列模式,所以不用怕token没有删除,如果确实为了以防万一,可以可方法上面添加synchronized加锁机制)