项目介绍

🦄 开源社区系统:基于 SpringBoot + MyBatis + MySQL + Redis + Kafka + Elasticsearch + Spring Security + ... 并提供详细的开发文档和配套教程。包含帖子、评论、私信、系统通知、点赞、关注、搜索、用户设置、数据统计等模块。3000+Star的项目还是比较不错的

环境搭建

项目地址:https://gitee.com/veal98/Echo

环境准备

JDK 1.8.0_65
Maven 3.9.9
redis 3.0.504
zookeeper 3.6.4
kafka 2.12-3.5.1

配置文件:

src/main/resources/application-develop.properties

创建对应数据库,并且修改数据库配置文件,以及文件存放路径

我这里创建数据库采用utf8 utf8_bin

创建后导入所有数据库文件

先启动zookeeper

zkServer

再启动 kafka

.\bin\windows\kafka-server-start.bat .\config\server.properties

然后启动springboot

访问自己设置的对应端口

代码审计

鉴权绕过

在看过用户登入凭证生成后发现无法伪造,转思路查看用户权限校验

权限校验对应.class文件

src/main/java/com/greate/community/controller/interceptor/LoginTicketInterceptor.java

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从 cookie 中获取凭证
        String ticket = CookieUtil.getValue(request, "ticket");
        if (ticket != null) {
            // 查询凭证
            LoginTicket loginTicket = userService.findLoginTicket(ticket);
            // 检查凭证状态(是否有效)以及是否过期
            if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) {
                // 根据凭证查询用户
                User user = userService.findUserById(loginTicket.getUserId());
                // 在本次请求中持有用户信息
                hostHolder.setUser(user);

                // 构建用户认证的结果,并存入 SecurityContext, 以便于 Spring Security 进行授权
                Authentication authentication = new UsernamePasswordAuthenticationToken(
                        user, user.getPassword(), userService.getAuthorities(user.getId())
                );
                SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
            }
        }

        return true;
    }

这里只判断ticket不会null值的身份校验,ticket的值为null时会直接放行,没有考虑到tucket为null的情况

未授权用户信息遍历

通过路由传入的userID进行数据库查询,这里会验证ticket的身份和查询的userID是否相同主要是根据ticket查询,根据前面的鉴权绕过就可以实现未授权用户信息遍历

漏洞触发数据包

GET /user/profile/102 HTTP/1.1
Host: 192.168.242.1:8081
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://192.168.242.1:8081/index
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate
Upgrade-Insecure-Requests: 1

可通过控制id未授权遍历用户信息

未授权任意文件上传

全局搜索upload关键词

查看代码

@PostMapping("/uploadMdPic")
    @ResponseBody
    public String uploadMdPic(@RequestParam(value = "editormd-image-file", required = false) MultipartFile file) {

        String url = null; // 图片访问地址
        try {
            // 获取上传文件的名称
            String trueFileName = file.getOriginalFilename();
            String suffix = trueFileName.substring(trueFileName.lastIndexOf("."));
            String fileName = CommunityUtil.generateUUID() + suffix;

            // 图片存储路径
            File dest = new File(editormdUploadPath + "/" + fileName);
            if (!dest.getParentFile().exists()) {
                dest.getParentFile().mkdirs();
            }

            // 保存图片到存储路径
            file.transferTo(dest);

            // 图片访问地址
            url = domain + contextPath + "/editor-md-upload/" + fileName;
        } catch (Exception e) {
            e.printStackTrace();
            return CommunityUtil.getEditorMdJSONString(0, "上传失败", url);
        }

        return CommunityUtil.getEditorMdJSONString(1, "上传成功", url);
    }

这里获取我们上传的文件,然后通过lastIndexOf 方法获取文件后缀,然后,通过CommunityUtil.generateUUID()生成一个文件名,并将获取到的后缀和文件名进行拼接。

最后,文件是通过 file.transferTo 方法进行保存的。在此过程中,没有检测文件后缀,那么这里存在任意文件上传,加上前面的鉴权绕过就是未授权任意文件上传,但是很可惜springboot没有jsp解析环境,这里还限制了目录

通过代码使用ai构造上传文件的html文件

测试上传抓包

访问文件