diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile | 2 | ||||
| -rw-r--r-- | src/main.go | 555 | 
2 files changed, 286 insertions, 271 deletions
| diff --git a/src/Makefile b/src/Makefile index 807b9f9..4ac1c16 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,6 +1,6 @@  PROJECT_NAME := cve-poc-mon  BUILD_DIR := build -GOFLAGS := -ldflags "-s -w" -trimpath +GOFLAGS := -ldflags "-s -w" -trimpath -buildvcs=false  GO_BUILD := go build $(GOFLAGS)  .PHONY: all clean linux windows darwin tidy diff --git a/src/main.go b/src/main.go index 11ebfda..ab1b835 100644 --- a/src/main.go +++ b/src/main.go @@ -1,292 +1,307 @@  package main  import ( -    "encoding/json" -    "flag" -    "fmt" -    "net/http" -    "net/url" -    "os" -    "strings" -    "time" - -    "github.com/go-git/go-git/v5" -    "github.com/go-git/go-git/v5/plumbing" +	"encoding/json" +	"flag" +	"fmt" +	"net/http" +	"net/url" +	"os" +	"strings" +	"time" + +	"github.com/go-git/go-git/v5" +	"github.com/go-git/go-git/v5/plumbing"  ) +func init() { +	const usageHeader = ` +github cve proof of concept scraper + +author: heqnx - https://heqnx.com + +` +	flag.Usage = func() { +		fmt.Fprint(os.Stderr, usageHeader) +		fmt.Fprintf(os.Stderr, "usage of %s:\n", os.Args[0]) +		flag.PrintDefaults() +	} +	flag.CommandLine.SetOutput(os.Stderr) +} +  func main() { -    flag.Usage = func() { -        fmt.Fprintf(flag.CommandLine.Output(), "this program searches github for public repositories created today that match the keyword 'cve-<current_year>'\n") -        fmt.Fprintf(flag.CommandLine.Output(), "usage:\n") -        flag.PrintDefaults() -    } -    githubToken := flag.String("token", "", "github api token") -    cloneDir := flag.String("cloneDir", "cve-pocs", "directory to clone repositories") -    clonedListFile := flag.String("clonedList", "cve-pocs.txt", "file to store cloned repository urls") -    autoUpdate := flag.Bool("auto-update", false, "automatically update previously cloned repositories") -    customDate := flag.String("date", "", "specify a custom date in YYYY-MM-DD format") -    silent := flag.Bool("silent", false, "suppress update messages") -    flag.Parse() - -    readmeFile := *cloneDir + "/README.md" - -    if *githubToken == "" { -        fmt.Println("[err] github token is required; use -token flag to provide the token") -        return -    } - -    if _, err := os.Stat(*cloneDir); os.IsNotExist(err) { -        err := os.Mkdir(*cloneDir, 0755) -        if err != nil { -            fmt.Printf("[err] failed to create clone directory: %v\n", err) -            return -        } -    } - -    clonedRepos := loadClonedRepos(*clonedListFile) - -    //today := time.Now().UTC().Format("2006-01-02") -    var today string -    if *customDate != "" { -        _, err := time.Parse("2006-01-02", *customDate) -        if err != nil { -            fmt.Printf("[err] invalid date format: %s; use YYYY-MM-DD\n", *customDate) -            return -        } -        today = *customDate -    } else { -        today = time.Now().UTC().Format("2006-01-02") -    } - -    year := time.Now().Year() -    KEYWORD := fmt.Sprintf("cve-%d", year) - -    fmt.Printf("[inf] searching for repositories with keyword: %s, created on: %s\n", KEYWORD, today) - -    baseURL := "https://api.github.com" -    resource := "/search/repositories" - -    params := url.Values{} -    params.Add("q", fmt.Sprintf("\"%s\" created:%s", KEYWORD, today)) -    params.Add("sort", "updated") -    params.Add("order", "desc") -    params.Add("per_page", "100") - -    u, err := url.ParseRequestURI(baseURL) -    if err != nil { -        fmt.Printf("[err] failed to parse url: %v\n", err) -        return -    } -    u.Path = resource -    u.RawQuery = params.Encode() - -    urlStr := fmt.Sprintf("%v", u) -    fmt.Println("[inf] github api url:", urlStr) - -    req, err := http.NewRequest("GET", urlStr, nil) -    if err != nil { -        fmt.Printf("[err] failed to create http request: %v\n", err) -        return -    } - -    req.Header.Add("Authorization", "token "+*githubToken) -    req.Header.Add("Accept", "application/vnd.github.v3+json") - -    client := &http.Client{} -    resp, err := client.Do(req) -    if err != nil { -        fmt.Printf("[err] failed to make GET request: %v\n", err) -        return -    } -    defer resp.Body.Close() - -    if resp.StatusCode != http.StatusOK { -        fmt.Printf("[err] received non-ok http status %s\n", resp.Status) -        return -    } - -    var result map[string]interface{} -    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { -        fmt.Printf("[err] failed to parse response: %v\n", err) -        return -    } - -    repos, ok := result["items"].([]interface{}) -    if !ok || len(repos) == 0 { -        fmt.Println("[wrn] no repositories found or failed to parse repositories") -        return -    } - -    for _, repo := range repos { -        if repoMap, ok := repo.(map[string]interface{}); ok { -            repoName := repoMap["name"].(string) -            owner := repoMap["owner"].(map[string]interface{})["login"].(string) -            repoURL := repoMap["html_url"].(string) - -            description, ok := repoMap["description"].(string) -            if !ok { -                description = "No description" -            } - -            cloneName := fmt.Sprintf("%s_%s", owner, repoName) - -            if _, cloned := clonedRepos[repoURL]; !cloned { -                fmt.Printf("[inf] new poc: %s\n", repoURL) -                fmt.Printf("[inf] description: %s\n", description) -                fmt.Println() - -                if cloneRepo(repoURL, *cloneDir, cloneName) { -                    clonedRepos[repoURL] = struct{}{} -                    appendToFile(*clonedListFile, repoURL) -                    updateReadme(readmeFile, repoURL, description) -                } -            } -        } -    } -    if *autoUpdate { -        updateClonedRepositories(*cloneDir, clonedRepos, *silent) -    } +	githubToken := flag.String("token", "", "github api token") +	cloneDir := flag.String("cloneDir", "cve-pocs", "directory to clone repositories") +	clonedListFile := flag.String("clonedList", "cve-pocs.txt", "file to store cloned repository urls") +	autoUpdate := flag.Bool("auto-update", false, "automatically update previously cloned repositories") +	customDate := flag.String("date", "", "specify a custom date in YYYY-MM-DD format") +	silent := flag.Bool("silent", false, "suppress update messages") +	flag.Parse() + +	if flag.NFlag() == 0 && flag.NArg() == 0 { +		flag.Usage() +		os.Exit(1) +	} + +	readmeFile := *cloneDir + "/README.md" + +	if *githubToken == "" { +		fmt.Println("[err] github token is required; use -token flag to provide the token") +		return +	} + +	if _, err := os.Stat(*cloneDir); os.IsNotExist(err) { +		err := os.Mkdir(*cloneDir, 0755) +		if err != nil { +			fmt.Printf("[err] failed to create clone directory: %v\n", err) +			return +		} +	} + +	clonedRepos := loadClonedRepos(*clonedListFile) + +	//today := time.Now().UTC().Format("2006-01-02") +	var today string +	if *customDate != "" { +		_, err := time.Parse("2006-01-02", *customDate) +		if err != nil { +			fmt.Printf("[err] invalid date format: %s; use YYYY-MM-DD\n", *customDate) +			return +		} +		today = *customDate +	} else { +		today = time.Now().UTC().Format("2006-01-02") +	} + +	year := time.Now().Year() +	KEYWORD := fmt.Sprintf("cve-%d", year) + +	fmt.Printf("[inf] searching for repositories with keyword: %s, created on: %s\n", KEYWORD, today) + +	baseURL := "https://api.github.com" +	resource := "/search/repositories" + +	params := url.Values{} +	params.Add("q", fmt.Sprintf("\"%s\" created:%s", KEYWORD, today)) +	params.Add("sort", "updated") +	params.Add("order", "desc") +	params.Add("per_page", "100") + +	u, err := url.ParseRequestURI(baseURL) +	if err != nil { +		fmt.Printf("[err] failed to parse url: %v\n", err) +		return +	} +	u.Path = resource +	u.RawQuery = params.Encode() + +	urlStr := fmt.Sprintf("%v", u) +	fmt.Println("[inf] github api url:", urlStr) + +	req, err := http.NewRequest("GET", urlStr, nil) +	if err != nil { +		fmt.Printf("[err] failed to create http request: %v\n", err) +		return +	} + +	req.Header.Add("Authorization", "token "+*githubToken) +	req.Header.Add("Accept", "application/vnd.github.v3+json") + +	client := &http.Client{} +	resp, err := client.Do(req) +	if err != nil { +		fmt.Printf("[err] failed to make GET request: %v\n", err) +		return +	} +	defer resp.Body.Close() + +	if resp.StatusCode != http.StatusOK { +		fmt.Printf("[err] received non-ok http status %s\n", resp.Status) +		return +	} + +	var result map[string]interface{} +	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { +		fmt.Printf("[err] failed to parse response: %v\n", err) +		return +	} + +	repos, ok := result["items"].([]interface{}) +	if !ok || len(repos) == 0 { +		fmt.Println("[wrn] no repositories found or failed to parse repositories") +		return +	} + +	for _, repo := range repos { +		if repoMap, ok := repo.(map[string]interface{}); ok { +			repoName := repoMap["name"].(string) +			owner := repoMap["owner"].(map[string]interface{})["login"].(string) +			repoURL := repoMap["html_url"].(string) + +			description, ok := repoMap["description"].(string) +			if !ok { +				description = "No description" +			} + +			cloneName := fmt.Sprintf("%s_%s", owner, repoName) + +			if _, cloned := clonedRepos[repoURL]; !cloned { +				fmt.Printf("[inf] new poc: %s\n", repoURL) +				fmt.Printf("[inf] description: %s\n", description) +				fmt.Println() + +				if cloneRepo(repoURL, *cloneDir, cloneName) { +					clonedRepos[repoURL] = struct{}{} +					appendToFile(*clonedListFile, repoURL) +					updateReadme(readmeFile, repoURL, description) +				} +			} +		} +	} +	if *autoUpdate { +		updateClonedRepositories(*cloneDir, clonedRepos, *silent) +	} +} + +func extractRepoNameFromURL(repoURL string) string { +	parts := strings.Split(repoURL, "/") +	return parts[len(parts)-2] + "_" + parts[len(parts)-1]  }  func cloneRepo(repoURL, cloneDir, cloneName string) bool { -    _, err := git.PlainClone(cloneDir+"/"+cloneName, false, &git.CloneOptions{ -        URL: repoURL, -    }) -    if err != nil { -        fmt.Printf("[err] failed to clone repository: %v\n", err) -        return false -    } -    return true +	_, err := git.PlainClone(cloneDir+"/"+cloneName, false, &git.CloneOptions{ +		URL: repoURL, +	}) +	if err != nil { +		fmt.Printf("[err] failed to clone repository: %v\n", err) +		return false +	} +	return true  }  func loadClonedRepos(filePath string) map[string]struct{} { -    clonedRepos := make(map[string]struct{}) - -    file, err := os.Open(filePath) -    if err != nil { -        if os.IsNotExist(err) { -            return clonedRepos -        } -        fmt.Printf("[err] failed to open cloned repos list file: %v\n", err) -        return clonedRepos -    } -    defer file.Close() - -    var line string -    for { -        _, err := fmt.Fscanln(file, &line) -        if err != nil { -            break -        } -        clonedRepos[strings.TrimSpace(line)] = struct{}{} -    } - -    return clonedRepos +	clonedRepos := make(map[string]struct{}) + +	file, err := os.Open(filePath) +	if err != nil { +		if os.IsNotExist(err) { +			return clonedRepos +		} +		fmt.Printf("[err] failed to open cloned repos list file: %v\n", err) +		return clonedRepos +	} +	defer file.Close() + +	var line string +	for { +		_, err := fmt.Fscanln(file, &line) +		if err != nil { +			break +		} +		clonedRepos[strings.TrimSpace(line)] = struct{}{} +	} + +	return clonedRepos  }  func appendToFile(filePath, line string) { -    file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) -    if err != nil { -        fmt.Printf("[err] failed to open file %s: %v\n", filePath, err) -        return -    } -    defer file.Close() - -    if _, err := file.WriteString(line + "\n"); err != nil { -        fmt.Printf("[err] failed to write to file %s: %v\n", filePath, err) -    } +	file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) +	if err != nil { +		fmt.Printf("[err] failed to open file %s: %v\n", filePath, err) +		return +	} +	defer file.Close() + +	if _, err := file.WriteString(line + "\n"); err != nil { +		fmt.Printf("[err] failed to write to file %s: %v\n", filePath, err) +	}  }  func updateReadme(readmeFile, repoURL, description string) { -    file, err := os.OpenFile(readmeFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) -    if err != nil { -        fmt.Printf("[err] failed to open README file %s: %v\n", readmeFile, err) -        return -    } -    defer file.Close() - -    entry := fmt.Sprintf("- [%s](%s) %s\n", repoURL, repoURL, description) -    if _, err := file.WriteString(entry); err != nil { -        fmt.Printf("[err] failed to write to README file %s: %v\n", readmeFile, err) -    } +	file, err := os.OpenFile(readmeFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) +	if err != nil { +		fmt.Printf("[err] failed to open README file %s: %v\n", readmeFile, err) +		return +	} +	defer file.Close() + +	entry := fmt.Sprintf("- [%s](%s) %s\n", repoURL, repoURL, description) +	if _, err := file.WriteString(entry); err != nil { +		fmt.Printf("[err] failed to write to README file %s: %v\n", readmeFile, err) +	}  }  func updateClonedRepositories(cloneDir string, clonedRepos map[string]struct{}, silent bool) { -    for repoURL := range clonedRepos { -        cloneName := extractRepoNameFromURL(repoURL) -        repoPath := fmt.Sprintf("%s/%s", cloneDir, cloneName) - -        r, err := git.PlainOpen(repoPath) -        if err != nil { -            if !silent { -                fmt.Printf("[err] failed to open repository %s: %v\n", repoPath, err) -            } -            continue -        } - -        headRef, err := r.Head() -        if err != nil { -            if !silent { -                fmt.Printf("[err] failed to get HEAD for repository %s: %v\n", repoPath, err) -            } -            continue -        } - -        defaultBranch := "" -        if headRef.Name().IsBranch() { -            defaultBranch = headRef.Name().Short() -        } else { -            if !silent { -                fmt.Printf("[err] HEAD is not a branch for repository %s, skipping\n", repoPath) -            } -            continue -        } - -        err = r.Fetch(&git.FetchOptions{ -            RemoteName: "origin", -        }) -        if err != nil && err != git.NoErrAlreadyUpToDate { -            if !silent { -                fmt.Printf("[err] failed to fetch updates for repository %s: %v\n", repoPath, err) -            } -            continue -        } - -        w, err := r.Worktree() -        if err != nil { -            if !silent { -                fmt.Printf("[err] failed to get worktree for repository %s: %v\n", repoPath, err) -            } -            continue -        } - -        remoteBranchRef := fmt.Sprintf("origin/%s", defaultBranch) -        remoteRef, err := r.Reference(plumbing.ReferenceName("refs/remotes/"+remoteBranchRef), true) -        if err != nil { -            if !silent { -                fmt.Printf("[err] failed to get reference for %s in repository %s: %v\n", remoteBranchRef, repoPath, err) -            } -            continue -        } - -        err = w.Reset(&git.ResetOptions{ -            Mode:   git.HardReset, -            Commit: remoteRef.Hash(), -        }) -        if err != nil { -            if !silent { -                fmt.Printf("[err] failed to reset repository %s to %s: %v\n", repoPath, remoteBranchRef, err) -            } -            continue -        } - -        if !silent { -            fmt.Printf("[inf] repository %s updated successfully to %s.\n", repoPath, defaultBranch) -        } -    } -} - -func extractRepoNameFromURL(repoURL string) string { -    parts := strings.Split(repoURL, "/") -    return parts[len(parts)-2] + "_" + parts[len(parts)-1] +	for repoURL := range clonedRepos { +		cloneName := extractRepoNameFromURL(repoURL) +		repoPath := fmt.Sprintf("%s/%s", cloneDir, cloneName) + +		r, err := git.PlainOpen(repoPath) +		if err != nil { +			if !silent { +				fmt.Printf("[err] failed to open repository %s: %v\n", repoPath, err) +			} +			continue +		} + +		headRef, err := r.Head() +		if err != nil { +			if !silent { +				fmt.Printf("[err] failed to get HEAD for repository %s: %v\n", repoPath, err) +			} +			continue +		} + +		defaultBranch := "" +		if headRef.Name().IsBranch() { +			defaultBranch = headRef.Name().Short() +		} else { +			if !silent { +				fmt.Printf("[err] HEAD is not a branch for repository %s, skipping\n", repoPath) +			} +			continue +		} + +		err = r.Fetch(&git.FetchOptions{ +			RemoteName: "origin", +		}) +		if err != nil && err != git.NoErrAlreadyUpToDate { +			if !silent { +				fmt.Printf("[err] failed to fetch updates for repository %s: %v\n", repoPath, err) +			} +			continue +		} + +		w, err := r.Worktree() +		if err != nil { +			if !silent { +				fmt.Printf("[err] failed to get worktree for repository %s: %v\n", repoPath, err) +			} +			continue +		} + +		remoteBranchRef := fmt.Sprintf("origin/%s", defaultBranch) +		remoteRef, err := r.Reference(plumbing.ReferenceName("refs/remotes/"+remoteBranchRef), true) +		if err != nil { +			if !silent { +				fmt.Printf("[err] failed to get reference for %s in repository %s: %v\n", remoteBranchRef, repoPath, err) +			} +			continue +		} + +		err = w.Reset(&git.ResetOptions{ +			Mode:   git.HardReset, +			Commit: remoteRef.Hash(), +		}) +		if err != nil { +			if !silent { +				fmt.Printf("[err] failed to reset repository %s to %s: %v\n", repoPath, remoteBranchRef, err) +			} +			continue +		} + +		if !silent { +			fmt.Printf("[inf] repository %s updated successfully to %s.\n", repoPath, defaultBranch) +		} +	}  } |