Information Technology Grimoire

Version .0.0.1

IT Notes from various projects because I forget, and hopefully they help you too.

SSH Socks Proxy Helper

I often am on a computer without the ability to install tools like putty, or secure CRT. I also often use socks proxies to navigate web traffic. Here is a cut and paste script that allows for easy management of SSH servers using them as socks proxies, jumphosts, etc.

SSH Socks Proxy Helper

What is a Socks Proxy?

Like a tunnel. You have to open the tunnel, then point your web browser to it. It’s often used to bypass firewalls or appear to surf from a different geographic location. It uses the same security for the “tunnel” to the proxy as ssh. From the proxy to your web target, that is not encrypted at all. You can also send DNS requests through the proxy. You need to set your proxy settings in your browser to look for port 7070 and the proxy will be 127.0.0.1.

Why this script?

Because commands like this are hard for me to remember:

ssh -p 7070 -t -L7070:localhost:5590 root@jumphost ssh -p 22 -t -D5590 user@finalhost

Execution Policy

You may or may not be able to run a script, but you can almost always paste the entire script. If you want to run the script like .\somescript.ps1 then you need to set the execution policy after viewing it:

Get-ExecutionPolicy
Set-ExecutionPolicy RemoteSigned

If you want to run a single script bypassing the policy, do like so:

powershell.exe -ExecutionPolicy Bypass -File "path\to\script.ps1"

Remember, you can just paste the script too and not think about the policy.

If you have your Execution policy set though, you can run the script like this instead:

PS C:\Users\User> .\sshjumper.ps1

Version

This works on my version, I don’t gaurantee anything else.

PS C:\Users\User> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      5.1.22621.4391
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.22621.4391
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

Create servers.csv

Simple text database with notes, user, etc. Use this format exactly and name it “servers.csv”. I’m using Get-Content to show you my first line:

PS C:\Users\User> Get-Content .\servers.csv -TotalCount 1
Username,Hostname,Port,Role,Description

Fill it out with whatever your servers are and the notes. Role and description are basically two notes columns. Don’t be overly verbose, UI isn’t made to handle that.

Create sshjumper.ps1

NOte you’ll need to udpate the $jumphostUser and $jumphostIP to be your own jumphost/bastion host.

# Jumphost Details
$jumphostUser      = "root"
$jumphostIP        = "1.2.3.4"
$jumphostPort      = 22
$localPort         = 7070
$remotePortOnJump  = 6590

Add-Type -AssemblyName PresentationFramework

# Load Servers from CSV
$servers = Import-Csv -Path ".\servers.csv"

# Add a Status property to each server so it can be bound in the grid
$servers | ForEach-Object {
    $_ | Add-Member -NotePropertyName "Status" -NotePropertyValue "" -Force
}

# Create the Window
[xml]$xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="SSH Server Selector and Proxy Helper" Height="600" Width="900">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="3*" />
            <RowDefinition Height="1*" />
        </Grid.RowDefinitions>
        <DataGrid Name="ServerTable" Grid.Row="0" AutoGenerateColumns="False" SelectionMode="Single">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Username" Binding="{Binding Username}" Width="100" />
                <DataGridTextColumn Header="Hostname" Binding="{Binding Hostname}" Width="200" />
                <DataGridTextColumn Header="Port" Binding="{Binding Port}" Width="75" />
                <DataGridTextColumn Header="Role" Binding="{Binding Role}" Width="150" />
                <DataGridTextColumn Header="Description" Binding="{Binding Description}" Width="200" />
                <!-- New Column for Port Check Status -->
                <DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="75" />
            </DataGrid.Columns>
        </DataGrid>
        <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">

            <!-- Radio Buttons with ToolTips -->
            <RadioButton Name="DirectButton" Content="Direct" IsChecked="True" Margin="10">
                <RadioButton.ToolTip>
                    <ToolTip Content="Generic Direct connection" />
                </RadioButton.ToolTip>
            </RadioButton>
            <RadioButton Name="JumpOnceButton" Content="Socks Proxy" Margin="10">
                <RadioButton.ToolTip>
                    <ToolTip Content="socks proxy using local port forwarding" />
                </RadioButton.ToolTip>
            </RadioButton>
            <RadioButton Name="JumpThroughButton" Content="Remote Socks Proxy" Margin="10">
                <RadioButton.ToolTip>
                    <ToolTip Content="via remote socks proxy, uses hard coded jumphostIP as 1st hop" />
                </RadioButton.ToolTip>
            </RadioButton>

            <!-- Existing Connect Button -->
            <Button Name="ConnectButton" Content="Connect" Width="100" Height="30" Margin="10" />

            <!-- New Status Button -->
            <Button Name="StatusButton" Content="Status" Width="100" Height="30" Margin="10" />
        </StackPanel>
    </Grid>
</Window>
"@

# Parse XAML and Create GUI Elements
$reader         = New-Object System.Xml.XmlNodeReader($xaml)
$window         = [Windows.Markup.XamlReader]::Load($reader)

$ServerTable        = $window.FindName("ServerTable")
$DirectButton       = $window.FindName("DirectButton")
$JumpThroughButton  = $window.FindName("JumpThroughButton")
$JumpOnceButton     = $window.FindName("JumpOnceButton")
$ConnectButton      = $window.FindName("ConnectButton")
$StatusButton       = $window.FindName("StatusButton")

# Populate the DataGrid with Servers
$ServerTable.ItemsSource = $servers

# Function to Run SSH Commands in a Visible Terminal
function Run-SSHCommand {
    param (
        [string]$command
    )
    Start-Process -FilePath "cmd.exe" -ArgumentList "/k $command"
}

# Event Handler for Connect Button
$ConnectButton.Add_Click({
    try {
        # If JumpOnce is selected, connect directly to the jumphost to set up a local SOCKS tunnel
        if ($JumpOnceButton.IsChecked -eq $true) {
            $connection = "ssh -p ${jumphostPort} -D ${localPort} ${jumphostUser}@${jumphostIP}"
            Write-Output "Executing Jumphost Tunnel Command: $connection"
            [System.Windows.MessageBox]::Show("Executing Jumphost Tunnel: $connection", "SSH Command")
            Run-SSHCommand -command $connection
            return
        }

        # Check if a server is selected
        $selectedServer = $ServerTable.SelectedItem
        if (-not $selectedServer) {
            [System.Windows.MessageBox]::Show("Please select a server!", "Error")
            return
        }

        $username   = $selectedServer.Username
        $TargetHost = $selectedServer.Hostname
        $port       = $selectedServer.Port

        if ($DirectButton.IsChecked -eq $true) {
            $connection = "ssh -p ${port} ${username}@${TargetHost}"
            Write-Output "Executing Direct SSH Command: $connection"
        }
        elseif ($JumpThroughButton.IsChecked -eq $true) {
            $connection = "ssh -tt -p ${jumphostPort} -L ${localPort}:localhost:${remotePortOnJump} ${jumphostUser}@${jumphostIP} ssh -tt -p ${port} -D ${remotePortOnJump} ${username}@${TargetHost}"
            Write-Output "Executing Jumphost SSH Command: $connection"
        }

        [System.Windows.MessageBox]::Show("Executing: $connection", "SSH Command")
        Run-SSHCommand -command $connection
    }
    catch {
        [System.Windows.MessageBox]::Show("An error occurred: $_", "Error")
    }
})

# Event Handler for Status Button
$StatusButton.Add_Click({
    Write-Output "Be Patient, Checking all Listed Servers.  Default timeout 1s"
    [System.Windows.MessageBox]::Show("Be Patient, Checking all Listed Servers. Default timeout 1s", "Checking Open Ports")
    # Check the port for each server in the table and update Status
    foreach ($svr in $ServerTable.ItemsSource) {
        try {
            $tcpClient = New-Object System.Net.Sockets.TcpClient
            $async     = $tcpClient.BeginConnect($svr.Hostname, $svr.Port, $null, $null)
            $wait      = $async.AsyncWaitHandle.WaitOne(1000)  # 1 second timeout
            if ($wait -eq $true) {
                $tcpClient.EndConnect($async)
                $svr.Status = "Open"
            } else {
                $svr.Status = "Closed"
            }
        } catch {
            $svr.Status = "Closed"
        } finally {
            $tcpClient.Dispose()
        }
    }
    $ServerTable.Items.Refresh()
})

# Show the Window
$window.ShowDialog()
Last updated on 6 Jan 2024
Published on 6 Jan 2024