ssh
27 TopicsSSH Brute Force Protection with SSH Proxy
This script is designed to add brute force protection for SSH services on a virtual server. It makes use of the Protocol Security feature called SSH Proxy which makes it possible to look into the ssh session and see the actual logon results. Documentation for setting up SSH Proxy can be found here: https://techdocs.f5.com/en-us/bigip-14-1-0/big-ip-network-firewall-policies-and-implementations-14-1-0/ssh-proxy-security.html The script basically looks in the ltm log for these lines: Jun 8 11:40:57 f5-01 info tmm[12656]: 23003164 "Jun 08 2025 11:40:57","ssh_serverside_auth_fail","10.1.0.2","10.10.9.64","49862","22","4092","TCP","root","Password authentication failure" and counts the number of failed attempts for each IP. When a threshold has been reached that IP goes into a AFM address-list. This address-list needs to be part of a rule and policy, and attached to the SSH VS. This way be can block access for that source. The name of the address-list is specified in a variable in the script, so you can adjust it easily for your needs. You should use iCall to run the script periodically: sys icall script ssh_bf { app-service none definition { exec /shared/scripts/ban_failed_ssh.sh > /var/log/ssh_log.log } description none events none } sys icall handler periodic ssh_bf { interval 300 script ssh_bf } Make sure that the location of the script matches the exec statement in the iCall script, and that the script is executable. If you are having an HA, you also need to make sure you have it on both units. You can consume the above config by running this command: tmsh load sys config merge from-terminal and simply paste it in and on an empty line hit CTRL-d. Next you need to have a logging profile configured with these settings: It is important you have that particular publisher set as it is sending the failures to the ltm log. You should be aware of the limitations of logging locally before you make use of this solution. If it is a very busy unit the disk might not be happy with some extra writing. In a perfect world you could log these failures to a remote logging server, but then you need to have the counting logic running elsewhere and have the script use the API to ingest the IPs. Or make use of a IP Intelligence feed which can load the IPs from a webserver. This is however out of scope for this solution, but I might make a follow up article on this if time allows it. With the AFM policy on the SSH VS and the logging profile attached, you now only need to save this script to your favorite script location on the BigIP: #!/bin/bash # # ban_failed_ssh.sh - Detect and ban IPs with repeated SSH login failures from /var/log/ltm # # This script uses AFM address lists and maintains a state file with banned IPs and expiry timestamps. # Author: Thomas Domingo Dahlmann # # --- Configuration --- LOG_FILE="/var/log/ltm" STATE_FILE="/var/tmp/ssh_ban_state.csv" AFM_ADDR_LIST="/Common/ssh_banned_ips" DUMMY_IP="192.0.2.254" BAN_THRESHOLD=3 BAN_WINDOW_SECONDS=300 # 5 minutes BAN_DURATION_SECONDS=1800 # 1 hour TMP_LOG="/var/tmp/ssh_ban_tmp.log" DEBUG=1 # Set to 1 for verbose debug logging, 0 to disable # --- Logging helper --- log_debug() { [[ "$DEBUG" -eq 1 ]] && echo "[DEBUG] $(date '+%F %T') - $*" >&2 } # --- Ensure state file and dummy IP exists --- ensure_state_file() { touch "$STATE_FILE" if ! grep -q "^$DUMMY_IP," "$STATE_FILE"; then echo "$DUMMY_IP,9999999999" >> "$STATE_FILE" log_debug "Adding dummy IP $DUMMY_IP to state file and AFM list" tmsh modify security firewall address-list "$AFM_ADDR_LIST" { addresses add { "$DUMMY_IP" } } 2>/dev/null else log_debug "Dummy IP $DUMMY_IP already present in state" fi } # --- Remove expired IPs from state and AFM --- cleanup_expired_bans() { local now now=$(date +%s) local new_state=() log_debug "Cleaning up expired bans at epoch time $now" while IFS=',' read -r ip expiry; do [[ -z "$ip" || -z "$expiry" ]] && continue if [[ "$ip" == "$DUMMY_IP" ]]; then new_state+=("$ip,9999999999") continue fi if (( expiry > now )); then log_debug "Keeping active ban for $ip (expires at $expiry)" new_state+=("$ip,$expiry") else log_debug "Unbanning expired IP $ip (expired at $expiry)" tmsh modify security firewall address-list "$AFM_ADDR_LIST" { addresses delete { "$ip" } } 2>/dev/null fi done < "$STATE_FILE" printf "%s\n" "${new_state[@]}" > "$STATE_FILE" log_debug "State file rewritten with ${#new_state[@]} active entries" } # --- Add or update ban for IP --- ban_ip() { local ip=$1 local expiry=$(( $(date +%s) + BAN_DURATION_SECONDS )) log_debug "Initiating ban for IP $ip until $(date -d "@$expiry") [$expiry]" # Remove old entry grep -v "^$ip," "$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE" log_debug "Removed previous entry for $ip (if any)" # Add new ban echo "$ip,$expiry" >> "$STATE_FILE" log_debug "Added $ip,$expiry to $STATE_FILE" # Add to AFM list tmsh modify security firewall address-list "$AFM_ADDR_LIST" { addresses add { "$ip" } } 2>/dev/null log_debug "Added IP $ip to AFM address list $AFM_ADDR_LIST" } # --- Check recent log entries for violations --- check_for_new_violations() { local now start_epoch now=$(date +%s) start_epoch=$(( now - BAN_WINDOW_SECONDS )) log_debug "Checking for new violations between $start_epoch and $now" > "$TMP_LOG" awk -v threshold="$start_epoch" ' match($0, /"([A-Z][a-z]{2} [ 0-9][0-9] [0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2})","ssh_.*auth_fail","([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)"/, m) { cmd = "date -d \"" m[1] "\" +%s" cmd | getline epoch close(cmd) if (epoch >= threshold) { print epoch, m[2] } } ' "$LOG_FILE" > "$TMP_LOG" log_debug "Parsed entries written to $TMP_LOG:" [[ "$DEBUG" -eq 1 ]] && cat "$TMP_LOG" >&2 awk '{count[$2]++} END { for (ip in count) if (count[ip] >= '"$BAN_THRESHOLD"') print ip }' "$TMP_LOG" | while read -r ip; do log_debug "Found IP with threshold violations: $ip" if grep -q "^$ip," "$STATE_FILE"; then log_debug "IP $ip already in state file, skipping ban" else ban_ip "$ip" fi done } # --- Main --- log_debug "==== SSH Ban Monitor Started ====" ensure_state_file cleanup_expired_bans check_for_new_violations log_debug "==== SSH Ban Monitor Finished ====" Happy hunting š69Views1like0CommentsUsing the WAF instead of a jump server for ssh-tunneling?
Hello everyone, This is how it works at the moment: We go from server A, in the internal network, with a public IP via ssh to a jump server in the DMZ. From the jump server we then go on to server B in the secure zone. I am relatively new to this and have been given the task of seeing if the WAF can replace the jump server. We use Advanced Web Application Firewall, r2600 with BIG-IP 17.1.1.3 Is this possible and what do we need for it? Thank you in advance for your help ! Best regards.127Views0likes1CommentRunning Wireshark captures from F5 BIG-IP
My colleague, Simon Kowallik, recently posted something really cool to our internal message boards. It started with the question: āHave you ever wanted to run captures with Wireshark on BIG-IP?ā Answer: Yes, for like twelve years I wanted to do this! In the post below, Simon shows us how to use the packet tracing tool Wireshark (or any other tool that reads pcaps from tcpdump) directly with BIG-IP using only some slight of hand. Anyway, I thought this was so awesome that it deserved wider audience so here it is, republished with Simonās permission. Have fun! Posted by Simon Kowallik in on Jul 7, 2013 9:02:38 AM We actually can do that without installing X, wireshark and hundreds of libraries on BIG-IP. Which is not an option anyway. š There are a few things we need: SSH access to the BIG-IP, bash or tmsh is fine Proper SSH client on our Desktop, eg. OpenSSH or alternatives (putty & plink) The trick is to launch an ssh session without a login shell and run tcpdump through it on the remote system making tcpdump write raw packets to STDOUT while piping it to our local wireshark reading from STDIN. Here are two examples: cygwin on Windows # ssh -l root 192.168.1.245 "tcpdump -w - -s0 -pi 0.0 tcp or udp or icmp" | /cygdrive/c/Program\ Files/Wireshark/Wireshark.exe -k -i - Linux # ssh -l root 192.168.1.245 "tcpdump -w - -s0 -pi 0.0 tcp or udp or icmp" | /usr/bin/wireshark -k -i - Windows CMD with plink (download from putty homepage): plink.exe -l root -pw default 192.168.1.245 "tcpdump -w - -s0 -pi 0.0 tcp or udp or icmp" | "c:\Program Files\Wireshark\wireshark.exe" -k -i - I think you can figure out how it works. If not, here are a few hints: Tcpdump's option -w with - as an argument writes to STDOUT instead of a file Wireshark's -i option reads from an interface, - as an argument makes STDIN the interface. STDIN/STDOUT is represented by - on most platforms. Caveats Tcpdump does buffer the output when writing to a file (our STDOUT in our case), which unfortunately means it might take some time until we can see the traffic in wireshark. Tcpdump offers options to influence the buffering however this is not implemented in our version of Libpcap (tested on 11.4HF1). This is especially annoying if we want to capture low volume traffic. What we could do is capturing icmp echo requests+replies additionally to the traffic we are interested in, and remove them again with the wireshark display filter. Then start a ping to push the interesting packets to wireshark faster. Words of warning You are piping the whole packet capture through ssh, so make sure you define your tcpdump filter reasonable, otherwise bad things might happen.4.1KViews0likes15CommentsConnecting to F5 using SSH via Ansible
I am in the process of writing a playbook which uses SSH to connect to F5 and run a bash command (ntpdate -d time_server) to confirm NTP connectivity across the environment. For SSH I am using root. Here is the task that I have in the playbook. - name: "Check NTP on {{override_host}}" vars: ansible_connection: ssh ansible_user: "root" ansible_password: "{{root_pwd}}" ansible_ssh_private_key_file: "~/.ssh/f5-ansible-ssh" command: cmd: ntpdate -d {{item}} loop: "{{new_ntp_servers}}" register: ntp_status The error message that I get is as follows MSG: The module failed to execute correctly, you probably need to set the interpreter. See stdout/stderr for the exact error MODULE_STDOUT: /bin/sh: /usr/local/bin/python3.9: No such file or directory MODULE_STDERR: ******************************* IMPORTANT NOTE ****************************** Banner ***************************************************************************** Shared connection to ltm closed. Any help would be greatly appreciated...1.6KViews0likes3CommentsSetting up Forwarding IP VS on LTM to route SSH traffic
I am trying to route SSH traffic through a LTM onto a subnet. This is a prototype setup and so is slightly restrictive in that I have only one public IP address for external traffic to come into the LTM (which is a LAB license setup), behind this I have a "outer" n/w where I have a jump server and a web server and an "inner" n/w where I have app servers. I have setup HTTP virtual servers and have an iRule to route http traffic to the appropriate web server virtual IP address and onto an app server if needed. So in this setup I am attempting to route SSH requests via the single external IP address into the outer n/w layer. I have tried a network based forwarding IP VS to on available. Example VS definition... ltm virtual SSH-Forwarding-VS { description "Virtual Server for routing SSH traffic" destination 0.0.0.0:ssh ip-forward ip-protocol tcp mask any profiles { lab-forwarding-fastL4 { } } source 0.0.0.0/0 translate-address disabled translate-port disabled vs-index 11 } Yet all that I succeed in achieving is opening a SSH session with the actual LTM itself :-( I used this as a reference: http://packetpushers.net/stateless-routing-f5-ltm/ This prototype environment has been created in the AWS cloud, so the VPC, subnets and security groups have been setup to allow the traffic through. Any suggestions appreciated, thanks!1.1KViews0likes5CommentsSetting up Forwarding IP VS on LTM to route SSH traffic
I am trying to route SSH traffic through a LTM onto a subnet. This is a prototype setup and so is slightly restrictive in that I have only one public IP address for external traffic to come into the LTM (which is a LAB license setup), behind this I have a "outer" n/w where I have a jump server and a web server and an "inner" n/w where I have app servers. I have setup HTTP virtual servers and have an iRule to route http traffic to the appropriate web server virtual IP address and onto an app server if needed. So in this setup I am attempting to route SSH requests via the single external IP address into the outer n/w layer. I have tried a network based forwarding IP VS to on available. Example VS definition... ltm virtual SSH-Forwarding-VS { description "Virtual Server for routing SSH traffic" destination 0.0.0.0:ssh ip-forward ip-protocol tcp mask any profiles { lab-forwarding-fastL4 { } } source 0.0.0.0/0 translate-address disabled translate-port disabled vs-index 11 } Yet all that I succeed in achieving is opening a SSH session with the actual LTM itself :-( I used this as a reference: http://packetpushers.net/stateless-routing-f5-ltm/ This prototype environment has been created in the AWS cloud, so the VPC, subnets and security groups have been setup to allow the traffic through. Any suggestions appreciated, thanks!688Views0likes5Commentsgtm_add fails on SSH conneciton
BIG-IP running 11.5.1 HF8 When creating a GTM failover pair the process falls down when I run the gtm_add on the secondary unit. ssh: connect to host 10.10.22.2 port 22: Connection refused ERROR: Can't read remote cert via /usr/bin/ssh. The Big-IPs have three interfaces; Management, HA and External I want to setup the GTM pairing between the External interfaces as this is also the interface that will be used for the connection between the datacentres. I can ping the IP address. I can SSH to the other two interfaces. SSH is allowed and all IP addressing is allowed. But when I try to SSH from one to the other on the External interface the connection is refused. sys sshd { allow { ALL } banner disabled banner-text none description none inactivity-timeout 0 include none log-level info login enabled } The only hardware between the two devices is the a switch stack.679Views0likes4CommentsHow to pass client IP onto access logs for TCP (port:22) connections?
We have bitbucket installed and we would want to capture client ip address for every ssh git operation. We were able to capture client IP for http git operation. We have apache httpd configured and we added the following configuration to make it work (under "IfModule log_config_module" section). RemoteIPHeader x-client-ip RemoteIPInternalProxy LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" %% %T %D" combined LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %% %T %D SSL: %{SSL_PROTOCOL}x %{SSL_CIPHER}x" combined-ssl By this, we're able to capture client IP for all git operations of http. But, we're not able to get the client IP for SSH Git operations. Currently, it is capturing LTM IP in the access logs.426Views0likes2CommentsSSH-proxy and keyboard interactive authentication not working
I am trying to test the SSH proxy funtionality of AFM, but I am not succeeding at all. I am aiming for keyboard interactive authentication (username/password), but all I get is "Authentication failed." For my test-setup I have followed this to the letter: https://support.f5.com/kb/en-us/products/big-ip-afm/manuals/product/network-firewall-policies-implementations-12-1-0/13.print.html Section: Defining SSH proxy password or keyboard interactive authentication My setup goes: 10.128.1.1 --> 10.128.10.100 (VS with SSH-proxy profile) --> 10.128.10.128 (backend server) Directly SSH 10.128.1.1 ---> 10.128.10.128 work just fine If I goes through the virtuel Bigip on 10.128.10.100 I get: debug1: Host '10.128.10.100' is known and matches the RSA host key. debug1: Found key in /Users/testuser/.ssh/known_hosts:4 debug1: rekey after 4294967296 blocks debug1: SSH2_MSG_NEWKEYS sent debug1: expecting SSH2_MSG_NEWKEYS debug1: SSH2_MSG_NEWKEYS received debug1: rekey after 4294967296 blocks debug1: SSH2_MSG_SERVICE_ACCEPT received debug1: Authentications that can continue: publickey,keyboard-interactive,password debug1: Next authentication method: publickey debug1: Trying private key: /Users/testuser/.ssh/id_rsa debug1: Trying private key: /Users/testuser/.ssh/id_dsa debug1: Trying private key: /Users/testuser/.ssh/id_ecdsa debug1: Trying private key: /Users/testuser/.ssh/id_ed25519 debug1: Next authentication method: keyboard-interactive Authentication failed. I have taken to public key from the backend server /etc/ssh/ssh_host_rsa_key.pub and placed a copy in "Real Server Auth Public Key" field. I have taken a private key generated on the virtual Bigip, using ssh-keygen, and placed a copy in "Proxy Server Auth Private key" field. I made sure that HostKey /etc/ssh/ssh_host_rsa_key is not commented out on the backend server. According to the article linked to above, it should now work, but it does not. Can anyone help me?1.1KViews0likes1CommentGolang SSH script for F5?
I am testing a small program written in go to SSH into an F5 do some work but still getting a failure to connect . Has anyone else ran into a similar issue? Code is below: package main import ( "bytes" "fmt" "golang.org/x/crypto/ssh" "log" ) func main() { devices := make([]string, 0) devices = append(devices, "xxxxxx:22") // An SSH client is represented with a ClientConn. // // To authenticate with the remote server you must pass at least one // implementation of AuthMethod via the Auth field in ClientConfig. config := &ssh.ClientConfig{ User: "xxxxxxx", Auth: []ssh.AuthMethod{ ssh.Password("xxxxxxxx"), }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } for _, d := range devices { client, err := ssh.Dial("tcp", d, config) if err != nil { log.Fatal("Failed to dial: ", err) } // Each ClientConn can support multiple interactive sessions, // represented by a Session. session, err := client.NewSession() if err != nil { log.Fatal("Failed to create session: ", err) } // Once a Session is created, you can execute a single command on // the remote side using the Run method. var b bytes.Buffer session.Stdout = &b if err := session.Run("ls -l"); err != nil { log.Fatal("Failed to run: " + err.Error()) } fmt.Println(b.String()) err = session.Close() if err != nil { fmt.Printf("Failed to close session for %v\n", d) } } }812Views0likes1Comment