fscan/Plugins/services/oracle/connector.go
ZacharyZcR 3de7b21fe0 feat: Oracle数据库插件迁移到新架构完成
- 实现Oracle TNS协议连接器,支持多种服务名
- 支持高危用户(SYS/SYSTEM)检测和SYSDBA权限
- 实现服务识别和弱密码检测功能
- 集成国际化消息系统
- 测试通过:服务识别和高危用户认证功能
2025-08-09 13:02:11 +08:00

207 lines
5.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package oracle
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
_ "github.com/sijms/go-ora/v2"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
)
// OracleConnection Oracle连接包装器
type OracleConnection struct {
db *sql.DB
target string
info string
serviceName string
}
// OracleConnector Oracle连接器实现
type OracleConnector struct{}
// NewOracleConnector 创建Oracle连接器
func NewOracleConnector() *OracleConnector {
return &OracleConnector{}
}
// Connect 连接到Oracle服务器不进行认证
func (c *OracleConnector) Connect(ctx context.Context, info *common.HostInfo) (interface{}, error) {
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 尝试建立连接但不进行认证,使用空凭据进行连接尝试
db, dbInfo, serviceName, err := c.createConnection(ctx, info.Host, info.Ports, "", "", "")
if err != nil {
// 检查是否是Oracle服务相关错误
if c.isOracleError(err) {
// 即使连接失败但可以识别为Oracle服务
return &OracleConnection{
db: nil,
target: target,
info: "Oracle Database (Service Detected)",
serviceName: "",
}, nil
}
return nil, err
}
return &OracleConnection{
db: db,
target: target,
info: dbInfo,
serviceName: serviceName,
}, nil
}
// Authenticate 使用凭据进行认证
func (c *OracleConnector) Authenticate(ctx context.Context, conn interface{}, cred *base.Credential) error {
oracleConn, ok := conn.(*OracleConnection)
if !ok {
return fmt.Errorf("invalid connection type")
}
// 解析目标地址
parts := strings.Split(oracleConn.target, ":")
if len(parts) != 2 {
return fmt.Errorf("invalid target format")
}
host := parts[0]
port := parts[1]
// 使用提供的凭据创建新连接
db, info, serviceName, err := c.createConnection(ctx, host, port, cred.Username, cred.Password, "")
if err != nil {
return err
}
// 更新连接信息
if oracleConn.db != nil {
oracleConn.db.Close()
}
oracleConn.db = db
oracleConn.info = info
oracleConn.serviceName = serviceName
return nil
}
// Close 关闭连接
func (c *OracleConnector) Close(conn interface{}) error {
if oracleConn, ok := conn.(*OracleConnection); ok && oracleConn.db != nil {
return oracleConn.db.Close()
}
return nil
}
// createConnection 创建Oracle数据库连接
func (c *OracleConnector) createConnection(ctx context.Context, host, port, username, password, serviceName string) (*sql.DB, string, string, error) {
timeout := time.Duration(common.Timeout) * time.Second
// 常见Oracle服务名列表
commonServiceNames := []string{"XE", "ORCL", "ORCLPDB1", "XEPDB1", "PDBORCL"}
// 如果未指定服务名,尝试所有常见服务名
serviceNamesToTry := []string{serviceName}
if serviceName == "" {
serviceNamesToTry = commonServiceNames
}
var lastErr error
for _, svcName := range serviceNamesToTry {
if svcName == "" {
continue
}
// 构造连接字符串
connStr := fmt.Sprintf("oracle://%s:%s@%s:%s/%s?connect_timeout=%d",
username, password, host, port, svcName, int(timeout.Seconds()))
// 对SYS用户使用SYSDBA权限
if strings.ToUpper(username) == "SYS" {
connStr += "&sysdba=1"
}
// 建立数据库连接
db, err := sql.Open("oracle", connStr)
if err != nil {
lastErr = err
continue
}
// 设置连接参数
db.SetConnMaxLifetime(timeout)
db.SetConnMaxIdleTime(timeout)
db.SetMaxIdleConns(0)
db.SetMaxOpenConns(1)
// 创建ping上下文
pingCtx, pingCancel := context.WithTimeout(ctx, timeout)
// 测试连接
err = db.PingContext(pingCtx)
pingCancel()
if err != nil {
db.Close()
lastErr = err
// 如果是认证错误,继续尝试下一个服务名
if strings.Contains(err.Error(), "ORA-01017") {
continue
}
continue
}
// 获取数据库信息
info := c.getDatabaseInfo(db, ctx)
return db, info, svcName, nil
}
return nil, "", "", lastErr
}
// getDatabaseInfo 获取Oracle数据库信息
func (c *OracleConnector) getDatabaseInfo(db *sql.DB, ctx context.Context) string {
var version string
err := db.QueryRowContext(ctx, "SELECT BANNER FROM V$VERSION WHERE ROWNUM = 1").Scan(&version)
if err != nil {
// 尝试其他查询
err = db.QueryRowContext(ctx, "SELECT VERSION FROM PRODUCT_COMPONENT_VERSION WHERE PRODUCT LIKE 'Oracle%' AND ROWNUM = 1").Scan(&version)
if err != nil {
return "Oracle Database"
}
}
return version
}
// isOracleError 检查是否是Oracle相关错误
func (c *OracleConnector) isOracleError(err error) bool {
if err == nil {
return false
}
errorStr := strings.ToLower(err.Error())
oracleErrorIndicators := []string{
"ora-",
"oracle",
"tns:",
"listener",
"database",
"connection refused",
"invalid authorization specification",
"service name",
"sid",
}
for _, indicator := range oracleErrorIndicators {
if strings.Contains(errorStr, indicator) {
return true
}
}
return false
}