CVE-2022-24861 (Java, published 2022-04-20T19:15Z)
Description: value: Databasir is a team-oriented relational database model document management platform. Databasir 1.01 has remote code execution vulnerability. JDBC drivers are not validated prior to use and may be provided by users of the system. This can lead to code execution by any basic user who has access to the system. Users are advised to upgrade. There are no known workarounds to this issue.
Severity: MEDIUM (CVSS v3 base score: 8.8)
Weakness: CWE-20 \u2014 Improper Input Validation
The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
Repository: https://github.com/vran-dev/databasir
Files changed:
api/src/main/resources/application-local.properties
api/src/main/resources/application.properties
core/src/main/java/com/databasir/core/domain/DomainErrors.java
core/src/main/java/com/databasir/core/domain/database/service/DatabaseTypeService.java
core/src/main/java/com/databasir/core/infrastructure/connection/CustomDatabaseConnectionFactory.java
core/src/main/java/com/databasir/core/infrastructure/driver/DriverResources.java
core/src/main/java/com/databasir/core/infrastructure/jwt/JwtTokens.java
core/src/test/java/com/databasir/core/domain/database/service/DatabaseTypeServiceTest.java
core/src/test/resources/application-ut.properties
Commit message: fix some security bug (#103)

* fix: use hard-code secret

* feat: add driver class validate

* feat: optimize drvier resource code

* fix:ut failed

Vulnerable code:
databasir.db.driver-directory=drivers
databasir.db.driver-directory=drivers
    DRIVER_CLASS_NAME_OBTAIN_ERROR("A_10031", "获取驱动类名失败"),

                driverResources.delete(data.getDatabaseType());
            driverResources.delete(data.getDatabaseType());
        return driverResources.resolveSqlDriverNameFromJar(request.getJdbcDriverFileUrl());
        DatabaseTypePojo type = databaseTypeDao.selectByDatabaseType(context.getDatabaseType());
        File driverFile = driverResources.loadOrDownload(context.getDatabaseType(), type.getJdbcDriverFileUrl());
            clazz = Class.forName(type.getJdbcDriverClassName(), true, loader);
            throw DomainErrors.CONNECT_DATABASE_FAILED.exception("驱动初始化异常, 请检查 Driver name：" + e.getMessage());
    public void delete(String databaseType) {
    public String resolveSqlDriverNameFromJar(String driverFileUrl) {
        File driverFile = doDownload(driverFileUrl, tempFilePath);
        String className = doResolveSqlDriverNameFromJar(driverFile);
    public File loadOrDownload(String databaseType, String driverFileUrl) {
        String filePath = driverFilePath(driverBaseDirectory, databaseType);
        Path path = Path.of(filePath);
        if (Files.exists(path)) {
            // ignore
            log.debug("{} already exists, ignore download from {}", filePath, driverFileUrl);
            return path.toFile();
        return this.doDownload(driverFileUrl, filePath);
    private File doDownload(String driverFileUrl, String filePath) {
        Path path = Path.of(filePath);
                log.error("create file error " + filePath, e);
                    log.info("{} download success ", filePath);
                    log.error("{} download error from {}: {} ", filePath, driverFileUrl, response);
        } catch (IllegalArgumentException e) {
            log.error(filePath + " download driver error", e);
    private String doResolveSqlDriverNameFromJar(File driverFile) {
        JarFile jarFile = null;
            jarFile = new JarFile(driverFile);
        } catch (IOException e) {
            log.error("resolve driver class name error", e);
            throw DomainErrors.DRIVER_CLASS_NAME_OBTAIN_ERROR.exception(e.getMessage());
        final JarFile driverJar = jarFile;
        String driverClassName = jarFile.stream()
                .filter(entry -> entry.getName().contains("META-INF/services/java.sql.Driver"))
                .findFirst()
                .map(entry -> {
                    InputStream stream = null;
                    BufferedReader reader = null;
                    try {
                        stream = driverJar.getInputStream(entry);
                        reader = new BufferedReader(new InputStreamReader(stream));
                        return reader.readLine();
                    } catch (IOException e) {
                        log.error("resolve driver class name error", e);
                        throw DomainErrors.DRIVER_CLASS_NAME_OBTAIN_ERROR.exception(e.getMessage());
                    } finally {
                        IOUtils.closeQuietly(reader, ex -> log.error("close reader error", ex));
                    }
                })
                .orElseThrow(DomainErrors.DRIVER_CLASS_NAME_OBTAIN_ERROR::exception);
        IOUtils.closeQuietly(jarFile, ex -> log.error("close jar file error", ex));
        return driverClassName;
    private static final String SECRET = "Databasir2022";
        Algorithm algorithm = Algorithm.HMAC256(SECRET);
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET))
databasir.db.driver-directory=drivers

Fixed code:
databasir.db.driver-directory=drivers
databasir.jwt.secret=DatabasirJwtSecret
databasir.db.driver-directory=drivers
databasir.jwt.secret=${random.uuid}
    DRIVER_CLASS_NOT_FOUND("A_10031", "获取驱动类名失败"),
        driverResources.validateJar(request.getJdbcDriverFileUrl(), request.getJdbcDriverClassName());
            driverResources.validateJar(request.getJdbcDriverFileUrl(), request.getJdbcDriverClassName());
                driverResources.deleteByDatabaseType(data.getDatabaseType());
            driverResources.deleteByDatabaseType(data.getDatabaseType());
        return driverResources.resolveDriverClassName(request.getJdbcDriverFileUrl());
        String databaseType = context.getDatabaseType();
        DatabaseTypePojo type = databaseTypeDao.selectByDatabaseType(databaseType);
        File driverFile = driverResources.loadOrDownloadByDatabaseType(databaseType, type.getJdbcDriverFileUrl());

            clazz = Class.forName(type.getJdbcDriverClassName(), false, loader);
            throw DomainErrors.CONNECT_DATABASE_FAILED.exception("驱动初始化异常, 请检查驱动类名：" + e.getMessage());
import org.apache.commons.lang3.ClassUtils;
import org.springframework.web.client.RestClientException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Optional;
    public void deleteByDatabaseType(String databaseType) {
    public Optional<File> loadByDatabaseType(String databaseType) {
        Path path = Paths.get(driverFilePath(driverBaseDirectory, databaseType));
        if (Files.exists(path)) {
            return Optional.of(path.toFile());
        } else {
            return Optional.empty();
        }
    }

    public File loadOrDownloadByDatabaseType(String databaseType, String driverFileUrl) {
        return loadByDatabaseType(databaseType)
                .orElseGet(() -> download(driverFileUrl, driverFilePath(driverBaseDirectory, databaseType)));
    }

    public String resolveDriverClassName(String driverFileUrl) {
        File driverFile = download(driverFileUrl, tempFilePath);
        String className = resolveDriverClassName(driverFile);
    public String resolveDriverClassName(File driverFile) {
        JarFile jarFile = null;
        try {
            jarFile = new JarFile(driverFile);
        } catch (IOException e) {
            log.error("resolve driver class name error", e);
            throw DomainErrors.DRIVER_CLASS_NOT_FOUND.exception(e.getMessage());

        final JarFile driverJar = jarFile;
        String driverClassName = jarFile.stream()
                .filter(entry -> entry.getName().contains("META-INF/services/java.sql.Driver"))
                .findFirst()
                .map(entry -> {
                    InputStream stream = null;
                    BufferedReader reader = null;
                    try {
                        stream = driverJar.getInputStream(entry);
                        reader = new BufferedReader(new InputStreamReader(stream));
                        return reader.readLine();
                    } catch (IOException e) {
                        log.error("resolve driver class name error", e);
                        throw DomainErrors.DRIVER_CLASS_NOT_FOUND.exception(e.getMessage());
                    } finally {
                        IOUtils.closeQuietly(reader, ex -> log.error("close reader error", ex));
                    }
                })
                .orElseThrow(DomainErrors.DRIVER_CLASS_NOT_FOUND::exception);
        IOUtils.closeQuietly(jarFile, ex -> log.error("close jar file error", ex));
        return driverClassName;
    private File download(String driverFileUrl, String targetFile) {
        Path path = Path.of(targetFile);
                log.error("create file error " + targetFile, e);
                    log.info("{} download success ", targetFile);
                    log.error("{} download error from {}: {} ", targetFile, driverFileUrl, response);
        } catch (RestClientException e) {
            log.error(targetFile + " download driver error", e);
    public void validateJar(String driverFileUrl, String className) {
        String tempFilePath = "temp/" + UUID.randomUUID() + ".jar";
        File driverFile = download(driverFileUrl, tempFilePath);
        URLClassLoader loader = null;
            loader = new URLClassLoader(
                    new URL[]{driverFile.toURI().toURL()},
                    this.getClass().getClassLoader()
            );
        } catch (MalformedURLException e) {
            log.error("load driver jar error ", e);
            throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception(e.getMessage());
        try {
            Class clazz = Class.forName(className, false, loader);
            boolean isValid = ClassUtils.getAllInterfaces(clazz)
                    .stream()
                    .anyMatch(cls -> cls.getName().equals("java.sql.Driver"));
            if (!isValid) {
                throw DomainErrors.DRIVER_CLASS_NOT_FOUND.exception("不合法的驱动类，请重新指定");
            }
        } catch (ClassNotFoundException e) {
            log.error("init driver error", e);
            throw DomainErrors.DRIVER_CLASS_NOT_FOUND.exception("驱动初始化异常, 请检查驱动类名：" + e.getMessage());
        } finally {
            IOUtils.closeQuietly(loader);
            try {
                Files.deleteIfExists(driverFile.toPath());
            } catch (IOException e) {
                log.error("delete driver error " + tempFilePath, e);
            }
        }
import org.springframework.beans.factory.annotation.Value;
    @Value("${databasir.jwt.secret}")
    private String tokenSecret;
        Algorithm algorithm = Algorithm.HMAC256(tokenSecret);
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(tokenSecret))
import com.databasir.core.infrastructure.driver.DriverResources;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.mockito.ArgumentMatchers.anyString;

    @MockBean
    private DriverResources driverResources;

    @BeforeEach
    public void setUp() {
        Mockito.doNothing().when(driverResources).validateJar(anyString(), anyString());
    }

databasir.db.driver-directory=drivers
databasir.jwt.secret=DatabasirJwtSecret