Java开发常见漏洞及解决方案
创始人
2024-05-10 15:41:21
0

文章目录

      • 1.健康监控Actuator暴露端口
      • 2.SSRF漏洞攻击
      • 3.富文本XSS攻击
      • 4.暴力破解短信验证码登录
      • 5.恶意短信轰炸骚扰用户
      • 6.低版本Fastjson导致RCE漏洞
      • 7.SQL注入漏洞
      • 8.水平越权信息泄露
      • 9.权限绕过漏洞

1.健康监控Actuator暴露端口

​ Actuator是Springboot提供的用来对应用系统进行自省和监控的功能模块,借助于Actuator开发者可以很方便的对应用系统的某些监控指标进行查看,统计等。

​ 在springboot项目中引入Actuator:

org.springframework.bootspring-boot-starter-actuator

漏洞问题:

​ 系统启动后,我们通过访问Actuator暴露的一些端口,可以获取到信息,这样也就存在信息暴露的风险,,以下是一些暴露的端口。

(1)http://ip+port/actuator

​ 通过http://ip+port/actuator可以访问到系统暴露的端点集合,每一个端点都可以进行调用,例http://127.0.0.1:9876/api/actuator,查询结果如下:
在这里插入图片描述
(2)http://ip+port/actuator/env

​ 通过http://ip+port/actuator/env可以访问到系统配置环境信息,包含properties中数据库用户名、密码等敏感信息,例http://127.0.0.1:9876/api/actuator/env,查询结果如下:

(3)http://ip+port/actuator/heapdump

​ 通过http://ip+port/actuator/heapdump可以下载堆转储文件,在浏览器中执行此请求,会下载一份文件:

​ 然后利用heapdump_tool工具可以从此headdump文件中提取出数据库等敏感信息:

解决方案

1.在properties中禁止某些端口的访问,例如:

management:endpoints:web:exposure:#禁止env、heapdump、threaddump接口的访问exclude:- env- heapdump- threaddump

此时再访问禁止的端口,显示404:

2.在properties中完全禁用actuator,例如:

management:server:port: -1

此时再访问actuator的端口,都显示404:

2.SSRF漏洞攻击

​ 在项目开发中,有些需求是前端配置ip或者url信息,服务端通过此配置信息进行远程调用。例如系统提供前端页面配置数据库的链接信息:

系统支持前端页面配置获取第三方token的链接信息,通过服务端去调用获取的场景:

漏洞问题:

​ 可以对外网服务器所在的内网、本地进行端口扫描,获取一些服务的banner信息;攻击运行在内网或本地的应用程序(比如溢出);利用ftp、file协议读取本地文件等。

(1)扫描内网服务器开放的端口

​ 数据库链接配置:通过配置内网服务器的ip、端口信息,点击测试连接数据库,当此ip不存在或者端口不存在时,接口返回的报错速度会非常快;当访问到存活ip的存活端口,那么返回的时间会有非常大的差别。

不存在的端口,执行时间:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yctSFGca-1673429158542)(E:\我的工作文档\服务端文档\技术文档\java开发漏洞\微信截图_20230109112422.png)]
存活ip、存活端口,执行时间:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j8l5kSdz-1673429158543)(E:\我的工作文档\服务端文档\技术文档\java开发漏洞\微信截图_20230109112440.png)]
(2)访问内网文件

​ 服务器调用第三方系统配置链接信息:当接口地址配置ftp或file时,可以访问到服务器的文件信息。

解决方案:

1.禁止host配置为内网地址;

2.统一错误信息,避免用户可以根据错误信息来判断远端服务端的端口状态;

3.禁用不需要的协议,仅允许http和https请求,防止ftp、telnet、file、ldap等协议引起的问题;

4.对url进行非法特殊字符校验,防止CRLF(换行符\r\n)攻击;

5.对确实需要访问的内网ip、端口进行白名单的配置。

(1)校验url方法:

//校验url信息
private void checkUrl(String url) {try {URL url = new URL(url);//ip或者域名String host = url.getHost();//协议,http/httpsString protocol = url.getProtocol();//判断是否存在非法特殊字符,防止CRLF攻击String docode = URLDecoder.decode(tokenUrl,"utf-8");if(docode.contains("\r\n")){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "url包含特殊字符,请确保参数的准确性");}//只允许http、https协议if(!(protocol.equals("https") || protocol.equals("http"))){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "协议只支持http/https,请确保参数的准确性");}//获取端口int port = url.getPort();if(-1 == port){if(protocol.equals("https")){port = 443;} else {port = 80;}}//获取系统配置的白名单ip集合Map ignoredMap = apiWhiteListConfig.getIgnoredMap();//校验host和port是否合法IpUtil.checkHostAndPort(host, String.valueOf(port),ignoredMap);} catch (MalformedURLException e) {throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "url解析异常,请确保参数的准确性");} catch (UnsupportedEncodingException e){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "url编码异常,请确保参数的准确性");}}

(2)配置内网白名单

properties配置文件中配置白名单集合:

#出于屏蔽SSRF攻击,给出放开的内网白名单及端口号
inner:ignored:url-list:- 10.233.144.x:6777- 10.111.144.x:8888- 10.234.100.x:9088

加载配置的白名单集合:

@Configuration
@ConfigurationProperties(prefix="inner.ignored")
@Data
public class ApiWhiteListConfig {//忽略的内网访问集合,key:ip,value:portprivate Map ignoredMap = new HashMap();private List urlList;@PostConstructpublic void init(){if(null != urlList && urlList.size() > 0){for(int i = 0;i < urlList.size();i++){String str = urlList.get(i);String[] arr = str.split(":");if(null != arr && arr.length == 2){ignoredMap.put(arr[0],arr[1]);}}}}
}

(3)校验host和port是否合法

//ip工具
public class IpUtil {static List ipFilterRegexList = new ArrayList<>();//指定内网ip的匹配正则集合static {Set ipFilter = new HashSet();ipFilter.add("^10\\.(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])"+ "\\.(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])" + "\\.(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])$");ipFilter.add("^172\\.(1[6789]|2[0-9]|3[01])\\" + ".(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])\\"+ ".(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])$");ipFilter.add("^192\\.168\\.(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])\\"+ ".(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])$");ipFilter.add("127.0.0.1");ipFilter.add("0.0.0.0");ipFilter.add("localhost");for (String reg : ipFilter) {ipFilterRegexList.add(Pattern.compile(reg));}}/*** @Description: 判断ip是否属于内网* @Author: qingyun* @Param: [ip]* @Return: boolean* @Date: 2023/1/6 15:50*/public static boolean ipIsInner(String ip) {for (Pattern reg : ipFilterRegexList) {Matcher matcher = reg.matcher(ip);if (matcher.find()){return true;}}return false;}/*** @Description: 根据主机host,获取对应的ip* @Author: qingyun* @Param: [hostName]* @Return: java.lang.String[]* @Date: 2023/1/6 15:44*/public static String[] getAllIPByHostName(String hostName){try {InetAddress[] addrs=InetAddress.getAllByName(hostName);//根据域名创建主机地址对象String[] ips=new String[addrs.length];for (int i = 0; i < addrs.length; i++) {ips[i] = addrs[i].getHostAddress();//获取主机IP地址}return ips;} catch (UnknownHostException e) {return null;}}/*** @Description: 校验host和port是否合法* @Author: qingyun* @Param: [host, port, ignoredMap]* @Return: void* @Date: 2023/1/6 17:32*/public static void checkHostAndPort(String host, String port, Map ignoredMap) {//获取到host解析后的真实ip地址String[] allIPByHostName = getAllIPByHostName(host);if(null == allIPByHostName || allIPByHostName.length == 0){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "host:"+host+"解析异常,请确保您输入的地址是真实有效的");}if(null != ignoredMap && !ignoredMap.isEmpty()){boolean flag = true;for(int i = 0;i < allIPByHostName.length;i++){if(!ignoredMap.containsKey(allIPByHostName[i])){   //host配置的ip不存在与白名单内flag = false;break;} else {  //当包含此ip时,还需要比对下放开的端口if(!port.equals(ignoredMap.get(allIPByHostName[i]))){  //放开的端口不相同flag = false;break;}}}if(flag){   //host和port存在于配置的白名单内,可以直接放行return;}}//校验此host解析出来的ip是否是内网,内网则直接返回,防止ssrf攻击for(int j = 0;j < allIPByHostName.length;j++){if(ipIsInner(allIPByHostName[j])){  //是内网地址,则直接抛出异常throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "host:"+host+"不是外网地址,请确保参数的准确性");}}}
}

(4)统一错误处理,不返回具体报错信息

 try {    //校验地址是否符合要求checkUrl(String url)//调用远端服务器String result = HttpClientUtils.doPost(url, param.toString(), null, null);      } catch (Exception e) {e.printStackTrace();throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "接口请求调用失败,请确保参数的准确性");}

3.富文本XSS攻击

​ 在项目中,有些输入框的文本内容需要带有样式、引入图片等需求,传统的input输入框没法满足,这时候就需要引入富文本编辑器,前端显示富文本的时候使用html标签。例如:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ubzokp42-1673429158544)(E:\我的工作文档\服务端文档\技术文档\java开发漏洞\微信截图_20230109142438.png)]
漏洞问题:

​ 当富文本内容包含一些前端js的alert、script等字符时,在进行文本回显的时候,浏览器会执行上述脚本,从而劫持用户会话、危害网站、插入恶意内容、重定向用户、使用恶意软件劫持用户浏览器等。

(1)恶意弹窗

​ 在富文本中包含以下恶意js脚本:




​ 在富文本回显的时候,出现弹框:

服务端解决方案:

1.引入jsoup过滤前端提交过来的富文本

​ jsoup是一款java的HTML解析器,可直接解析某个URL地址、HTML文本内容。java引入jsoup的方式:在pom.xml中引入jar包:

 org.jsoupjsoup1.15.3

​ 调用jsoup提供的API进行字符过滤:

  /*** @Description: 获取经过json处理后的数据,防止xss攻击* @Author: qingyun* @Return: java.lang.String* @Date: 2023/1/5 14:36*/private String getJsonCleanValue(String msg) {Safelist safelist = Safelist.relaxed();String res = Jsoup.clean(msg,safelist);return res;}

2.当Safelist.relaxed()提供的白名单不够用时,进行扩展

​ 首先来看下Safelist.relaxed()定义的白名单集合:

 public static Safelist relaxed() {return new Safelist().addTags( "a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6","i", "img", "li", "ol", "p", "pre", "q", "small", "span", "strike", "strong","sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul").addAttributes("a", "href", "title").addAttributes("blockquote", "cite").addAttributes("col", "span", "width").addAttributes("colgroup", "span", "width").addAttributes("img", "align", "alt", "height", "src", "title", "width").addAttributes("ol", "start", "type").addAttributes("q", "cite").addAttributes("table", "summary", "width").addAttributes("td", "abbr", "axis", "colspan", "rowspan", "width").addAttributes( "th", "abbr", "axis", "colspan", "rowspan", "scope","width").addAttributes("ul", "type").addProtocols("a", "href", "ftp", "http", "https", "mailto").addProtocols("blockquote", "cite", "http", "https").addProtocols("cite", "cite", "http", "https").addProtocols("img", "src", "http", "https").addProtocols("q", "cite", "http", "https");}

addTags(String … tags):参数的内容表示html标签(元素)的白名单;

addAttributes(String tag,String … attributes):表示指定标签允许配置哪些属性;

addProtocols(String tag,String attribute,String … protocols):表示指定标签的指定属性允许使用哪些协议。

​ 当默认的白名单不满足需求时,可以在既有的基础上添加新的白名单,例如:

 Safelist safelist = Safelist.relaxed().addTags("test").addAttributes("test","src","href").addProtocols("test","src","http");

​ 测试结果如下:

 //输入String str = "";//输出//输入,不支持的https协议String str = "";//输出

前端解决方案

1.引入js-xss过滤待显示富文本

​ 前端将数据渲染到页面呈现之前,先对内容进行过滤,推荐使用js-xss,官网:http://jsxss.com。

(1)在Node.js上使用

安装:

$ npm install xss --save

使用:

var xss = require('xss');
console.log(xss('click me'));

(2)在浏览器器上使用

引入文件:

https://raw.github.com/leizongmin/js-xss/master/dist/xss.js

使用:

console.log(filterXSS('click me'));

4.暴力破解短信验证码登录

​ 现在越来越多的网站支持通过手机号+验证码的方式登录系统,验证码一般4位或者6位数字。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5gQTBYqn-1673429158545)(E:\我的工作文档\服务端文档\技术文档\java开发漏洞\微信截图_20230109171826.png)]
漏洞问题:

​ 当不法用户输入某个已知的其他用户的手机号,点击获取验证码,然后使用程序生成符合位数的验证码进行暴力登录,破解成功后即可获得合法用户的权限,甚至可以破解管理员的密码进而控制整个站点。

解决方案:

1.使用验证码进行验证登录;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7WcXoGtR-1673429158546)(E:\我的工作文档\服务端文档\技术文档\java开发漏洞\微信截图_20230109172700.png)]
2.生成短信验证码时,设置失效时间、可以验证的剩余次数(每次验证数量减1,数量小于0则删除);

(1)生成验证码

​ 验证码存放到redis中,设置失效时间、设置可以验证的剩余次数。

//组织存在redis中的key值
String key = RedisKeyPrefixConstants.PHONE_NUM+phone;
//使用hash方式来存,因为每次验证需要单独更新可验证的次数值
redisTemplate.opsForHash().put(key,"code",verCode);
//设置可以验证的剩余次数
redisTemplate.opsForHash().put(key,"num",5);
//设置过期时间
redisTemplate.expire(key,300,TimeUnit.SECONDS);

(2)校验验证码

​ 判断是否有给此手机号发送过验证码,没发送过直接验证失败;有发送过验证码,判断输入的验证码是否等于redis中存放的,若是相等,则校验通过,删除redis中的记录;若是不相等,则剩余次数减1,然后判断剩余次数是否小于0,小于0,则删除redis中的记录,验证失败,若是大于0则提示验证码错误。

//传递用户手机号、输入的验证码
public boolean verifyCaptcha(String phone, String captcha){String key = RedisKeyPrefixConstants.PHONE_NUM+phone;//获取此手机号的验证码Object object = redisTemplate.opsForHash().get(key, "code");if(null == object){//没有给此手机号发送的验证码记录throw WebErrorUtils.commonError(null, HttpStatus.PROCESSING, "未请求验证码或验证码已失效,请重新登录!");}String res= object.toString();//比较输入的验证码是否正确if(res.equals(captcha)){//验证通过,删除redis的记录redisTemplate.delete(key);return true;}else{//校验次数减1double num = redisTemplate.opsForHash().increment(key,"num",-1);if(num < 0 ){//剩余可以校验的次数小于0,已经用完,则删除redis的记录redisTemplate.delete(key);throw WebErrorUtils.commonError(null, HttpStatus.PROCESSING, "未请求验证码或验证码已失效,请重新登录!");} else {//还有剩余可以校验的次数,返回验证码错误throw WebErrorUtils.commonError(null, HttpStatus.PROCESSING, "验证码错误!");}}}

5.恶意短信轰炸骚扰用户

​ 现在越来越多的网站支持通过手机号+验证码的方式登录系统,验证码一般4位或者6位数字。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LECLGTPk-1673429158547)(E:\我的工作文档\服务端文档\技术文档\java开发漏洞\微信截图_20230109171826.png)]
漏洞问题:

​ 当不法用户输入某个已知的其他用户的手机号,点击获取验证码,或者用程序循环去跑发送短信验证码的接口,导致此手机号的用户被短信轰炸,受到骚扰。

解决方案:

1.前端进行控制

​ 在点击了获取验证码后,前端隐藏此按钮或者让此按钮灰显不能点击,然后出现一个倒计时的标签,等倒计时结束后才允许点击获取验证码按钮。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B3qv0584-1673429158548)(E:\我的工作文档\服务端文档\技术文档\java开发漏洞\微信截图_20230109175155.png)]
2.服务端进行控制

​ 只是前端进行控制,也会存在绕过倒计时限制进行接口调用的情况,例如使用其他api工具直接调用接口。服务端需要对发送短信的接口进行限制,这里使用redis来存已发送的验证码。当某个手机号请求发送验证码,先从redis中查看是否在有效期内给此号码发送过验证码,若发送过,则提示已经发送过,不再重复发送;若不存在,则新生成一个验证码,发送到此手机号上,并存到redis中。

public void sendCaptcha(String phone){String verCode;String key = RedisKeyPrefixConstants.PHONE_NUM+phone;Object object = redisTemplate.opsForHash().get(key, "code");if(null != object){throw WebErrorUtils.commonError(null, HttpStatus.PROCESSING, "该用户验证码已发送,且未过期,请输入验证码登录或注册!");}else {//生成一个6位数的验证码Random r = new Random(System.currentTimeMillis());int low = 100000;int high = 999999;int code = (r.nextInt(high - low) + low);verCode = String.valueOf(code);//使用hash方式来存,因为每次验证需要单独更新可验证的次数值redisTemplate.opsForHash().put(key,"code",verCode);//设置可以验证的剩余次数redisTemplate.opsForHash().put(key,"num",5);//设置过期时间redisTemplate.expire(key,300,TimeUnit.SECONDS);}try {//调用发送短信的方法sendMsg(phone, verCode);}catch (Throwable throwable){//短信发送失败,删除redis记录redisTemplate.delete(key);throw WebErrorUtils.commonError(throwable, HttpStatus.PROCESSING, "短信发送失败!");}}

6.低版本Fastjson导致RCE漏洞

​ Fastjson是java的一个库,可以将java对象通过toJSONString()转化为json格式的字符串,也可以将json格式的字符串通过parseObject()转化为java对象。

​ 在springboot中引入fastjson:

 com.alibabafastjson1.2.83

漏洞问题:

​ 前端使用json格式把参数传递到服务端,服务端在进行反序列化(json格式转为java对象)的时候,会进入parseField方法,进入该方法后,会调用setValue(object,value)方法,这里会执行构造的恶意代码,最终造成代码执行。Fastjson采用黑白名单的方法来防御反序列化漏洞,导致当不法用户不断发掘新的反序列化类时,就算在autoType关闭的情况下仍然可以绕过黑白名单防御机制,造成远程命令执行漏洞。

解决方案:

1.更新Fastjson到最新版本

​ 在<=1.2.68版本中,攻击者可以通过精心构造的json请求,远程执行恶意代码,所以把Fastjson版本更新到最新版解决此问题。

7.SQL注入漏洞

​ 在开发的接口中,存在需要前端输入参数,服务端根据参数组织sql查询数据的情况。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OFnACZfA-1673429158549)(E:\我的工作文档\服务端文档\技术文档\java开发漏洞\微信截图_20230110181929.png)]
漏洞问题:

​ 若是服务端对用户输入数据的合法性没有判断或过滤不严,攻击者可以在事先定义好的查询语句结尾添加上额外的sql语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务执行非授权的任意查询,从而进一步得到响应的数据信息。

(1)sql拼接参数传递问题

​ 对于mybatis,组织参数到sql中,可以使用KaTeX parse error: Expected 'EOF', got '#' at position 16: {}进行参数的拼接,也可以使用#̲{}进行参数的占位,当进行sq…{}修饰的会把参数值原样拼接到sql中;#{}修饰的会使用占位符?占位,并把参数值作为参数传递进去,当参数为字符串类型时,会默认给参数值加上单引号。

​ 例如以下查询sql:

select * from user where id = ${id}

若参数值id=1 or 1=1,则经过编译后的sql为:

select * from user where id = 1 or 1=1

此时会查询出所有的用户数据,导致信息泄露。

(2)没法使用#{}设置参数的order by排序字段

​ 当sql的查询结果支持多种方式排序,且排序字段和排序方式由前端进行传递时,若是我们使用#{}的方式来占位参数,编译出来的sql是错误的,编译之后会给此排序字段加上单引号。

​ 例如以下查询sql:

select * from user order by #{orderFiled} #{order}

若参数值orderFiled = ‘created_time’,order=‘desc’,经过编译后的sql为:

select * from user order by  'created_time' 'desc'

此sql是有问题的,排序字段和排序方式不能加单引号。那只能使用${}进行sql拼接的方式,此时若参数值orderFiled = ‘created_time’,order=‘desc,(SELECT*FROM(SELECT+SLEEP(3)UNION/**/SELECT+1)a)’,经过编译后的的sql为:

select * from user order by  created_time desc,(SELECT*FROM(SELECT+SLEEP(3)UNION/**/SELECT+1)a)

此时mysql会被恶意查询拖延时间,甚至拖垮mysql服务器。

解决方案:

1.sql组织时尽量使用#{}占位符代替${}拼接符;

2.动态字段排序order by这样没法使用#{}的情况,可以使用以下两种方式处理:

①代码中判断:服务端收到参数后,对参数的值进行校验,判断是否属于允许的值,允许才放行;校验通过后拼接sql即可使用${}的方式。

//校验排序字段,也可以定义枚举值的方式判断
if(!("created_time".equalsIgnoreCase(orderFiled) || "updated_time".equalsIgnoreCase(orderFiled))){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "排序字段非法");}//校验排序方式,也可以定义枚举值的方式判断
if(!("desc".equalsIgnoreCase(order) || "asc".equalsIgnoreCase(order))){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "排序方式非法");
}

②组织sql时判断:判断参数的值是否等于系统约定的值,等于才进行拼接,不等于使用默认的排序方式。

SELECT * FROM USER 
ORDER BY 
 ${orderFiled}  ${order}  updated_time DESC,created_time DESC 
 

8.水平越权信息泄露

​ 服务端给出的接口,基本都是根据参数查询、删除、更新、添加数据,用得比较多的是根据某个id去操作数据,而出于性能考虑,id一般都采用整数自增的方式,这样就会存在id被枚举的情况。

漏洞问题:

​ 若是对某条记录的操作不加权限的验证,直接返回此参数对应的记录或者修改记录,则会越权操作本来不属于此用户的记录,导致信息会被恶意用户盗取甚至篡改其他合法用户的信息。

(1)通过枚举访问资源

​ 有些场景下,用户发布一个对外的链接地址(不需要登录直接访问),此链接对应的资源只想让他熟悉的人知道,用户会把此链接单独发给他信赖的人。若是此链接地址的结尾是一个自增型的整数,则用户通过枚举结尾,就能轻松访问到其他用户发布的链接资源。

​ 例如以下的链接地址:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qetq5IxU-1673429158550)(E:\我的工作文档\服务端文档\技术文档\java开发漏洞\微信截图_20230110174635.png)]
(2)越权查询记录

​ 用户使用系统时,可以通过浏览器查看调用了哪些接口,以及各个接口传递的参数。若是对访问的记录不加权限验证,则可能出现越权查看其他用户记录的情况。

​ 例如以下的接口访问数据:

当枚举id的值进行访问,没加权限验证,则可以越权访问记录。

解决方案:

1.对外公开的链接资源,结尾地址使用uuid,避免被枚举;

2.对于系统资源权限的验证,可以结合着项目当中引入的框架来定,例如使用spring security,在接口上使用注解的方式加上权限的标识,在管理后台对用户进行权限的分配,一般都是用户与角色挂钩,角色与菜单和权限挂钩;

3.对于比较细粒度的权限校验,可以结合着redis来实现,判断此记录是否属于此人的资源,属于才让访问,不属于则抛出异常。可以在记录产生的时候直接加到redis中,并且与某个用户进行关联,当操作此记录的时候,再从redis中取出,看此用户是否有权限操作。

//判断登录用户是否有权操作此记录
private boolean checkXXXById(Integer id) {//获取到当前用户String currentUserId = xxx.getLoginIdAsString();//组织redis的keyString key = RedisKeyPrefixConstants.USER_XXX_ID_SET+currentUserId;Boolean aBoolean = redisTemplate.hasKey(key);if(aBoolean) {   //存在此key的缓存值Boolean member = redisTemplate.opsForSet().isMember(key, id);if(member) {    //set集合中存在此值return true;}}else {   //不存在此缓存的值,则查询数据库List xxxIdList = xxxkDao.getxxxIdList(currentUserId);  //if(null != xxxIdList && xxxIdList.size() > 0){//把查询结果放到redis中redisTemplate.opsForSet().add(key,xxxIdList.toArray());//判断从数据库中查询的记录是否包含此id,包含则放行if(xxxIdList.contains(id)){return true;}}}return false;}

9.权限绕过漏洞

​ 在项目开发中,会有一些需求要等第一步校验通过,返回成功状态码之后,才进行第二步的操作。例如访问一个带密码的链接,需要先等密码验证通过后才加载资源。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-clD316Vf-1673429158551)(E:\我的工作文档\服务端文档\技术文档\java开发漏洞\微信截图_20230110183437.png)]
漏洞问题:

​ 当第一步校验后,抓包修改返回值,然后放包,即可绕过权限进行第二步的加载,导致攻击者访问未经授权的资源。

(1)抓包修改校验结果访问资源

​ 对于需要密码访问的链接,随便输入密码,对校验结果进行抓包,修改状态值为通过, 然后放包,即可查看到资源。

解决方案:

1.校验密码是否正确的时候,生成一个uuid的会话状态码,以此uuid+这个链接id作为redis的key值,把校验结果作为value存放到redis中,并设置过期时间为3秒,然后把校验结果和uuid状态码返回给前端;

2.执行第二步的时候,需要把第一步的uuid值、链接id作为参数传到服务端,服务端根据这两个参数组织redis的key,查询redis是否有此记录,若是没有,直接返回,说明是伪造参数或者已经过期;若是存在,则判断第一步校验的结果状态值,通过则放行,否则返回。

(1)第一步校验密码逻辑

 public XXX checkVisitPassword(String id, String visitPassword) {if(StringUtils.isEmpty(id)) {throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "id参数为空,请确保参数的准确性");}if(StringUtils.isBlank(visitPassword)) {throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "密码为空,请确保参数的准确性");}//查询是否存在此id与密码的值YYY yyy = yyyDao.getYyyByIdAndVisitPassword(id,visitPassword);XXX xxx = new XXX();xxx.setId(id);//生成uuid会话值String sessionId = IdUtil.simpleUUID();xxx.setSessionId(sessionId);//校验结果状态值String state = "ok";if(null == yyy) {  //不通过xxx.setCode(1);state = "error";} else {       //通过xxx.setCode(0);}//校验结果状态值存到redis中,过期时间3秒redisTemplate.opsForValue().set(RedisKeyPrefixConstants.VIEW_REQUESTID_STATE+id+":"+sessionId,state,3, TimeUnit.SECONDS);return xxx;}

(2)第二步获取资源逻辑

public XXX getMessage(String id,String sessionId) {if(StringUtils.isEmpty(id)) {throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "id参数为空,请确保参数的准确性");}if(StringUtils.isEmpty(sessionId)){         //会话id为空throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "sessionId参数为空,请确保参数的准确性");}String key = RedisKeyPrefixConstants.VIEW_REQUESTID_STATE+id+":"+sessionId;Object object = redisTemplate.opsForValue().get(key);if(null == object) {   //redis缓存中不存在此记录throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "输入密码的会话已失效,请重新加载访问");} else {redisTemplate.delete(key);String state = object.toString();if(StringUtils.isEmpty(state)){         //回话状态为空throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "输入密码的会话已失效,请重新加载访问");}if("error".equals(state)){throw WebErrorUtils.commonError(null, HttpStatus.UNPROCESSABLE_ENTITY, "输入的密码错误,请重新加载访问");}}//校验通过,查询数据返回XXX xxx = xxxService.findById(id);return xxx;}

相关内容

热门资讯

findx耍原生安卓系统,深度... 亲爱的读者们,你是否厌倦了那些花里胡哨的定制系统,渴望回到那个纯净的安卓世界?今天,我要带你一起探索...
一加系统属于安卓系统吗,引领智... 你有没有想过,手机里的那个神奇的“一加系统”到底是不是安卓系统的一员呢?这可是个让人好奇不已的问题哦...
小米2刷安卓系统吗,探索安卓系... 亲爱的读者,你是否曾经对小米2这款手机刷安卓系统的事情感到好奇呢?今天,就让我带你一探究竟,揭开小米...
安卓7.0系统线刷包,深度解析... 你有没有发现,你的安卓手机最近有点儿“蔫儿”了?别急,别急,今天就来给你揭秘如何让你的安卓手机重焕生...
白菜系统和安卓拍照,开启智能生... 你知道吗?最近我在用手机拍照的时候,发现了一个超级酷的功能,简直让我爱不释手!那就是——白菜系统和安...
安卓系统查杀病毒,全方位守护您... 手机里的安卓系统是不是有时候会突然弹出一个查杀病毒的提示?别慌,这可不是什么大问题,今天就来给你详细...
iso系统与安卓各系统哪个好,... 你有没有想过,手机操作系统就像是我们生活中的不同交通工具,各有各的特色和优势。今天,咱们就来聊聊这个...
中柏怎么换安卓系统,解锁更多可... 你有没有发现,中柏的安卓系统有时候用起来还挺不顺手的?别急,今天就来手把手教你如何给中柏手机升级安卓...
安卓热点绕过系统验证,揭秘操作... 你是不是也遇到过这种情况?手机里的安卓热点突然不灵光了,系统验证总是跳出来,让人头疼不已。别急,今天...
安卓系统怎么关闭小艺,安卓系统... 亲爱的安卓用户们,你是否也和我一样,对手机里的小艺助手有些爱恨交加呢?有时候,它贴心得让人感动,有时...
安卓系统计划软件推荐,精选计划... 你有没有发现,手机里的安卓系统越来越智能了?这不,最近我可是挖到了一些超棒的安卓计划软件,它们不仅能...
收钱吧安卓系统插件,便捷支付新... 你有没有发现,现在的生活越来越离不开手机了?手机里装满了各种应用,而今天我要跟你聊聊一个特别实用的工...
鸿蒙系统是否还属于安卓,独立于... 你有没有想过,那个在我们手机上默默无闻的鸿蒙系统,它到底是不是安卓的“亲戚”呢?这个问题,估计不少手...
安卓系统手机用什么钱包,轻松管... 你有没有想过,你的安卓系统手机里装了那么多应用,但最离不开的,可能就是那个小小的钱包了。没错,就是那...
安卓系统能玩部落冲突吗,部落冲... 你有没有想过,安卓系统上的手机,是不是也能玩那款风靡全球的《部落冲突》呢?这款游戏自从推出以来,就吸...
智能机器人安卓系统,引领未来智... 你知道吗?在科技飞速发展的今天,智能机器人已经不再是科幻电影里的专属了。它们正悄悄地走进我们的生活,...
华为win10系统改装安卓系统... 你有没有想过,你的华为笔记本电脑里的Windows 10系统,能不能来个华丽变身,变成安卓系统呢?这...
旧电脑上安什么安卓系统,适配不... 你那台旧电脑是不是已经闲置好久了?别让它默默无闻地躺在角落里,给它来个华丽变身吧!今天,就让我来告诉...
安卓app语言跟随系统,随系统... 你知道吗?在手机世界里,有一个神奇的小功能,它就像你的贴身翻译官,无论你走到哪里,都能帮你轻松应对各...
惠城安卓系统降级在哪,揭秘降级... 你有没有遇到过手机系统升级后,发现新系统让你头疼不已,想回到那个熟悉的安卓系统呢?别急,今天就来告诉...