Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ examples/
├── neutron/ # POC 扫描工具
├── gogo/ # 端口扫描和指纹识别工具
├── spray/ # HTTP 批量探测工具
├── pending_pocs/ # 加载待审核 / 未启用的 POC
├── pending_fingerprints/ # 加载待审核 / 未启用的指纹
└── cases/ # 小颗粒度使用案例(cookbook)
├── match_detail/ # 获取 fingers matcher 详情(cmd + test)
└── spray_crawl_finger/ # 单 URL 爬虫 + 深度指纹探测(cmd + test)
Expand Down Expand Up @@ -292,6 +294,79 @@ go test ./cases/spray_crawl_finger -v

要点:`spray.NewConfig().WithMatchDetail()` 负责把 matcher 细节带进 `common.Framework`,随后在 `spray.Context` 上打开 `SetCrawlPlugin(true)` 和 `SetFinger(true)` 即可。

### pending_pocs - 加载待审核 / 未启用的 POC

默认 SDK 只导出 `status=active` 的 POC(向后兼容老用户)。如果客户端需要把
**待审核(`pending`)**、**草稿(`draft`)**、**已禁用(`inactive`)** 等规则也一起拉下来,
通过 `cyberhub.NewExportFilter().WithStatuses(...)` 显式声明即可:

```go
filter := cyberhub.NewExportFilter().
WithStatuses("active", "pending", "draft")
config := neutron.NewConfig().WithCyberhub(url, key)
config.ExportFilter = filter
engine, _ := neutron.NewEngine(config)
```

如需按审核工单状态过滤(如只看正在待审核工单的规则),再加:

```go
filter.WithReviewStatus("pending")
```

合法值:

- `WithStatuses(...)`:`active` / `pending` / `draft` / `inactive` / `deprecated`
- `WithReviewStatus(...)`:`pending` / `approved` / `rejected` / `draft` / `none`

完整示例:

```bash
# 默认 active
go run ./pending_pocs -url http://127.0.0.1:8080 -key your_api_key

# 加载 active + pending + draft
go run ./pending_pocs -url ... -key ... -statuses active,pending,draft

# 只看正在待审核工单的规则
go run ./pending_pocs -url ... -key ... -review pending
```

注意:未显式调用 `WithStatuses(...)` / `WithReviewStatus(...)` 的旧客户端不会受影响 ——
SDK 仍只导出 active 状态的 POC。

### pending_fingerprints - 加载待审核 / 未启用的指纹

和 POC 不同,**SDK 拉取指纹时不会强制 `status=active`**,后端默认就会返回
`active + 非空 pending + inactive + deprecated`。但 `draft` 和"`raw_content` 为空的 pending"
仍会被后端 `shouldHideDraftOnlyFingerprints` 规则隐掉。如果需要拿到这部分"空壳"
待审核指纹,仍要显式声明:

```go
filter := cyberhub.NewExportFilter().
WithStatuses("active", "pending", "draft", "inactive")
config := fingers.NewConfig().WithCyberhub(url, key)
config.ExportFilter = filter
engine, _ := fingers.NewEngine(config)
```

CLI 演示:

```bash
# 走后端默认(不含 draft 和空 pending)
go run ./pending_fingerprints -url http://127.0.0.1:8080 -key your_api_key

# 显式拉全部非删除态(含 draft / 空 pending)
go run ./pending_fingerprints -url ... -key ... -statuses active,pending,draft,inactive

# 只看正在待审核工单的指纹
go run ./pending_fingerprints -url ... -key ... -review pending
```

> 提示:`ExportFilter` 是 POC 和指纹共用的,`WithStatuses(...)` / `WithReviewStatus(...)`
> 在 `fingers.Engine` 和 `neutron.Engine` 上的语义一致;只是默认行为不同
> (POC 默认 active,指纹默认非 deleted)。

---

## 常见问题
Expand Down
91 changes: 91 additions & 0 deletions examples/pending_fingerprints/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Example: load 待审核 / 草稿 / 未启用 (non-active) fingerprints from a Cyberhub backend.
//
// 与 examples/pending_pocs 的区别:
//
// - POC 导出:SDK 默认强制 status=active;要拉 pending/draft,必须显式 WithStatuses(...)。
// - 指纹导出:SDK 不强制状态,后端默认就会返回 active + 非空 pending + inactive + deprecated;
// 但 draft 和"raw_content 为空的 pending"会被后端 shouldHideDraftOnlyFingerprints
// 规则隐掉。如果客户端要拿到这部分"空壳待审核"指纹,仍需显式
// WithStatuses("pending") / WithStatuses("draft") 等。
//
// 用法:
//
// pending_fingerprints -url http://127.0.0.1:8080 -key YOUR_KEY
// pending_fingerprints -url ... -key ... -statuses active,pending,draft
// pending_fingerprints -url ... -key ... -review pending
package main

import (
"flag"
"fmt"
"os"
"strings"

"github.com/chainreactors/sdk/fingers"
"github.com/chainreactors/sdk/pkg/cyberhub"
)

var (
cyberhubURL = flag.String("url", "", "Cyberhub URL (e.g., http://127.0.0.1:8080)")
apiKey = flag.String("key", "", "Cyberhub API Key")
statuses = flag.String("statuses", "",
"指纹生命周期状态(逗号分隔):active / pending / draft / inactive / deprecated;留空走后端默认(不含 draft 和空 pending)")
review = flag.String("review", "",
"审核流程状态:pending / approved / rejected / draft / none,留空表示不按审核状态过滤")
preview = flag.Int("preview", 10, "最多打印多少条指纹摘要")
)

func main() {
flag.Parse()

if *cyberhubURL == "" || *apiKey == "" {
fmt.Println("usage: pending_fingerprints -url <cyberhub_url> -key <api_key> [-statuses active,pending] [-review pending]")
flag.PrintDefaults()
os.Exit(1)
}

// 1. 构造 ExportFilter;不调 WithStatuses 时走后端默认语义。
filter := cyberhub.NewExportFilter()
if list := splitCSV(*statuses); len(list) > 0 {
filter.WithStatuses(list...)
}
if *review != "" {
filter.WithReviewStatus(*review)
}

// 2. 挂到 fingers.Config 上(和 POC 路径完全对称)
config := fingers.NewConfig().WithCyberhub(*cyberhubURL, *apiKey)
config.ExportFilter = filter

// 3. 创建引擎,触发拉取
engine, err := fingers.NewEngine(config)
if err != nil {
fmt.Printf("create engine failed: %v\n", err)
os.Exit(1)
}

fmt.Printf("加载到 %d 条指纹 (statuses=%q review=%q)\n", engine.Count(), *statuses, *review)

items := config.FullFingers.Fingers()
limit := *preview
if limit > len(items) {
limit = len(items)
}
for i := 0; i < limit; i++ {
f := items[i]
fmt.Printf(" [%s] protocol=%s tags=%v\n", f.Name, f.Protocol, f.Tags)
}
if len(items) > limit {
fmt.Printf("... (省略 %d 条)\n", len(items)-limit)
}
}

func splitCSV(s string) []string {
out := []string{}
for _, p := range strings.Split(s, ",") {
if p = strings.TrimSpace(p); p != "" {
out = append(out, p)
}
}
return out
}
85 changes: 85 additions & 0 deletions examples/pending_pocs/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Example: load 待审核 / 未启用 (non-active) POCs from a Cyberhub backend.
//
// 默认情况下 SDK 只会拉取 status=active 的 POC(向后兼容老用户)。
// 如需加载待审核 / 草稿 / 已禁用的规则,显式通过
// cyberhub.NewExportFilter().WithStatuses(...) / .WithReviewStatus(...) 指定。
//
// 用法:
//
// pending_pocs -url http://127.0.0.1:8080 -key YOUR_KEY
// pending_pocs -url ... -key ... -statuses pending,draft
// pending_pocs -url ... -key ... -review pending
package main

import (
"flag"
"fmt"
"os"
"strings"

"github.com/chainreactors/sdk/neutron"
"github.com/chainreactors/sdk/pkg/cyberhub"
)

var (
cyberhubURL = flag.String("url", "", "Cyberhub URL (e.g., http://127.0.0.1:8080)")
apiKey = flag.String("key", "", "Cyberhub API Key")
statuses = flag.String("statuses", "active,pending,draft",
"POC 生命周期状态(逗号分隔):active / pending / draft / inactive / deprecated")
review = flag.String("review", "",
"审核流程状态:pending / approved / rejected / draft / none,留空表示不按审核状态过滤")
preview = flag.Int("preview", 10, "最多打印多少条 POC 摘要")
)

func main() {
flag.Parse()

if *cyberhubURL == "" || *apiKey == "" {
fmt.Println("usage: pending_pocs -url <cyberhub_url> -key <api_key> [-statuses active,pending] [-review pending]")
flag.PrintDefaults()
os.Exit(1)
}

// 1. 构造 ExportFilter,显式声明需要的状态
filter := cyberhub.NewExportFilter().
WithStatuses(splitCSV(*statuses)...)
if *review != "" {
filter.WithReviewStatus(*review)
}

// 2. 挂到 neutron.Config 上(和 examples/filter/main.go 同款用法)
config := neutron.NewConfig().WithCyberhub(*cyberhubURL, *apiKey)
config.ExportFilter = filter

// 3. 创建引擎,触发拉取
engine, err := neutron.NewEngine(config)
if err != nil {
fmt.Printf("create engine failed: %v\n", err)
os.Exit(1)
}

tpls := engine.Get()
fmt.Printf("加载到 %d 条 POC (statuses=%s review=%q)\n", len(tpls), *statuses, *review)

limit := *preview
if limit > len(tpls) {
limit = len(tpls)
}
for i := 0; i < limit; i++ {
t := tpls[i]
fmt.Printf(" [%s] %s severity=%s\n", t.Id, t.Info.Name, t.Info.Severity)
}
if len(tpls) > limit {
fmt.Printf("... (省略 %d 条)\n", len(tpls)-limit)
}
}

func splitCSV(s string) []string {
out := []string{}
for _, p := range strings.Split(s, ",") {
if p = strings.TrimSpace(p); p != "" {
out = append(out, p)
}
}
return out
}
64 changes: 59 additions & 5 deletions pkg/cyberhub/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,12 @@ func (c *Client) ExportPOCs(ctx context.Context, tags []string, severities []str
params.Add("sources", source)
}

// 只导出激活状态的 POC
params.Set("status", "active")

// 添加筛选参数
// 添加筛选参数(包括 Statuses / ReviewStatus,调用方未显式指定时下方再回退 active)
applyFilterParams(params, firstFilter(filters))

// 向后兼容:调用方未显式指定 POC 状态时,默认仅导出 active
applyDefaultPOCStatus(params)

endpoint := fmt.Sprintf("%s/pocs/export?%s", c.baseURL, params.Encode())

var response POCListResponse
Expand All @@ -141,6 +141,11 @@ func (c *Client) ExportPOCs(ctx context.Context, tags []string, severities []str

// ExportPOCsByNames 按名称列表导出 POC
func (c *Client) ExportPOCsByNames(ctx context.Context, names []string) ([]POCResponse, error) {
return c.ExportPOCsByNamesWithFilter(ctx, names, nil)
}

// ExportPOCsByNamesWithFilter 按名称列表导出 POC,并应用额外筛选条件。
func (c *Client) ExportPOCsByNamesWithFilter(ctx context.Context, names []string, filter *ExportFilter) ([]POCResponse, error) {
if len(names) == 0 {
return nil, nil
}
Expand All @@ -149,7 +154,12 @@ func (c *Client) ExportPOCsByNames(ctx context.Context, names []string) ([]POCRe
for _, name := range names {
params.Add("names", name)
}
params.Set("status", "active")

// 添加筛选参数;调用方未显式指定状态时下方再回退 active,保持旧调用行为。
applyFilterParams(params, filter)

// 按名称导出沿用默认行为:仅导出 active 状态。
applyDefaultPOCStatus(params)

endpoint := fmt.Sprintf("%s/pocs/export?%s", c.baseURL, params.Encode())

Expand Down Expand Up @@ -237,6 +247,50 @@ func applyFilterParams(params url.Values, filter *ExportFilter) {
params.Set("page", "1")
params.Set("page_size", strconv.Itoa(filter.Limit))
}

// 生命周期状态:透传为 statuses=...(多值),后端走 IN(...) 分支。
// 用 dedup 逻辑避免重复透传。
if len(filter.Statuses) > 0 {
existingStatuses := make(map[string]struct{})
for _, s := range params["statuses"] {
if s == "" {
continue
}
existingStatuses[s] = struct{}{}
}
for _, s := range filter.Statuses {
s = strings.TrimSpace(s)
if s == "" {
continue
}
if _, exists := existingStatuses[s]; exists {
continue
}
params.Add("statuses", s)
existingStatuses[s] = struct{}{}
}
}

// 审核流程状态:单值,存在即覆盖。
if rs := strings.TrimSpace(filter.ReviewStatus); rs != "" {
params.Set("review_status", rs)
}
}

// applyDefaultPOCStatus 在调用方未显式指定任何 POC 状态相关参数时,
// 把请求收敛回"仅导出 active",保持与旧版 SDK 一致的行为。
// 显式 statuses= / status= / review_status= 任一存在时,不再注入默认值。
func applyDefaultPOCStatus(params url.Values) {
if len(params["statuses"]) > 0 {
return
}
if params.Get("status") != "" {
return
}
if params.Get("review_status") != "" {
return
}
params.Set("status", "active")
}

type requestBodyProvider struct {
Expand Down
Loading
Loading