Allegra 一系列CVE漏洞分析

二月份zdi公布了Allegra一系列的漏洞,包括硬编码、文件读取、文件上传、zipslip、反序列化等漏洞,简单记了一下。

Allegra是国外的一个项目管理系统,官方链接:https://www.trackplus.com/

影响版本

Allegra < 7.5.1

任意文件读取( CVE-2024-22507)

Allegra使用struts开发,支持foo!method的方式处理路由。
该漏洞入口点在AttachmentGlobalAction#download方法,最终调用

AttachBL.download(diskFileName, this.fileName, outstream, false);

.......

        try {
            File file = new File(fileName);
            boolean isImage = isImage(fileOriginalName);
            String extension = FilenameUtils.getExtension(fileOriginalName);
            if (isImage && convertSvgToPng && isSvg(extension)) {
                String imgUrl = file.toURI().toURL().toString();
                BufferedImage image = convertSVGToPNG(imgUrl, 0.0F, 0.0F);
                ByteArrayOutputStream svgOS = new ByteArrayOutputStream();
                ImageIO.write(image, "png", svgOS);
                ByteArrayInputStream byteArrayISSVG = new ByteArrayInputStream(svgOS.toByteArray());
                instream = new BufferedInputStream(byteArrayISSVG);
                byteArrayISSVG.close();
                svgOS.close();
            } else {
                instream = new BufferedInputStream(new FileInputStream(file));
            }

这里未对fileName作处理,导致可直接拼接文件名读取任意文件,如可读取jwt密钥文件可构造token访问某些api接口。

文件上传(CVE-2024-22510)

该漏洞入口点在BrandingAction#uploadFile,最终关键调用代码

public static String uploadFile(String fileNameToReplace, String uploadedFileName, File uploadedFile) {
        LOGGER.debug("Uploading new branding file, fileNameToReplace: " + fileNameToReplace);
        if (uploadedFileName != null && uploadedFile != null) {
            try {
                String uploadedFileExt = FilenameUtils.getExtension(uploadedFileName);
                String logosDirAbsPath = HandleHome.getTrackplus_Home() + File.separator + "logos";
                String fileToReplaceAbsPath = logosDirAbsPath + File.separator + fileNameToReplace;
                File fileToReplace = new File(fileToReplaceAbsPath);
                boolean fileToReplaceExists = fileToReplace.exists();
                if (fileToReplaceExists) {
                    LOGGER.debug("Deleting existing branding file!");
                    Files.delete(fileToReplace.toPath());
                }

                String uploadedFileNewPath = logosDirAbsPath + File.separator + FilenameUtils.removeExtension(fileNameToReplace) + "." + uploadedFileExt;
                LOGGER.debug("uploadedFileNewPath: " + uploadedFileNewPath);
                Files.move(Paths.get(uploadedFile.getAbsolutePath()), Paths.get(uploadedFileNewPath), StandardCopyOption.REPLACE_EXISTING);
                File uploadedFileInLogoDir = new File(uploadedFileNewPath);
                LOGGER.debug("File: " + uploadedFileInLogoDir.getAbsolutePath() + " saved successfully: " + uploadedFileInLogoDir.exists());
                String base64 = getBase64Img(uploadedFileInLogoDir);
                LogoBL.clearLogoCache();
                return BrandingJson.encodeFileUploadSuccess(base64);
            } catch (IOException var11) {
                LOGGER.error(var11.getMessage(), var11);
            }
        }

上传路径和后缀未限制,最终会以fileNameToReplace参数重命名文件,同样也可控。

反序列化(CVE-2024-22505)

漏洞sink点在ExcelFieldMatchBL#renderFieldMatch方法中,关键代码

static String renderFieldMatch(String excelMappingsDirectory, String fileName, String entity, Integer selectedSheet, Integer personID, Locale locale) {
        File fileOnDisk = new File(excelMappingsDirectory, fileName);
        Workbook workbook = ExcelImportBL.loadWorkbook(excelMappingsDirectory, fileName);
        if (workbook == null) {
            boolean deleted = fileOnDisk.delete();
            LOGGER.debug("Deleted: {}", deleted);
            return JSONUtility.encodeJSONFailure(LocalizeUtil.getLocalizedTextFromApplicationResources("admin.actions.importExcel.err.noWorkbook", locale));
        } else {
            Integer selectedShetCopy = selectedSheet;
            if (selectedSheet == null) {
                selectedShetCopy = 0;
            }

            Map<String, Integer> columNameToFieldIDMap = null;
            Set lastSavedIdentifierFieldIDIsSet = null;

            try {
                FileInputStream fis = new FileInputStream(new File(excelMappingsDirectory, getMappingFileName(entity)));
                Throwable var12 = null;

                try {
                    ObjectInputStream objectInputStream = new ObjectInputStream(fis);
                    columNameToFieldIDMap = (Map)objectInputStream.readObject();
                    .........

读取某个文件直接反序列化,产品想要实现的功能应该是先将导入的表格序列化到listmapping中,后续再进行反序列化,要结合上面的跨目录上传将文件传到/Allegra/trackdata/excelImport/1/listmapping再触发,路由/excelFieldMatch.action?fileName=1.xlsx&entity=list。

zipslip(CVE-2024-22504)

漏洞入口点在Import#importFromAllegraFormat,该接口使用jwt认证,需要用到上面读到的token,直接看sink点:

label147: {
    try {
        while(true) {
            if ((zipEntry = zipInputStream.getNextEntry()) == null) {
                break label147;
            }

            extarctZippedFile(unzipTempDirectory, zipInputStream, zipEntry);
        }

....

private static void extarctZippedFile(File unzipTempDirectory, ZipInputStream zipInputStream, ZipEntry zipEntry) {
        File destFile = new File(unzipTempDirectory, zipEntry.getName());
        int bugfferSize = true;
        byte[] data = new byte[2048];
        FileOutputStream fos = null;
        BufferedOutputStream dest = null;

        try {
            fos = new FileOutputStream(destFile);
            dest = new BufferedOutputStream(fos, 2048);

            int count;
            while((count = zipInputStream.read(data, 0, 2048)) != -1) {
                dest.write(data, 0, count);
            }

很明显可解压到任意位置造成任意文件写入,路由/rest/v2/items/exchange/import/importFromAllegraFormat,制作恶意压缩包上传就行了。

漏洞修复

更新到最新版https://www.trackplus.com/index.php/de/service/download.html

0 条评论
某人
表情
可输入 255