CVE-2023-28630 (published 2023-03-27T21:15Z)

GoCD is an open source continuous delivery server. In GoCD versions from 20.5.0 and below 23.1.0, if the server environment is not correctly configured by administrators to provide access to the relevant PostgreSQL or MySQL backup tools, the credentials for database access may be unintentionally leaked to admin alerts on the GoCD user interface. The vulnerability is triggered only if the GoCD server host is misconfigured to have backups enabled, but does not have access to the `pg_dump` or `mysqldump` utility tools to backup the configured database type (PostgreSQL or MySQL respectively). In such cases, failure to launch the expected backup utility reports the shell environment used to attempt to launch in the server admin alert, which includes the plaintext database password supplied to the configured tool. This vulnerability does not affect backups of the default on-disk H2 database that GoCD is configured to use. This issue has been addressed and fixed in GoCD 23.1.0. Users are advised to upgrade. Users unable to upgrade may disable backups, or administrators should ensure that the required `pg_dump` (PostgreSQL) or `mysqldump` (MySQL) binaries are available on the GoCD server when backups are triggered.

CVSS v3 base score: 4.4, MEDIUM (CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-532
cwe_name: Insertion of Sensitive Information into Log File
description: Information written to log files can be of a sensitive nature and give valuable guidance to an attacker or expose sensitive user information.
extended_description: 
            While logging all information may be helpful during development stages, it is important that logging levels be set appropriately before a product ships so that sensitive user data and system information are not accidentally exposed to potential attackers.
            Different log files may be produced and stored for:
            
               Server log files (e.g. server.log).  This can give information on whatever application left the file. Usually this can give full path names and system information, and sometimes usernames and passwords.
               log files that are used for debugging
            
         
         

Fix information:
repo_url: https://github.com/gocd/gocd
commit_details: repo_url: https://github.com/gocd/gocd
author: Chad Wilson
author_date: 2022-11-24 23:50:21+08:00
msg: Improve error messages on failure to launch backup process

ztexec can include env vars in the error message which we don't want in this case.
repository: repo_url: https://github.com/gocd/gocd
repo_name: gocd/gocd
description: GoCD - Continuous Delivery server main repository
repo_language: Java
owner: gocd
file_changes: filename: BackupProcessor.java
change_type: ModificationType.MODIFY
diff: @@ -23,4 +23,12 @@ public interface BackupProcessor {
     void backup(File targetDir, DataSource dataSource, DbProperties dbProperties) throws Exception;
 
     boolean accepts(String url);
+
+    default void throwBackupError(String command, int errorCode) {
+        throwBackupError(command, errorCode, null);
+    }
+
+    default void throwBackupError(String command, int errorCode, Throwable cause) {
+        throw new RuntimeException(String.format("There was an error backing up the database using `%s`. The `%s` process failed with code %s. Please see the server logs for more detail.", command, command, errorCode), cause);
+    }
 }

programming_language: Java
filename: MySQLBackupProcessor.java
change_type: ModificationType.MODIFY
diff: @@ -23,15 +23,14 @@ import com.thoughtworks.go.server.database.DbProperties;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.tools.ant.types.Commandline;
 import org.zeroturnaround.exec.ProcessExecutor;
+import org.zeroturnaround.exec.ProcessInitException;
 import org.zeroturnaround.exec.ProcessResult;
 import org.zeroturnaround.exec.stream.slf4j.Slf4jStream;
 
 import javax.sql.DataSource;
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
+import java.util.*;
 import java.util.concurrent.TimeoutException;
 
 import static org.apache.commons.lang3.StringUtils.isNotBlank;
@@ -39,16 +38,20 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
 @Slf4j
 public class MySQLBackupProcessor implements BackupProcessor {
 
+    private static final String COMMAND = "mysqldump";
+
     @Override
     public void backup(File targetDir, DataSource dataSource, DbProperties dbProperties) throws InterruptedException, TimeoutException, IOException {
-        ProcessResult processResult = createProcessExecutor(targetDir, dbProperties).execute();
-
-        if (processResult.getExitValue() == 0) {
-            log.info("MySQL backup finished successfully.");
-        } else {
-            log.warn("There was an error backing up the database using `mysqldump`. The `mysqldump` process exited with status code {}.", processResult.getExitValue());
-            throw new RuntimeException("There was an error backing up the database using `mysqldump`. The `mysqldump` process exited with status code " + processResult.getExitValue() +
-                    ". Please see the server logs for more errors");
+        try {
+            ProcessResult processResult = createProcessExecutor(targetDir, dbProperties).execute();
+
+            if (processResult.getExitValue() == 0) {
+                log.info("MySQL backup finished successfully.");
+            } else {
+                throwBackupError(COMMAND, processResult.getExitValue());
+            }
+        } catch (ProcessInitException e) {
+            throwBackupError(COMMAND, e.getErrorCode(), e.getCause());
         }
     }
 
@@ -60,16 +63,15 @@ public class MySQLBackupProcessor implements BackupProcessor {
     private ProcessExecutor createProcessExecutor(File targetDir, DbProperties dbProperties) {
         ConnectionUrl connectionUrlInstance = ConnectionUrl.getConnectionUrlInstance(dbProperties.url(), dbProperties.connectionProperties());
 
-        LinkedHashMap<String, String> env = new LinkedHashMap<>();
+        Map<String, String> env = new LinkedHashMap<>();
         if (isNotBlank(dbProperties.password())) {
             env.put("MYSQL_PWD", dbProperties.password());
         }
         // override with any user specified environment
         env.putAll(dbProperties.extraBackupEnv());
 
-        ArrayList<String> argv = new ArrayList<>();
-        argv.add("mysqldump");
-
+        List<String> argv = new ArrayList<>();
+        argv.add(COMMAND);
 
         String dbName = connectionUrlInstance.getDatabase();
         HostInfo mainHost = connectionUrlInstance.getMainHost();
@@ -87,7 +89,7 @@ public class MySQLBackupProcessor implements BackupProcessor {
             Collections.addAll(argv, Commandline.translateCommandline(dbProperties.extraBackupCommandArgs()));
         }
 
-        argv.add("--result-file=" + new File(targetDir, "db." + dbName).toString());
+        argv.add("--result-file=" + new File(targetDir, "db." + dbName));
         argv.add(connectionUrlInstance.getDatabase());
 
         ProcessExecutor processExecutor = new ProcessExecutor();

programming_language: Java
filename: PostgresqlBackupProcessor.java
change_type: ModificationType.MODIFY
diff: @@ -22,32 +22,33 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.tools.ant.types.Commandline;
 import org.postgresql.Driver;
 import org.zeroturnaround.exec.ProcessExecutor;
+import org.zeroturnaround.exec.ProcessInitException;
 import org.zeroturnaround.exec.ProcessResult;
 import org.zeroturnaround.exec.stream.slf4j.Slf4jStream;
 
 import javax.sql.DataSource;
 import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Properties;
+import java.util.*;
 
 import static org.apache.commons.lang3.StringUtils.isNotBlank;
 
 @Slf4j
 public class PostgresqlBackupProcessor implements BackupProcessor {
 
+    private static final String COMMAND = "pg_dump";
+
     @Override
     public void backup(File targetDir, DataSource dataSource, DbProperties dbProperties) throws Exception {
-        ProcessResult processResult = createProcessExecutor(targetDir, dbProperties).execute();
-
-        if (processResult.getExitValue() == 0) {
-            log.info("PostgreSQL backup finished successfully.");
-        } else {
-            log.warn("There was an error backing up the database using `pg_dump`. The `pg_dump` process exited with status code {}.", processResult.getExitValue());
-            throw new RuntimeException("There was an error backing up the database using `pg_dump`. The `pg_dump` process exited with status code " + processResult.getExitValue() +
-                    ". Please see the server logs for more errors.");
+        try {
+            ProcessResult processResult = createProcessExecutor(targetDir, dbProperties).execute();
 
+            if (processResult.getExitValue() == 0) {
+                log.info("PostgreSQL backup finished successfully.");
+            } else {
+                throwBackupError(COMMAND, processResult.getExitValue());
+            }
+        } catch (ProcessInitException e) {
+            throwBackupError(COMMAND, e.getErrorCode(), e.getCause());
         }
     }
 
@@ -60,8 +61,7 @@ public class PostgresqlBackupProcessor implements BackupProcessor {
         Properties connectionProperties = dbProperties.connectionProperties();
         Properties pgProperties = Driver.parseURL(dbProperties.url(), connectionProperties);
 
-        ArrayList<String> argv = new ArrayList<>();
-        LinkedHashMap<String, String> env = new LinkedHashMap<>();
+        Map<String, String> env = new LinkedHashMap<>();
         if (isNotBlank(dbProperties.password())) {
             env.put("PGPASSWORD", dbProperties.password());
         }
@@ -69,8 +69,10 @@ public class PostgresqlBackupProcessor implements BackupProcessor {
         // override with any user specified environment
         env.putAll(dbProperties.extraBackupEnv());
 
+        List<String> argv = new ArrayList<>();
+        argv.add(COMMAND);
+
         String dbName = pgProperties.getProperty("PGDBNAME");
-        argv.add("pg_dump");
         argv.add("--host=" + pgProperties.getProperty("PGHOST"));
         argv.add("--port=" + pgProperties.getProperty("PGPORT"));
         argv.add("--dbname=" + dbName);

programming_language: Java