Skip to content

Commit

Permalink
Nuclei CVE integrated (#337)
Browse files Browse the repository at this point in the history
* resolved multi account conflicts with cve PR
* resolved conflicts
* resolved conflicts
* minor changes in cve attackpath to integrate the aws inspector cves
* minor correction in alert slideover component

---------

Co-authored-by: Tushar Goel <tushargoel@MacBook-Air.local>
  • Loading branch information
Tushar200018 and Tushar Goel committed Jul 3, 2023
1 parent f50a890 commit 4dfc7da
Show file tree
Hide file tree
Showing 34 changed files with 1,892 additions and 317 deletions.
35 changes: 30 additions & 5 deletions backend/control/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import (
"sync"
"time"

"github.com/Zeus-Labs/ZeusCloud/rules"
"github.com/Zeus-Labs/ZeusCloud/rules/types"

"github.com/Zeus-Labs/ZeusCloud/models"
"github.com/Zeus-Labs/ZeusCloud/rules"

"github.com/neo4j/neo4j-go-driver/v4/neo4j"
"gorm.io/gorm"
)
Expand All @@ -38,6 +39,7 @@ type CartographyJobRequest struct {
AwsAccessKeyId string `json:"aws_access_key_id,omitempty"`
AwsSecretAccessKey string `json:"aws_secret_access_key,omitempty"`
RegionNames []string `json:"region_names,omitempty"`
VulnerabilityScan string `json:"vulnerability_scan,omitempty"`
}

// ExecuteRules attempts to
Expand Down Expand Up @@ -117,20 +119,22 @@ func ExecuteRules(postgresDb *gorm.DB, driver neo4j.Driver, rulesToExecute []typ

// Attempts to start the cartography job by hitting the /start_job cartography api
func StartCartographyJob(account models.AccountDetails) error {
// TODO: Change this when nuclei is merged

var cjr CartographyJobRequest
if account.ConnectionMethod == "profile" {
cjr = CartographyJobRequest{
AccountName: account.AccountName,
Profile: account.Profile,
RegionNames: account.RegionNames,
AccountName: account.AccountName,
Profile: account.Profile,
RegionNames: account.RegionNames,
VulnerabilityScan: account.VulnerabilityScan,
}
} else if account.ConnectionMethod == "access_key" {
cjr = CartographyJobRequest{
AccountName: account.AccountName,
AwsAccessKeyId: account.AwsAccessKeyId,
AwsSecretAccessKey: account.AwsSecretAccessKey,
RegionNames: account.RegionNames,
VulnerabilityScan: account.VulnerabilityScan,
}
} else {
// Invalid Connection Method
Expand Down Expand Up @@ -306,3 +310,24 @@ func GetAwsProfiles() (AwsProfilesResponse, error) {
}
return apr, nil
}

type VulnRuleInfo struct {
CveIdentifier string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
CvssScore float64 `json:"cvss_score,omitempty"`
YamlTemplate string `json:"yaml_template,omitempty"`
}

func GetVulnRuleInfo() ([]VulnRuleInfo, error) {
resp, err := http.Get(os.Getenv("CARTOGRAPHY_URI") + "/get_templates_info")
if err != nil {
return nil, err
}
defer resp.Body.Close()
var vri []VulnRuleInfo
if err := json.NewDecoder(resp.Body).Decode(&vri); err != nil {
return nil, err
}
return vri, nil
}
2 changes: 1 addition & 1 deletion backend/handlers/get_alerts.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func GetAllAlerts(postgresDb *gorm.DB) func(w http.ResponseWriter, r *http.Reque
ruleCategory := r.URL.Query().Get("rulecategory")

// Check if rule category is valid.
var ruleCategoryMap = map[string]bool{"attackpath": true, "all": true, "misconfiguration": true}
var ruleCategoryMap = map[string]bool{"attackpath": true, "all": true, "misconfiguration": true, "vulnerability": true}
if _, ok := ruleCategoryMap[ruleCategory]; !ok {
log.Println("Invalid rule category provided")
http.Error(w, "Invalid rule category provided provided", 500)
Expand Down
2 changes: 1 addition & 1 deletion backend/handlers/get_rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func GetRules(postgresDb *gorm.DB) func(w http.ResponseWriter, r *http.Request)
ruleCategory := r.URL.Query().Get("rulecategory")

// Check if rule category is valid.
var ruleCategoryMap = map[string]bool{"attackpath": true, "all": true, "misconfiguration": true}
var ruleCategoryMap = map[string]bool{"attackpath": true, "all": true, "misconfiguration": true, "vulnerability": true}
if _, ok := ruleCategoryMap[ruleCategory]; !ok {
log.Println("Invalid rule category provided")
http.Error(w, "Invalid rule category provided provided", 500)
Expand Down
35 changes: 35 additions & 0 deletions backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,22 @@ import (
"log"
"net/http"
"os"
"time"

"github.com/Zeus-Labs/ZeusCloud/control"
"github.com/Zeus-Labs/ZeusCloud/models"
"github.com/Zeus-Labs/ZeusCloud/rules"
"github.com/Zeus-Labs/ZeusCloud/rules/types"
"github.com/Zeus-Labs/ZeusCloud/rules/vulnerability"

"github.com/Zeus-Labs/ZeusCloud/constants"
"github.com/Zeus-Labs/ZeusCloud/db"
"github.com/Zeus-Labs/ZeusCloud/handlers"
"github.com/Zeus-Labs/ZeusCloud/middleware"
)

const scanStatusInterval = 5 * time.Second

func demoMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if os.Getenv("MODE") == constants.DemoEnvModeStr {
Expand Down Expand Up @@ -55,6 +59,36 @@ func main() {
continue
}
}

// wait till cartography server is started in order for the nuclei templates to get downloaded
for {
if _, err := control.GetScanStatus(); err != nil {
time.Sleep(scanStatusInterval)
continue
}
break
}
vriList, err := control.GetVulnRuleInfo()
if err != nil {
log.Fatal(err)
}
for _, vri := range vriList {
vulnRule := vulnerability.Vulnerability{
Name: vri.Name,
CveDescription: vri.Description,
CvssScore: vri.CvssScore,
CveIdentifier: vri.CveIdentifier,
YamlTemplate: vri.YamlTemplate,
}
rules.VulnerabilityRulesToExecute = append(rules.VulnerabilityRulesToExecute, vulnRule)

err := rules.UpsertRuleData(postgresDb, vulnRule, "vulnerability")
if err != nil {
log.Printf("Unexpected error upserting rule_data %v", err)
continue
}
}

log.Println("Finished inserting postgres rules.")

// For demo environment, attempt to add account to kick of cartography.
Expand All @@ -73,6 +107,7 @@ func main() {

// Kick of rule execution loop and try to trigger a scan successfully.
rulesToExecute := append(append([]types.Rule{}, rules.AttackPathsRulesToExecute...), rules.MisconfigurationRulesToExecute...)
rulesToExecute = append(rulesToExecute, rules.VulnerabilityRulesToExecute...)
if err := control.ResetCartographyStatus(postgresDb); err != nil {
log.Printf("Error in resetting cartography job status on startup")
}
Expand Down
1 change: 1 addition & 0 deletions backend/models/account_details.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ type AccountDetails struct {
ScanStatus string `json:"scan_status"`
RunningTime float64 `json:"running_time,omitempty"`
RegionNames pq.StringArray `json:"region_names" gorm:"type:text[]"`
VulnerabilityScan string `json:"vulnerability_scan"`
}
3 changes: 3 additions & 0 deletions backend/models/rule_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ type RuleData struct {
Severity string `json:"severity"`
RuleCategory string `json:"rule_category"`
RiskCategories pq.StringArray `json:"risk_categories" gorm:"type:text[]"`
CvssScore float64 `json:"cvss_score,omitempty"`
Name string `json:"name,omitempty"`
YamlTemplate string `json:"yaml_template,omitempty"`
}
157 changes: 157 additions & 0 deletions backend/rules/attackpath/PubliclyExposedServerlessAdminCriticalCVE.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package attackpath

import (
"fmt"

"github.com/Zeus-Labs/ZeusCloud/rules/types"
"github.com/neo4j/neo4j-go-driver/v4/neo4j"
)

type PubliclyExposedServerlessAdminCriticalCVE struct{}

func (PubliclyExposedServerlessAdminCriticalCVE) UID() string {
return "attackpath/publicly_exposed_serverless_admin_permissions_critical_cve"
}

func (PubliclyExposedServerlessAdminCriticalCVE) Description() string {
return "Publicly exposed serverless function with critical CVE and effective admin permissions."
}

func (PubliclyExposedServerlessAdminCriticalCVE) Severity() types.Severity {
return types.Critical
}

func (PubliclyExposedServerlessAdminCriticalCVE) RiskCategories() types.RiskCategoryList {
return []types.RiskCategory{
types.PubliclyExposed,
types.IamMisconfiguration,
types.Vulnerability,
}
}

func (PubliclyExposedServerlessAdminCriticalCVE) Execute(tx neo4j.Transaction) ([]types.Result, error) {
records, err := tx.Run(
`MATCH (a:AWSAccount{inscope: true})-[:RESOURCE]->(lambda:AWSLambda)
OPTIONAL MATCH
(:IpRange{range:'0.0.0.0/0'})-[:MEMBER_OF_IP_RULE]->
(perm:IpPermissionInbound)-[:MEMBER_OF_EC2_SECURITY_GROUP]->
(elbv2_group:EC2SecurityGroup)<-[:MEMBER_OF_EC2_SECURITY_GROUP]-
(elbv2:LoadBalancerV2{scheme: 'internet-facing'})—[:ELBV2_LISTENER]->
(listener:ELBV2Listener),
(elbv2CVE:CVE {severity:"critical"})<-[HAS_VULNERABILITY]-(elbv2)-[:EXPOSE]->(lambda)
WHERE listener.port >= perm.fromport AND listener.port <= perm.toport
WITH a, lambda, collect(distinct elbv2.id) as public_elbv2_ids, collect(distinct elbv2CVE.template_id) as elbv2_cve_ids
OPTIONAL MATCH
(lambda)-[:STS_ASSUME_ROLE_ALLOW]->(role:AWSRole{is_admin: True})
WITH a, lambda, public_elbv2_ids, elbv2_cve_ids, collect(role.arn) as admin_roles, collect(role.admin_reason) as admin_reasons
WITH a, lambda, public_elbv2_ids, elbv2_cve_ids, admin_roles, admin_reasons,
(size(public_elbv2_ids) > 0 AND size(elbv2_cve_ids) > 0) as publicly_exposed_critical_cve_lambda,
(size(admin_roles) > 0) as is_admin
RETURN lambda.id as resource_id,
'AWSLambda' as resource_type,
a.id as account_id,
CASE
WHEN publicly_exposed_critical_cve_lambda AND is_admin THEN 'failed'
ELSE 'passed'
END as status,
CASE
WHEN publicly_exposed_critical_cve_lambda THEN (
'The function is publicly exposed. ' +
CASE
WHEN size(public_elbv2_ids) > 0 THEN (
'The function is publicly exposed through these ELBv2 load balancers: ' + substring(apoc.text.join(public_elbv2_ids, ', '), 0, 1000) + './n' +
CASE
WHEN size(elbv2_cve_ids) > 0 THEN 'The following critical CVEs are detected for the ELBv2 load balancers: ' + substring(apoc.text.join(elbv2_cve_ids, ', '), 0, 1000) + '.'
ELSE 'No critical CVEs are detected for the ELBv2 load balancers.'
END
)
ELSE 'The function is not publicly exposed through any ELBv2 load balancers.'
END
)
ELSE 'The function is neither directly publicly exposed, nor indirectly public exposed through an ELBv2 load balancer.'
END + '\n' +
CASE
WHEN is_admin THEN (
'The function is effectively an admin in the account because of: ' + admin_reasons[0] + '.'
)
ELSE 'The function was not detected as effectively an admin in the account.'
END as context`,
nil,
)
if err != nil {
return nil, err
}

var results []types.Result
for records.Next() {
record := records.Record()
resourceID, _ := record.Get("resource_id")
resourceIDStr, ok := resourceID.(string)
if !ok {
return nil, fmt.Errorf("resource_id %v should be of type string", resourceID)
}
resourceType, _ := record.Get("resource_type")
resourceTypeStr, ok := resourceType.(string)
if !ok {
return nil, fmt.Errorf("resource_type %v should be of type string", resourceType)
}
accountID, _ := record.Get("account_id")
accountIDStr, ok := accountID.(string)
if !ok {
return nil, fmt.Errorf("account_id %v should be of type string", accountID)
}
status, _ := record.Get("status")
statusStr, ok := status.(string)
if !ok {
return nil, fmt.Errorf("status %v should be of type string", status)
}
context, _ := record.Get("context")
contextStr, ok := context.(string)
if !ok {
return nil, fmt.Errorf("context %v should be of type string", context)
}
results = append(results, types.Result{
ResourceID: resourceIDStr,
ResourceType: resourceTypeStr,
AccountID: accountIDStr,
Status: statusStr,
Context: contextStr,
})
}
return results, nil
}

func (PubliclyExposedServerlessAdminCriticalCVE) ProduceRuleGraph(tx neo4j.Transaction, resourceId string) (neo4j.Result, error) {
params := map[string]interface{}{
"InstanceId": resourceId,
}
records, err := tx.Run(
`MATCH (a:AWSAccount{inscope: true})-[:RESOURCE]->(lambda:AWSLambda{id: $InstanceId})
OPTIONAL MATCH
(:IpRange{range:'0.0.0.0/0'})-[:MEMBER_OF_IP_RULE]->
(perm:IpPermissionInbound)-[:MEMBER_OF_EC2_SECURITY_GROUP]->
(elbv2_group:EC2SecurityGroup)<-[:MEMBER_OF_EC2_SECURITY_GROUP]-
(elbv2:LoadBalancerV2{scheme: 'internet-facing'})—[:ELBV2_LISTENER]->
(listener:ELBV2Listener),
(lambda)<-[:EXPOSE]-(elbv2)-[:HAS_VULNERABILITY]->(:CVE {severity:"critical"})
WHERE listener.port >= perm.fromport AND listener.port <= perm.toport
OPTIONAL MATCH
indirectPath=(iprange)-[:MEMBER_OF_IP_RULE]->(perm)-[:MEMBER_OF_EC2_SECURITY_GROUP]->
(elbv2_group)<-[:MEMBER_OF_EC2_SECURITY_GROUP]-(elbv2)-[:EXPOSE]->(lambda)
OPTIONAL MATCH
elbv2CvePath = (elbv2)-[HAS_VULNERABILITY]->(:CVE {severity:"critical"})
WITH a, lambda, collect(indirectPath) as indirectPaths, collect(elbv2CvePath) as elbv2CvePaths
OPTIONAL MATCH
adminRolePath=
(lambda)-[:STS_ASSUME_ROLE_ALLOW]->(role:AWSRole{is_admin: True})
WITH lambda, indirectPaths, elbv2CvePaths, collect(adminRolePath) as adminRolePaths
WITH indirectPaths + adminRolePaths + elbv2CvePaths AS paths
RETURN paths`,
params,
)
if err != nil {
return nil, err
}

return records, nil
}
Loading

0 comments on commit 4dfc7da

Please sign in to comment.