CVE-2022-39309 (Java, published 2022-10-14T20:15Z)
Description: value: GoCD is a continuous delivery server. GoCD helps you automate and streamline the build-test-release cycle for continuous delivery of your product. GoCD versions prior to 21.1.0 leak the symmetric key used to encrypt/decrypt any secure variables/secrets in GoCD configuration to authenticated agents. A malicious/compromised agent may then expose that key from memory, and potentially allow an attacker the ability to decrypt secrets intended for other agents/environments if they also are able to obtain access to encrypted configuration values from the GoCD server. This issue is fixed in GoCD version 21.1.0. There are currently no known workarounds.
Severity: nan (CVSS v3 base score: 6.5)
Weakness: CWE-668 \u2014 Exposure of Resource to Wrong Sphere
The product exposes a resource to the wrong control sphere, providing unintended actors with inappropriate access to the resource.
Repository: https://github.com/gocd/gocd
Files changed:
api/api-server-maintenance-mode-v1/src/test/groovy/com/thoughtworks/go/apiv1/servermaintenancemode/representers/MaintenanceModeInfoRepresenterTest.groovy
common/src/test/java/com/thoughtworks/go/config/materials/perforce/P4MaterialTest.java
common/src/test/java/com/thoughtworks/go/config/materials/tfs/TfsMaterialTest.java
common/src/test/java/com/thoughtworks/go/domain/materials/DummyMaterial.java
common/src/test/java/com/thoughtworks/go/domain/materials/svn/SvnMaterialTest.java
common/src/test/java/com/thoughtworks/go/server/service/MagicalMaterialAndMaterialConfigConversionTest.java
domain/src/main/java/com/thoughtworks/go/config/materials/Materials.java
domain/src/main/java/com/thoughtworks/go/config/materials/ScmMaterial.java
domain/src/main/java/com/thoughtworks/go/config/materials/git/GitMaterial.java
domain/src/main/java/com/thoughtworks/go/config/materials/mercurial/HgMaterial.java
domain/src/main/java/com/thoughtworks/go/config/materials/perforce/P4Material.java
domain/src/main/java/com/thoughtworks/go/config/materials/svn/SvnMaterial.java
domain/src/main/java/com/thoughtworks/go/config/materials/tfs/TfsMaterial.java
domain/src/main/java/com/thoughtworks/go/domain/materials/TestingMaterial.java
domain/src/main/java/com/thoughtworks/go/domain/materials/tfs/TfsMaterialInstance.java
domain/src/test/java/com/thoughtworks/go/helper/MaterialsMother.java
server/src/test-fast/java/com/thoughtworks/go/server/service/MaterialServiceTest.java
server/src/test-integration/java/com/thoughtworks/go/domain/materials/tfs/TfsMaterialPersistenceTest.java
Commit message: SCMMaterial changes #000

* SCMMaterial unlike SCMMaterialConfig objects are used for polling,
  they do not need to encrypt the password. Hence removing the
  encryptedPassword attribute.


Vulnerable code:
                "encrypted_password": svnMaterial.encryptedPassword
    @Test
    void shouldEncryptP4Password() throws Exception {
        GoCipher mockGoCipher = mock(GoCipher.class);
        when(mockGoCipher.encrypt("password")).thenReturn("encrypted");

        P4Material p4Material = new P4Material("example.com:1818", "view", mockGoCipher);
        p4Material.setPassword("password");
        p4Material.ensureEncrypted();

        assertThat(p4Material.getEncryptedPassword()).isEqualTo("encrypted");
        assertThat(p4Material.getPassword()).isNull();
    }

    @Test
    void shouldDecryptP4Password() throws Exception {
        GoCipher mockGoCipher = mock(GoCipher.class);
        when(mockGoCipher.decrypt("encrypted")).thenReturn("password");

        P4Material p4Material = new P4Material("example.com:1818", "view", mockGoCipher);
        ReflectionUtil.setField(p4Material, "encryptedPassword", "encrypted");
        p4Material.getPassword();

        assertThat(p4Material.getPassword()).isEqualTo("password");
    }

        tfsMaterialFirstCollectionFirstProject = new TfsMaterial(goCipher, new UrlArgument(TFS_FIRST_COLLECTION_URL), USERNAME, DOMAIN, PASSWORD, TFS_FIRST_PROJECT);
        tfsMaterialFirstCollectionSecondProject = new TfsMaterial(goCipher, new UrlArgument(TFS_FIRST_COLLECTION_URL), USERNAME, DOMAIN, PASSWORD, TFS_SECOND_PROJECT);
        TfsMaterial tfsMaterial = new TfsMaterial(new GoCipher(), new UrlArgument("my-url"), "loser", DOMAIN, "foo_bar_baz", "/dev/null");
        TfsMaterial tfsMaterial = new TfsMaterial(new GoCipher(), new UrlArgument("my-url"), "loser", DOMAIN, "foo_bar_baz", "/dev/null");
        TfsMaterial tfsMaterial = new TfsMaterial(new GoCipher(), new UrlArgument("http://foo:bar@my-url.com"), "loser", DOMAIN, "foo_bar_baz", "/dev/null"
        tfsMaterial = new TfsMaterial(new GoCipher(), null, "loser", DOMAIN, "foo_bar_baz", "/dev/null");
        TfsMaterial tfsMaterial = new TfsMaterial(new GoCipher(), new UrlArgument("http://foo:bar@my-url.com"), "loser", DOMAIN, "foo_bar_baz", "/dev/null"
        tfsMaterial = new TfsMaterial(new GoCipher(), null, "loser", DOMAIN, "foo_bar_baz", "/dev/null");
    @Test
    void shouldEncryptTfsPasswordAndMarkPasswordAsNull() throws Exception {
        GoCipher mockGoCipher = mock(GoCipher.class);
        when(mockGoCipher.encrypt("password")).thenReturn("encrypted");

        TfsMaterial tfsMaterial = new TfsMaterial(mockGoCipher, new UrlArgument("/foo"), "username", DOMAIN, "password", "");
        tfsMaterial.ensureEncrypted();

        assertThat(tfsMaterial.getPassword()).isNull();
        assertThat(tfsMaterial.getEncryptedPassword()).isEqualTo("encrypted");
    }

    @Test
    void shouldDecryptTfsPassword() throws Exception {
        GoCipher mockGoCipher = mock(GoCipher.class);
        when(mockGoCipher.decrypt("encrypted")).thenReturn("password");

        TfsMaterial tfsMaterial = new TfsMaterial(mockGoCipher, new UrlArgument("/foo"), "username", DOMAIN, null, "");

        ReflectionUtil.setField(tfsMaterial, "encryptedPassword", "encrypted");

        tfsMaterial.ensureEncrypted();
        assertThat(tfsMaterial.getPassword()).isEqualTo("password");
    }

        TfsMaterial material = new TfsMaterial(mockGoCipher, new UrlArgument("/foo"), "username", DOMAIN, "password", "");
    @Test
    void shouldErrorOutIfDecryptionFails() throws CryptoException {
        GoCipher mockGoCipher = mock(GoCipher.class);
        String fakeCipherText = "fake cipher text";
        when(mockGoCipher.decrypt(fakeCipherText)).thenThrow(new CryptoException("exception"));
        TfsMaterial material = new TfsMaterial(mockGoCipher, new UrlArgument("/foo"), "username", DOMAIN, "password", "");
        ReflectionUtil.setField(material, "encryptedPassword", fakeCipherText);
        try {
            material.getPassword();
            fail("Should have thrown up");
        } catch (Exception e) {
            assertThat(e.getMessage()).isEqualTo("Could not decrypt the password to get the real password");
        }
    }

    @Test
    void shouldErrorOutIfEncryptionFails() throws Exception {
        GoCipher mockGoCipher = mock(GoCipher.class);
        when(mockGoCipher.encrypt("password")).thenThrow(new CryptoException("exception"));
        try {
            new TfsMaterial(mockGoCipher, new UrlArgument("/foo"), "username", DOMAIN, "password", "");
            fail("Should have thrown up");
        } catch (Exception e) {
            assertThat(e.getMessage()).isEqualTo("Password encryption failed. Please verify your cipher key.");
        }
    }

        TfsMaterial material = new TfsMaterial(new GoCipher(), new UrlArgument("http://url/"), "user", "domain", "password", "$project/path/");
        TfsMaterial material = new TfsMaterial(new GoCipher(), new UrlArgument("http://url/"), "user", "domain", "password", "$project/path/");
        TfsMaterial material = new TfsMaterial(new GoCipher(), new UrlArgument("http://username:password@tfsrepo.com"), "username", "domain", "password", "$project/path/");
        TfsMaterial material = new TfsMaterial(new GoCipher(), new UrlArgument("http://username:password@tfsrepo.com"), "username", "domain", "password", "$project/path/");
            TfsMaterial tfsMaterial = new TfsMaterial(new GoCipher(), new UrlArgument("some-url"), null, null, "badger", null);
            TfsMaterial tfsMaterial = new TfsMaterial(new GoCipher(), new UrlArgument("some-url"), null, null, "{{SECRET:[secret_config_id][lookup_pass]}}", null);
            TfsMaterial tfsMaterial = new TfsMaterial(new GoCipher(), new UrlArgument("some-url"), null, null, "{{SECRET:[secret_config_id][lookup_pass]}}", null);
            TfsMaterial tfsMaterial = new TfsMaterial(new GoCipher(), new UrlArgument("some-url"), null, null, "{{SECRET:[secret_config_id][lookup_pass]}}", null);
        TfsMaterial material = new TfsMaterial(mock(GoCipher.class), new UrlArgument(TFS_FIRST_COLLECTION_URL), USERNAME, "", PASSWORD, TFS_FIRST_PROJECT);
import com.thoughtworks.go.security.GoCipher;
        super("DummyMaterial", new GoCipher());
    @Test
    void shouldEncryptSvnPasswordAndMarkPasswordAsNull() throws Exception {
        GoCipher mockGoCipher = mock(GoCipher.class);
        when(mockGoCipher.encrypt("password")).thenReturn("encrypted");

        SvnMaterial material = new SvnMaterial("/foo", "username", "password", false, mockGoCipher);
        material.ensureEncrypted();

        assertThat(material.getPassword()).isNull();
        assertThat(material.getEncryptedPassword()).isEqualTo("encrypted");
    }

    @Test
    void shouldDecryptSvnPassword() throws Exception {
        GoCipher mockGoCipher = mock(GoCipher.class);
        when(mockGoCipher.decrypt("encrypted")).thenReturn("password");

        SvnMaterial material = new SvnMaterial("/foo", "username", null, false, mockGoCipher);
        ReflectionUtil.setField(material, "encryptedPassword", "encrypted");

        material.ensureEncrypted();
        assertThat(material.getPassword()).isEqualTo("password");
    }

    @Test
    void shouldErrorOutIfDecryptionFails() throws CryptoException {
        GoCipher mockGoCipher = mock(GoCipher.class);
        String fakeCipherText = "fake cipher text";
        when(mockGoCipher.decrypt(fakeCipherText)).thenThrow(new CryptoException("exception"));
        SvnMaterial material = new SvnMaterial("/foo", "username", null, false, mockGoCipher);
        ReflectionUtil.setField(material, "encryptedPassword", fakeCipherText);
        try {
            material.getPassword();
            fail("Should have thrown up");
        } catch (Exception e) {
            assertThat(e.getMessage()).isEqualTo("Could not decrypt the password to get the real password");
        }
    }

    @Test
    void shouldErrorOutIfEncryptionFails() throws Exception {
        GoCipher mockGoCipher = mock(GoCipher.class);
        when(mockGoCipher.encrypt("password")).thenThrow(new CryptoException("exception"));
        try {
            new SvnMaterial("/foo", "username", "password", false, mockGoCipher);
            fail("Should have thrown up");
        } catch (Exception e) {
            assertThat(e.getMessage()).isEqualTo("Password encryption failed. Please verify your cipher key.");
        }
    }

            assertThat("Password setting is wrong for: " + material.getClass(), ReflectionUtil.getField(material, "password"), is(nullValue()));
            assertThat("Password setting is wrong for: " + material.getClass(), ReflectionUtil.getField(material, "encryptedPassword"), is(not(nullValue())));
import com.thoughtworks.go.security.GoCipher;
        return getExistingOrDefaultMaterial(new TfsMaterial(new GoCipher(), new UrlArgument(""), "", "", "", ""));
import com.thoughtworks.go.security.CryptoException;
import com.thoughtworks.go.security.GoCipher;
import static com.thoughtworks.go.util.ExceptionUtils.bomb;
import static org.apache.commons.lang3.StringUtils.isBlank;
    protected final GoCipher goCipher;
    protected String encryptedPassword;
    public ScmMaterial(String typeName, GoCipher goCipher) {
        this.goCipher = goCipher;
        if (StringUtils.isBlank(passwordToSet)) {
            encryptedPassword = null;
        }
        this.encryptedPassword = StringUtils.stripToNull(encryptedPassword);

        if (this.password == null) {
            return;
        }
        try {
            this.encryptedPassword = this.goCipher.encrypt(password);
        } catch (Exception e) {
            bomb("Password encryption failed. Please verify your cipher key.", e);
        }
        this.password = null;
    public final void setEncryptedPassword(String encryptedPassword) {
        this.encryptedPassword = encryptedPassword;
    }

    public final String getEncryptedPassword() {
        return encryptedPassword;
    }

        return currentPassword();
    public final String currentPassword() {
        try {
            return isBlank(encryptedPassword) ? null : this.goCipher.decrypt(encryptedPassword);
        } catch (CryptoException e) {
            throw new RuntimeException("Could not decrypt the password to get the real password", e);
        }
    }

import com.thoughtworks.go.security.GoCipher;
        super(TYPE, new GoCipher());
import com.thoughtworks.go.security.GoCipher;
        super(TYPE, new GoCipher());
    private P4Material(GoCipher goCipher) {
        super(TYPE, goCipher);
    public P4Material(String serverAndPort, String view, GoCipher goCipher) {
        this(goCipher);
    public P4Material(String serverAndPort, String view) {
        this(serverAndPort, view, new GoCipher());
    }

    public P4Material(String url, String view, String userName) {
        this(url, view);
    public P4Material(String url, String view, String userName, String folder) {
        this(url, view, userName, folder, new GoCipher());
    }

        this(config.getUrl(), config.getView(), config.getUserName(), config.getFolder(), config.getGoCipher());
    private P4Material(String serverAndPort, String view, String userName, String folder, GoCipher goCipher) {
        this(goCipher);
    private SvnMaterial(GoCipher goCipher) {
        super("SvnMaterial", goCipher);
    }

        super("SvnMaterial", goCipher);
import com.thoughtworks.go.security.GoCipher;
    public TfsMaterial(GoCipher goCipher) {
        super(TYPE, goCipher);
    public TfsMaterial(GoCipher goCipher, UrlArgument url, String userName, String domain, String password, String projectPath) {
        this(goCipher);
        this(config.getGoCipher(), new UrlArgument(config.getUrl()), config.getUserName(), config.getDomain(), config.getPassword(), config.getProjectPath());
import com.thoughtworks.go.security.GoCipher;
        super(TYPE, new GoCipher());
import com.thoughtworks.go.security.GoCipher;
        TfsMaterial tfsMaterial = new TfsMaterial(new GoCipher(), new UrlArgument(url), username, domain, password, projectPath);
import com.thoughtworks.go.security.GoCipher;
        return new TfsMaterial(new GoCipher(), new UrlArgument(url), "username", "domain", "password", "project-path");
import com.thoughtworks.go.security.GoCipher;
    public static RequestDataPoints TFS_LATEST_MODIFICATIONS = new RequestDataPoints(new TfsMaterial(mock(GoCipher.class)) {
import com.thoughtworks.go.security.GoCipher;
        TfsMaterial tfsCfg = new TfsMaterial(new GoCipher(), new UrlArgument("url"), "loser", "CORPORATE", "password", "/dev/null");
        TfsMaterial tfsCfg = new TfsMaterial(new GoCipher(), new UrlArgument("url"), "loser", "CORPORATE", "foo_bar_baz", "/dev/null");
        TfsMaterial tfsCfg = new TfsMaterial(new GoCipher(), new UrlArgument("url"), "loser", "CORPORATE", "foo_bar_baz", "/dev/null");

Fixed code:
                "encrypted_password": svnMaterial.config().getEncryptedPassword()
        tfsMaterialFirstCollectionFirstProject = new TfsMaterial(new UrlArgument(TFS_FIRST_COLLECTION_URL), USERNAME, DOMAIN, PASSWORD, TFS_FIRST_PROJECT);
        tfsMaterialFirstCollectionSecondProject = new TfsMaterial(new UrlArgument(TFS_FIRST_COLLECTION_URL), USERNAME, DOMAIN, PASSWORD, TFS_SECOND_PROJECT);
        TfsMaterial tfsMaterial = new TfsMaterial(new UrlArgument("my-url"), "loser", DOMAIN, "foo_bar_baz", "/dev/null");
        TfsMaterial tfsMaterial = new TfsMaterial(new UrlArgument("my-url"), "loser", DOMAIN, "foo_bar_baz", "/dev/null");
        TfsMaterial tfsMaterial = new TfsMaterial(new UrlArgument("http://foo:bar@my-url.com"), "loser", DOMAIN, "foo_bar_baz", "/dev/null"
        tfsMaterial = new TfsMaterial(null, "loser", DOMAIN, "foo_bar_baz", "/dev/null");
        TfsMaterial tfsMaterial = new TfsMaterial(new UrlArgument("http://foo:bar@my-url.com"), "loser", DOMAIN, "foo_bar_baz", "/dev/null"
        tfsMaterial = new TfsMaterial(null, "loser", DOMAIN, "foo_bar_baz", "/dev/null");
        TfsMaterial material = new TfsMaterial(new UrlArgument("/foo"), "username", DOMAIN, "password", "");
        TfsMaterial material = new TfsMaterial(new UrlArgument("http://url/"), "user", "domain", "password", "$project/path/");
        TfsMaterial material = new TfsMaterial(new UrlArgument("http://url/"), "user", "domain", "password", "$project/path/");
        TfsMaterial material = new TfsMaterial(new UrlArgument("http://username:password@tfsrepo.com"), "username", "domain", "password", "$project/path/");
        TfsMaterial material = new TfsMaterial(new UrlArgument("http://username:password@tfsrepo.com"), "username", "domain", "password", "$project/path/");
            TfsMaterial tfsMaterial = new TfsMaterial(new UrlArgument("some-url"), null, null, "badger", null);
            TfsMaterial tfsMaterial = new TfsMaterial(new UrlArgument("some-url"), null, null, "{{SECRET:[secret_config_id][lookup_pass]}}", null);
            TfsMaterial tfsMaterial = new TfsMaterial(new UrlArgument("some-url"), null, null, "{{SECRET:[secret_config_id][lookup_pass]}}", null);
            TfsMaterial tfsMaterial = new TfsMaterial(new UrlArgument("some-url"), null, null, "{{SECRET:[secret_config_id][lookup_pass]}}", null);
        TfsMaterial material = new TfsMaterial(new UrlArgument(TFS_FIRST_COLLECTION_URL), USERNAME, "", PASSWORD, TFS_FIRST_PROJECT);
        super("DummyMaterial");
            assertThat("Password setting is wrong for: " + material.getClass(), ReflectionUtil.getField(material, "password"), is("pass"));
        return getExistingOrDefaultMaterial(new TfsMaterial(new UrlArgument(""), "", "", "", ""));
    public ScmMaterial(String typeName) {
        return password;
        super(TYPE);
        super(TYPE);
    private P4Material() {
        super(TYPE);
    public P4Material(String serverAndPort, String view) {
        this();
    public P4Material(String serverAndPort, String view, String userName) {
        this(serverAndPort, view);
        this(config.getUrl(), config.getView(), config.getUserName(), config.getFolder());
    public P4Material(String serverAndPort, String view, String userName, String folder) {
        this();
        super("SvnMaterial");
    public TfsMaterial() {
        super(TYPE);
    public TfsMaterial(UrlArgument url, String userName, String domain, String password, String projectPath) {
        this();
        this(new UrlArgument(config.getUrl()), config.getUserName(), config.getDomain(), config.getPassword(), config.getProjectPath());
        super(TYPE);
        TfsMaterial tfsMaterial = new TfsMaterial(new UrlArgument(url), username, domain, password, projectPath);
        return new TfsMaterial(new UrlArgument(url), "username", "domain", "password", "project-path");
    public static RequestDataPoints TFS_LATEST_MODIFICATIONS = new RequestDataPoints(new TfsMaterial() {
        TfsMaterial tfsCfg = new TfsMaterial(new UrlArgument("url"), "loser", "CORPORATE", "password", "/dev/null");
        TfsMaterial tfsCfg = new TfsMaterial(new UrlArgument("url"), "loser", "CORPORATE", "foo_bar_baz", "/dev/null");
        TfsMaterial tfsCfg = new TfsMaterial(new UrlArgument("url"), "loser", "CORPORATE", "foo_bar_baz", "/dev/null");