Skip to content

Commit

Permalink
feat(inthewild): support inTheWild PoCs (#78)
Browse files Browse the repository at this point in the history
* feat(inthewild): support inTheWild PoCs

* docs: update README
  • Loading branch information
MaineK00n authored Mar 24, 2022
1 parent 694e678 commit 8af7dcb
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 4 deletions.
4 changes: 4 additions & 0 deletions GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,12 @@ fetch-rdb:
integration/exploitdb.old fetch awesomepoc --dbpath=$(PWD)/integration/go-exploitdb.old.sqlite3
integration/exploitdb.old fetch exploitdb --dbpath=$(PWD)/integration/go-exploitdb.old.sqlite3
integration/exploitdb.old fetch githubrepos --dbpath=$(PWD)/integration/go-exploitdb.old.sqlite3
integration/exploitdb.old fetch inthewild --dbpath=$(PWD)/integration/go-exploitdb.old.sqlite3

integration/exploitdb.new fetch awesomepoc --dbpath=$(PWD)/integration/go-exploitdb.new.sqlite3
integration/exploitdb.new fetch exploitdb --dbpath=$(PWD)/integration/go-exploitdb.new.sqlite3
integration/exploitdb.new fetch githubrepos --dbpath=$(PWD)/integration/go-exploitdb.new.sqlite3
integration/exploitdb.new fetch inthewild --dbpath=$(PWD)/integration/go-exploitdb.new.sqlite3

fetch-redis:
docker run --name redis-old -d -p 127.0.0.1:6379:6379 redis
Expand All @@ -102,10 +104,12 @@ fetch-redis:
integration/exploitdb.old fetch awesomepoc --dbtype redis --dbpath "redis://127.0.0.1:6379/0"
integration/exploitdb.old fetch exploitdb --dbtype redis --dbpath "redis://127.0.0.1:6379/0"
integration/exploitdb.old fetch githubrepos --dbtype redis --dbpath "redis://127.0.0.1:6379/0"
integration/exploitdb.old fetch inthewild --dbtype redis --dbpath "redis://127.0.0.1:6379/0"

integration/exploitdb.new fetch awesomepoc --dbtype redis --dbpath "redis://127.0.0.1:6380/0"
integration/exploitdb.new fetch exploitdb --dbtype redis --dbpath "redis://127.0.0.1:6380/0"
integration/exploitdb.new fetch githubrepos --dbtype redis --dbpath "redis://127.0.0.1:6380/0"
integration/exploitdb.new fetch inthewild --dbtype redis --dbpath "redis://127.0.0.1:6380/0"

diff-cveid:
@ python integration/diff_server_mode.py cveid --sample_rate 0.01
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ As the following vulnerabilities database
1. [ExploitDB(OffensiveSecurity)](https://www.exploit-db.com/) by CVE number or Exploit Database ID.
2. [GitHub Repositories](https://github.com/search?o=desc&q=CVE&s=&type=Repositories)
3. [Awesome Cve Poc](https://github.com/qazbnm456/awesome-cve-poc#toc473)
4. [inTheWild DB](https://github.com/gmatuz/inthewilddb)

### Docker Deployment
There's a Docker image available `docker pull princechrismc/go-exploitdb`. When using the container, it takes the same arguments as the [normal command line](#Usage).
There's a Docker image available `docker pull vulsio/go-exploitdb`. When using the container, it takes the same arguments as the [normal command line](#Usage).

### Installation for local deployment
###### Requirements
Expand Down Expand Up @@ -54,6 +55,7 @@ Available Commands:
awesomepoc Fetch the data of Awesome Poc
exploitdb Fetch the data of offensive security exploit db
githubrepos Fetch the data of github repos
inthewild Fetch the data of inTheWild Poc

Flags:
--batch-size int The number of batch size to insert. (default 500)
Expand Down
76 changes: 76 additions & 0 deletions commands/fetch-inthewild.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package commands

import (
"time"

"github.com/inconshreveable/log15"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/xerrors"

"github.com/vulsio/go-exploitdb/db"
"github.com/vulsio/go-exploitdb/fetcher"
"github.com/vulsio/go-exploitdb/models"
"github.com/vulsio/go-exploitdb/util"
)

var fetchInTheWildCmd = &cobra.Command{
Use: "inthewild",
Short: "Fetch the data of inTheWild Poc",
Long: `Fetch the data of inTheWild Poc`,
RunE: fetchInTheWild,
}

func init() {
fetchCmd.AddCommand(fetchInTheWildCmd)
}

func fetchInTheWild(_ *cobra.Command, _ []string) (err error) {
if err := util.SetLogger(viper.GetBool("log-to-file"), viper.GetString("log-dir"), viper.GetBool("debug"), viper.GetBool("log-json")); err != nil {
return xerrors.Errorf("Failed to SetLogger. err: %w", err)
}

driver, locked, err := db.NewDB(
viper.GetString("dbtype"),
viper.GetString("dbpath"),
viper.GetBool("debug-sql"),
db.Option{},
)
if err != nil {
if locked {
return xerrors.Errorf("Failed to initialize DB. Close DB connection before fetching. err: %w", err)
}
return xerrors.Errorf("Failed to open DB. err: %w", err)
}

fetchMeta, err := driver.GetFetchMeta()
if err != nil {
return xerrors.Errorf("Failed to get FetchMeta from DB. err: %w", err)
}
if fetchMeta.OutDated() {
return xerrors.Errorf("Failed to Insert CVEs into DB. err: SchemaVersion is old. SchemaVersion: %+v", map[string]uint{"latest": models.LatestSchemaVersion, "DB": fetchMeta.SchemaVersion})
}
// If the fetch fails the first time (without SchemaVersion), the DB needs to be cleaned every time, so insert SchemaVersion.
if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
}

log15.Info("Fetching inTheWild Poc Exploit")
var exploits []models.Exploit
if exploits, err = fetcher.FetchInTheWild(viper.GetBool("debug-sql")); err != nil {
return xerrors.Errorf("Failed to fetch InTheWild Exploit. err: %w", err)
}
log15.Info("inTheWild Poc Exploit", "count", len(exploits))

log15.Info("Insert Exploit into go-exploitdb.", "db", driver.Name())
if err := driver.InsertExploit(models.InTheWildType, exploits); err != nil {
return xerrors.Errorf("Failed to insert. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
}

fetchMeta.LastFetchedAt = time.Now()
if err := driver.UpsertFetchMeta(fetchMeta); err != nil {
return xerrors.Errorf("Failed to upsert FetchMeta to DB. dbpath: %s, err: %w", viper.GetString("dbpath"), err)
}

return nil
}
32 changes: 32 additions & 0 deletions db/rdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func (r *RDBDriver) MigrateDB() error {
&models.ShellCode{},
&models.Paper{},
&models.GitHubRepository{},
&models.InTheWild{},
); err != nil {
return xerrors.Errorf("Failed to migrate. err: %w", err)
}
Expand Down Expand Up @@ -175,6 +176,16 @@ func (r *RDBDriver) deleteAndInsertExploit(exploitType models.ExploitType, explo
}
}

itwIDs := []models.InTheWild{}
if err := tx.Model(&models.InTheWild{}).Select("id").Where("exploit_id IN ?", oldIDs[idx.From:idx.To]).Find(&osIDs).Error; err != nil {
return xerrors.Errorf("Failed to select old inTheWild: %w", err)
}
if len(itwIDs) > 0 {
if err := tx.Where("id IN ?", itwIDs).Delete(&models.InTheWild{}).Error; err != nil {
return xerrors.Errorf("Failed to delete: %w", err)
}
}

if err := tx.Where("id IN ?", oldIDs[idx.From:idx.To]).Delete(&models.Exploit{}).Error; err != nil {
return xerrors.Errorf("Failed to delete: %w", err)
}
Expand Down Expand Up @@ -231,6 +242,13 @@ func (r *RDBDriver) GetExploitByID(exploitUniqueID string) ([]models.Exploit, er
}
return nil, xerrors.Errorf("Failed to get GitHubRepository. err: %w", err)
}
case models.InTheWildType:
if err := r.conn.Where(&models.InTheWild{ExploitID: es[i].ID}).Take(&es[i].InTheWild).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, xerrors.Errorf("Failed to get inTheWild. DB relationship may be broken, use `$ go-exploitdb fetch inthewild` to recreate DB. err: %w", err)
}
return nil, xerrors.Errorf("Failed to get inTheWild. err: %w", err)
}
}
}
return es, nil
Expand Down Expand Up @@ -269,6 +287,13 @@ func (r *RDBDriver) GetExploitAll() ([]models.Exploit, error) {
}
return nil, xerrors.Errorf("Failed to Get GitHubRepository. err: %w", err)
}
case models.InTheWildType:
if err := r.conn.Where(&models.InTheWild{ExploitID: exploit.ID}).Take(&exploit.InTheWild).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, xerrors.Errorf("Failed to get inTheWild. DB relationship may be broken, use `$ go-exploitdb fetch inthewild` to recreate DB. err: %w", err)
}
return nil, xerrors.Errorf("Failed to Get inTheWild. err: %w", err)
}
}
es = append(es, exploit)
}
Expand Down Expand Up @@ -316,6 +341,13 @@ func (r *RDBDriver) GetExploitByCveID(cveID string) ([]models.Exploit, error) {
}
return nil, xerrors.Errorf("Failed to get GitHubRepository. err: %w", err)
}
case models.InTheWildType:
if err := r.conn.Where(&models.InTheWild{ExploitID: es[i].ID}).Take(&es[i].InTheWild).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, xerrors.Errorf("Failed to get inTheWild. DB relationship may be broken, use `$ go-exploitdb fetch inthewild` to recreate DB. err: %w", err)
}
return nil, xerrors.Errorf("Failed to get inTheWild. err: %w", err)
}
}
}
return es, nil
Expand Down
2 changes: 1 addition & 1 deletion fetcher/awesomepoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func FetchAwesomePoc() (exploits []models.Exploit, err error) {
for poc := range r.AwesomePoc {
exploit := models.Exploit{
ExploitType: models.AwesomePocType,
ExploitUniqueID: fmt.Sprintf("%s-%x", models.AwesomePocType, md5.Sum([]byte(poc.URL))),
ExploitUniqueID: fmt.Sprintf("%s-%x", models.AwesomePocType, md5.Sum([]byte(poc.CveID+poc.URL))),
URL: poc.URL,
Description: poc.Description,
CveID: poc.CveID,
Expand Down
3 changes: 1 addition & 2 deletions fetcher/githubrepos.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,8 @@ func FetchGitHubRepos(stars, forks int) (exploits []models.Exploit, err error) {
continue
}

eid := fmt.Sprintf("%s-%x", models.GitHubRepositoryType, md5.Sum([]byte(poc.URL)))
githubRepoExploit := models.Exploit{
ExploitUniqueID: eid,
ExploitUniqueID: fmt.Sprintf("%s-%x", models.GitHubRepositoryType, md5.Sum([]byte(cveID+poc.URL))),
ExploitType: models.GitHubRepositoryType,
URL: poc.URL,
CveID: cveID,
Expand Down
128 changes: 128 additions & 0 deletions fetcher/inthewild.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package fetcher

import (
"bytes"
"crypto/md5"
"database/sql"
"fmt"
"log"
"os"
"path/filepath"
"time"

"golang.org/x/xerrors"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"

"github.com/vulsio/go-exploitdb/models"
"github.com/vulsio/go-exploitdb/util"
)

// https://www.sqlite.org/fileformat.html
var sqlite3HeaderMagic = []byte("SQLite format 3\x00")

const dbURL string = "https://github.com/gmatuz/inthewilddb/blob/master/inthewild.db?raw=true"

type exploit struct {
CVEID string `gorm:"column:exploits.id"`
Description sql.NullString `gorm:"column:vulns.description"`
ReferenceURL string `gorm:"column:exploits.referenceURL"`
TimeStamp time.Time `gorm:"column:exploits.timestamp"`
Source string `gorm:"column:exploits.source"`
Type string `gorm:"column:exploits.type"`
}

// FetchInTheWild :
func FetchInTheWild(debugSQL bool) ([]models.Exploit, error) {
if err := os.MkdirAll(util.CacheDir(), 0700); err != nil {
return nil, xerrors.Errorf("Failed to mkdir: %w", err)
}
dbPath := filepath.Join(util.CacheDir(), "inthewild.db")
f, err := os.Create(dbPath)
if err != nil {
return nil, xerrors.Errorf("Failed to create inthewild.db in cache directory. err: %w", err)
}
defer f.Close()

res, err := util.FetchURL(dbURL)
if err != nil {
return nil, xerrors.Errorf("Failed to fetch inTheWild DB. err: %w", err)
}
if !bytes.Equal(res[:16], sqlite3HeaderMagic) {
return nil, xerrors.Errorf("Failed to fetch inTheWild DB. err: not sqlite3 database")
}
if n, err := f.Write(res); err != nil {
return nil, xerrors.Errorf("Failed to write db. err: %w", err)
} else if n != len(res) {
return nil, xerrors.Errorf("Failed to write db. err: not enough bytes written")
}

gormConfig := gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
Logger: logger.New(
log.New(os.Stderr, "\r\n", log.LstdFlags),
logger.Config{
LogLevel: logger.Silent,
},
),
}

if debugSQL {
gormConfig.Logger = logger.New(
log.New(os.Stderr, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: time.Second,
LogLevel: logger.Info,
Colorful: true,
},
)
}
db, err := gorm.Open(sqlite.Open(dbPath), &gormConfig)
if err != nil {
return nil, xerrors.Errorf("Failed to open inTheWild DB. err: %w", err)
}
sqlDB, err := db.DB()
if err != nil {
return nil, xerrors.Errorf("Failed to get DB Object. err : %w", err)
}
defer sqlDB.Close()

rows, err := db.
Table("exploits").
Select("exploits.id, vulns.description, exploits.referenceURL, exploits.timestamp, exploits.source, exploits.type").
Joins("LEFT JOIN vulns ON vulns.id = exploits.id").
Rows()
if err != nil {
return nil, xerrors.Errorf("Failed to select exploits. err: %w", err)
}
defer rows.Close()

exploits := []models.Exploit{}
var exploit exploit
for rows.Next() {
if err := rows.Scan(&exploit.CVEID, &exploit.Description, &exploit.ReferenceURL, &exploit.TimeStamp, &exploit.Source, &exploit.Type); err != nil {
return nil, xerrors.Errorf("Failed to scan rows. err: %w", err)
}

// manually correct CVE-ID. ref: https://github.com/gmatuz/inthewilddb/issues/6
if exploit.CVEID == "CVE 2020-12271" {
exploit.CVEID = "CVE-2020-12271"
}

exploits = append(exploits, models.Exploit{
ExploitType: models.InTheWildType,
ExploitUniqueID: fmt.Sprintf("%s-%x", models.InTheWildType, md5.Sum([]byte(exploit.CVEID+exploit.ReferenceURL+exploit.Source+exploit.Type))),
URL: exploit.ReferenceURL,
Description: exploit.Description.String,
CveID: exploit.CVEID,
InTheWild: &models.InTheWild{
TimeStamp: exploit.TimeStamp,
Source: exploit.Source,
Type: exploit.Type,
},
})
}

return exploits, nil
}
12 changes: 12 additions & 0 deletions models/exploit.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ var (
GitHubRepositoryType ExploitType = "GitHub"
// AwesomePocType :
AwesomePocType ExploitType = "AwesomePoc"
// InTheWildType :
InTheWildType ExploitType = "InTheWild"
)

// Exploit :
Expand All @@ -29,6 +31,7 @@ type Exploit struct {
CveID string `gorm:"type:varchar(255);index:idx_exploit_cve_id" json:"cve_id"`
OffensiveSecurity *OffensiveSecurity `json:"offensive_security"`
GitHubRepository *GitHubRepository `json:"github_repository"`
InTheWild *InTheWild `json:"in_the_wild"`
}

// GitHubRepository :
Expand Down Expand Up @@ -94,6 +97,15 @@ type Paper struct {
Language string `gorm:"type:varchar(255)" csv:"language" json:"language"`
}

// InTheWild :
type InTheWild struct {
ID int64 `json:"-"`
ExploitID int64 `gorm:"index:idx_in_the_wild_exploit_id" json:"-"`
TimeStamp time.Time `json:"timestamp"`
Source string `gorm:"type:varchar(255)" json:"source"`
Type string `gorm:"type:varchar(255)" json:"type"`
}

// MitreXML :
// http://cve.mitre.org/cve/cvrf.html
type MitreXML struct {
Expand Down

0 comments on commit 8af7dcb

Please sign in to comment.