Sliver 服务端数据模型全解析:server/db/models 的 ORM 架构与持久化设计

原创2026-09-24 02:21:1153 阅读
文章标签:网络安全

Sliver 服务端数据模型全解析:server/db/models 的 ORM 架构与持久化设计

本文以 server/db/models/README.md 为骨架,系统梳理 Sliver(Adversary Emulation Framework)服务端状态持久化的全部 ORM 模型:从 beacon、canary、certificates 到 crackstations 等 18 个模型文件各自负责的数据域、字段设计、关联关系与 GORM 钩子约定,并深入 server/dbserver/configs/database.go 等配套源码,讲清这些模型如何被自动迁移到 SQLite / PostgreSQL / MySQL,以及测试如何验证 protobuf 双向转换与索引设计。读完本文,你将能够独立定位任意一个服务端状态对应的表与字段,理解 Sliver 数据层的组织方式和扩展新模型的标准流程。

Overview:models 包在 Sliver 中的定位

根据 server/db/models/README.md 的定义,server/db/modelsDatabase models and ORM definitions for server state——即服务端全部状态的数据库模型与 ORM 定义层。它负责:

  • 定义数据模式(schema)与表结构;
  • 描述模型之间的关联关系(一对一、一对多、外键);
  • 提供查询辅助方法(如 ToProtobuf() / FromProtobuf() 转换、状态判定方法);
  • 通过 GORM 钩子(BeforeCreate)在写入前自动填充 UUID、时间戳等字段。

README 指出该子系统中的关键例程(Key routines)覆盖 beacon、canary、certificates 和 crackstations 四大块,它们是 C2 通信、DNS 蜜罐、TLS 证书体系与离线密码破解这四条核心业务线的数据基座。除 README 列出的 18 个模型文件外,目录中还包含 README 未列出的支撑文件:uuid.go(UUID 持久化类型)、legacy_slice_serializer.go(旧版切片序列化兼容)、wgip_reservations.go(WireGuard 隧道 IP 分配)、ai.go(AI 会话存储)以及若干 *_test.go 测试文件。

目录导览:18 个模型文件各司其职

下表完整对应 README 的文件清单,并补充各文件的职责:

文件 职责(README 原文) 核心模型
beacon.go 定义 beacon ORM 模型及其关联关系 BeaconBeaconTask
canary.go 存储 DNS canary 记录与状态标志 DNSCanary
certificate_authorities.go 存储服务端证书颁发机构 PEM 数据 CertificateAuthority
certificates.go 持久化监听器与操作员的证书元数据 Certificate
crackstations.go 建模破解节点与基准测试数据 CrackstationCrackJobCrackTaskCrackCommand
credentials.go 保存捕获的凭证条目与标签 Credential
host.go 表示发现的主机及主机专属元数据 HostIOCExtensionData
http-c2.go 存储 HTTP C2 配置数据 HttpC2Config 及 6 个子模型
implant.go 跟踪植入体构建、配置与产物 ImplantBuildImplantConfigImplantProfile
jobs.go 记录长期运行的服务器任务与状态 ListenerJob 及 5 种监听器
keyex.go 持久化植入体的密钥交换状态 KeyExHistory
keyvalue.go 通用键值存储(杂项设置) KeyValue
loot.go 描述战利品工件与文件位置 Loot
monitor.go 存储监控任务配置 MonitoringProvider
operator.go 表示操作员、权限与 MFA 数据 Operator
resource_id.go 分配人类可读的资源标识符 ResourceID
website.go 捕获托管网站配置与资产 WebsiteWebContent
wgkeys.go 存储 WireGuard 对端密钥与元数据 WGKeysWGPeer

贯穿所有模型的基础设施

自定义 UUID 类型

几乎每个模型的主键都是 UUID 类型(gorm:"primaryKey;->;<-:create;type:uuid;")。该类型定义在 uuid.go

type UUID uuid.UUID // 基于 Go 标准库 uuid 的命名类型

注释明确了引入它的原因:标准库类型未实现 database/sqlScannerdriver.Valuer 接口,因此 models 包用命名类型补齐持久化边界,同时保留 Sliver 既有数据库的规范字符串表示。关键能力:

  • NewUUID():生成随机 v4 UUID;
  • NilUUID():全零 UUID,用于判断"未赋值";
  • ParseUUID() / ParseUUIDOrNil():解析文本 UUID,兼容紧凑形式、{...} 花括号包裹与 urn:uuid: URN 前缀(见 ParseUUIDlen==34 / len==41 的分支处理);
  • Scan() / Value():支持从 UUIDuuid.UUID、16 字节原始数据、字符串四种来源读取,写入时统一输出规范小写字符串。

UUIDFromBytes 对长度有严格要求:必须是 16 字节,否则返回错误 "uuid: UUID must be exactly 16 bytes long"

BeforeCreate 钩子约定

全部模型统一实现了 BeforeCreate(tx *gorm.DB) error 钩子,模式高度一致:写入前自动生成 UUID、打上 CreatedAt = time.Now() 时间戳。例如 certificate_authorities.go 中的 CertificateAuthority.BeforeCreate

func (c *CertificateAuthority) BeforeCreate(tx *gorm.DB) (err error) {
	c.ID = NewUUID()
	c.CreatedAt = time.Now()
	return nil
}

这意味着业务代码创建记录时无需手动指定主键与创建时间,GORM 会在 INSERT 前自动填充,保证所有表的 ID 均为随机 UUID、创建时间均由数据库模型层统一维护。字段标签 gorm:"->;<-:create;" 进一步声明这些字段只读(->)、仅在创建时可写(<-:create)。

旧版切片序列化兼容层

legacy_slice_serializer.go 注册了一个名为 legacyjsonslice 的 GORM 自定义序列化器(schema.RegisterSerializer),专门处理 CrackCommand 中的历史列表字段(HashesOutfileFormatCPUAffinity 等,标签如 gorm:"type:text;serializer:legacyjsonslice")。其价值在于:既能按 JSON 数组读写,又能兼容历史上单元素标量存储与 PostgreSQL 花括号数组语法{1,3}),从而保证旧库升级(AutoMigrate 改列类型)时数据不丢失——这一点被 crackstations_test.go 中的 TestCrackCommandLegacyScalarSlicesSurviveMigration 明确覆盖。

数据库初始化与自动迁移

models 包本身只定义结构,真正把结构变成表的是 server/db/sql.go 中的 newDBClient()。它根据 configs.GetDatabaseConfig() 返回的方言选择驱动,支持三种后端:

方言 常量 驱动
SQLite configs.Sqlite glebarez/sqlite(纯 Go,免 CGO)
PostgreSQL configs.Postgres gorm.io/driver/postgres
MySQL configs.MySQL gorm.io/driver/mysql

初始化时,代码逐模型调用 AutoMigrate(而非一次性传入全部模型),注释明确说明原因:if one fails, subsequent models will not be created——单个模型迁移失败不影响后续模型建表。完整迁移清单包含约 50 个模型,从 AIConversationBeaconDNSCanaryCrackstationCertificateAuthorityHostImplantProfileListenerJobMonitoringProvider 等,与 README 的文件清单一一对应。迁移完成后还会对连接池做配置:SetMaxIdleConnsSetMaxOpenConns、连接最大复用时长 1 小时。

数据库连接参数来自 server/configs/database.go,配置文件为应用根目录下 configs/database.yaml(旧版 database.json 会被自动迁移)。核心配置项:

dialect: sqlite3        # sqlite3 | postgresql | mysql
database: sliver
username: ""
password: ""
host: "127.0.0.1"
port: 5432
params: {}              # 连接参数(URL 编码)
pragmas: {}             # 仅 SQLite 生效
max_idle_conns: 10      # 默认值见 getDefaultDatabaseConfig
max_open_conns: 100
log_level: warn

SQLite 默认启用一组 pragma 以兼顾并发与耐久性:journal_mode(WAL)busy_timeout(5000)synchronous(NORMAL)temp_store(MEMORY),并附加 cache=shared;用户若显式提供 _pragma 参数则不会覆盖其自定义值。

信标与任务模型:beacon.go

beacon.go 是 C2 异步通信(Beacon 模式)的数据核心,包含两个模型:

Beacon——代表一台运行 beacon 植入体的主机,字段覆盖完整的主机画像与通信状态:

  • 主机信息:HostnameUsernameUID/GIDOSArchLocaleIntegrityBeforeCreate 默认置为 "-")、Capabilities(uint64 能力位图);
  • 通信信息:TransportRemoteAddressActiveC2ProxyURLVersion
  • 心跳信息:LastCheckinReconnectIntervalIntervalJitterNextCheckin
  • 关联:ImplantBuildID 指向植入体构建,Tasks []BeaconTask 一对多挂载任务队列。

BeaconTask——beacon 的任务执行单元,是"异步下发-回传"机制的持久化载体:

type BeaconTask struct {
	ID          UUID      `gorm:"primaryKey;->;<-:create;type:uuid;"`
	EnvelopeID  int64     `gorm:"uniqueIndex"` // 任务信封唯一编号
	BeaconID    UUID      `gorm:"type:uuid;"`
	CreatedAt   time.Time `gorm:"->;<-:create;"`
	State       string    // pending / sent / completed / canceled
	SentAt      int64
	CompletedAt int64
	Description string
	Request     []byte    // *sliverpb.Envelope(序列化后的请求)
	Response    []byte    // *sliverpb.Envelope(序列化后的响应)
}

状态常量定义在文件顶部:PENDINGSENTCOMPLETEDCANCELEDBeforeCreate 钩子用 crypto/rand 生成 8 字节随机数填充 EnvelopeIDBeacon.Task() 方法把 *sliverpb.Envelopeproto.Marshal 序列化后包装成 PENDING 状态的 BeaconTask,是 RPC 层下派任务的入口。ToProtobuf(content bool) 的布尔参数控制是否携带大体积的 Request/Response 二进制内容,列表场景可传 false 避免拉取全部数据。测试 beacon_test.go 验证了 Capabilities 字段在模型与 protobuf 间的传递。

监听任务模型:jobs.go

jobs.go 持久化服务器上长期运行的各类监听器任务。顶层模型 ListenerJobJobID uint32(唯一)标识任务编号,Type 字段标明监听器类型,并同时内嵌五类监听器配置结构:

  • HTTPListenerDomainHostPortSecureWebsite、内嵌 Cert/Key([]byte)、AcmeEnforceOtpLongPollTimeoutLongPollJitterRandomizeJarmStaging——覆盖 HTTP(S) 监听、ACME 证书、长轮询、JARM 指纹随机化等特性;
  • MTLSListenerHostPort
  • DNSListenerDomains []DnsDomain(一对多)、CanariesHostPortEnforceOtp
  • WGListenerHostPortNPortKeyPortTunIP
  • MultiplayerListener:多人模式监听,含 WireGuardWireGuardOptIn 两个字段——注释说明 WireGuardOptIn 用于区分"用户显式选择"与"WireGuard 曾短暂作为默认值自动创建的行",ToProtobuf()WireGuard: j.WireGuard && j.WireGuardOptIn 保证了只有显式选择才会生效。

ListenerJobFromProtobuf() 根据 Type 常量(constants.HttpStrMtlsStrDnsStrWGStrMultiplayerModeStr 等)把 protobuf 请求还原为对应监听器模型。

植入体构建与配置:implant.go

implant.go 是植入体生命周期(构建→配置→运行)的持久化中心,也是整个 models 包字段最丰富的文件之一。

ImplantBuild——一次植入体构建产物。除 Name(唯一)、三个校验和字段(MD5SHA1SHA256)与 Burned 标志(是否已在威胁情报平台曝光)外,还按传输通道分组保存了一整套密钥材料:

  • ECC:PeerPublicKeyPeerPublicKeyDigestPeerPrivateKeyPeerPublicKeySignatureAgeServerPublicKeyMinisignServerPublicKey
  • MTLS:MtlsCACertMtlsCertMtlsKey
  • WireGuard:WGImplantPrivKeyWGServerPubKey
  • Stage:是否为分阶段(staged)构建。

ImplantConfig——植入体构建配置,字段按功能域划分:Go 目标(GOOS/GOARCH/TemplateName)、beacon 行为(IsBeaconBeaconIntervalBeaconJitter)、规避选项(EvasionObfuscateSymbolsSGNEnabledShellcodeEncoder)、传输开关(IncludeMTLS/IncludeWG/IncludeHTTP/IncludeDNS/IncludeTCP/IncludeNamePipe)、运行限制(LimitDomainJoinedLimitHostnameLimitUsernameLimitDatetimeLimitFileExistsLimitLocale)、输出格式(FormatIsSharedLibIsServiceIsShellcode)、Donut shellcode 选项(DonutEntropy 等 8 项)以及 C2 []ImplantC2CanaryDomains []CanaryDomain 两个关联列表。HttpC2ConfigName 字段把配置与 HTTP C2 模型串接起来。

配套子模型:

  • ImplantC2:C2 地址条目(PriorityURLOptions),IsC2Enabled() 辅助函数按 URL scheme 判断某类传输是否启用;
  • CanaryDomain:canary 域名,属于 ImplantConfig
  • ImplantProfile:可复用配置模板,Name 唯一,内嵌一个 *ImplantConfig
  • EncoderAsset:仅记录被嵌入植入体的资产名(不保存实际数据)。

ImplantConfigFromProtobuf 在还原时会对 C2 URL 做 url.Parse 校验并记录告警日志,无效 URL 会被跳过(见 copyC2List)。

HTTP C2 配置模型:http-c2.go

http-c2.go 存储 HTTP(S) C2 信道的外观配置,是"流量伪装"能力的数据基础。顶层 HttpC2ConfigName 唯一)拆分为服务端与植入体两侧配置:

HttpC2ServerConfigRandomVersionHeaders(随机版本头)、Headers []HttpC2HeaderCookies []HttpC2Cookie

HttpC2ImplantConfig:包含伪装与路径生成的全部参数:

  • UserAgentChromeBaseVersion(默认 106)、MacOSVersion(默认 10_15_7)、NonceQueryArgCharsNonceQueryLengthNonceMode
  • ExtraURLParametersHeaders
  • 生成参数:MinFileGen/MaxFileGenMinPathGen/MaxPathGenMinPathLength/MaxPathLength
  • Extensions(GORM 不支持原生字符串数组,用逗号分隔存 text,转换时 strings.Split/strings.Join);
  • PathSegments []HttpC2PathSegment

六个子模型 HttpC2HeaderHttpC2CookieHttpC2URLParameterHttpC2PathSegment 均通过外键回指父配置。其中 HttpC2URLParameter 的注释明确 Name 至少 3 字符、Probability 取值 0–100;HttpC2PathSegmentSegmentType 注释标注取值含义为 Poll / Session / Close。

文件末尾的 RandomizeImplantConfig() 是植入体生成时使用的核心入口:它从父配置中按 Min/Max 范围随机抽样路径段(RandomPathSegments 内部按 IsFile 分拣出文件与路径两类后分别抽样,randomSample 保证空输入不 panic),并按目标 OS/Arch 用 GenerateUserAgent 生成对应平台的 Chrome UA 字符串(Windows/Linux/macOS 分支,macOS 使用 Macintosh; Intel Mac OS X 模板)。相关边界行为由 http-c2_test.go 中的 TestRandomSampleEmptyValuesTestRandomPathSegmentsWithNoSegments 覆盖。

侦察与情报模型

canary.go:DNS Canary

DNSCanarycanary.go)记录 DNS canary 域名及其触发状态:ImplantNameDomainTriggered(是否被访问)、FirstTrigger/LatestTriggerCount(触发次数)。ToProtobuf()time.RFC1123 格式化时间戳,反向转换 DNSCanaryFromProtobuf 则解析同一格式——保持跨端时间字符串的可互换性。

host.go:主机与 IOC

Hosthost.go)表示被发现的受害主机:HostUUID(唯一)、HostnameOSVersionLocale,并通过 foreignKey:HostID;references:HostUUID 外键声明关联两个列表——IOCs []IOC(上传到远程系统的可疑文件:PathFileHash)与 ExtensionData []ExtensionData(扩展输出:NameOutput)。ToProtobuf() 会把 ExtensionData 展平成以 Name 为键的 map,便于客户端按扩展名检索输出。

凭证与战利品模型

Credentialcredentials.go)保存捕获的凭证:OriginHostUUID(来源主机)、Collection(集合标签)、UsernamePlaintextHash(注释指向 hashcat 的 example_hashes 作为哈希类型参考)、HashType(int32)、IsCrackedHashType 与 protobuf 中的 clientpb.HashType 直接对应,为破解子系统提供类型映射。

Lootloot.go)描述战利品工件:FileTypeNameSizeOriginHostID。注意它只存元数据与来源,实际文件内容由服务器文件系统管理,ToProtobuf() 输出 clientpb.Loot 供客户端展示。

操作员与多人模式模型

Operatoroperator.go)表示有权连接服务器的操作员账号:

  • Token注意存储的是令牌的 SHA256 哈希gorm:"uniqueIndex",注释明示 "This is the SHA256 of the token"),而非明文;
  • 三级权限位:PermissionAll(访问全部 gRPC API)、PermissionBuilder(构建器 API)、PermissionCrackstation(破解站 API),默认均为 false
  • WireGuard 支持:WGPubKeyWGTunIP

同文件的 GenerateOperatorToken() 使用 crypto/rand 读取 32 字节生成 64 位十六进制令牌,失败直接 panic(安全随机数不可降级)。

WireGuard 密钥模型wgkeys.go)包含三个模型:WGKeys(服务器私钥/公钥)、MultiplayerWGKeys(多人模式专用)、WGPeer(对端:含 TunIP)。三者共用 initWGKeysModel 填充 ID 与时间戳。wgip_reservations.go 中的 WGIPReservation 则负责跨所有消费者统一分配 WireGuard 隧道 IP(TunIP 唯一索引、OwnerType 取值 operator/peerOwnerID),避免 IP 冲突。

证书与 CA 模型

CertificateAuthoritycertificate_authorities.go)与 Certificatecertificates.go)结构几乎一致,均含 CommonNameCATypeKeyType(仅叶子证书)、CertificatePEMPrivateKeyPEM。区别在于用途:CA 模型专门存储服务端证书颁发机构的 PEM 数据(注释强调 Stored separately from leaf certificates),叶子证书模型则持久化各监听器与操作员的证书元数据。二者分离存储,符合"CA 私钥与业务证书私钥隔离"的安全实践。

密码破解子系统:crackstations.go

crackstations.go 是 models 包中关联结构最复杂的文件,完整建模了分布式 hashcat 破解流水线,也是 README 点名的关键例程之一:

  • Crackstation:破解节点(ID 即节点名),记录 OperatorNameHashcatVersionBenchmarkHashcatVersionBenchmarkSchemaVersion,关联 Tasks []CrackTaskBenchmarks []Benchmark
  • Benchmark:性能基准(HashTypePerSecondRate 每秒速率);
  • CrackFile / CrackFileChunk:破解文件(字典、规则等)的分块传输模型,MaxN(chunkSize) 按分块大小计算总块数(math.Ceil),Sha2_256 校验完整性;
  • CrackJob一个任务 = 父命令,其 keyspace 可能被拆成多个 CrackTask 分发到多个破解站。状态判定由 Status() 方法集中实现:按 CancelledAt/PausedAt/CompletedAt/Err 以及子任务状态推导出 CANCELLED/PAUSED/IN_PROGRESS/FAILED/COMPLETED;字段 RecoveredBytes 注释说明它用于限制流式结果批次的累积恢复负载,属于内部队列记账、不暴露给操作员;
  • CrackTask:分发到具体破解站的任务分片,含租约机制字段(LeaseExpiresAtLastHeartbeatAtLeaseTokenAttempt 重试次数)、输出字段(Stdout/Stderr 及截断标志与总字节数)、分片范围(ShardSkip/ShardLimit);
  • CrackResult:恢复出的 hash/明文对,Fingerprint 为服务器生成的幂等键(唯一索引,不通过 RPC 暴露);
  • CrackJobCredential:记录任务选中的凭证行(CrackJobID+CredentialID 联合唯一);
  • CrackCommand完整封存 hashcat 命令的全部参数——从 AttackModeHashTypeHashes 到后端选择(BackendIgnoreCUDA/BackendIgnoreHip/BackendIgnoreMetal/BackendIgnoreOpenCL)、性能调优(WorkloadProfileKernelAccelKernelLoopsKernelThreads)、规则与字符集(CustomCharset1-8RuleLeft/RuleRightRulesFile)、brain 客户端等,甚至保留了一批 *V7 后缀字段(如 HashModeHccapxMessagePairV7)以兼容 hashcat v7 参数变体。列表字段用 legacyjsonslice 序列化器持久化。

索引设计上,CrackJob.CompletedAt+CreatedAt 组成 idx_crack_jobs_top 复合索引(CompletedAt 优先)服务"top 轮询",CrackTaskCrackJobID+StateLeaseExpiresAt+State 分别服务任务查询与租约回收,CrackCommand.CrackJobID 索引服务命令回溯。crackstations_test.go 中的 TestCrackTopPollingIndexesPRAGMA index_info 直接断言了这三个索引的列顺序。

网站托管模型:website.go

website.go 支持 C2 服务器托管诱饵网站。WebsiteName 唯一)一对多关联 WebContentWebContent 保存 PathSizeContentTypeOriginalFileSha256,实际内容不落库——Website.ToProtobuf(webContentDir) 从磁盘按 webcontent.ID.String() 拼路径读取文件字节,读不到则跳过,体现了"数据库存元数据、文件系统存内容"的设计。

杂项存储模型

  • KeyExHistorykeyex.go):密钥交换历史,主键直接是 Sha256 字符串,用于防重放/去重;
  • KeyValuekeyvalue.go):通用键值存储,Key 唯一,承载各类杂项设置;
  • MonitoringProvidermonitor.go):监控服务提供商配置(Type 注释为 vt 或 xforce,APIKeyAPIPassword);
  • ResourceIDresource_id.go):分配人类可读资源标识,Type 取值 encoderstagerValue 为请求中引用的素数编号。

AI 会话模型:ai.go

README 未列出但实际存在于目录中的 ai.go 为 Sliver 的 AI 辅助能力提供持久化:AIConversation(会话)记录操作员、Provider、Model、ThinkingLevelSystemPrompt、上下文窗口用量(输入/输出/总 token、窗口大小及是否估算)、会话目标(TargetSessionID/TargetBeaconID),一对多关联 AIConversationMessage(消息,含角色、序号、工具调用四元组 ToolCallID/ToolName/ToolArguments/ToolResult、可见性与状态等)。消息的 IncludeInContext 在未显式设置时由可见性(AI_MESSAGE_VISIBILITY_CONTEXT)推导,控制哪些消息参与上下文窗口计费与提交。

如何验证:测试覆盖哪些保证

models 包的测试文件从三个维度锁定了数据层行为:

  1. protobuf 双向转换crackstations_test.goTestCrackCommandExtendedFieldsProtobufRoundTrip 用 60+ 字段的完整样例验证 FromProtobuf(ToProtobuf(x)) == xTestCrackTaskProtobufRoundTrip 验证任务字段(含二进制 Stdout/Stderr)往返一致;TestCrackTaskZeroTimestampsRemainUnset 保证零值时间戳不误编码;
  2. SQLite 实际落库往返TestCrackCommandSQLiteRepeatedFieldsRoundTripglebarez/sqlite 打开临时库真实执行 AutoMigrateCreateFirst,验证切片字段持久化;迁移测试模拟旧列类型升级与 PostgreSQL 花括号数组的过渡表示;
  3. 索引与边界TestCrackTopPollingIndexes 检查轮询索引列序;http-c2_test.go 覆盖随机抽样的空输入边界;beacon_test.go 校验字段透传。

小结

server/db/models 是 Sliver 服务端状态的单一事实源(single source of truth):从 C2 信标、监听任务、植入体构建到破解流水线、证书体系、WireGuard 密钥与 AI 会话,全部围绕"GORM 模型 + UUID 主键 + BeforeCreate 钩子 + protobuf 转换方法"这一套统一约定组织。理解这 18+ 个模型文件及其关联结构,就等于掌握了 Sliver 服务端数据层的完整地图;在此基础上,通过 server/db/sql.go 的 AutoMigrate 清单可以看清全量表集合,通过 *_test.go 可以确认每个模型对外承诺的持久化与转换行为。

登录后查看全文
sliver