对一vpn软件的逻辑漏洞挖掘(薅羊毛)
1111111
这里先大概讲一下 薅羊毛漏洞 的逻辑
一般都是这四大金刚导致的
1.用户标识机制不完善
2.优惠券发放的逻辑存在缺陷
3.相关的业务逻辑关键点设计的不严谨
4.开发的代码写错了QAQ
挨个说明一下
用户标识机制不完善
依赖单一标识符: 系统仅以邮箱、手机号等单一信息作为用户唯一标识,未建立综合身份识别机制(如设备指纹、实名信息等)。漏洞风险就是用户注销后,使用同一信息重新注册即可被系统视为新用户。这种情况在很多企业中并不算很少见,下面为我跟某src审核聊天得知的这一情况
未保留注销记录:系统在用户注销后彻底删除其数据,或未将注销账户信息与优惠券领取记录关联,导致无法识别历史用户。本次介绍的漏洞就是这个情况,利用的薄弱点,测试用户可能不关联记录,从而发掘出该漏洞
下面代码就是存在该漏洞的直观体现
白盒展示
@Service
public class VulnerableUserService {
@Autowired
private UserRepository userRepository;
// 注册逻辑:仅检查邮箱是否存在
public User register(String email, String password) {
// 漏洞点:仅通过邮箱判断用户是否存在,未考虑已注销账户
if (userRepository.findByEmail(email) != null) {
throw new RuntimeException("邮箱已注册");
}
User newUser = new User(email, password);
return userRepository.save(newUser);
}
// 注销逻辑:直接删除所有数据
public void deleteAccount(Long userId) {
userRepository.deleteById(userId);
}
}
优惠券发放逻辑存在缺陷
新用户判定条件简单:优惠券发放仅以账户是否“首次注册”为判断条件,未考虑用户注销后重新注册的情况,导致漏洞被利用。
未关联历史行为:发放优惠券时,系统未查询该用户身份(包括已注销账户)的历史领取记录,导致重复发放。
白盒展示
优惠券服务
@Service
public class VulnerableCouponService {
@Autowired
private UserRepository userRepository;
// 发放优惠券逻辑
public Coupon grantCoupon(String email) {
User user = userRepository.findByEmail(email);
// 漏洞点:只检查当前是否存在账户,不检查历史注销账户(
if (user == null) {
throw new RuntimeException("用户不存在");
}
// 漏洞点:仅检查该账户是否领取过,不关联历史账户
if (user.getCoupons().isEmpty()) {
Coupon coupon = new Coupon("NEW_USER_100");
user.getCoupons().add(coupon);
userRepository.save(user);
return coupon;
} else {
throw new RuntimeException("已领取过优惠券");
}
}
}
业务流程设计疏忽
注销机制与优惠券逻辑未联动: 产品设计时未考虑用户注销场景对营销活动的影响,两个功能模块独立运作,缺乏数据互通。
未设置冷却机制:同一身份信息频繁注册/注销时,缺乏风险控制策略(如限制同一设备/IP的注册频率),这也就是常说的并发
白盒展示
// 注册控制器 @RestController public class VulnerableAuthController {@Autowired
private VulnerableUserService userService;
@PostMapping("/register")
public User register(@RequestParam String email,
@RequestParam String password) {
// 漏洞点:无设备指纹、无IP频率限制
return userService.register(email, password);
}
}
技术实现漏洞
数据库设计缺陷: 用户表与优惠券领取记录表未正确关联,或注销操作未触发领取状态重置,两个逻辑各忙各的,导致数据不一致。
代码逻辑错误: 例如,检查用户是否存在时,未包含已注销账户,错误判断为新用户。
白盒展示
用户逻辑@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String email;
// 漏洞点:无软删除标记,物理删除后无法追溯
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Coupon> coupons = new ArrayList<>();
}
// 用户Repository直接使用JPA默认删除
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email); // 无法查询已删除用户
}
ok,废话不多说,上实战
打开该软件界面,上来就是测试用户,免费送两天
我们现在是临时账号,可以猜测一下我们的数据没有存储到数据库中,可以达到每次重置都以新用户的身份登录,为了验证一下,特地等到仅剩一天
OK,清除一手缓存,验证猜想
重新注册,发现我们的时间又变为两天了