CVE-2021-21270: Cleartext Transmission of Sensitive Information (CWE-319)
value: OctopusDSC is a PowerShell module with DSC resources that can be used to install and configure an Octopus Deploy Server and Tentacle agent. In OctopusDSC version 4.0.977 and earlier a customer API key used to connect to Octopus Server is exposed via logging in plaintext. This vulnerability is patched in version 4.0.1002.
CWE description: The product transmits sensitive or security-critical data in cleartext in a communication channel that can be sniffed by unauthorized actors.
CVSS v3 base score: 5.5
Published: 2021-01-22T18:15Z
Repository: https://github.com/OctopusDeploy/OctopusDSC
Language: Other
Files changed: OctopusDSC/DSCResources/cTentacleAgent/cTentacleAgent.psm1, OctopusDSC/OctopusDSCHelpers.ps1
Commit date: 2021-01-20 15:09:30 +1100
Commit message: [Bug fix] Prevent exposing API key secret in stdout logging (#270)

* remove Write-Verbose line

* use masking function in commands

* simplify masking function
Diff: diff --git a/OctopusDSC/DSCResources/cTentacleAgent/cTentacleAgent.psm1 b/OctopusDSC/DSCResources/cTentacleAgent/cTentacleAgent.psm1
index 366e789..8d6b85b 100644
--- a/OctopusDSC/DSCResources/cTentacleAgent/cTentacleAgent.psm1
+++ b/OctopusDSC/DSCResources/cTentacleAgent/cTentacleAgent.psm1
@@ -1276,8 +1276,7 @@ function Register-Tentacle {
 
     if ($TenantedDeploymentParticipation -ne "") {
         $registerArguments += @("--tenanted-deployment-participation", $TenantedDeploymentParticipation)
     }
 
-    Write-Verbose "Registering with arguments: $registerArguments"
     Invoke-TentacleCommand $registerArguments
 }
diff --git a/OctopusDSC/OctopusDSCHelpers.ps1 b/OctopusDSC/OctopusDSCHelpers.ps1
index c2f5593..18ee4f3 100644
--- a/OctopusDSC/OctopusDSCHelpers.ps1
+++ b/OctopusDSC/OctopusDSCHelpers.ps1
@@ -151,50 +151,39 @@ Function Invoke-WithRetries {
     }
 
     return $returnvalue
 }
 
-Function Get-MaskedOutput
-{
+function Get-MaskedOutput {
     [CmdletBinding()]
     param($arguments)
 
-    $reg = [System.Text.RegularExpressions.RegEx]::new("--masterkey|--password|--license",
-                [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
+    $singleAsterixArgs = "--masterkey|--license|--licence|--trust|--password|--remove-trust|--apikey|--pw|--pfx-password|--proxyPassword";
+    $connectionStringArgs = "--connectionstring";
 
-    if(($arguments -match "--masterkey|--password|--license"))
-    {
-        for($x=0;$x -lt $arguments.count; $x++)
-        {
-            if(($arguments[$x] -match "--masterkey|--password|--license|--trust|--remove-trust|--apikey|--password|--pw|--pfx-password|--proxyPassword"))
-            {
-                $arguments[$x+1] = $arguments[$x+1] -replace ".", "*"
-            }
+    # Scrub sensitive values
+    for($x=0; $x -lt $arguments.count; $x++) {
+        if($arguments[$x] -match $singleAsterixArgs) {
+            $arguments[$x+1] = "**********"
+        } elseif($arguments[$x] -match $connectionStringArgs) {
+            $arguments[$x+1] = $arguments[$x+1] -replace "(password|pwd)=[^;|`"]*", "`$1=********"
         }
-        $out = $arguments
-    }
-    elseif(($arguments -match "password|pwd"))
-    {
-        $out = $arguments -replace "(password|pwd)=[^;|`"]*", "`$1=********"
-    }
-    else
-    {
-        $out = @("************************")
     }
-    return $out
+    return $arguments
+}
+
+function Write-VerboseWithMaskedCommand ($cmdArgs) {
+    $copiedarguments = @() # hack to pass a copy of the array, not a reference
+    $copiedarguments += $cmdArgs
+    $maskedarguments = Get-MaskedOutput $copiedarguments
+    Write-Verbose "Executing command '$octopusServerExePath $($maskedarguments -join ' ')'"
 }
 
 function Invoke-OctopusServerCommand ($cmdArgs) {
-    # todo: fix this up
-    if ((($cmdArgs -match "masterkey|password|license|pwd=").Count -eq 0)) {
-        Write-Verbose "Executing command '$octopusServerExePath $($cmdArgs -join ' ')'"
-    } else {
-        $copiedarguments = @() # hack to pass a copy of the array, not a reference
-        $copiedarguments += $cmdArgs
-        $maskedarguments = Get-MaskedOutput $copiedarguments
-        Write-Verbose "Executing command '$octopusServerExePath $($maskedarguments -join ' ')'"
-    }
+
+    Write-VerboseWithMaskedCommand($cmdArgs);
+
     $LASTEXITCODE = 0
     $output = & $octopusServerExePath $cmdArgs 2>&1
 
     Write-CommandOutput $output
     if (($null -ne $LASTEXITCODE) -and ($LASTEXITCODE -ne 0)) {
@@ -208,19 +197,13 @@ function Test-TentacleExecutableExists {
     $tentacleDir = "${env:ProgramFiles}\Octopus Deploy\Tentacle"
     return ((test-path $tentacleDir) -and (test-path "$tentacleDir\tentacle.exe"))
 }
 
 function Invoke-TentacleCommand ($cmdArgs) {
-    # todo: fix this up
-    if ((($cmdArgs -match "masterkey|password|license|pwd=").Count -eq 0)) {
-        Write-Verbose "Executing command '$tentacleExePath $($cmdArgs -join ' ')'"
-    } else {
-        $copiedarguments = @() # hack to pass a copy of the array, not a reference
-        $copiedarguments += $cmdArgs
-        $maskedarguments = Get-MaskedOutput $copiedarguments
-        Write-Verbose "Executing command '$tentacleExePath $($maskedarguments -join ' ')'"
-    }
+
+    Write-VerboseWithMaskedCommand($cmdArgs);
+
     $LASTEXITCODE = 0
     $output = & $tentacleExePath $cmdArgs 2>&1
 
     Write-CommandOutput $output
     if (($null -ne $LASTEXITCODE) -and ($LASTEXITCODE -ne 0)) {

Vulnerable code:     Write-Verbose "Registering with arguments: $registerArguments"
Function Get-MaskedOutput
{
    $reg = [System.Text.RegularExpressions.RegEx]::new("--masterkey|--password|--license",
                [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
    if(($arguments -match "--masterkey|--password|--license"))
    {
        for($x=0;$x -lt $arguments.count; $x++)
        {
            if(($arguments[$x] -match "--masterkey|--password|--license|--trust|--remove-trust|--apikey|--password|--pw|--pfx-password|--proxyPassword"))
            {
                $arguments[$x+1] = $arguments[$x+1] -replace ".", "*"
            }
        $out = $arguments
    }
    elseif(($arguments -match "password|pwd"))
    {
        $out = $arguments -replace "(password|pwd)=[^;|`"]*", "`$1=********"
    }
    else
    {
        $out = @("************************")
    return $out
    # todo: fix this up
    if ((($cmdArgs -match "masterkey|password|license|pwd=").Count -eq 0)) {
        Write-Verbose "Executing command '$octopusServerExePath $($cmdArgs -join ' ')'"
    } else {
        $copiedarguments = @() # hack to pass a copy of the array, not a reference
        $copiedarguments += $cmdArgs
        $maskedarguments = Get-MaskedOutput $copiedarguments
        Write-Verbose "Executing command '$octopusServerExePath $($maskedarguments -join ' ')'"
    }
    # todo: fix this up
    if ((($cmdArgs -match "masterkey|password|license|pwd=").Count -eq 0)) {
        Write-Verbose "Executing command '$tentacleExePath $($cmdArgs -join ' ')'"
    } else {
        $copiedarguments = @() # hack to pass a copy of the array, not a reference
        $copiedarguments += $cmdArgs
        $maskedarguments = Get-MaskedOutput $copiedarguments
        Write-Verbose "Executing command '$tentacleExePath $($maskedarguments -join ' ')'"
    }
Fixed code: function Get-MaskedOutput {
    $singleAsterixArgs = "--masterkey|--license|--licence|--trust|--password|--remove-trust|--apikey|--pw|--pfx-password|--proxyPassword";
    $connectionStringArgs = "--connectionstring";
    # Scrub sensitive values
    for($x=0; $x -lt $arguments.count; $x++) {
        if($arguments[$x] -match $singleAsterixArgs) {
            $arguments[$x+1] = "**********"
        } elseif($arguments[$x] -match $connectionStringArgs) {
            $arguments[$x+1] = $arguments[$x+1] -replace "(password|pwd)=[^;|`"]*", "`$1=********"
    return $arguments
}

function Write-VerboseWithMaskedCommand ($cmdArgs) {
    $copiedarguments = @() # hack to pass a copy of the array, not a reference
    $copiedarguments += $cmdArgs
    $maskedarguments = Get-MaskedOutput $copiedarguments
    Write-Verbose "Executing command '$octopusServerExePath $($maskedarguments -join ' ')'"

    Write-VerboseWithMaskedCommand($cmdArgs);


    Write-VerboseWithMaskedCommand($cmdArgs);