Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

GuardDutyを使ったサイバー攻撃の検出と分析.pdf

 GuardDutyを使ったサイバー攻撃の検出と分析.pdf

みなさん、SOC(Security Operation Center)という言葉をご存知でしょうか。映画やアニメでよくある怪獣から攻撃を受けた作戦本部をイメージしてください。SOCはサイバー攻撃のアラートを受けるそれのようなものです。しかし、単にアラートを受けるといっても中々ハードルが高いのが現実です。ざっと思いつくだけでも、以下を考えなければいけません。 (1)どのログをとるのか、(2)どうやってログをとるのか、(3)どこにログを収集するのか、(4)どういった条件を満たしたらアラートとなるのか、(5)アラートを何処に飛ばすのか、(6)アラートをどう分析するのか、(7)対応をどう監査するのか、こういったことを自前で考えられる組織は少なく、セキュリティベンダに高額でアウトソースしているところは多いと思います。 昨年のAWS re:inventで発表されたGuardDutyは、そういった面倒な多くの部分を解決してくれるマネージドSOCサービスです。本セッションではGuardDutyがどういった攻撃を検知してくれるか、どういった課題を解決するかといった点をコードと交えて紹介いたします。≥≤                           

Kengo Suzuki

July 29, 2018
Tweet

More Decks by Kengo Suzuki

Other Decks in Technology

Transcript

  1. ׬

  2. ׬

  3. ػցֶश - σʔλ in AWS - AWS಺ͷϦιʔε - VPC Flow

    Logs - DNS Logs - CloudTrail Events Logs
  4. ػցֶश - σʔλ from ΠϯςϦδΣϯε - ΦʔϓϯͳΠϯςϦδΣϯεσʔλ - Taxii, STIX,

    OTX - ΠϯςϦδΣϯεσʔλ from αʔϏε - Alien Vault, Fire Eye, Proof Point
  5. ैདྷͷߏ੒ 4*&. 8"' -# 8"' -# 'JSFXBMM 'JSFXBMM *%4 1SPYZ

    'JMF *OUFHSJUZ 'JMF *OUFHSJUZ 'JMF *OUFHSJUZ
  6. Firewall - Πϯλʔωοτͱͷڥք๷ޚ - L3~4ϨΠϠʔͷstatefulͳηογϣϯपΓͷݕ஌ - ྫ: ωοτϫʔΫ੍ޚ - ྫ:

    ϙʔτ੍ޚ - ྫ: syn flood߈੍ܸޚ - Untrust΍TrustͳκʔϯΛఆٛ - Juniper Network, Cisco, Yamaha… - ࠓͷ࣌୅ʮͰʁʯͱͳΓ͕ͪ
  7. SIEM - Security Information and Event Managementͷུ - ઌड़ͷ੡඼܈ͷϩάΛू໿͠ɺΠϕϯτΛ؅ཧ͢Δ -

    ྫ: ୹࣌ؒͰFirewallʹର͠ɺಉҰૹ৴ݩ͔ΒN݅ͷDeny௨৴͕͋ͬͨ ΒΞϥʔτΛ্͛Δ - ྫ: WAFͰSQL߈ܸγάωνϟʹͻ͔͔ͬΔϦΫΤετʹ200Λฦͨ͠ ΒΞϥʔτΛ্͛Δ - ArchSight, Splunk, Sumo-logic, ELK, Graylog - Eventͷઃఆ is େม
  8. Applicable to AWSʁ        _,,;' '" '' ゛''" ゛' ';;,,       (rヽ,;''"""''゛゛゛'';,

    ノr)       ,;'゛ i _  、_ iヽ゛';,    お前AWSでも同じことできんの?       ,;'" ''| ヽ・〉 〈・ノ |゙゛ `';,       ,;'' "|   ▼   |゙゛ `';,       ,;''  ヽ_人_ /  ,;'_      /シ、  ヽ⌒⌒ /   リ \     |   "r,, `"'''゙´  ,,ミ゛   |     |      リ、    ,リ    |     |   i   ゛r、ノ,,r" i   _|     |   `ー――----┴ ⌒´ )     (ヽ  ______ ,, _´)      (_⌒ ______ ,, ィ       丁           |        |           |
  9. ݕ஌Ͱ͖ΔڴҖ &$ *". #BDLEPPS $SZQUP $VSSFODIZ 1FOUFTU 1FSTJTUFODF 3FDPO #FIBWJPSJBM

    5SB⒏D 7PMVNF 4UFBMUI 5SPKBO 6OBVUIPSJ[FE "DDFTT $POTPMF-PHJO *OTUBODF $SFEFOUJBM &YpMUSBUJPO - Ϧιʔε - ໨త - ڴҖͷछྨ - ୔ࢁ IUUQTEPDTBXTBNB[PODPNHVBSEEVUZMBUFTUVHHVBSEEVUZ@pOEJOHUZQFTIUNMVOBVUIPSJ[FE
  10. &$ *". #BDLEPPS $SZQUP $VSSFODIZ 1FOUFTU 1FSTJTUFODF 3FDPO #FIBWJPSJBM 5SB⒏D

    7PMVNF 4UFBMUI 5SPKBO 6OBVUIPSJ[FE "DDFTT $POTPMF-PHJO *OTUBODF $SFEFOUJBM &YpMUSBUJPO - Ϧιʔε - ໨త IUUQTEPDTBXTBNB[PODPNHVBSEEVUZMBUFTUVHHVBSEEVUZ@pOEJOHUZQFTIUNMVOBVUIPSJ[FE ݕ஌Ͱ͖ΔڴҖ
  11. جຊతʹ͸LambdaͰ֦ு { "version": "0", "id": "c8c4daa7-a20c-2f03-0070-b7393dd542ad", "detail-type": "GuardDuty Finding", "source":

    "aws.guardduty", "account": "123456789012", "time": "1970-01-01T00:00:00Z", "region": "us-east-1", "resources": [], "detail": { "schemaVersion": "2.0", "accountId": "123456789012", "region": "ap-northeast-1", "type": "UnauthorizedAccess:EC2/RDPBruteForce", "resource": { "resourceType": "Instance", "instanceDetails": { "instanceId": "i-99999999", "instanceType": "m3.xlarge", "launchTime": "2016-08-02T02:05:06Z", "platform": null, "productCodes": [ { "productCodeId": "GeneratedFindingProductCodeId", "productCodeType": "GeneratedFindingProductCodeType } ], "iamInstanceProfile": { "arn": "GeneratedFindingInstanceProfileArn", "id": "GeneratedFindingInstanceProfileId" }, "networkInterfaces": [ { "ipv6Addresses": [], "networkInterfaceId": "eni-bfcffe88", "privateDnsName": "GeneratedFindingPrivateDnsName",
  12. Slack௨஌ func main() { lambda.Start(HandleRequest) } func HandleRequest(request CloudWatchEventForGuardDuty) {

    if err := postOnSlack(request); err != nil { log.Fatal().Err(err).Msg("failed.") } } func mapSeverityToLevel(request CWEvent) (*Severity, error) { severity := request.Detail.Severity s := &Severity{} s.AccountAlias = request.Detail.AccountID if 0 <= severity && severity < 4 { s.Level = "Low" s.Color = "#707070" return s, nil } else if 4 <= severity && severity < 7 { s.Level = "Medium" s.Color = "warning" return s, nil } else if severity < 10 { s.Level = "High" s.Color = "danger" s.Announce = true return s, nil } return nil, errors.New("Severity was not in right range: 0~10.0") }
  13. Slack௨஌ func postOnSlack(request CWevent) error { severity, err := initializeSeverity(request.Detail.Severity)

    if err != nil { return err } pretext := "'" + request.Detail.Type + "' type found." if severity.Announce { pretext = "<!here> " + pretext } attachment := slack.Attachment{ Color: severity.Color, Pretext: pretext, Title: request.Detail.Title, Fields: []slack.AttachmentField{ { Title: "Severity", Value: severity.Level, Short: true, }, { Title: "Account", Value: request.Detail.AccountID, Short: true, }, { Title: "Description", Value: request.Detail.Description, Short: false, }, }, } payload := slack.PostMessageParameters{ Attachments: []slack.Attachment{attachment}, } body, err := json.Marshal(payload) if err != nil { return errors.New("failed to encode payload") } if _, err := http.Post(slackURL, contentType, bytes.NewReader(body) != nil { return errors.New(fmt.Sprintf("posting Slack failed: %v", err)) } return nil }
  14. ݟग़͠ func postOnJira(request CloudWatchEventForGuardDuty, c chan error) { defer close(c)

    body := &JiraIssueCreateRequest{ Fields: struct { Project struct { ID string `json:"id"` } `json:"project"` Summary string `json:"summary"` Description string `json:"description"` IssueType struct { ID string `json:"id"` } `json:"issuetype"` }{ Project: struct { ID string `json:"id"` }{ID: jiraProjectId}, Summary: request.Detail.Title, Description: request.Detail.Description + " in account: " + request.Detail.AccountI IssueType: struct { ID string `json:"id"` }{ID: jiraIssueTypeId}, }, } payload, err := json.Marshal(body) if err != nil { c <- err return } req, _ := http.NewRequest(http.MethodPost, jiraURL, bytes.NewBuffer(payload)) req.Header.Add("Authorization", jiraBasicAuthentication) req.Header.Add("Content-Type", contentType) log.Info().Str("status", "sending a posting a request Jira").Msg("ok") // TODO Handle non 200 case if _, err := http.DefaultClient.Do(req); err != nil { c <- err return } log.Info().Str("status", "finishing a request Jira").Msg("ok") } func HandleRequest(request CloudWatchEventForGuardDuty) (err error) { errJIRAChan, errPDChan := make(chan error), make(chan error) go postOnPagerDuty(request, errPDChan) go postOnJira(request, errJIRAChan) ok1, ok2:= true, true for ok1 || ok2 { select { case err, ok1 = <-errJIRAChan: if err != nil && ok1 { log.Info().Err(err).Str("app", "jira").Msg(err.Error()) } case err, ok2 = <-errPDChan: if err != nil && ok2 { log.Info().Err(err).Str("app", "PagerDuty").Msg(err.Error()) } } } return err } func getAccountAlias(accountId string) string { if v, ok := accountMap[accountId]; ok { return v } return accountId }
  15. ݟग़͠ func HandleRequest(request CloudWatchEventForGuardDuty) (err error) { errJIRAChan, errPDChan :=

    make(chan error), make(chan error) go postOnPagerDuty(request, errPDChan) go postOnJira(request, errJIRAChan) ok1, ok2:= true, true for ok1 || ok2 { select { case err, ok1 = <-errJIRAChan: if err != nil && ok1 { log.Info().Err(err).Str("app", "jira").Msg(err.Error()) } case err, ok2 = <-errPDChan: if err != nil && ok2 { log.Info().Err(err).Str("app", "PagerDuty").Msg(err.Error()) } } } return err } func getAccountAlias(accountId string) string { if v, ok := accountMap[accountId]; ok { return v } return accountId } func postOnPagerDuty(request CloudWatchEventForGuardDuty, c chan error) { defer close(c) client := pagerduty.NewClient(pagerDutyToken) priorities, err := client.ListPriorities() if err != nil { c <- err return } var priority string severity := request.Detail.Severity if 0 <= severity && severity < 4 { priority = priorities.Priorities[2].ID } else if 4 <= severity && severity < 7 { priority = priorities.Priorities[1].ID } else if severity < 10 { priority = priorities.Priorities[0].ID } o := pagerduty.CreateIncidentOptions{ Title: request.Detail.Title, Service: pagerduty.APIReference{ ID: pagerDutyServiceId, Type: "service_reference", }, Priority: pagerduty.APIReference{ ID: priority, Type: "priority_reference", }, Body: pagerduty.APIDetails{ Type: "incident_body", Details: request.Detail.Description + " in account: " + getAccountAlias(request.Detail.AccountID), }, } _, err = client.CreateIncident(pagerDutyEmail, &pagerduty.CreateIncident{o}) if err != nil { c <- err return } }
  16. ࣗಈִ཭(Forensic४උʣ TOBQTIPU &#4 ᶃ ᶄ ᶅ ᶆ - "Digital Forensic

    Analysis of Amazon Linux EC2 Instances” by SANSͷҰ෦ΛίʔυԽ AWS FIntech ϦϑΝϨϯεΨΠ υ IUUQTXXXTBOTPSHSFBEJOHSPPNXIJUFQBQFST DMPVEEJHJUBMGPSFOTJDBOBMZTJTBNB[POMJOVYFD JOTUBODFT
  17. ݟग़͠ // Take SnapShot of suspected EC2 instance for taking

    evidence func (e *EC2Forensic) CreateEvidenceSnapshot() (snapshotId string, err error) { describeEc2AttributeInput := &ec2.DescribeInstanceAttributeInput{ Attribute: aws.String("blockDeviceMapping"), InstanceId: aws.String(e.InstanceId), } createSnapshotInput := &ec2.CreateSnapshotInput{ Description: aws.String("Snapshot taken for forensic purpose"), TagSpecifications: []*ec2.TagSpecification{ { ResourceType: aws.String("snapshot"), Tags: []*ec2.Tag{{Key: aws.String("Name"), Value: aws.String("forensic-snapshot")}}, }, }, } output, err := e.svc.DescribeInstanceAttribute(describeEc2AttributeInput) if err != nil { return "", err } createSnapshotInput.VolumeId = output.BlockDeviceMappings[0].Ebs.VolumeId snapShot, err := e.svc.CreateSnapshot(createSnapshotInput) if err != nil { return "", err } // Check State var snapShotState string for snapShotState != "completed" { output, err := e.svc.DescribeSnapshots(&ec2.DescribeSnapshotsInput{SnapshotIds: []*string{snapShot.SnapshotId}}) if err != nil { return "", nil } snapShotState = *output.Snapshots[0].State } return *snapShot.SnapshotId, nil }
  18. ݟग़͠ func (e *EC2Forensic) CreateEvidenceEBS(snapshotId string) (volumeId string, err error)

    { input := &ec2.CreateVolumeInput{ //ToDO Dynamically retrieve AZ from subnet-id AvailabilityZone: aws.String("ap-northeast-1a"), SnapshotId: aws.String(snapshotId), TagSpecifications: []*ec2.TagSpecification{ { ResourceType: aws.String("volume"), Tags: []*ec2.Tag{{Key: aws.String("Name"), Value: aws.String("forensic-ebs-volume")}}, }, }, } output, err := e.svc.CreateVolume(input) if err != nil { return "", err } // Check State var volumeState string if volumeState != "ok" { output, err := e.svc.DescribeVolumeStatus(&ec2.DescribeVolumeStatusInput{ VolumeIds: []*string{output.VolumeId}, }) if err != nil { return "", err } volumeState = *output.VolumeStatuses[0].VolumeStatus.Status } return *output.VolumeId, nil }
  19. ݟग़͠ func (e *EC2Forensic) StartForensicWorkstation() (workstationId string, err error) {

    input := &ec2.RunInstancesInput{ TagSpecifications: []*ec2.TagSpecification{ { ResourceType: aws.String("instance"), Tags: []*ec2.Tag{ {Key: aws.String("Name"), Value: aws.String("forensic-workstation")}, {Key: aws.String("Target"), Value: aws.String(e.InstanceId)}, }, }, }, MaxCount: aws.Int64(1), MinCount: aws.Int64(1), SecurityGroupIds: []*string{aws.String(forensicSgId)}, SubnetId: aws.String(forensicSubnetId), // Recommended in https://www.sans.org/reading-room/whitepapers/cloud/digital-forensic-analysis-amazon-linux-ec2-instances-38235? InstanceType: aws.String("t2.large"), // Latest(2018/07/15) ami id of Ubuntu Server 16.04 LTS (HVM), SSD Volume Type // Recommended in https://www.sans.org/reading-room/whitepapers/cloud/digital-forensic-analysis-amazon-linux-ec2-instances-38235? ImageId: aws.String("ami-940cdceb"), } output, err := e.svc.RunInstances(input) if err != nil { return "", err } var instanceState string for instanceState != "running" { output, err := e.svc.DescribeInstanceStatus(&ec2.DescribeInstanceStatusInput{ InstanceIds: []*string{output.Instances[0].InstanceId}, IncludeAllInstances: aws.Bool(true), }) if err != nil { return "", err } instanceState = *output.InstanceStatuses[0].InstanceState.Name } return *output.Instances[0].InstanceId, nil }
  20. ݟग़͠ func (e *EC2Forensic) AttachEvidenceToWorkstation(workstationId, evidenceVolumeId string) (err error) {

    _, err = e.svc.AttachVolume(&ec2.AttachVolumeInput{ InstanceId: aws.String(workstationId), VolumeId: aws.String(evidenceVolumeId), Device: aws.String("/dev/sdf"), }) return } func notify(isFailed bool, isCompleted bool, message string) { var color string var status string if isFailed { color = "warning" status = “failed" } else if isCompleted { color = "good" status = "completed" } else { color = "#707070" status = "completed" } log.Info().Str("duration", returnDuration()).Str("status", message).Msg(status) payload := slack.PostMessageParameters{ Attachments: []slack.Attachment{{ Color: color, Pretext: "Forensic Preparation Status", Title: message, }}, } body, err := json.Marshal(payload) if err != nil { log.Info().Str("status", "failed").Msg("slack notification failed: failed to encode payload") } if _, err := http.Post(slackURL, "application/json", bytes.NewReader(body)); err != nil { log.Info().Str("status", "failed").Msg("slack notification failed: http post failed") }
  21. { "version": "0", "id": "c8c4daa7-a20c-2f03-0070-b7393dd542ad", "detail-type": "GuardDuty Finding", "source": "aws.guardduty",

    "account": "123456789012", "time": "1970-01-01T00:00:00Z", "region": "us-east-1", "resources": [], "detail": { "schemaVersion": "2.0", "accountId": "123456789012", "region": "ap-northeast-1", "type": "UnauthorizedAccess:EC2/RDPBruteForce", "resource": { "resourceType": "Instance", "instanceDetails": { "instanceId": "i-99999999", "instanceType": "m3.xlarge", "launchTime": "2016-08-02T02:05:06Z", "platform": null, "productCodes": [ { "productCodeId": "GeneratedFindingProductCodeId", "productCodeType": "GeneratedFindingProductCodeType" } ], { "version": "0", "id": "c8c4daa7-a20c-2f03-0070-b7393dd542ad", "detail-type": "GuardDuty Finding", "source": "aws.guardduty", "account": "123456789012", "time": "1970-01-01T00:00:00Z", "region": "us-east-1", "resources": [], "detail": { "schemaVersion": "2.0", "accountId": "123456789012", "region": "ap-northeast-1", "partition": "aws", "id": "08b1830ad3896e10860152a387a36b00", "arn": "arn:aws:guardduty:ap-northeast-1:123456789012:detector/e6b15a3c39d02cb9287 "type": "UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration", "resource": { "resourceType": "AccessKey", "accessKeyDetails": { "accessKeyId": "GeneratedFindingAccessKey "principalId": "GeneratedFindingPrincipal "userType": "IAMUser", "userName": "GeneratedFindingUserName" } }, "ipAddressV4": "198.51.100.1" } ], "sample": true }, "eventFirstSeen": "2018-04-27T07:51:12.402Z", "eventLastSeen": "2018-05-11T14:07:26.951Z", "archived": true, "count": 37 }, "severity": 8, શવҧ͏ ʢݕ஌ର৅ͷϦιʔεʣ &$PS*".
  22. "service": { "serviceName": "guardduty", "detectorId": "e6b15a3c39d02cb928758a13a65eb04e", "action": { "actionType": "AWS_API_CALL",

    "awsApiCallAction": { "api": "GeneratedFindingAPIName", "serviceName": "GeneratedFindingAPIServiceName", "callerType": "Remote IP", "remoteIpDetails": { "ipAddressV4": "198.51.100.0", "organization": { "asn": "-1", "asnOrg": "GeneratedFindingASNOrg", "isp": "GeneratedFindingISP", "org": "GeneratedFindingORG" }, "country": { "countryName": "GeneratedFindingCountryName" }, "city": { "cityName": "GeneratedFindingCityName" }, "geoLocation": { "service": { "serviceName": "guardduty", "detectorId": "e6b15a3c39d02cb928758a13a65eb04e", "action": { "actionType": "NETWORK_CONNECTION", "networkConnectionAction": { "connectionDirection": "INBOUND", "remoteIpDetails": { "ipAddressV4": "198.51.100.0", "organization": { "asn": "-1", "asnOrg": "GeneratedFindingASNO "isp": "GeneratedFindingISP", "org": "GeneratedFindingORG" }, "country": { "countryName": "GeneratedFindin }, "city": { "cityName": "GeneratedFindingCi }, "geoLocation": { "lat": 0, "lon": 0 } }, શવҧ͏ 5ISFBEͷ"DUJPOʣ &$PS*".