Zabbix Error “an array is expected” with Powershell

When trying to interact with the Zabbix-API via Powershell one might run into the problem, that an API request will not be executed successfully, but an error similar to the following one, will be displayed:

  code message         data                                                           
  ---- -------         ----                                                           
-32602 Invalid params. Invalid parameter "/1/filter/conditions": an array is expected.

Especially when using some code like in the below snippet which uses @(…) and @{…} to define data via arrays and hashtables:

function ZbxUpdateTestAction
{

    $params = @{
        body =  @{
            "jsonrpc"= "2.0"
            "method"= "action.update"
            "params"= @{
                "actionid" = "117"
                "status" = 0
                "filter" = @{
                    "evaltype" = 0
                    "conditions" = @(
                        @{
                            "conditiontype" = 4
                            "operator" = 5
                            "value"= 3
                        }
                        @{
                            "conditiontype" = 0
                            "operator" = 0
                            "value"= 435
                        }
                    )
                }
            }
            "id"= 1
            "auth" = "$api_token"
        } | ConvertTo-Json
        uri = $zbx_api_uri
        headers = @{
            "Content-Type" = "application/json"
            "Authorization" = "Bearer $api_token"
            }
        method = "Post"
    }
    $result = Invoke-WebRequest @params
    return $result.Content | ConvertFrom-Json
}

The above snippet just shows a test function to manually update an action and set conditions to send out alerts for severities of warning or higher for a specific host group.

However – when executed it will fail with the error from snippet 1.

This is because Powershell does the JSON-conversion only to a certain depth. If your structure reaches a deeper level, it will simply stop converting the dict or array which results in the above error.

Troubleshooting it, could be quite a pain in the ass as it’s not that simple to spot and when gradually extending the object it just works fine till you exceed a depth of 2.

To fix this – simple specify the depth for ConvertTo-Json

e.g.

function ZbxUpdateTestAction
{

    $params = @{
        body =  @{
            "jsonrpc"= "2.0"
            "method"= "action.update"
            "params"= @{
                "actionid" = "117"
                "status" = 0
                "filter" = @{
                    "evaltype" = 0
                    "conditions" = @(
                        @{
                            "conditiontype" = 4
                            "operator" = 5
                            "value"= 3
                        }
                        @{
                            "conditiontype" = 0
                            "operator" = 0
                            "value"= 435
                        }
                    )
                }
            }
            "id"= 1
            "auth" = "$api_token"
        } | ConvertTo-Json -Depth 5
        uri = $zbx_api_uri
        headers = @{
            "Content-Type" = "application/json"
            "Authorization" = "Bearer $api_token"
            }
        method = "Post"
    }
    $result = Invoke-WebRequest @params
    return $result.Content | ConvertFrom-Json
}

Compare line 29 between the two snippets

Refs:

Cisco – perimeter security – EEM disable port

Sometimes it is necessary to connect a device on your security permiters edge to your network. Doing so without any protection can be don, but might not be wise.

So how do you secure your PoE Surveillance-Cam which covers the from of your office or your IP-based intercom which are accessible by the public?

802.1x would be a good solution. However – for that you would require a CA/PKI + machine-certificates for the devices, would have to rotate them on a regular base which might not be possible and could be wuite some effort.

Furthermore – not all devices support 802.1x – so how could we secure these devices?

Before we start, lets analyse the threat:

A devices such as a PoE-Surveillance cam, an IP-InterCom or similar devices are located outside of a “protected” perimeter where anybody might have easy access to the device. An attacker could easily unplug the network-cable, add a switch in between and get access to the network.
MAC based ACLs could be used to ensure that only “allowlisted” devices (so devices with the correct MAC address) are granted access.
However running tcpdump, getting the allowed devices MAC and “cloning” it via macchanger --mac=XX:XX:XX:XX:XX:XX isn’t really that hard.
So that doesn’t really work! Yes, the network should be segmented anyways to reduce the impact of such a security incident. But still – there might be some interesting piece of hardware/software in that network which could be exploited by an attacker to get a foothold in the network or at least gather some additional information about other devices which are used.

So the best approach is, to do not even let the attacker in.

With Cisco we could do so by utilizing the EEM (Embeded Event Manager).
That piece of software allows us to react on events that are registered by the switch.

So what we want to archive is:

  • Given an (I)OT-device on the edge of your network perimeter
  • when the device is unplugged
  • then disable the port the device is attached to

Unplugging the network cable generates a port-down syslog entry on the switch, which can be utilized to execute a command/command set.

If we want to “secure” a device located on port 20 the following configuration could be used to manually shut down (disable) the port once a disconnect was detected.

event manager applet Interface_Down_PoE-CAM
 event syslog pattern "Line protocol on Interface GigabitEthernet1/0/20, changed state to down"
 action 1.0 cli command "enable"
 action 1.5 cli command "config t"
 action 2.0 cli command "interface GigabitEthernet1/0/20"
 action 2.5 cli command "shutdown"
 action 3.0 cli command "end"
 action 3.5 cli command "who"

The above example will immediately set the port to shutdown, once the line protocol went down/device was unplugged from the network.

However – keep in mind, that a restart of the device (especially for devices which use external PowerSupplies might also cause a port-down event while restarting which will result in a disabled port.
Doing maintenance could also result in a port down event, if somebody shuts down/unplugs the device while doing the maintenance tasks!

So is using this method to secure the perimeter, it is very important to also have some monitoring in place which will notify about disabled ports.

If ports shall be automatically enabled again (e.g. when office hours start) the EEM + KRON can be used.
As it seemed that execution actions like with the EEM are not supported in the KRON-policies i used the workaround to generate another syslogevent via cron which does the “reset” (no shutdown) for the perimeter ports.

! define the cron-occurence - so when our job should be run. 
kron occurrence Daily_8AM at 8:00 recurring
 ! list policies which shall be excuted
 policy-list Policy_A
 policy-list Policy_B
!
kron policy-list Policy_A
 ! send the following message to syslog:
 cli send log 0 "set_port_Gi1/0/35_UP"
!
kron policy-list Policy_B
 ! send the following message to syslog:
 cli send log 0 "set_port_Gi1/0/20_UP"
!
! DISABLE the port when a defined syslog message is detected
event manager applet EEM_01
 event syslog pattern "Line protocol on Interface GigabitEthernet1/0/20, changed state to down"
 action 1.0 cli command "enable"
 action 1.5 cli command "config t"
 action 2.0 cli command "interface GigabitEthernet1/0/20"
 action 2.5 cli command "shutdown"
 action 3.0 cli command "end"
 action 3.5 cli command "who"
!
! DISABLE the port when a defined syslog message is detected
event manager applet EEM_02
 event syslog pattern "Line protocol on Interface GigabitEthernet1/0/35, changed state to down"
 action 1.0 cli command "enable"
 action 1.5 cli command "config t"
 action 2.0 cli command "interface GigabitEthernet1/0/35"
 action 2.5 cli command "shutdown"
 action 3.0 cli command "end"
 action 3.5 cli command "who"
!
! enable the port when a defined syslog message is detected
event manager applet EEM_03
 event syslog pattern "set_port_Gi1/0/35_UP"
 action 1.0 cli command "enable"
 action 1.5 cli command "config t"
 action 2.0 cli command "interface GigabitEthernet1/0/35"
 action 2.5 cli command "no shutdown"
 action 3.0 cli command "end"
 action 3.5 cli command "who"
!
! enable the port when a defined syslog message is detected
event manager applet EEM_04
 event syslog pattern "set_port_Gi1/0/20_UP"
 action 1.0 cli command "enable"
 action 1.5 cli command "config t"
 action 2.0 cli command "interface GigabitEthernet1/0/20"
 action 2.5 cli command "no shutdown"
 action 3.0 cli command "end"
 action 3.5 cli command "who"
!
end

References:

Nerdfonts in zsh for exa

After switching to exa as an ls-replacement i also wanted to make use of the nerd-font support ho have icons displayed for files.

alias etree='exa --color --tree --icons=always'

However, in reality the fonts never looked the same in my terminal as in the web preview

So, to easy things up (getting the zip, unzipping it in ~/.local/share/fonts and updateing the fonts-cache) there is a little function which can be placed in the .zshrc/.bashrc to automate things.

#Function to install NerdFonts
function install_nerdfont()
{
	if [ -not $1 ];
	then	
		echo -e "\e[91mParameter missing!\e[0m"
	fi

	cd ~/.local/share/fonts
	wget $1
	unzip -u *.zip
	rm *.zip
	fc-cache -fv	
	cd -

}

Once executed with the download-URL as a paremeter, the font will be installed to your home directory.

If the fonts should be installed system-wide, this can be archived by placing them in /usr/local/share/fonts (folder might needs to be created if it does not exist).

Once the fonts are installed – the terminal-profile must be configured to use the newly installed fonts and that’s it.

Running into “The parameter is incorrect.” / “HRESULT 0x80070057” when managing scheduled tasks via powershell

It took me quite some troubleshooting time, but in the end it’s a quite interessting conclusion.

After trying to create a scheduled task that runs every second Saturday per month, I ran into the following error:

Set-ScheduledTask : The parameter is incorrect.
At C:\ITX\itxWindowsUpdate.ps1:436 char:5
+     Set-ScheduledTask -InputObject $task -User $(Get-LocalUser -Name  ...
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (PS_ScheduledTask:Root/Microsoft/...S_ScheduledTask) [Set-ScheduledTask], CimException
    + FullyQualifiedErrorId : HRESULT 0x80070057,Set-ScheduledTask

As Windows Powershell is not able to create monthly tasks I used schtasks.exe utility to create the skeleton for the task/minimal task containing the trigger-definition and afterwards manipulate it via powershell to add the missing configuration.

To do so i used Get-ScheduledTask to obtaint he object references for the task and Set-ScheduledTask to write the object again.

Turns out – the task obtained via Get-ScheduledTask contains the trigger defintion for the montly repetition which can’t be handled by powershell and therefor the task itself can not be saved again but results in the above error. 😜

As it seems there are only two ways on how to deal with that problem:
Export the task-definition as an XML and just reimport it via powershell on other systems which require the same configuration (currently untested) or run it daily and deal with the problem within the script.

Lil bit of the debugging-code as a reference for future projects.

Write-Host -ForegroundColor Yellow "Modifying the task"
$action=New-ScheduledTaskAction -Execute "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -Argument '-NoProfile -command "& C:\BLUB\WindowsUpdate\WindowsUpdate.ps1 -reboot_machine 0"'
$principal = New-ScheduledTaskPrincipal -LogonType Password -RunLevel Highest -UserId "$($env:COMPUTERNAME)\Administrator"
#$principal = New-ScheduledTaskPrincipal -RunLevel Highest -UserId "$($env:COMPUTERNAME)\Administrator"

#$task=New-ScheduledTask -Action $action -Description "Daily Update Task for Monitoring and Defender Updates" -Trigger $trigger -Settings $settings -Principal $

$task=$(Get-ScheduledTask -TaskName $task_name -TaskPath \BLUB\)
#$task.Actions=$action
$task.Description="TEST_test"
#$task.Principal=$principal
#Register-ScheduledTask -TaskName $task_name -InputObject $task -TaskPath BLUB -User $(Get-LocalUser -Name Administrator) -Password $password
$task | Set-ScheduledTask -User $(Get-LocalUser -Name Administrator) -Password $password
#-User $(Get-LocalUser -Name Administrator) -Password $password 
#Set-ScheduledTask -TaskPath "BLUB" -TaskName $task_name -Action $action -Principal $principal
#-Principal $principal
# -User $(Get-LocalUser -Name Administrator) 

Customizing powershell prompt

After enabling OpenSSH on Windows I had the issue that powershell sessions could be mixed up quite easily as it does not display which host is opened in which session.

to work around that a custom prompt function can be specified in the powershell profile

powershell profile is loaded on startup of a powershell window. By default there is no file but it can be created. The file itself is referenced by the PS env $PROFILE.

PS C:\Users\usr> $PROFILE
C:\Users\usr\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

To change the default prompt (PS C:\User\usr>) the file can be edited and a prompt function can be added:

function prompt{
	"$("[$([System.Environment]::UserName)@$([System.Environment]::MachineName)] > ")"
}

REF: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_prompts?view=powershell-7.2

Refs for enabling OpenSSH in Windows:

Installation: https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse?tabs=powershell

Configuration:

https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_server_configuration

Spam the Scammer

Got a phising SMS this week whis lead me to a fake DHL-website and asked me for personal data, credit card data and so on.

Though if they like collecting data so much I might help them and wrote the following script to help them collecting credit card data:

#!/bin/python3
import requests
import ccard
import pprint
import random
from termcolor import colored


target_addr="https://<SCAMMERS-DOMAIN>/PHP/send.php"
counter=0
while 1:
    cc=str(ccard.visa())
    cc_str=cc[0:4]+" "+cc[4:8]+" "+cc[8:12]+" "+cc[12:16]+" "
    cvv=(random.randint(100, 999))
    year=(random.randint(23, 27))
    month=(random.randint(1, 12))
    valid_date=str("{:02d}".format(month))+"/"+str("{:02d}".format(year))
    cc_data={"cc":cc_str,"exp":valid_date,"cvv":cvv,"type":"livraison"}
    r=requests.post(target_addr,json=cc_data)
    if r.status_code != 200:
        print(colored("ERROR - statuscode: "+r.status_code, "red"))
    else:
        counter=counter+1
        print(counter, end="\r")

The script will just generate face credit card numbers CVVs and face validation-dates and post it to the script, the scammer used to receive the data from the phising-site.

WSUS Server unable to obtain updates

If a WSUS server fails to obtain updates with the following error: “wsus the request failed with http status 404 not found” this might be because it still uses an old/outdated URL (https://fe2.update.microsoft.com/v6) to obtain the updates from.

This can be checked and changed with the following PS1 Snippet:

$server = Get-WsusServer
$config = $server.GetConfiguration()
# Check current settings before you change them 
$config.MUUrl
$config.RedirectorChangeNumber
# Update the settings if MUUrl is https://fe2.update.microsoft.com/v6
$config.MUUrl = "https://sws.update.microsoft.com"
$config.RedirectorChangeNumber = 4002
$config.Save()
iisreset
Restart-Service *Wsus* -v

PowerShell AD Snippet

Simple snippet to get a list when the passwort of a service user was set the last time.

Get-ADUser -Filter 'Name -like "svc*"' -Properties * | select Name,@{name ="pwdLastSet"; expression={[datetime]::FromFileTime($_.pwdLastSet)}}

Requires the Active Directory Powershell module to run.

Command must also be run in an elevated powershell session (Run as Administrator)

Otherwise the pwdLastSet attribute will not be obtained!

Sid To Username

Param
(
    [parameter(
        Mandatory=$true,
        HelpMessage="User SID"
        )
    ]
    [String]
    [alias("sid")]
    $user_sid
)
$objSID = New-Object System.Security.Principal.SecurityIdentifier $user_sid 
$objUser = $objSID.Translate( [System.Security.Principal.NTAccount]) 
$objUser.Value

Simply check if login on an ESXi works and return it’s license

Just a simple script to test ESXi Connectivity and return the current license state of the server by using PowerCLI

param(
    [Parameter(Mandatory = $true, HelpMessage = 'Provide username for login on ESXi')]
    [String] $username,
    [Parameter(Mandatory = $true, HelpMessage = 'Provide password for login on ESXi', ParameterSetName = 'Secret')]
    [Security.SecureString] $password

)

#check user/pwd
if($username -like "" -Or $password -like "")
{
    Write-Host -ForegroundColor Red "Username/Password seems wrong"
    exit(1)
}

#define domain
$domain="forensik.justiz.gv.at"

#disable certificate checking as we have self signed certs
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false

$hosts="SITE01-ESX01","SITE01-ESX02","SITE01-ESX03","SITE01-ESX04","SITE01-ESX05","SITE01-ESX06","SITE02-ESX01","SITE02-ESX02","SITE02-ESX03","SITE02-ESX04","SITE02-ESX05","SITE02-ESX06" 



foreach($var_host in $hosts)
{
    Write-Host -ForegroundColor Yellow $var_host"."$domain
    Connect-VIServer -Server $var_host"."$domain -User $username -Password ([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)))
    $(Get-VMHost).Name
    $(Get-VMHost).Version
    $(Get-VMHost).LicenseKey
    $(Get-VMHost).Uid
    Disconnect-VIServer $var_host"."$domain -WarningAction SilentlyContinue -Confirm:$false
    Write-Host -ForegroundColor Cyan "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
}

Dovecot: convert mdbox to mbox format

Dovecot comes with a proprietary mailbox-format (sd/mdbox) that provides some benefits in regard of compression and performance but is not really readable with nothing else than an editor.

It seems that there are very little information on how to convert the sd/mdbox-fomat to mbox (at least nothing copy and paste like if you aren’t a dovecot admin and have no clue ;))

So here is one possible-way it could be archived:

To work around adapting the dovecot-config and running a dovecot-service for dsync to use, we can also pass configuration parameters (by default dsync uses the dovecot-configuration file stored at /etc/dovecot/dovecot.conf) directly to dsync.
With that trick there is no need to configure & start a dovecot service.

An example command to convert a mailbox in mdbox-format located in the users home dir under mail to mbox format would look like:

dsync -o "mail_location=mdbox:~/mail" backup mbox:~/dstest

Converting to other formats like maildir might require additional configuration parameters within the /etc/dovecot/dovecot.conf file as namespaces or similar.

IMPORTANT:
Override options specified via “-o” must be passed directly after the dsync command and before “backup”


For converting a mailbox the “backup” attribute is recommended as this will do a 1-way-sync.