Skip to content

Commit

Permalink
feat: add scan reporter
Browse files Browse the repository at this point in the history
  • Loading branch information
emmanuelgautier committed Oct 15, 2023
1 parent 807ddb1 commit 82332fb
Show file tree
Hide file tree
Showing 11 changed files with 272 additions and 95 deletions.
2 changes: 1 addition & 1 deletion cmd/scan/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func NewScanCmd() (scanCmd *cobra.Command) {
jwt = stdin
}

reports, err := scan.NewScanner(url, &jwt).WithAllScans().Execute()
reports, _, err := scan.NewScanner(url, &jwt).WithAllScans().Execute()
if err != nil {
log.Fatal(err)
}
Expand Down
29 changes: 16 additions & 13 deletions internal/request/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,34 @@ package request

import (
"fmt"
"io"
"net/http"
)

func SendRequestWithBearerAuth(url string, token string) (int, []byte, error) {
client := &http.Client{}

req, err := http.NewRequest("GET", url, nil)
func prepareVulnAPIRequest(method string, url string) (*http.Request, error) {
req, err := http.NewRequest(method, url, nil)
if err != nil {
return 0, nil, err
return nil, err
}

req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
req.Header.Set("User-Agent", "vulnapi/0.1")

resp, err := client.Do(req)
return req, nil
}

func SendRequestWithBearerAuth(url string, token string) (*http.Request, *http.Response, error) {
req, err := prepareVulnAPIRequest("GET", url)
if err != nil {
return 0, nil, err
return req, nil, err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return 0, nil, err
return req, resp, err
}
defer resp.Body.Close()

return resp.StatusCode, body, nil
return req, resp, nil
}
60 changes: 60 additions & 0 deletions report/report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package report

import (
"fmt"
"net/http"
"time"
)

type VulnerabilityScanAttempt struct {
Request *http.Request
Response *http.Response

Err error
}

type ScanReport struct {
scans []*VulnerabilityScanAttempt
vulns []*VulnerabilityReport

startTime time.Time
endTime time.Time
}

func NewScanReport() *ScanReport {
return &ScanReport{
startTime: time.Now(),
}
}

func (sc *ScanReport) Start() *ScanReport {
sc.startTime = time.Now()
return sc
}

func (sc *ScanReport) End() *ScanReport {
sc.endTime = time.Now()
return sc
}

func (sc *ScanReport) AddScanAttempt(a *VulnerabilityScanAttempt) *ScanReport {
sc.scans = append(sc.scans, a)
return sc
}

func (sc *ScanReport) GetScanAttempts() []*VulnerabilityScanAttempt {
return sc.scans
}

func (sc *ScanReport) AddVulnerabilityReport(vr *VulnerabilityReport) *ScanReport {
sc.vulns = append(sc.vulns, vr)
return sc
}

func (sc *ScanReport) GetVulnerabilityReports() []*VulnerabilityReport {
return sc.vulns
}

func (sc *ScanReport) String() string {
return fmt.Sprintf("StartTime: %s EndTime: %s", sc.startTime, sc.endTime)
}
38 changes: 38 additions & 0 deletions report/reporter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package report

type Reporter struct {
reports []ScanReport
}

func NewReporter() *Reporter {
return &Reporter{
reports: []ScanReport{},
}
}

func (rr *Reporter) AddReport(r ScanReport) {
rr.reports = append(rr.reports, r)
}

func (rr *Reporter) GetReports() []ScanReport {
return rr.reports
}

func (rr *Reporter) GetVulnerabilityReports() []*VulnerabilityReport {
var vrs []*VulnerabilityReport
for _, r := range rr.GetReports() {
vrs = append(vrs, r.GetVulnerabilityReports()...)
}

return vrs
}

func (rr *Reporter) HasHighRiskSeverityVulnerability() bool {
for _, r := range rr.GetVulnerabilityReports() {
if r.IsHighRiskSeverity() {
return true
}
}

return false
}
20 changes: 20 additions & 0 deletions report/vuln.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package report

type VulnerabilityReport struct {
SeverityLevel float64 // https://nvd.nist.gov/vuln-metrics/cvss
Name string
Description string
Url *string
}

func (vr *VulnerabilityReport) IsLowRiskSeverity() bool {
return vr.SeverityLevel < 4
}

func (vr *VulnerabilityReport) IsMediumRiskSeverity() bool {
return vr.SeverityLevel > 4 && vr.SeverityLevel < 7
}

func (vr *VulnerabilityReport) IsHighRiskSeverity() bool {
return vr.SeverityLevel > 7
}
34 changes: 21 additions & 13 deletions scan/jwt/alg_none.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
package jwt

import (
"fmt"

"github.com/cerberauth/vulnapi/internal/request"
"github.com/cerberauth/vulnapi/report"
restapi "github.com/cerberauth/vulnapi/scan/rest_api"
"github.com/golang-jwt/jwt/v5"
)

func AlgNoneJwtScanHandler(url string, token string) []error {
newToken, err := createNewJWTWithClaimsAndMethod(token, jwt.SigningMethodNone, jwt.UnsafeAllowNoneSignatureType)
if err != nil {
return []error{err}
}
const (
AlgNoneVulnerabilitySeverityLevel = 9
AlgNoneVulnerabilityName = ""
AlgNoneVulnerabilityDescription = ""
)

func AlgNoneJwtScanHandler(url string, token string) (*report.ScanReport, error) {
r := report.NewScanReport()

statusCode, _, err := request.SendRequestWithBearerAuth(url, newToken)
newToken, err := createNewJWTWithClaimsAndMethod(token, jwt.SigningMethodNone, jwt.UnsafeAllowNoneSignatureType)
if err != nil {
return []error{err}
return r, err
}
vsa := restapi.ScanRestAPI(url, newToken)
r.AddScanAttempt(vsa).End()

if statusCode > 200 && statusCode <= 300 {
return []error{fmt.Errorf("unexpected status code %d with an alg none forged token", statusCode)}
if vsa.Response.StatusCode < 300 {
r.AddVulnerabilityReport(&report.VulnerabilityReport{
SeverityLevel: AlgNoneVulnerabilitySeverityLevel,
Name: AlgNoneVulnerabilityName,
Description: AlgNoneVulnerabilityDescription,
})
}

return nil
return r, nil
}
51 changes: 25 additions & 26 deletions scan/jwt/not_verified.go
Original file line number Diff line number Diff line change
@@ -1,46 +1,45 @@
package jwt

import (
"fmt"

"github.com/cerberauth/vulnapi/internal/request"
"github.com/cerberauth/vulnapi/report"
restapi "github.com/cerberauth/vulnapi/scan/rest_api"
"github.com/golang-jwt/jwt/v5"
)

func NotVerifiedScanHandler(url string, token string) []error {
const (
NotVerifiedVulnerabilitySeverityLevel = 9
NotVerifiedVulnerabilityName = ""
NotVerifiedVulnerabilityDescription = ""
)

func NotVerifiedScanHandler(url string, token string) (*report.ScanReport, error) {
r := report.NewScanReport()

newTokenA, err := createNewJWTWithClaimsAndMethod(token, jwt.SigningMethodHS256, []byte("a"))
if err != nil {
return []error{err}
return r, err
}

newTokenB, err := createNewJWTWithClaimsAndMethod(token, jwt.SigningMethodHS256, []byte("b"))
if err != nil {
return []error{err}
}

statusCodeA, _, errRequestA := request.SendRequestWithBearerAuth(url, newTokenA)
statusCodeB, _, errRequestB := request.SendRequestWithBearerAuth(url, newTokenB)

var errors []error
if errRequestA != nil {
errors = append(errors, errRequestA)
return r, err
}

if errRequestB != nil {
errors = append(errors, errRequestB)
}
vsa1 := restapi.ScanRestAPI(url, newTokenA)
r.AddScanAttempt(vsa1)

if statusCodeA > 200 && statusCodeA <= 300 {
errors = append(errors, fmt.Errorf("unexpected status code %d with an invalid forged token", statusCodeA))
}
vsa2 := restapi.ScanRestAPI(url, newTokenB)
r.AddScanAttempt(vsa2)

if statusCodeA != statusCodeB {
errors = append(errors, fmt.Errorf("status code are not the same between the two attempts"))
}
r.End()

if len(errors) > 0 {
return errors
if vsa1.Response != vsa2.Response {
r.AddVulnerabilityReport(&report.VulnerabilityReport{
SeverityLevel: NotVerifiedVulnerabilitySeverityLevel,
Name: NotVerifiedVulnerabilityName,
Description: NotVerifiedVulnerabilityDescription,
})
}

return nil
return r, nil
}
35 changes: 22 additions & 13 deletions scan/jwt/null_signature.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package jwt

import (
"fmt"
"strings"

"github.com/cerberauth/vulnapi/internal/request"
"github.com/cerberauth/vulnapi/report"
restapi "github.com/cerberauth/vulnapi/scan/rest_api"
)

const (
NullSigVulnerabilitySeverityLevel = 9
NullSigVulnerabilityName = ""
NullSigVulnerabilityDescription = ""
)

func createNewJWTWithoutSignature(originalTokenString string) (string, error) {
Expand All @@ -17,20 +23,23 @@ func createNewJWTWithoutSignature(originalTokenString string) (string, error) {
return strings.Join([]string{parts[0], parts[1], ""}, "."), nil
}

func NullSignatureScanHandler(url string, token string) []error {
newToken, err := createNewJWTWithoutSignature(token)
if err != nil {
return []error{err}
}
func NullSignatureScanHandler(url string, token string) (*report.ScanReport, error) {
r := report.NewScanReport()

statusCode, _, err := request.SendRequestWithBearerAuth(url, newToken)
newToken, err := createNewJWTWithoutSignature(token)
if err != nil {
return []error{err}
return r, err
}

if statusCode > 200 && statusCode <= 300 {
return []error{fmt.Errorf("unexpected status code %d with a null signature", statusCode)}
vsa := restapi.ScanRestAPI(url, newToken)
r.AddScanAttempt(vsa).End()

if vsa.Response.StatusCode < 300 {
r.AddVulnerabilityReport(&report.VulnerabilityReport{
SeverityLevel: NullSigVulnerabilitySeverityLevel,
Name: NullSigVulnerabilityName,
Description: NullSigVulnerabilityDescription,
})
}

return nil
return r, nil
}
Loading

0 comments on commit 82332fb

Please sign in to comment.