Skip to content

Commit

Permalink
feat: update README
Browse files Browse the repository at this point in the history
close #16
close #40
  • Loading branch information
zema1 committed Jul 16, 2023
1 parent 27ffefe commit 923e4da
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 73 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
## v1.1.0 (2023.07.16)

### 新增

- 增加开源检索信息,CVE 漏洞推送时自动搜索 Github 仓库和 Nuclei
模板相关连接 [#38](https://github.com/zema1/watchvuln/issues/38)
- 增加 `mysql``postgres` 数据库支持 [#36](https://github.com/zema1/watchvuln/issues/36)
- 奇安信威胁情报中心(`ti`) 改为奇安信安全监测平台(`nox`) [#40](https://github.com/zema1/watchvuln/issues/40)
- 增加漏洞修复方案的抓取和推送逻辑
- 数据库表增加 `create_time``update_time` 字段
- 重构控制器逻辑, 优化代码结构

### 变更

- 因 seebug 存在 waf,默认不再启用 seebug 数据源
- 初始化时的 `pagesize` 从 100 改为 10

## v1.0.0 (2023.06.05)

### 新增
Expand Down
84 changes: 46 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
| 名称 | 地址 | 推送策略 |
|---------------|---------------------------------------|--------------------------------------------------|
| 阿里云漏洞库 | https://avd.aliyun.com/high-risk/list | 等级为高危或严重 |
| OSCS开源安全情报预警 | https://www.oscs1024.com/cm | 等级为严重**并且**包含 `预警` 标签 |
| 奇安信威胁情报中心 | https://ti.qianxin.com/vulnerability | 等级为高危严重**并且**包含 `奇安信CERT验证` `POC公开` `技术细节公布`标签之一 |
| 知道创宇Seebug漏洞库 | https://www.seebug.org/ | 等级为高危或严重 |
| OSCS开源安全情报预警 | https://www.oscs1024.com/cm | 等级为高危或严重**并且**包含 `预警` 标签 |
| 奇安信安全监测平台 | https://nox.qianxin.com/KeyPoint | 等级为高危严重**并且**包含 `奇安信CERT验证` `POC公开` `技术细节公布`标签之一 |
| 知道创宇Seebug漏洞库 | https://www.seebug.org/ | 存在 WAF,默认不启用,若手动启用,则推送等级为高危或严重 |

> 如果有侵权,请提交 issue, 我会删除相关源。
> 所有信息来自网站公开页面, 如果有侵权,请提交 issue, 我会删除相关源。
>
> 如果有更好的信息源也可以反馈给我,需要能够响应及时 & 有办法过滤出有价值的漏洞
具体来说,消息的推送有两种情况, 两种情况有内置去重,不会重复推送:
Expand All @@ -38,21 +39,22 @@

Docker 方式推荐使用环境变量来配置服务参数

| 环境变量名 | 说明 | 默认值 |
|-------------------------|--------------------------------------------|----------------------|
| `DINGDING_ACCESS_TOKEN` | 钉钉机器人 url 的 `access_token` 部分 | |
| `DINGDING_SECRET` | 钉钉机器人的加签值 (仅支持加签方式) | |
| `LARK_ACCESS_TOKEN` | 飞书机器人 url 的 `/open-apis/bot/v2/hook/` 后的部分 | |
| `LARK_SECRET` | 飞书机器人的加签值 (仅支持加签方式) | |
| `WECHATWORK_KEY ` | 微信机器人 url 的 `key` 部分 | |
| `SERVERCHAN_KEY ` | Server酱的 `SCKEY` | |
| `WEBHOOK_URL` | 自定义 webhook 服务的完整 url | |
| `BARK_URL` | Bark 服务的完整 url, 路径需要包含 DeviceKey | |
| `SOURCES` | 启用哪些漏洞信息源,逗号分隔, 可选 `avd`, `ti`, `oscs` | `avd,ti,oscs,seebug` |
| `INTERVAL` | 检查周期,支持秒 `60s`, 分钟 `10m`, 小时 `1h`, 最低 `1m` | `30m` |
| `ENABLE_CVE_FILTER` | 启用 CVE 过滤,开启后多个数据源的统一 CVE 将只推送一次 | `true` |
| `NO_FILTER` | 禁用上述推送过滤策略,所有新发现的漏洞都会被推送 | `false` |
| `NO_START_MESSAGE` | 禁用服务启动的提示信息 | `false` |
| 环境变量名 | 说明 | 默认值 |
|-------------------------|-------------------------------------------------|-----------------------------|
| `DB_CONN` | 数据库链接字符串,详情见 [数据库连接](#数据库连接) | `sqlite3://vuln_v3.sqlite3` |
| `DINGDING_ACCESS_TOKEN` | 钉钉机器人 url 的 `access_token` 部分 | |
| `DINGDING_SECRET` | 钉钉机器人的加签值 (仅支持加签方式) | |
| `LARK_ACCESS_TOKEN` | 飞书机器人 url 的 `/open-apis/bot/v2/hook/` 后的部分 | |
| `LARK_SECRET` | 飞书机器人的加签值 (仅支持加签方式) | |
| `WECHATWORK_KEY ` | 微信机器人 url 的 `key` 部分 | |
| `SERVERCHAN_KEY ` | Server酱的 `SCKEY` | |
| `WEBHOOK_URL` | 自定义 webhook 服务的完整 url | |
| `BARK_URL` | Bark 服务的完整 url, 路径需要包含 DeviceKey | |
| `SOURCES` | 启用哪些漏洞信息源,逗号分隔, 可选 `avd`, `ti`, `nox`, `seebug` | `avd,nox,oscs` |
| `INTERVAL` | 检查周期,支持秒 `60s`, 分钟 `10m`, 小时 `1h`, 最低 `1m` | `30m` |
| `ENABLE_CVE_FILTER` | 启用 CVE 过滤,开启后多个数据源的统一 CVE 将只推送一次 | `true` |
| `NO_FILTER` | 禁用上述推送过滤策略,所有新发现的漏洞都会被推送 | `false` |
| `NO_START_MESSAGE` | 禁用服务启动的提示信息 | `false` |

比如使用钉钉机器人

Expand Down Expand Up @@ -145,26 +147,17 @@ docker run --restart always -d \
</details>


初次运行会在本地建立全量数据库,大约需要 1~5 分钟,可以使用 `docker logs -f [containerId]` 来查看进度,
初次运行会在本地建立全量数据库,大约需要 1 分钟,可以使用 `docker logs -f [containerId]` 来查看进度,
完成后会在群内收到一个提示消息,表示服务已经在正常运行了。

### 使用二进制

前往 Release 下载对应平台的二进制,然后在命令行执行。
前往 Release 下载对应平台的二进制,然后在命令行执行。命令行参数请参考 Docker 环境变量部分的说明,可以一一对应。

```bash
NAME:
watchvuln - A high valuable vulnerability watcher and pusher

USAGE:
watchvuln [global options] command [command options] [arguments...]

VERSION:
v1.0.0

COMMANDS:
help, h Shows a list of commands or help for one command

GLOBAL OPTIONS:
[Push Options]

Expand All @@ -179,11 +172,13 @@ GLOBAL OPTIONS:

[Launch Options]

--enable-cve-filter enable a filter that vulns from multiple sources with same cve id will be sent only once (default: true)
--interval value, -i value checking every [interval], supported format like 30s, 30m, 1h (default: "30m")
--no-filter, --nf ignore the valuable filter and push all discovered vulns (default: false)
--no-start-message, --nm disable the hello message when server starts (default: false)
--sources value, -s value set vuln sources (default: "avd,ti,oscs,seebug")
--db-conn value, --db value database connection string (default: "sqlite3://vuln_v3.sqlite3")
--enable-cve-filter enable a filter that vulns from multiple sources with same cve id will be sent only once (default: true)
--interval value, -i value checking every [interval], supported format like 30s, 30m, 1h (default: "30m")
--no-filter, --nf ignore the valuable filter and push all discovered vulns (default: false)
--no-github-search, --ng don't search github repos and pull requests for every cve vuln (default: false)
--no-start-message, --nm disable the hello message when server starts (default: false)
--sources value, -s value set vuln sources (default: "avd,nox,oscs")
[Other Options]
Expand Down Expand Up @@ -253,14 +248,27 @@ $ ./watchvuln --dt DINGDING_ACCESS_TOKEN --ds DINGDING_SECRET --wk WECHATWORK_KE
</details>
## 数据库连接
默认使用 sqlite3 作为数据库,数据库文件为 `vuln_v3.sqlite3`,如果需要使用其他数据库,可以通过 `--db`
参数或是环境变量 `DB_CONN` 指定连接字符串,当前支持的数据库有:
- `sqlite3://filename`
- `mysql://user:pass@host:port/dbname`
- `postgres://user:pass@host:port/dbname`
注意:该项目不做数据向后兼容保证,版本升级可能存在数据不兼容的情况,如果报错需要删库重来。
## 常见问题
1. 服务重启后支持增量更新吗
支持,数据会保存在运行目录的 `vuln_vx.sqlite3` 中,这是一个 sqlite3 的数据库,服务重启后将按照一定的策略去增量抓取。
2. 如何强制重新创建本地数据库
支持,每次检查会按照一定的策略去增量抓取
2. Docker 拉取镜像提示 `not found`
删除运行目录的 `vuln_vx.sqlite3` 文件再重新运行即可
你使用的 Docker 版本太老了,不支持新的镜像格式,需要升级一下 Docker
版本,参考 [#16](https://github.com/zema1/watchvuln/issues/16)
## 其他
Expand Down
53 changes: 30 additions & 23 deletions ctrl/ctrl.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ func init() {
sql.Register("postgres", &stdlib.Driver{})
}

const MaxPageBase = 2
const (
MaxPageBase = 2
InitDataPageSize = 10
GetUpdatePageSize = 10
)

type WatchVulnApp struct {
config *WatchVulnAppConfig
Expand Down Expand Up @@ -70,7 +74,7 @@ func NewApp(config *WatchVulnAppConfig, textPusher push.TextPusher, rawPusher pu
switch part {
case "avd":
grabs = append(grabs, grab.NewAVDCrawler())
case "nox":
case "nox", "ti":
grabs = append(grabs, grab.NewNoxCrawler())
case "oscs":
grabs = append(grabs, grab.NewOSCSCrawler())
Expand Down Expand Up @@ -102,12 +106,15 @@ func NewApp(config *WatchVulnAppConfig, textPusher push.TextPusher, rawPusher pu
func (w *WatchVulnApp) Run(ctx context.Context) error {
w.log.Infof("initialize local database..")
// 抓取前3页作为基准漏洞数据
eg, initCtx := errgroup.WithContext(ctx)
var eg errgroup.Group
eg.SetLimit(len(w.grabbers))
for _, grabber := range w.grabbers {
grabber := grabber
eg.Go(func() error {
return w.initData(initCtx, grabber)
if err := w.initData(ctx, grabber); err != nil {
return errors.Wrap(err, grabber.ProviderInfo().Name)
}
return nil
})
}
err := eg.Wait()
Expand Down Expand Up @@ -215,7 +222,7 @@ func (w *WatchVulnApp) Run(ctx context.Context) error {
if err != nil {
w.log.Warn(err)
}
w.log.Infof("%s found %d prs from github, %v", v.CVE, len(links), links)
w.log.Infof("%s found %d links from github, %v", v.CVE, len(links), links)
if len(links) != 0 {
v.GithubSearch = grab.MergeUniqueString(v.GithubSearch, links)
_, err = dbVuln.Update().SetGithubSearch(v.GithubSearch).Save(ctx)
Expand Down Expand Up @@ -246,11 +253,10 @@ func (w *WatchVulnApp) Close() {
}

func (w *WatchVulnApp) initData(ctx context.Context, grabber grab.Grabber) error {
pageSize := 10
source := grabber.ProviderInfo()
total, err := grabber.GetPageCount(ctx, pageSize)
total, err := grabber.GetPageCount(ctx, InitDataPageSize)
if err != nil {
return nil
return err
}
if total == 0 {
return fmt.Errorf("%s got unexpected zero page", source.Name)
Expand All @@ -266,9 +272,9 @@ func (w *WatchVulnApp) initData(ctx context.Context, grabber grab.Grabber) error
for i := 1; i <= total; i++ {
i := i
eg.Go(func() error {
dataChan, err := grabber.ParsePage(ctx, i, pageSize)
dataChan, err := grabber.ParsePage(ctx, i, InitDataPageSize)
if err != nil {
return err
return errors.Wrap(err, grabber.ProviderInfo().Name)
}
for data := range dataChan {
if _, err = w.createOrUpdate(ctx, source, data); err != nil {
Expand All @@ -286,8 +292,7 @@ func (w *WatchVulnApp) initData(ctx context.Context, grabber grab.Grabber) error
}

func (w *WatchVulnApp) collectUpdate(ctx context.Context) ([]*grab.VulnInfo, error) {
pageSize := 10
eg, ctx := errgroup.WithContext(ctx)
var eg errgroup.Group
eg.SetLimit(len(w.grabbers))

var mu sync.Mutex
Expand All @@ -297,24 +302,24 @@ func (w *WatchVulnApp) collectUpdate(ctx context.Context) ([]*grab.VulnInfo, err
grabber := grabber
eg.Go(func() error {
source := grabber.ProviderInfo()
pageCount, err := grabber.GetPageCount(ctx, pageSize)
pageCount, err := grabber.GetPageCount(ctx, GetUpdatePageSize)
if err != nil {
return err
return errors.Wrap(err, grabber.ProviderInfo().Name)
}
if pageCount > MaxPageBase {
pageCount = MaxPageBase
}
for i := 1; i <= pageCount; i++ {
dataChan, err := grabber.ParsePage(ctx, i, pageSize)
dataChan, err := grabber.ParsePage(ctx, i, GetUpdatePageSize)
if err != nil {
return err
return errors.Wrap(err, grabber.ProviderInfo().Name)
}
hasNewVuln := false

for data := range dataChan {
isNewVuln, err := w.createOrUpdate(ctx, source, data)
if err != nil {
return err
return errors.Wrap(err, grabber.ProviderInfo().Name)
}
if isNewVuln {
w.log.Infof("found new vuln: %s", data)
Expand Down Expand Up @@ -361,7 +366,7 @@ func (w *WatchVulnApp) createOrUpdate(ctx context.Context, source *grab.Provider
if err != nil {
return false, err
}
w.log.Infof("vuln %s(%s) created from %s %s", newVuln.Title, newVuln.Key, source.Name)
w.log.Infof("vuln %s(%s) created from %s", newVuln.Title, newVuln.Key, source.Name)
return true, nil
}

Expand Down Expand Up @@ -438,6 +443,11 @@ func (w *WatchVulnApp) FindGithubPoc(ctx context.Context, cveId string) ([]strin
}

func (w *WatchVulnApp) findGithubRepo(ctx context.Context, cveId string) ([]string, error) {
w.log.Infof("finding github repo of %s", cveId)
re, err := regexp.Compile(fmt.Sprintf("(?i)[\b/_]%s[\b/_]", cveId))
if err != nil {
return nil, err
}
lastYear := time.Now().AddDate(-1, 0, 0).Format("2006-01-02")
query := fmt.Sprintf(`language:Python language:JavaScript language:C language:C++ language:Java language:PHP language:Ruby language:Rust language:C# created:>%s %s`, lastYear, cveId)
result, _, err := w.githubClient.Search.Repositories(ctx, query, &github.SearchOptions{
Expand All @@ -447,10 +457,6 @@ func (w *WatchVulnApp) findGithubRepo(ctx context.Context, cveId string) ([]stri
return nil, err
}
var links []string
re, err := regexp.Compile(fmt.Sprintf(`(?)\b%s\b`, cveId))
if err != nil {
return nil, err
}
for _, repo := range result.Repositories {
if re.MatchString(repo.GetHTMLURL()) {
links = append(links, repo.GetHTMLURL())
Expand All @@ -460,6 +466,7 @@ func (w *WatchVulnApp) findGithubRepo(ctx context.Context, cveId string) ([]stri
}

func (w *WatchVulnApp) findNucleiPR(ctx context.Context, cveId string) ([]string, error) {
w.log.Infof("finding nuclei PR of %s", cveId)
if w.prs == nil {
// 检查200个pr
for page := 1; page < 2; page++ {
Expand All @@ -480,7 +487,7 @@ func (w *WatchVulnApp) findNucleiPR(ctx context.Context, cveId string) ([]string
}

var links []string
re, err := regexp.Compile(fmt.Sprintf(`(?)\b%s\b`, cveId))
re, err := regexp.Compile(fmt.Sprintf("(?i)[\b/_]%s[\b/_]", cveId))
if err != nil {
return nil, err
}
Expand Down
11 changes: 9 additions & 2 deletions ctrl/ctrl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import (
"context"
"fmt"
"github.com/stretchr/testify/require"
"regexp"
"testing"
)

func TestGithubSearch(t *testing.T) {
assert := require.New(t)
t.Skipf("local tests")

app, err := NewApp(&WatchVulnAppConfig{
DBConn: "",
DBConn: "sqlite3://vuln_v3.sqlite3",
Sources: nil,
Interval: 30,
EnableCVEFilter: false,
Expand All @@ -21,7 +23,12 @@ func TestGithubSearch(t *testing.T) {
Version: "",
}, nil, nil)
assert.Nil(err)
links, err := app.FindGithubPoc(context.Background(), "CVE-2023-25157")
links, err := app.FindGithubPoc(context.Background(), "CVE-2023-37582")
assert.Nil(err)
fmt.Println(links)
}

func TestReMatch(t *testing.T) {
re := regexp.MustCompile("(?i)[\b/_]CVE-2023-37582[\b/_]")
fmt.Println(re.MatchString("https://github.com/Malayke/CVE-2023-37582_EXPLOIT"))
}
10 changes: 5 additions & 5 deletions grab/nox.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"fmt"
"github.com/imroc/req/v3"
"github.com/kataras/golog"
"strconv"
"strings"
)

Expand Down Expand Up @@ -132,10 +131,11 @@ func (t *NoxCrawler) ParsePage(ctx context.Context, page, size int) (chan *VulnI
From: "https://nox.qianxin.com/vulnerability/detail/" + d.QvdCode,
Creator: t,
}
err = t.updateDetail(strconv.Itoa(d.Id), info)
if err != nil {
t.log.Warnf("failed to update %s detail, %s", d.QvdCode, err)
}
// 有限制先去掉了: 您今日的漏洞详情查看次数已达上限,请明天再试
//err = t.updateDetail(strconv.Itoa(d.Id), info)
//if err != nil {
// t.log.Warnf("failed to update %s detail, %s", d.QvdCode, err)
//}
result <- info
}
}()
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func main() {
Name: "sources",
Aliases: []string{"s"},
Usage: "set vuln sources",
Value: "avd,nox,oscs,seebug",
Value: "avd,nox,oscs",
Category: "[Launch Options]",
},
&cli.StringFlag{
Expand Down
Loading

0 comments on commit 923e4da

Please sign in to comment.