Skip to content

Commit 06748e4

Browse files
committed
initial commit
0 parents  commit 06748e4

17 files changed

+1790
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.idea/
2+
feeds/
3+
tmp/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020 Daehee Park
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# cvebaser

cmd/cvebaser/main.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"flag"
6+
"fmt"
7+
"time"
8+
9+
"github.com/cvebase/cvebaser"
10+
"github.com/gobwas/cli"
11+
)
12+
13+
func main() {
14+
cli.Main(cli.Commands{
15+
"lint": new(lintCommand),
16+
})
17+
}
18+
19+
type lintCommand struct {
20+
commit string
21+
repoPath string
22+
}
23+
24+
func (l *lintCommand) DefineFlags(fs *flag.FlagSet) {
25+
fs.StringVar(&l.commit,
26+
"c", l.commit,
27+
"commit hash",
28+
)
29+
fs.StringVar(&l.repoPath,
30+
"r", l.repoPath,
31+
"path to cvebase.com repo",
32+
)
33+
// TODO add concurrency option
34+
}
35+
36+
func (l *lintCommand) Run(_ context.Context, _ []string) error {
37+
repo, err := cvebaser.NewRepo(l.repoPath, &cvebaser.GitOpts{})
38+
if err != nil {
39+
return err
40+
}
41+
42+
start := time.Now()
43+
if l.commit != "" {
44+
err = repo.LintCommit(l.commit)
45+
if err != nil {
46+
return err
47+
}
48+
} else {
49+
err = repo.LintAll(20)
50+
if err != nil {
51+
return err
52+
}
53+
}
54+
duration := time.Since(start)
55+
56+
// TODO print number of files modified
57+
58+
fmt.Printf("\n--- %.1f seconds ---\n", duration.Seconds())
59+
60+
return nil
61+
}

cve.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package cvebaser
2+
3+
import (
4+
"fmt"
5+
"path"
6+
"strconv"
7+
"strings"
8+
9+
"github.com/daehee/nvd"
10+
)
11+
12+
type CVE struct {
13+
CVEID string `json:"-" yaml:"id"`
14+
Pocs []string `json:"pocs,omitempty" yaml:"pocs,omitempty"`
15+
Courses []string `json:"courses,omitempty" yaml:"courses,omitempty"`
16+
Writeups []string `json:"writeups,omitempty" yaml:"writeups,omitempty"`
17+
Advisory string `json:"advisory,omitempty" yaml:"-"`
18+
}
19+
20+
func (m *CVE) dedupeSort() {
21+
m.Pocs = sortUniqStrings(m.Pocs)
22+
m.Writeups = sortUniqStrings(m.Writeups)
23+
m.Courses = sortUniqStrings(m.Courses)
24+
}
25+
26+
// isValidCVESubPath checks if cve file is placed in correct year and sequence sub-directories.
27+
func isValidCVESubPath(cveID, path string) bool {
28+
// Truncate path to slice containing relative path
29+
splitPath := strings.Split(path, "/")
30+
// ../../../../cvebase.com/cve/2018/xxx/CVE-2018-0142.md ->
31+
// [2018, xxx, CVE-2018-0142.md]
32+
splitPath = splitPath[len(splitPath)-3:]
33+
34+
validPath, err := cveSubPath(cveID)
35+
if err != nil {
36+
return false
37+
}
38+
splitValid := strings.Split(validPath, "/")
39+
40+
// Compare equality of slice values
41+
for i, v := range splitPath {
42+
if v != splitValid[i] {
43+
return false
44+
}
45+
}
46+
47+
return true
48+
}
49+
50+
// cvePathToRelPath truncates cve filepath to relative path starting with year subdirectory
51+
func cvePathToRelPath(p string) string {
52+
// ../../../../cvebase.com/cve/2018/0xxx/CVE-2018-0142.md ->
53+
// 2018/0xxx/CVE-2018-0142.md
54+
splitPath := strings.Split(p, "/")
55+
return strings.Join(splitPath[len(splitPath)-3:], "/")
56+
}
57+
58+
// cveSubPath converts a CVE ID to cve relative path starting with year subdirectory
59+
func cveSubPath(cveID string) (string, error) {
60+
year, sequence := nvd.ParseCVEID(cveID)
61+
seqDir, err := cveSeqDir(sequence)
62+
if err != nil {
63+
return "", fmt.Errorf("error parsing %s sequence to dir: %v", cveID, err)
64+
}
65+
return path.Join(strconv.Itoa(year), seqDir, fmt.Sprintf("%s.md", cveID)), nil
66+
}
67+
68+
// cveSeqDir converts a cve sequence number to a "x"-padded sequence directory name
69+
func cveSeqDir(seq int) (string, error) {
70+
seqStr := nvd.PadCVESequence(seq)
71+
subDir := seqStr[:len(seqStr)-3]
72+
if len(subDir) < 1 {
73+
return "", fmt.Errorf("CVE sequence invalid: %s -> %s", seqStr, subDir)
74+
}
75+
return fmt.Sprintf("%sxxx", subDir), nil
76+
}

cve_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package cvebaser
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestCVE_DedupeSort(t *testing.T) {
10+
tests := []struct {
11+
cve CVE
12+
wantCVE CVE
13+
}{
14+
{
15+
CVE{
16+
CVEID: "CVE-2020-14882",
17+
Pocs: []string{"https://github.com", "https://github.com", "https://github.com", "https://exploit-db.com"},
18+
Writeups: []string{"https://github.com", "https://github.com", "https://github.com", "https://exploit-db.com"},
19+
Courses: []string{"https://github.com", "https://github.com", "https://github.com", "https://exploit-db.com"},
20+
Advisory: "lorem ipsum dolor",
21+
},
22+
CVE{
23+
CVEID: "CVE-2020-14882",
24+
Pocs: []string{"https://exploit-db.com", "https://github.com"},
25+
Writeups: []string{"https://exploit-db.com", "https://github.com"},
26+
Courses: []string{"https://exploit-db.com", "https://github.com"},
27+
Advisory: "lorem ipsum dolor",
28+
},
29+
},
30+
}
31+
32+
for _, tt := range tests {
33+
tt.cve.dedupeSort()
34+
assert.EqualValues(t, tt.wantCVE.Pocs, tt.cve.Pocs)
35+
assert.EqualValues(t, tt.wantCVE.Writeups, tt.cve.Writeups)
36+
assert.EqualValues(t, tt.wantCVE.Courses, tt.cve.Courses)
37+
}
38+
}
39+
40+
func TestCVESubPath(t *testing.T) {
41+
want := "2020/14xxx/CVE-2020-14882.md"
42+
got, err := cveSubPath("CVE-2020-14882")
43+
assert.NoError(t, err)
44+
assert.Equal(t, want, got)
45+
}
46+
47+
func TestIsValidCVEDirPath(t *testing.T) {
48+
got := isValidCVESubPath("CVE-2016-0974", "../../../../cvebase.com/cve/2016/0xxx/CVE-2016-0974.md")
49+
assert.True(t, got)
50+
}
51+
52+
func TestCVESeqDir(t *testing.T) {
53+
tests := []struct {
54+
sequence int
55+
want string
56+
}{
57+
{974, "0xxx"},
58+
{14882, "14xxx"},
59+
{97, "0xxx"},
60+
}
61+
62+
for _, tt := range tests {
63+
got, err := cveSeqDir(tt.sequence)
64+
assert.NoError(t, err)
65+
assert.Equal(t, got, tt.want)
66+
}
67+
}

file.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package cvebaser
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
"os"
8+
9+
"github.com/gohugoio/hugo/parser/pageparser"
10+
"gopkg.in/yaml.v3"
11+
)
12+
13+
// ParseCVEMDFile reads markdown file contents containing YAML and markdown
14+
// and returns CVE data struct
15+
func ParseCVEMDFile(reader io.Reader) (cve CVE, err error) {
16+
pf, err := pageparser.ParseFrontMatterAndContent(reader)
17+
if err != nil {
18+
return cve, err
19+
}
20+
fm, err := yaml.Marshal(pf.FrontMatter)
21+
if err != nil {
22+
return cve, err
23+
}
24+
err = yaml.Unmarshal(fm, &cve)
25+
if err != nil {
26+
return cve, err
27+
}
28+
cve.Advisory = string(pf.Content)
29+
return
30+
}
31+
32+
// ParseResearcherMDFile reads markdown file contents containing YAML and markdown
33+
// and returns Researcher data struct
34+
func ParseResearcherMDFile(reader io.Reader) (researcher Researcher, err error) {
35+
pf, err := pageparser.ParseFrontMatterAndContent(reader)
36+
if err != nil {
37+
return researcher, err
38+
}
39+
fm, err := yaml.Marshal(pf.FrontMatter)
40+
if err != nil {
41+
return researcher, err
42+
}
43+
err = yaml.Unmarshal(fm, &researcher)
44+
if err != nil {
45+
return researcher, err
46+
}
47+
researcher.Bio = string(pf.Content)
48+
return
49+
}
50+
51+
// ParseMDFile reads markdown file contents containing YAML and markdown
52+
// and returns either CVE or Researcher data struct
53+
func ParseMDFile(r io.Reader, tPtr interface{}) error {
54+
pf, err := pageparser.ParseFrontMatterAndContent(r)
55+
if err != nil {
56+
return fmt.Errorf("error parsing front matter: %v", err)
57+
}
58+
fm, err := yaml.Marshal(pf.FrontMatter)
59+
if err != nil {
60+
return fmt.Errorf("error marshaling yaml: %v", err)
61+
}
62+
err = yaml.Unmarshal(fm, tPtr)
63+
if err != nil {
64+
return fmt.Errorf("erorr unmarshaling yaml: %v", err)
65+
}
66+
67+
// Set field value for markdown text depending on CVE or Researcher
68+
switch t := tPtr.(type) {
69+
case *CVE:
70+
t.Advisory = string(pf.Content)
71+
case *Researcher:
72+
t.Bio = string(pf.Content)
73+
default:
74+
return fmt.Errorf("unknown type: %+v", tPtr)
75+
}
76+
77+
return nil
78+
}
79+
80+
const yamlDelimLf = "---\n"
81+
82+
func CompileToFile(
83+
f *os.File,
84+
path string,
85+
t interface{}, /* CVE or Researcher to marshal */
86+
) error {
87+
// Configure yaml encoding for custom indent spacing
88+
var d bytes.Buffer
89+
yamlEncoder := yaml.NewEncoder(&d)
90+
// go-yaml v3 now defaults to 4 spaces, so manually set to 2
91+
yamlEncoder.SetIndent(2)
92+
err := yamlEncoder.Encode(t)
93+
if err != nil {
94+
return fmt.Errorf("error marshaling yaml to %s", path)
95+
}
96+
yamlEncoder.Close()
97+
98+
// Lead with marshaling first so that
99+
// if fails doesn't error with a pre-maturely truncated file
100+
// d, err := yaml.Marshal(t)
101+
102+
// TODO Check if file contents have changed before writing, otherwise return early
103+
104+
// Clear file for writing
105+
f.Truncate(0)
106+
f.Seek(0, 0)
107+
108+
_, err = f.WriteString(yamlDelimLf)
109+
if err != nil {
110+
return fmt.Errorf("error writing to %s: %v", path, err)
111+
}
112+
_, err = f.Write(d.Bytes())
113+
if err != nil {
114+
return fmt.Errorf("error writing to %s: %v", path, err)
115+
}
116+
_, err = f.WriteString(yamlDelimLf)
117+
if err != nil {
118+
return fmt.Errorf("error writing to %s: %v", path, err)
119+
}
120+
121+
// Write markdown content from struct field depending on type CVE or Researcher
122+
switch t := t.(type) {
123+
case CVE:
124+
_, err = f.WriteString(t.Advisory)
125+
if err != nil {
126+
return fmt.Errorf("error writing to %s: %v", path, err)
127+
}
128+
case Researcher:
129+
_, err = f.WriteString(t.Bio)
130+
if err != nil {
131+
return fmt.Errorf("error writing to %s: %v", path, err)
132+
}
133+
default:
134+
return fmt.Errorf("unknown type: %+v", t)
135+
}
136+
137+
return nil
138+
}

0 commit comments

Comments
 (0)