Page 1 of 1

Use of the API without .NET, JAVA, or gacmd. A proof-of-concept in Powershell.

Posted: Tue Jan 10, 2017 6:28 pm
by Cignul9
Here's a Powershell script that doesn't drive gacmd, use the .NET api or the Java api. I wiresharked the web calls gacmd was making. Powershell pretends to be that client to the server. The good news is this is sessionless; one web call gives one result. The data in motion security is totally dependent upon the fact that this web call should happen over a secure connection, ie: https. Going through a reverse proxy should work just fine.

Now, I didn't completely reverse engineer gacmd. I just wanted to show that it can be done and encourage the GoAnywhere devs to release the web call details, maybe give us a wsdl or a wadl (and for god sake please provide support for json bodies or something more reasonable). I only support three commands. Hack this up and add more. While your at it here are some improvements you could throw in:
  • * ParameterSets for each command
    * Parameter descriptors, maybe take input from the pipeline, that kind of thing
    * Better exception handling
    * Better output format. I'm just dumping xml. I'm lazy.
    * More commands supported (lookin at you for that wsdl, devs)
    * Some kind of GPL license up in there. I don't care just take it.
This is useful if you want to integrate with CRM, for example, and your CRM people want web calls not Java or .NET API's. This way you can demonstrate how to do that. If anyone's interested I can post decoded HTTP so you can get a raw look at a request and response.
Code: Select all
[CmdletBinding()]
param(
  [string]$url = [ your server url here, including the port ex: https://mft.mycompany.com:8000 ],
  [string]$user = [ the account name use to manage web users ],
  [string]$password [ the account password you use to manage web users ],
  [string]$command,
  [string]$webUserTemplate,
  [string]$webUserName,
  [string]$webUserPassword,
  [string]$webUserFirstName,
  [string]$webUserLastName,
  [string]$webUserDescription,
  [string]$webUserEmail,
  [switch]$generatePassword,
  [switch]$forcePasswordChange,
  [switch]$emailPassword
)
function Create-MultiPartBody{
  param(
    [object]$parts,
    [string]$boundary
  )
  $body = ''
  foreach($part in $Parts.GetEnumerator()){
    if($part.Name -eq 'commandAction'){
      $disposition = "form-data; name=`"file`"; filename=`"$($part.Name)`""
      $type = 'application/octet-stream; charset=ISO-8859-1'
      $encoding = 'binary'
    }
    else{
      $disposition = "form-data; name=`"$($part.Name)`""
      $type = 'text/plain; charset=UTF-8'
      $encoding = '8bit'
    }
    $bodySegment = @"
--$($boundary)
Content-Disposition: $($disposition)
Content-Type: $($type)
Content-Transfer-Encoding: $($encoding)

$($part.Value)

"@
    $body += $bodySegment
  }
  $body += "--$($boundary)--`n"
  $body.Replace("`n","`r`n")
  return $body
}
function Create-CommandXml{
  param(
    [object]$command
  )
  [xml]$doc = ''
  $rootNode = $doc.CreateElement('command')
  [void]$doc.AppendChild($rootNode)
  $command.GetEnumerator() | ?{$_.Value -ne $null} | %{
    $parameter = $doc.CreateElement($_.Name)
    if($_.Value -is [boolean] -or $_.Value -is [switch]){
      $value = $_.Value.ToString().ToLower()
    }else{
      $value = $_.Value
    }
    $parameter.InnerText = $value
    [void]$rootNode.AppendChild($parameter)
  }
  return $doc.OuterXml.Trim()
}
function Send-WebRequest{
  param(
    [string]$url,
    [object]$contentParts
  )
  $apiURL = "$($url)/goanywhere/servlets/commandCenter"
  Write-Verbose "URL: `n$($apiURL)"
  $randomString = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes([System.Guid]::NewGuid().ToString()))
  $boundary = $randomString.Substring($randomString.Length-39)
  $userAgent = 'Jakarta Commons-HttpClient/3.1'
  $headers = [ordered]@{
    'Transfer-Encoding' = 'chunked';
    'Content-Type' =  "multipart/form-data; boundary=$($boundary)";
  }
  $body = Create-MultiPartBody $contentParts $boundary
  Write-Verbose "Body: `n$($body)"
  try{
    $response = Invoke-WebRequest `
      -Uri $apiURL `
      -DisableKeepAlive `
      -UserAgent $userAgent `
      -Headers $headers `
      -Method POST `
      -Body $body
  }
  catch{
    Write-Verbose $_.Exception.Message
  }
  return $response
}
$commandActions = @{
  deleteWebUser = [ordered]@{
    userName              = $webUserName;
  }
  addWebUser = [ordered]@{
    webUserTemplate       = $webUserTemplate;
    userName              = $webUserName;
    userPassword          = $webUserPassword;
    firstName             = $webUserFirstName;
    lastName              = $webUserLastName;
    description           = $webUserDescription;
    email                 = $webUserEmail;
  }
  resetWebUserPassword = [ordered]@{
    userName              = $webUserName;
    generatePassword      = $generatePassword;
    userPassword          = $webUserPassword;
    forcePasswordChange   = $forcePasswordChange;
    displayPassword       = $true;
    emailPassword         = $emailPassword;
  }
}
$contentParts = [ordered]@{
  command = $command
  user = $user;
  password = $password;
  passwordIsEncrypted = 'false';
  commandAction = Create-CommandXml $commandActions.$Command
}
$response = Send-WebRequest $url $contentParts
$response.Content

#.\Set-MFTWebUser.ps1 -webUserName 'TestUser' -command 'addWebUser' -webUserDescription 'Delete this user' -webUserEmail 'thedudeabides@gmail.com'
#.\Set-MFTWebUser.ps1 -webUserName 'TestUser' -command 'resetWebUserPassword' -generatePassword -emailPassword
#.\Set-MFTWebUser.ps1 -webUserName 'TestUser' -command 'deleteWebUser'