From fbf5b1c71577f75cee491333add1a72a524f81bd Mon Sep 17 00:00:00 2001 From: aeppling Date: Thu, 23 Nov 2023 13:10:33 +0100 Subject: [PATCH 1/2] office 365 benchmark & gather --- Kexa/models/o365/ressource.models.ts | 6 + Kexa/rules/BenchmarkOffice365.yaml | 266 ++++++++++++++++ Kexa/services/addOn/o365Gathering.service.ts | 315 ++++++++++++------- config/default.json | 12 +- 4 files changed, 487 insertions(+), 112 deletions(-) create mode 100644 Kexa/rules/BenchmarkOffice365.yaml diff --git a/Kexa/models/o365/ressource.models.ts b/Kexa/models/o365/ressource.models.ts index 94d452f9..15bd0ef1 100644 --- a/Kexa/models/o365/ressource.models.ts +++ b/Kexa/models/o365/ressource.models.ts @@ -12,4 +12,10 @@ export interface o365Resources { incident: Array | null; app_access_policy: Array | null; + group: Array | null; + policy: Array | null; + conditional_access: Array | null; + + sharepoint_settings: Array | null; + } \ No newline at end of file diff --git a/Kexa/rules/BenchmarkOffice365.yaml b/Kexa/rules/BenchmarkOffice365.yaml new file mode 100644 index 00000000..e153021c --- /dev/null +++ b/Kexa/rules/BenchmarkOffice365.yaml @@ -0,0 +1,266 @@ +- version: 1.0.0 + date: 07-18-2023 + alert: + fatal: + enabled: true + type: + - log + #- email + to: + - myEmail@gmail.com + error: + enabled: true + type: + - log + #- email + #- sms + to: + - myEmail@gmail.com + warning: + enabled: true + type: + - log + #- email + to: + - myEmail@gmail.com + info: + enabled: true + type: + - log + #- email + to: + - myEmail@gmail.com + global: + enabled: true + type: + - log + #- webhook + #- sms + #- email + to: + #- http://127.0.0.1:5000/test + - myEmail@gmail.com + conditions: + - level: 0 + min: 1 + - level: 1 + min: 1 + - level: 2 + min: 1 + - level: 3 + min: 1 + rules: + - name: "o365-between-two-and-four-global-admins" + description: "verify there is at least two and no more than four global administrator" + remediation: "Set at least two and a maximum of four Global Administrator users" + applied: false + level: 2 + cloudProvider: o365 + objectName: directory_role + conditions: + - operator: OR + criteria: + - property: displayName + condition: DIFFERENT + value: "Global Administrator" + - operator: AND + criteria: + - property: assignedUsers + condition: COUNT_SUP_OR_EQUAL + value: 2 + - property: assignedUsers + condition: COUNT_INF_OR_EQUAL + value: 4 + - name: "o365-is-group-public" + description: "verify if public groups exist" + remediation: "Set the visibility of this group to 'Private' or 'Hidden membership'" + applied: false + level: 1 + cloudProvider: o365 + objectName: group + conditions: + - operator: OR + criteria: + - property: visibility + condition: DIFFERENT + value: "Public" + - name: "o365-is-user-mfa-activated" + description: "verify if mfa is activated for users" + remediation: "test" + applied: false + level: 2 + cloudProvider: o365 + objectName: auth_methods + conditions: + - property: methods + condition: SOME + value: + - property: dataType + condition: EQUAL + value: "#microsoft.graph.microsoftAuthenticatorAuthenticationMethod" + - name: "o365-do-timeout-exist" + description: "verify if there is active timeout policies" + applied: false + level: 2 + cloudProvider: o365 + objectName: policy + conditions: + - property: . + condition: SOME + value: + - property: displayName + condition: EQUAL + value: ActivityBasedTimeoutPolicy + - name: "o365-is-idle-timeout-set-3h" + description: "verify if idle timeout for inactive user is set to three hours" + applied: false + level: 2 + cloudProvider: o365 + objectName: policy + conditions: + - operator: XOR + criteria: + - property: displayName + condition: DIFFERENT + value: ActivityBasedTimeoutPolicy + - property: definition.ActivityBasedTimeoutPolicy.ApplicationPolicies + condition: ALL + value: + - property: WebSessionIdleTimeout + condition: EQUAL + value: 03:00:00 + - name: "o365-security-default" + description: "verify if security default policy is disabled" + applied: true + level: 2 + cloudProvider: o365 + objectName: policy + conditions: + - operator: NAND + criteria: + - property: displayName + condition: EQUAL + value: 'Security Defaults' + - property: isEnabled + condition: EQUAL + value: true + - name: "o365-is-microsoft-auth-enable" + description: "verify if microsoft authenticator is enabled" + applied: true + level: 2 + cloudProvider: o365 + objectName: policy + conditions: + - operator: XOR + criteria: + - property: displayName + condition: DIFFERENT + value: 'Authentication Methods Policy' + - property: authenticationMethodConfigurations + condition: SOME + value: + - property: id + condition: EQUAL + value: 'MicrosoftAuthenticator' + - property: state + condition: EQUAL + value: 'disabled' + - name: "o365-default-user-cannot-create-tenant" + description: "ensure that default user is not allowed to create tenant" + applied: false + level: 2 + cloudProvider: o365 + objectName: policy + conditions: + - operator: NAND + criteria: + - property: displayName + condition: EQUAL + value: 'Authorization Policy' + - property: defaultUserRolePermissions.allowedToCreateTenants + condition: EQUAL + value: true + - name: "o365-conditional-access-block-inheritance-auth" + description: "ensure that the authentication inheritance blocking policy is enable and active" + applied: false + level: 2 + cloudProvider: o365 + objectName: conditional_access + conditions: + - property: . + condition: SOME + value: + - operator: AND + criteria: + - property: templateId + condition: EQUAL + value: '0b2282f9-2862-4178-88b5-d79340b36cb8' + - property: state + condition: EQUAL + value: 'enabled' + - name: "o365-conditional-access-users-mfa" + description: "ensure that the MFA is enforced for all users" + applied: false + level: 2 + cloudProvider: o365 + objectName: conditional_access + conditions: + - property: . + condition: SOME + value: + - operator: AND + criteria: + - property: templateId + condition: EQUAL + value: 'a3d0a415-b068-4326-9251-f9cdf9feeb64' + - property: state + condition: EQUAL + value: 'enabled' + - name: "o365-conditional-access-admins-mfa" + description: "ensure that the MFA is enforced for all users in admins roles" + applied: false + level: 2 + cloudProvider: o365 + objectName: conditional_access + conditions: + - property: . + condition: SOME + value: + - operator: AND + criteria: + - property: templateId + condition: EQUAL + value: 'c7503427-338e-4c5e-902d-abe252abfb43' + - property: state + condition: EQUAL + value: 'enabled' + - name: "o365-sharepoint-external-user-cannot-reshare" + description: "ensure that sharepoint external user cannot reshare files" + applied: false + level: 2 + cloudProvider: o365 + objectName: sharepoint_settings + conditions: + - property: isResharingByExternalUsersEnabled + condition: EQUAL + value: false + - name: "o365-sharepoint-external-sharing-disabled" + description: "ensure that sharepoint external sharing is disabled" + applied: false + level: 2 + cloudProvider: o365 + objectName: sharepoint_settings + conditions: + - property: sharingCapability + condition: EQUAL + value: 'disabled' + - name: "o365-sharepoint-sync-restricted-unmanaged-device" + description: "ensure that sync is restricted for unmanaged devices" + applied: false + level: 2 + cloudProvider: o365 + objectName: sharepoint_settings + conditions: + - property: isUnmanagedSyncAppForTenantRestricted + condition: EQUAL + value: true \ No newline at end of file diff --git a/Kexa/services/addOn/o365Gathering.service.ts b/Kexa/services/addOn/o365Gathering.service.ts index f8928d4e..f35d1900 100644 --- a/Kexa/services/addOn/o365Gathering.service.ts +++ b/Kexa/services/addOn/o365Gathering.service.ts @@ -11,11 +11,15 @@ * - secure_score * - auth_methods * - organization - * - directory + * - directory_role * - sp * - alert * - incident * - app_access_policy + * - group + * - policy + * - conditional_access + * - sharepoint_settings */ import { getConfigOrEnvVar } from "../manageVarEnvironnement.service"; @@ -49,7 +53,11 @@ export async function collectData(o365Config:o365Config[]): Promise ({ - userId: user.id, - assignedLicenses: user.assignedLicenses, - })); - jsonData.usersLicenses = JSON.parse(JSON.stringify(adaptedResponse)); + }) + if (response.status != 200) { + logger.warn("O365 - Error when calling graph API for subsribed Skus "); + return null; + } + else { + jsonData = JSON.parse(JSON.stringify(response.data.value)); + } + const assignedResponse = await axios.get(`${endpoint}/users?$select=id,assignedLicenses`, { + headers: { + Authorization: `Bearer ${accessToken}` } - - } catch (e: any) { - logger.error(e.response.data); + }) + if (assignedResponse.status != 200) { + logger.warn("O365 - Error when calling graph API for users (skus) "); } + else { + const adaptedResponse = assignedResponse.data.value.map((user: any) => ({ + userId: user.id, + assignedLicenses: user.assignedLicenses, + })); + jsonData.usersLicenses = JSON.parse(JSON.stringify(adaptedResponse)); + } + + } catch (e: any) { + logger.error(e.response.data); + } return jsonData ?? null; } @@ -241,7 +262,10 @@ async function genericListing(endpoint: string, accessToken: string, queryEndpoi return null; } else { - jsonData = JSON.parse(JSON.stringify(response.data.value)); + if (response.data.value) + jsonData = JSON.parse(JSON.stringify(response.data.value)); + else + jsonData = JSON.parse(JSON.stringify(response.data)); } } catch (e: any) { logger.error(e.response.data); @@ -267,27 +291,28 @@ async function listAuthMethods(endpoint: string, accessToken: string, userList: let jsonData = []; for (const element of userList) { - try { - const response = await axios.get(`${endpoint}/users/${element.id}/authentication/methods`, { - headers: { - Authorization: `Bearer ${accessToken}` - } - }) - if (response.status != 200) { - logger.warn("O365 - Error when calling graph API for Auth Methods "); - return null; - } else { - let tmpJson = {methods: [], userId: {}, userName: {}}; - tmpJson.methods = JSON.parse(JSON.stringify(response.data.value)); - tmpJson.userId = element.id; - tmpJson.userName = element.displayName; - tmpJson.methods.forEach((method: any) => { - method.dataType = method['@odata.type']; - method.userId = element.id; - delete method['@odata.type']; - }) - jsonData.push(tmpJson); + try { + const response = await axios.get(`${endpoint}/users/${element.id}/authentication/methods`, { + headers: { + Authorization: `Bearer ${accessToken}` } + }) + if (response.status != 200) { + logger.warn("O365 - Error when calling graph API for Auth Methods "); + return null; + } else { + let tmpJson = {methods: [], userId: {}, userName: {}, userRole: {}}; + tmpJson.methods = JSON.parse(JSON.stringify(response.data.value)); + tmpJson.userId = element.id; + tmpJson.userName = element.displayName; + tmpJson.userRole = element.displayName; + tmpJson.methods.forEach((method: any) => { + method.dataType = method['@odata.type']; + method.userId = element.id; + delete method['@odata.type']; + }) + jsonData.push(tmpJson); + } } catch (e: any) { logger.error(e.response.data); } @@ -304,8 +329,16 @@ async function listOrganization(endpoint: string, accessToken: string, headers: async function listDirectoryRole(endpoint: string, accessToken: string, headers: Headers): Promise | null> { let jsonData : any[] | null; + let jsonDataDetails : any[] | null; jsonData = await genericListing(endpoint, accessToken, "directoryRoles", "Directory roles"); + + if (jsonData) { + for (let i = 0; i < jsonData.length; i++) { + jsonDataDetails = await genericListing(endpoint, accessToken, "directoryRoles/" + jsonData[i].id + "/members", "Directory roles assignments"); + jsonData[i].assignedUsers = jsonDataDetails; + } + } return jsonData ?? null; } @@ -333,22 +366,94 @@ async function listIncidents(endpoint: string, accessToken: string, headers: Hea async function listAppAccessPolicy(endpoint: string, accessToken: string, headers: Headers, userList: any): Promise | null> { const axios = require("axios"); let jsonData: any | []; - for (let i = 0; i < userList.length; i++) { - try { - const licenseResponse = await axios.get(`${endpoint}/users/${userList[i].id}/memberOf`, { - headers: { - Authorization: `Bearer ${accessToken}` - } - }); - if (licenseResponse.status != 200) { - logger.warn("O365 - Error when calling graph API for user " + jsonData[i].displayName); - continue; + for (let i = 0; i < userList.length; i++) { + try { + const licenseResponse = await axios.get(`${endpoint}/users/${userList[i].id}/memberOf`, { + headers: { + Authorization: `Bearer ${accessToken}` } - jsonData = licenseResponse.data.value; - } catch (e) { - logger.error('O365 - Error fetching user '); - logger.error(e); + }); + if (licenseResponse.status != 200) { + logger.warn("O365 - Error when calling graph API for user " + jsonData[i].displayName); + continue; } + jsonData = licenseResponse.data.value; + } catch (e) { + logger.error('O365 - Error fetching user '); + logger.error(e); + } + } + return jsonData ?? null; +} + +async function listGroups(endpoint: string, accessToken: string, headers: Headers): Promise | null> { + let jsonData : any[] | null; + let jsonDataOwners: any[] | null; + + jsonData = await genericListing(endpoint, accessToken, "groups", "Groups"); + if (jsonData) { + for (let i = 0; i < jsonData.length; i++) { + jsonDataOwners = await genericListing(endpoint, accessToken, "groups/" + jsonData[i].id + "/owners", "Groups"); } + } + return jsonData ?? null; +} + +// NEED TO LINK THIS TO A USER AUTH METHOD ??? +async function listPolicies3(endpoint: string, accessToken: string, headers: Headers): Promise | null> { + let jsonData : any[] | null; + + jsonData = await genericListing(endpoint, accessToken, "policies/authenticationStrengthPolicies", "Policies"); return jsonData ?? null; } + +// NEED RESOURCE TO TEST ON PORTAL +async function listConditionalAccess(endpoint: string, accessToken: string, headers: Headers): Promise | null> { + let jsonDataPolicies : any[] | null; + + jsonDataPolicies = await genericListing(endpoint, accessToken, "identity/conditionalAccess/policies", "Identity policies"); + return jsonDataPolicies ?? null; +} + + +/// NEED PERMISSIONS VALIDATION ON PORTAL +async function listPolicies(endpoint: string, accessToken: string, headers: Headers): Promise | null> { + //let jsonData : any[] | null; + let jsonData = []; + let jsonDataAuthMethods, jsonDataTimeout, + jsonDataSecurityDefault,jsonDataAuth, jsonTenant : any[] | null; + + jsonDataTimeout = await genericListing(endpoint, accessToken, "policies/activityBasedTimeoutPolicies", "Policies Timeout"); + if (jsonDataTimeout) { + for (let i = 0; i < jsonDataTimeout.length; i++) { + jsonDataTimeout[i].definition = JSON.parse(jsonDataTimeout[i].definition); + jsonData.push(jsonDataTimeout[i]); + } + } + jsonDataSecurityDefault = await genericListing(endpoint, accessToken, "policies/identitySecurityDefaultsEnforcementPolicy", "Security Default Policy"); + if (jsonDataSecurityDefault) + jsonData.push(jsonDataSecurityDefault); + jsonDataAuthMethods = await genericListing(endpoint, accessToken, "policies/authenticationMethodsPolicy", "Auth Methods Policies"); + if (jsonDataAuthMethods) { + jsonData.push(jsonDataAuthMethods); + } + jsonDataAuth = await genericListing(endpoint, accessToken, "policies/authorizationPolicy", "Authorization Policies"); + if (jsonDataAuth) { + jsonData.push(jsonDataAuth); + } + jsonTenant = await genericListing(endpoint, accessToken, "policies/defaultAppManagementPolicy", "App Management Policy"); + if (jsonTenant) {//policies/defaultAppManagementPolicy + jsonData.push(jsonTenant); + } + console.log(jsonData); + return jsonData ?? null; +} + +async function listSharepointSettings(endpoint: string, accessToken: string, headers: Headers): Promise | null> { + let jsonData = []; + let jsonSettings : any[] | null; + + jsonSettings = await genericListing(endpoint, accessToken, "admin/sharepoint/settings", "Sharepoint Settings"); + jsonData.push(jsonSettings); + return jsonData ?? null; +} \ No newline at end of file diff --git a/config/default.json b/config/default.json index 8988afbe..2fe3f9b2 100644 --- a/config/default.json +++ b/config/default.json @@ -1,13 +1,11 @@ { - "http":[ + "o365": [ { - "Description": "main end point for the api 4urcloud", - "prefix": "DEMO-", + "description": "organization 4urcloud", + "prefix": "DEMO_", "rules": [ - "Deployement" - ], - "METHOD": "GET", - "URL": "https://api.4urcloud.eu/" + "BenchmarkOffice365" + ] } ] } \ No newline at end of file From 1297aa4d66e99d01d03882504853c30b895552ba Mon Sep 17 00:00:00 2001 From: aeppling Date: Thu, 23 Nov 2023 13:15:28 +0100 Subject: [PATCH 2/2] minors correct --- Kexa/rules/BenchmarkOffice365.yaml | 2 +- Kexa/services/addOn/o365Gathering.service.ts | 50 ++++++++++++-------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/Kexa/rules/BenchmarkOffice365.yaml b/Kexa/rules/BenchmarkOffice365.yaml index e153021c..6ea3a268 100644 --- a/Kexa/rules/BenchmarkOffice365.yaml +++ b/Kexa/rules/BenchmarkOffice365.yaml @@ -100,7 +100,7 @@ value: "#microsoft.graph.microsoftAuthenticatorAuthenticationMethod" - name: "o365-do-timeout-exist" description: "verify if there is active timeout policies" - applied: false + applied: true level: 2 cloudProvider: o365 objectName: policy diff --git a/Kexa/services/addOn/o365Gathering.service.ts b/Kexa/services/addOn/o365Gathering.service.ts index f35d1900..2200f680 100644 --- a/Kexa/services/addOn/o365Gathering.service.ts +++ b/Kexa/services/addOn/o365Gathering.service.ts @@ -85,25 +85,38 @@ export async function collectData(o365Config:o365Config[]): Promise