@etcsec-com/etc-collector 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +60 -0
- package/.env.test.example +33 -0
- package/.github/workflows/ci.yml +83 -0
- package/.github/workflows/release.yml +246 -0
- package/.prettierrc.json +10 -0
- package/CHANGELOG.md +15 -0
- package/Dockerfile +57 -0
- package/LICENSE +190 -0
- package/README.md +194 -0
- package/dist/api/controllers/audit.controller.d.ts +21 -0
- package/dist/api/controllers/audit.controller.d.ts.map +1 -0
- package/dist/api/controllers/audit.controller.js +179 -0
- package/dist/api/controllers/audit.controller.js.map +1 -0
- package/dist/api/controllers/auth.controller.d.ts +16 -0
- package/dist/api/controllers/auth.controller.d.ts.map +1 -0
- package/dist/api/controllers/auth.controller.js +146 -0
- package/dist/api/controllers/auth.controller.js.map +1 -0
- package/dist/api/controllers/export.controller.d.ts +27 -0
- package/dist/api/controllers/export.controller.d.ts.map +1 -0
- package/dist/api/controllers/export.controller.js +80 -0
- package/dist/api/controllers/export.controller.js.map +1 -0
- package/dist/api/controllers/health.controller.d.ts +5 -0
- package/dist/api/controllers/health.controller.d.ts.map +1 -0
- package/dist/api/controllers/health.controller.js +16 -0
- package/dist/api/controllers/health.controller.js.map +1 -0
- package/dist/api/controllers/jobs.controller.d.ts +13 -0
- package/dist/api/controllers/jobs.controller.d.ts.map +1 -0
- package/dist/api/controllers/jobs.controller.js +125 -0
- package/dist/api/controllers/jobs.controller.js.map +1 -0
- package/dist/api/controllers/providers.controller.d.ts +15 -0
- package/dist/api/controllers/providers.controller.d.ts.map +1 -0
- package/dist/api/controllers/providers.controller.js +112 -0
- package/dist/api/controllers/providers.controller.js.map +1 -0
- package/dist/api/dto/AuditRequest.dto.d.ts +6 -0
- package/dist/api/dto/AuditRequest.dto.d.ts.map +1 -0
- package/dist/api/dto/AuditRequest.dto.js +3 -0
- package/dist/api/dto/AuditRequest.dto.js.map +1 -0
- package/dist/api/dto/AuditResponse.dto.d.ts +17 -0
- package/dist/api/dto/AuditResponse.dto.d.ts.map +1 -0
- package/dist/api/dto/AuditResponse.dto.js +3 -0
- package/dist/api/dto/AuditResponse.dto.js.map +1 -0
- package/dist/api/dto/TokenRequest.dto.d.ts +6 -0
- package/dist/api/dto/TokenRequest.dto.d.ts.map +1 -0
- package/dist/api/dto/TokenRequest.dto.js +3 -0
- package/dist/api/dto/TokenRequest.dto.js.map +1 -0
- package/dist/api/dto/TokenResponse.dto.d.ts +12 -0
- package/dist/api/dto/TokenResponse.dto.d.ts.map +1 -0
- package/dist/api/dto/TokenResponse.dto.js +3 -0
- package/dist/api/dto/TokenResponse.dto.js.map +1 -0
- package/dist/api/middlewares/authenticate.d.ts +12 -0
- package/dist/api/middlewares/authenticate.d.ts.map +1 -0
- package/dist/api/middlewares/authenticate.js +141 -0
- package/dist/api/middlewares/authenticate.js.map +1 -0
- package/dist/api/middlewares/errorHandler.d.ts +3 -0
- package/dist/api/middlewares/errorHandler.d.ts.map +1 -0
- package/dist/api/middlewares/errorHandler.js +30 -0
- package/dist/api/middlewares/errorHandler.js.map +1 -0
- package/dist/api/middlewares/rateLimit.d.ts +3 -0
- package/dist/api/middlewares/rateLimit.d.ts.map +1 -0
- package/dist/api/middlewares/rateLimit.js +34 -0
- package/dist/api/middlewares/rateLimit.js.map +1 -0
- package/dist/api/middlewares/validate.d.ts +4 -0
- package/dist/api/middlewares/validate.d.ts.map +1 -0
- package/dist/api/middlewares/validate.js +31 -0
- package/dist/api/middlewares/validate.js.map +1 -0
- package/dist/api/routes/audit.routes.d.ts +5 -0
- package/dist/api/routes/audit.routes.d.ts.map +1 -0
- package/dist/api/routes/audit.routes.js +24 -0
- package/dist/api/routes/audit.routes.js.map +1 -0
- package/dist/api/routes/auth.routes.d.ts +6 -0
- package/dist/api/routes/auth.routes.d.ts.map +1 -0
- package/dist/api/routes/auth.routes.js +22 -0
- package/dist/api/routes/auth.routes.js.map +1 -0
- package/dist/api/routes/export.routes.d.ts +5 -0
- package/dist/api/routes/export.routes.d.ts.map +1 -0
- package/dist/api/routes/export.routes.js +16 -0
- package/dist/api/routes/export.routes.js.map +1 -0
- package/dist/api/routes/health.routes.d.ts +4 -0
- package/dist/api/routes/health.routes.d.ts.map +1 -0
- package/dist/api/routes/health.routes.js +11 -0
- package/dist/api/routes/health.routes.js.map +1 -0
- package/dist/api/routes/index.d.ts +10 -0
- package/dist/api/routes/index.d.ts.map +1 -0
- package/dist/api/routes/index.js +20 -0
- package/dist/api/routes/index.js.map +1 -0
- package/dist/api/routes/providers.routes.d.ts +5 -0
- package/dist/api/routes/providers.routes.d.ts.map +1 -0
- package/dist/api/routes/providers.routes.js +13 -0
- package/dist/api/routes/providers.routes.js.map +1 -0
- package/dist/api/validators/audit.schemas.d.ts +60 -0
- package/dist/api/validators/audit.schemas.d.ts.map +1 -0
- package/dist/api/validators/audit.schemas.js +55 -0
- package/dist/api/validators/audit.schemas.js.map +1 -0
- package/dist/api/validators/auth.schemas.d.ts +17 -0
- package/dist/api/validators/auth.schemas.d.ts.map +1 -0
- package/dist/api/validators/auth.schemas.js +21 -0
- package/dist/api/validators/auth.schemas.js.map +1 -0
- package/dist/app.d.ts +3 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +62 -0
- package/dist/app.js.map +1 -0
- package/dist/config/config.schema.d.ts +65 -0
- package/dist/config/config.schema.d.ts.map +1 -0
- package/dist/config/config.schema.js +95 -0
- package/dist/config/config.schema.js.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +75 -0
- package/dist/config/index.js.map +1 -0
- package/dist/container.d.ts +47 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +137 -0
- package/dist/container.js.map +1 -0
- package/dist/data/database.d.ts +13 -0
- package/dist/data/database.d.ts.map +1 -0
- package/dist/data/database.js +68 -0
- package/dist/data/database.js.map +1 -0
- package/dist/data/jobs/token-cleanup.job.d.ts +23 -0
- package/dist/data/jobs/token-cleanup.job.d.ts.map +1 -0
- package/dist/data/jobs/token-cleanup.job.js +96 -0
- package/dist/data/jobs/token-cleanup.job.js.map +1 -0
- package/dist/data/migrations/migration.runner.d.ts +13 -0
- package/dist/data/migrations/migration.runner.d.ts.map +1 -0
- package/dist/data/migrations/migration.runner.js +136 -0
- package/dist/data/migrations/migration.runner.js.map +1 -0
- package/dist/data/models/Token.model.d.ts +30 -0
- package/dist/data/models/Token.model.d.ts.map +1 -0
- package/dist/data/models/Token.model.js +3 -0
- package/dist/data/models/Token.model.js.map +1 -0
- package/dist/data/repositories/token.repository.d.ts +16 -0
- package/dist/data/repositories/token.repository.d.ts.map +1 -0
- package/dist/data/repositories/token.repository.js +97 -0
- package/dist/data/repositories/token.repository.js.map +1 -0
- package/dist/providers/azure/auth.provider.d.ts +5 -0
- package/dist/providers/azure/auth.provider.d.ts.map +1 -0
- package/dist/providers/azure/auth.provider.js +13 -0
- package/dist/providers/azure/auth.provider.js.map +1 -0
- package/dist/providers/azure/azure-errors.d.ts +40 -0
- package/dist/providers/azure/azure-errors.d.ts.map +1 -0
- package/dist/providers/azure/azure-errors.js +121 -0
- package/dist/providers/azure/azure-errors.js.map +1 -0
- package/dist/providers/azure/azure-retry.d.ts +41 -0
- package/dist/providers/azure/azure-retry.d.ts.map +1 -0
- package/dist/providers/azure/azure-retry.js +85 -0
- package/dist/providers/azure/azure-retry.js.map +1 -0
- package/dist/providers/azure/graph-client.d.ts +26 -0
- package/dist/providers/azure/graph-client.d.ts.map +1 -0
- package/dist/providers/azure/graph-client.js +146 -0
- package/dist/providers/azure/graph-client.js.map +1 -0
- package/dist/providers/azure/graph.provider.d.ts +23 -0
- package/dist/providers/azure/graph.provider.d.ts.map +1 -0
- package/dist/providers/azure/graph.provider.js +161 -0
- package/dist/providers/azure/graph.provider.js.map +1 -0
- package/dist/providers/azure/queries/app.queries.d.ts +6 -0
- package/dist/providers/azure/queries/app.queries.d.ts.map +1 -0
- package/dist/providers/azure/queries/app.queries.js +9 -0
- package/dist/providers/azure/queries/app.queries.js.map +1 -0
- package/dist/providers/azure/queries/policy.queries.d.ts +6 -0
- package/dist/providers/azure/queries/policy.queries.d.ts.map +1 -0
- package/dist/providers/azure/queries/policy.queries.js +9 -0
- package/dist/providers/azure/queries/policy.queries.js.map +1 -0
- package/dist/providers/azure/queries/user.queries.d.ts +7 -0
- package/dist/providers/azure/queries/user.queries.d.ts.map +1 -0
- package/dist/providers/azure/queries/user.queries.js +10 -0
- package/dist/providers/azure/queries/user.queries.js.map +1 -0
- package/dist/providers/interfaces/IGraphProvider.d.ts +31 -0
- package/dist/providers/interfaces/IGraphProvider.d.ts.map +1 -0
- package/dist/providers/interfaces/IGraphProvider.js +3 -0
- package/dist/providers/interfaces/IGraphProvider.js.map +1 -0
- package/dist/providers/interfaces/ILDAPProvider.d.ts +37 -0
- package/dist/providers/interfaces/ILDAPProvider.d.ts.map +1 -0
- package/dist/providers/interfaces/ILDAPProvider.js +3 -0
- package/dist/providers/interfaces/ILDAPProvider.js.map +1 -0
- package/dist/providers/ldap/acl-parser.d.ts +8 -0
- package/dist/providers/ldap/acl-parser.d.ts.map +1 -0
- package/dist/providers/ldap/acl-parser.js +157 -0
- package/dist/providers/ldap/acl-parser.js.map +1 -0
- package/dist/providers/ldap/ad-mappers.d.ts +8 -0
- package/dist/providers/ldap/ad-mappers.d.ts.map +1 -0
- package/dist/providers/ldap/ad-mappers.js +162 -0
- package/dist/providers/ldap/ad-mappers.js.map +1 -0
- package/dist/providers/ldap/ldap-client.d.ts +33 -0
- package/dist/providers/ldap/ldap-client.d.ts.map +1 -0
- package/dist/providers/ldap/ldap-client.js +195 -0
- package/dist/providers/ldap/ldap-client.js.map +1 -0
- package/dist/providers/ldap/ldap-errors.d.ts +48 -0
- package/dist/providers/ldap/ldap-errors.d.ts.map +1 -0
- package/dist/providers/ldap/ldap-errors.js +120 -0
- package/dist/providers/ldap/ldap-errors.js.map +1 -0
- package/dist/providers/ldap/ldap-retry.d.ts +14 -0
- package/dist/providers/ldap/ldap-retry.d.ts.map +1 -0
- package/dist/providers/ldap/ldap-retry.js +102 -0
- package/dist/providers/ldap/ldap-retry.js.map +1 -0
- package/dist/providers/ldap/ldap-sanitizer.d.ts +12 -0
- package/dist/providers/ldap/ldap-sanitizer.d.ts.map +1 -0
- package/dist/providers/ldap/ldap-sanitizer.js +104 -0
- package/dist/providers/ldap/ldap-sanitizer.js.map +1 -0
- package/dist/providers/ldap/ldap.provider.d.ts +21 -0
- package/dist/providers/ldap/ldap.provider.d.ts.map +1 -0
- package/dist/providers/ldap/ldap.provider.js +165 -0
- package/dist/providers/ldap/ldap.provider.js.map +1 -0
- package/dist/providers/ldap/queries/computer.queries.d.ts +6 -0
- package/dist/providers/ldap/queries/computer.queries.d.ts.map +1 -0
- package/dist/providers/ldap/queries/computer.queries.js +9 -0
- package/dist/providers/ldap/queries/computer.queries.js.map +1 -0
- package/dist/providers/ldap/queries/group.queries.d.ts +6 -0
- package/dist/providers/ldap/queries/group.queries.d.ts.map +1 -0
- package/dist/providers/ldap/queries/group.queries.js +9 -0
- package/dist/providers/ldap/queries/group.queries.js.map +1 -0
- package/dist/providers/ldap/queries/user.queries.d.ts +7 -0
- package/dist/providers/ldap/queries/user.queries.d.ts.map +1 -0
- package/dist/providers/ldap/queries/user.queries.js +10 -0
- package/dist/providers/ldap/queries/user.queries.js.map +1 -0
- package/dist/providers/smb/smb.provider.d.ts +68 -0
- package/dist/providers/smb/smb.provider.d.ts.map +1 -0
- package/dist/providers/smb/smb.provider.js +382 -0
- package/dist/providers/smb/smb.provider.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +44 -0
- package/dist/server.js.map +1 -0
- package/dist/services/audit/ad-audit.service.d.ts +70 -0
- package/dist/services/audit/ad-audit.service.d.ts.map +1 -0
- package/dist/services/audit/ad-audit.service.js +1019 -0
- package/dist/services/audit/ad-audit.service.js.map +1 -0
- package/dist/services/audit/attack-graph.service.d.ts +62 -0
- package/dist/services/audit/attack-graph.service.d.ts.map +1 -0
- package/dist/services/audit/attack-graph.service.js +702 -0
- package/dist/services/audit/attack-graph.service.js.map +1 -0
- package/dist/services/audit/audit.service.d.ts +4 -0
- package/dist/services/audit/audit.service.d.ts.map +1 -0
- package/dist/services/audit/audit.service.js +10 -0
- package/dist/services/audit/audit.service.js.map +1 -0
- package/dist/services/audit/azure-audit.service.d.ts +37 -0
- package/dist/services/audit/azure-audit.service.d.ts.map +1 -0
- package/dist/services/audit/azure-audit.service.js +153 -0
- package/dist/services/audit/azure-audit.service.js.map +1 -0
- package/dist/services/audit/detectors/ad/accounts.detector.d.ts +37 -0
- package/dist/services/audit/detectors/ad/accounts.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/accounts.detector.js +881 -0
- package/dist/services/audit/detectors/ad/accounts.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/adcs.detector.d.ts +21 -0
- package/dist/services/audit/detectors/ad/adcs.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/adcs.detector.js +227 -0
- package/dist/services/audit/detectors/ad/adcs.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/advanced.detector.d.ts +63 -0
- package/dist/services/audit/detectors/ad/advanced.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/advanced.detector.js +867 -0
- package/dist/services/audit/detectors/ad/advanced.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/attack-paths.detector.d.ts +16 -0
- package/dist/services/audit/detectors/ad/attack-paths.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/attack-paths.detector.js +369 -0
- package/dist/services/audit/detectors/ad/attack-paths.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/compliance.detector.d.ts +28 -0
- package/dist/services/audit/detectors/ad/compliance.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/compliance.detector.js +896 -0
- package/dist/services/audit/detectors/ad/compliance.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/computers.detector.d.ts +30 -0
- package/dist/services/audit/detectors/ad/computers.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/computers.detector.js +799 -0
- package/dist/services/audit/detectors/ad/computers.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/gpo.detector.d.ts +17 -0
- package/dist/services/audit/detectors/ad/gpo.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/gpo.detector.js +257 -0
- package/dist/services/audit/detectors/ad/gpo.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/groups.detector.d.ts +19 -0
- package/dist/services/audit/detectors/ad/groups.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/groups.detector.js +488 -0
- package/dist/services/audit/detectors/ad/groups.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/index.d.ts +15 -0
- package/dist/services/audit/detectors/ad/index.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/index.js +51 -0
- package/dist/services/audit/detectors/ad/index.js.map +1 -0
- package/dist/services/audit/detectors/ad/kerberos.detector.d.ts +17 -0
- package/dist/services/audit/detectors/ad/kerberos.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/kerberos.detector.js +293 -0
- package/dist/services/audit/detectors/ad/kerberos.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/monitoring.detector.d.ts +23 -0
- package/dist/services/audit/detectors/ad/monitoring.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/monitoring.detector.js +328 -0
- package/dist/services/audit/detectors/ad/monitoring.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/network.detector.d.ts +39 -0
- package/dist/services/audit/detectors/ad/network.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/network.detector.js +257 -0
- package/dist/services/audit/detectors/ad/network.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/password.detector.d.ts +14 -0
- package/dist/services/audit/detectors/ad/password.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/password.detector.js +235 -0
- package/dist/services/audit/detectors/ad/password.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/permissions.detector.d.ts +20 -0
- package/dist/services/audit/detectors/ad/permissions.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/permissions.detector.js +392 -0
- package/dist/services/audit/detectors/ad/permissions.detector.js.map +1 -0
- package/dist/services/audit/detectors/ad/trusts.detector.d.ts +11 -0
- package/dist/services/audit/detectors/ad/trusts.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/ad/trusts.detector.js +186 -0
- package/dist/services/audit/detectors/ad/trusts.detector.js.map +1 -0
- package/dist/services/audit/detectors/azure/app-security.detector.d.ts +11 -0
- package/dist/services/audit/detectors/azure/app-security.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/azure/app-security.detector.js +184 -0
- package/dist/services/audit/detectors/azure/app-security.detector.js.map +1 -0
- package/dist/services/audit/detectors/azure/conditional-access.detector.d.ts +10 -0
- package/dist/services/audit/detectors/azure/conditional-access.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/azure/conditional-access.detector.js +130 -0
- package/dist/services/audit/detectors/azure/conditional-access.detector.js.map +1 -0
- package/dist/services/audit/detectors/azure/privilege-security.detector.d.ts +8 -0
- package/dist/services/audit/detectors/azure/privilege-security.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/azure/privilege-security.detector.js +113 -0
- package/dist/services/audit/detectors/azure/privilege-security.detector.js.map +1 -0
- package/dist/services/audit/detectors/azure/user-security.detector.d.ts +14 -0
- package/dist/services/audit/detectors/azure/user-security.detector.d.ts.map +1 -0
- package/dist/services/audit/detectors/azure/user-security.detector.js +198 -0
- package/dist/services/audit/detectors/azure/user-security.detector.js.map +1 -0
- package/dist/services/audit/detectors/index.d.ts +2 -0
- package/dist/services/audit/detectors/index.d.ts.map +1 -0
- package/dist/services/audit/detectors/index.js +38 -0
- package/dist/services/audit/detectors/index.js.map +1 -0
- package/dist/services/audit/response-formatter.d.ts +176 -0
- package/dist/services/audit/response-formatter.d.ts.map +1 -0
- package/dist/services/audit/response-formatter.js +240 -0
- package/dist/services/audit/response-formatter.js.map +1 -0
- package/dist/services/audit/scoring.service.d.ts +15 -0
- package/dist/services/audit/scoring.service.d.ts.map +1 -0
- package/dist/services/audit/scoring.service.js +139 -0
- package/dist/services/audit/scoring.service.js.map +1 -0
- package/dist/services/auth/crypto.service.d.ts +19 -0
- package/dist/services/auth/crypto.service.d.ts.map +1 -0
- package/dist/services/auth/crypto.service.js +135 -0
- package/dist/services/auth/crypto.service.js.map +1 -0
- package/dist/services/auth/errors.d.ts +19 -0
- package/dist/services/auth/errors.d.ts.map +1 -0
- package/dist/services/auth/errors.js +46 -0
- package/dist/services/auth/errors.js.map +1 -0
- package/dist/services/auth/token.service.d.ts +41 -0
- package/dist/services/auth/token.service.d.ts.map +1 -0
- package/dist/services/auth/token.service.js +208 -0
- package/dist/services/auth/token.service.js.map +1 -0
- package/dist/services/config/config.service.d.ts +6 -0
- package/dist/services/config/config.service.d.ts.map +1 -0
- package/dist/services/config/config.service.js +64 -0
- package/dist/services/config/config.service.js.map +1 -0
- package/dist/services/export/export.service.d.ts +28 -0
- package/dist/services/export/export.service.d.ts.map +1 -0
- package/dist/services/export/export.service.js +28 -0
- package/dist/services/export/export.service.js.map +1 -0
- package/dist/services/export/formatters/csv.formatter.d.ts +8 -0
- package/dist/services/export/formatters/csv.formatter.d.ts.map +1 -0
- package/dist/services/export/formatters/csv.formatter.js +46 -0
- package/dist/services/export/formatters/csv.formatter.js.map +1 -0
- package/dist/services/export/formatters/json.formatter.d.ts +40 -0
- package/dist/services/export/formatters/json.formatter.d.ts.map +1 -0
- package/dist/services/export/formatters/json.formatter.js +58 -0
- package/dist/services/export/formatters/json.formatter.js.map +1 -0
- package/dist/services/jobs/azure-job-runner.d.ts +38 -0
- package/dist/services/jobs/azure-job-runner.d.ts.map +1 -0
- package/dist/services/jobs/azure-job-runner.js +199 -0
- package/dist/services/jobs/azure-job-runner.js.map +1 -0
- package/dist/services/jobs/index.d.ts +4 -0
- package/dist/services/jobs/index.d.ts.map +1 -0
- package/dist/services/jobs/index.js +20 -0
- package/dist/services/jobs/index.js.map +1 -0
- package/dist/services/jobs/job-runner.d.ts +64 -0
- package/dist/services/jobs/job-runner.d.ts.map +1 -0
- package/dist/services/jobs/job-runner.js +952 -0
- package/dist/services/jobs/job-runner.js.map +1 -0
- package/dist/services/jobs/job-store.d.ts +27 -0
- package/dist/services/jobs/job-store.d.ts.map +1 -0
- package/dist/services/jobs/job-store.js +261 -0
- package/dist/services/jobs/job-store.js.map +1 -0
- package/dist/services/jobs/job.types.d.ts +67 -0
- package/dist/services/jobs/job.types.d.ts.map +1 -0
- package/dist/services/jobs/job.types.js +36 -0
- package/dist/services/jobs/job.types.js.map +1 -0
- package/dist/types/ad.types.d.ts +74 -0
- package/dist/types/ad.types.d.ts.map +1 -0
- package/dist/types/ad.types.js +3 -0
- package/dist/types/ad.types.js.map +1 -0
- package/dist/types/adcs.types.d.ts +58 -0
- package/dist/types/adcs.types.d.ts.map +1 -0
- package/dist/types/adcs.types.js +38 -0
- package/dist/types/adcs.types.js.map +1 -0
- package/dist/types/attack-graph.types.d.ts +135 -0
- package/dist/types/attack-graph.types.d.ts.map +1 -0
- package/dist/types/attack-graph.types.js +58 -0
- package/dist/types/attack-graph.types.js.map +1 -0
- package/dist/types/audit.types.d.ts +34 -0
- package/dist/types/audit.types.d.ts.map +1 -0
- package/dist/types/audit.types.js +3 -0
- package/dist/types/audit.types.js.map +1 -0
- package/dist/types/azure.types.d.ts +61 -0
- package/dist/types/azure.types.d.ts.map +1 -0
- package/dist/types/azure.types.js +3 -0
- package/dist/types/azure.types.js.map +1 -0
- package/dist/types/config.types.d.ts +63 -0
- package/dist/types/config.types.d.ts.map +1 -0
- package/dist/types/config.types.js +3 -0
- package/dist/types/config.types.js.map +1 -0
- package/dist/types/error.types.d.ts +33 -0
- package/dist/types/error.types.d.ts.map +1 -0
- package/dist/types/error.types.js +70 -0
- package/dist/types/error.types.js.map +1 -0
- package/dist/types/finding.types.d.ts +133 -0
- package/dist/types/finding.types.d.ts.map +1 -0
- package/dist/types/finding.types.js +3 -0
- package/dist/types/finding.types.js.map +1 -0
- package/dist/types/gpo.types.d.ts +39 -0
- package/dist/types/gpo.types.d.ts.map +1 -0
- package/dist/types/gpo.types.js +15 -0
- package/dist/types/gpo.types.js.map +1 -0
- package/dist/types/token.types.d.ts +26 -0
- package/dist/types/token.types.d.ts.map +1 -0
- package/dist/types/token.types.js +3 -0
- package/dist/types/token.types.js.map +1 -0
- package/dist/types/trust.types.d.ts +45 -0
- package/dist/types/trust.types.d.ts.map +1 -0
- package/dist/types/trust.types.js +71 -0
- package/dist/types/trust.types.js.map +1 -0
- package/dist/utils/entity-converter.d.ts +17 -0
- package/dist/utils/entity-converter.d.ts.map +1 -0
- package/dist/utils/entity-converter.js +285 -0
- package/dist/utils/entity-converter.js.map +1 -0
- package/dist/utils/graph.util.d.ts +66 -0
- package/dist/utils/graph.util.d.ts.map +1 -0
- package/dist/utils/graph.util.js +382 -0
- package/dist/utils/graph.util.js.map +1 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +86 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/type-name-normalizer.d.ts +5 -0
- package/dist/utils/type-name-normalizer.d.ts.map +1 -0
- package/dist/utils/type-name-normalizer.js +218 -0
- package/dist/utils/type-name-normalizer.js.map +1 -0
- package/docker-compose.yml +26 -0
- package/docs/api/README.md +178 -0
- package/docs/api/openapi.yaml +1524 -0
- package/eslint.config.js +54 -0
- package/jest.config.js +38 -0
- package/package.json +97 -0
- package/scripts/fetch-ad-cert.sh +142 -0
- package/src/.gitkeep +0 -0
- package/src/api/.gitkeep +0 -0
- package/src/api/controllers/.gitkeep +0 -0
- package/src/api/controllers/audit.controller.ts +313 -0
- package/src/api/controllers/auth.controller.ts +258 -0
- package/src/api/controllers/export.controller.ts +153 -0
- package/src/api/controllers/health.controller.ts +16 -0
- package/src/api/controllers/jobs.controller.ts +187 -0
- package/src/api/controllers/providers.controller.ts +165 -0
- package/src/api/dto/.gitkeep +0 -0
- package/src/api/dto/AuditRequest.dto.ts +8 -0
- package/src/api/dto/AuditResponse.dto.ts +19 -0
- package/src/api/dto/TokenRequest.dto.ts +8 -0
- package/src/api/dto/TokenResponse.dto.ts +14 -0
- package/src/api/middlewares/.gitkeep +0 -0
- package/src/api/middlewares/authenticate.ts +203 -0
- package/src/api/middlewares/errorHandler.ts +54 -0
- package/src/api/middlewares/rateLimit.ts +35 -0
- package/src/api/middlewares/validate.ts +32 -0
- package/src/api/routes/.gitkeep +0 -0
- package/src/api/routes/audit.routes.ts +77 -0
- package/src/api/routes/auth.routes.ts +71 -0
- package/src/api/routes/export.routes.ts +34 -0
- package/src/api/routes/health.routes.ts +14 -0
- package/src/api/routes/index.ts +40 -0
- package/src/api/routes/providers.routes.ts +24 -0
- package/src/api/validators/.gitkeep +0 -0
- package/src/api/validators/audit.schemas.ts +59 -0
- package/src/api/validators/auth.schemas.ts +59 -0
- package/src/app.ts +87 -0
- package/src/config/.gitkeep +0 -0
- package/src/config/config.schema.ts +108 -0
- package/src/config/index.ts +82 -0
- package/src/container.ts +221 -0
- package/src/data/.gitkeep +0 -0
- package/src/data/database.ts +78 -0
- package/src/data/jobs/token-cleanup.job.ts +166 -0
- package/src/data/migrations/.gitkeep +0 -0
- package/src/data/migrations/001_initial_schema.sql +47 -0
- package/src/data/migrations/migration.runner.ts +125 -0
- package/src/data/models/.gitkeep +0 -0
- package/src/data/models/Token.model.ts +35 -0
- package/src/data/repositories/.gitkeep +0 -0
- package/src/data/repositories/token.repository.ts +160 -0
- package/src/providers/.gitkeep +0 -0
- package/src/providers/azure/.gitkeep +0 -0
- package/src/providers/azure/auth.provider.ts +14 -0
- package/src/providers/azure/azure-errors.ts +189 -0
- package/src/providers/azure/azure-retry.ts +168 -0
- package/src/providers/azure/graph-client.ts +315 -0
- package/src/providers/azure/graph.provider.ts +294 -0
- package/src/providers/azure/queries/app.queries.ts +9 -0
- package/src/providers/azure/queries/policy.queries.ts +9 -0
- package/src/providers/azure/queries/user.queries.ts +10 -0
- package/src/providers/interfaces/.gitkeep +0 -0
- package/src/providers/interfaces/IGraphProvider.ts +117 -0
- package/src/providers/interfaces/ILDAPProvider.ts +142 -0
- package/src/providers/ldap/.gitkeep +0 -0
- package/src/providers/ldap/acl-parser.ts +231 -0
- package/src/providers/ldap/ad-mappers.ts +280 -0
- package/src/providers/ldap/ldap-client.ts +259 -0
- package/src/providers/ldap/ldap-errors.ts +188 -0
- package/src/providers/ldap/ldap-retry.ts +267 -0
- package/src/providers/ldap/ldap-sanitizer.ts +273 -0
- package/src/providers/ldap/ldap.provider.ts +293 -0
- package/src/providers/ldap/queries/computer.queries.ts +9 -0
- package/src/providers/ldap/queries/group.queries.ts +9 -0
- package/src/providers/ldap/queries/user.queries.ts +10 -0
- package/src/providers/smb/smb.provider.ts +653 -0
- package/src/server.ts +60 -0
- package/src/services/.gitkeep +0 -0
- package/src/services/audit/.gitkeep +0 -0
- package/src/services/audit/ad-audit.service.ts +1481 -0
- package/src/services/audit/attack-graph.service.ts +1104 -0
- package/src/services/audit/audit.service.ts +12 -0
- package/src/services/audit/azure-audit.service.ts +286 -0
- package/src/services/audit/detectors/ad/accounts.detector.ts +1232 -0
- package/src/services/audit/detectors/ad/adcs.detector.ts +449 -0
- package/src/services/audit/detectors/ad/advanced.detector.ts +1270 -0
- package/src/services/audit/detectors/ad/attack-paths.detector.ts +600 -0
- package/src/services/audit/detectors/ad/compliance.detector.ts +1421 -0
- package/src/services/audit/detectors/ad/computers.detector.ts +1188 -0
- package/src/services/audit/detectors/ad/gpo.detector.ts +485 -0
- package/src/services/audit/detectors/ad/groups.detector.ts +685 -0
- package/src/services/audit/detectors/ad/index.ts +84 -0
- package/src/services/audit/detectors/ad/kerberos.detector.ts +424 -0
- package/src/services/audit/detectors/ad/monitoring.detector.ts +501 -0
- package/src/services/audit/detectors/ad/network.detector.ts +538 -0
- package/src/services/audit/detectors/ad/password.detector.ts +324 -0
- package/src/services/audit/detectors/ad/permissions.detector.ts +637 -0
- package/src/services/audit/detectors/ad/trusts.detector.ts +315 -0
- package/src/services/audit/detectors/azure/app-security.detector.ts +246 -0
- package/src/services/audit/detectors/azure/conditional-access.detector.ts +186 -0
- package/src/services/audit/detectors/azure/privilege-security.detector.ts +176 -0
- package/src/services/audit/detectors/azure/user-security.detector.ts +280 -0
- package/src/services/audit/detectors/index.ts +18 -0
- package/src/services/audit/response-formatter.ts +604 -0
- package/src/services/audit/scoring.service.ts +234 -0
- package/src/services/auth/.gitkeep +0 -0
- package/src/services/auth/crypto.service.ts +230 -0
- package/src/services/auth/errors.ts +47 -0
- package/src/services/auth/token.service.ts +420 -0
- package/src/services/config/.gitkeep +0 -0
- package/src/services/config/config.service.ts +75 -0
- package/src/services/export/.gitkeep +0 -0
- package/src/services/export/export.service.ts +99 -0
- package/src/services/export/formatters/csv.formatter.ts +124 -0
- package/src/services/export/formatters/json.formatter.ts +160 -0
- package/src/services/jobs/azure-job-runner.ts +312 -0
- package/src/services/jobs/index.ts +9 -0
- package/src/services/jobs/job-runner.ts +1280 -0
- package/src/services/jobs/job-store.ts +384 -0
- package/src/services/jobs/job.types.ts +182 -0
- package/src/types/.gitkeep +0 -0
- package/src/types/ad.types.ts +91 -0
- package/src/types/adcs.types.ts +107 -0
- package/src/types/attack-graph.types.ts +260 -0
- package/src/types/audit.types.ts +42 -0
- package/src/types/azure.types.ts +68 -0
- package/src/types/config.types.ts +79 -0
- package/src/types/error.types.ts +69 -0
- package/src/types/finding.types.ts +284 -0
- package/src/types/gpo.types.ts +72 -0
- package/src/types/smb2.d.ts +73 -0
- package/src/types/token.types.ts +32 -0
- package/src/types/trust.types.ts +140 -0
- package/src/utils/.gitkeep +0 -0
- package/src/utils/entity-converter.ts +453 -0
- package/src/utils/graph.util.ts +609 -0
- package/src/utils/logger.ts +111 -0
- package/src/utils/type-name-normalizer.ts +302 -0
- package/tests/.gitkeep +0 -0
- package/tests/e2e/.gitkeep +0 -0
- package/tests/fixtures/.gitkeep +0 -0
- package/tests/integration/.gitkeep +0 -0
- package/tests/integration/README.md +156 -0
- package/tests/integration/ad-audit.integration.test.ts +216 -0
- package/tests/integration/api/.gitkeep +0 -0
- package/tests/integration/api/endpoints.integration.test.ts +431 -0
- package/tests/integration/auth/jwt-authentication.integration.test.ts +358 -0
- package/tests/integration/providers/.gitkeep +0 -0
- package/tests/integration/providers/azure-basic.integration.test.ts +167 -0
- package/tests/integration/providers/ldap-basic.integration.test.ts +152 -0
- package/tests/integration/providers/ldap-connectivity.test.ts +44 -0
- package/tests/integration/providers/ldap-provider.integration.test.ts +347 -0
- package/tests/mocks/.gitkeep +0 -0
- package/tests/setup.ts +16 -0
- package/tests/unit/.gitkeep +0 -0
- package/tests/unit/api/middlewares/authenticate.test.ts +446 -0
- package/tests/unit/providers/.gitkeep +0 -0
- package/tests/unit/providers/azure/azure-errors.test.ts +193 -0
- package/tests/unit/providers/azure/azure-retry.test.ts +254 -0
- package/tests/unit/providers/azure/graph-provider.test.ts +313 -0
- package/tests/unit/providers/ldap/ad-mappers.test.ts +392 -0
- package/tests/unit/providers/ldap/ldap-provider.test.ts +376 -0
- package/tests/unit/providers/ldap/ldap-retry.test.ts +377 -0
- package/tests/unit/providers/ldap/ldap-sanitizer.test.ts +301 -0
- package/tests/unit/sample.test.ts +19 -0
- package/tests/unit/services/.gitkeep +0 -0
- package/tests/unit/services/audit/detectors/ad/accounts.detector.test.ts +393 -0
- package/tests/unit/services/audit/detectors/ad/advanced.detector.test.ts +380 -0
- package/tests/unit/services/audit/detectors/ad/computers.detector.test.ts +440 -0
- package/tests/unit/services/audit/detectors/ad/groups.detector.test.ts +276 -0
- package/tests/unit/services/audit/detectors/ad/kerberos.detector.test.ts +215 -0
- package/tests/unit/services/audit/detectors/ad/password.detector.test.ts +226 -0
- package/tests/unit/services/audit/detectors/ad/permissions.detector.test.ts +244 -0
- package/tests/unit/services/audit/detectors/azure/app-security.detector.test.ts +349 -0
- package/tests/unit/services/audit/detectors/azure/conditional-access.detector.test.ts +374 -0
- package/tests/unit/services/audit/detectors/azure/privilege-security.detector.test.ts +374 -0
- package/tests/unit/services/audit/detectors/azure/user-security.detector.test.ts +297 -0
- package/tests/unit/services/auth/crypto.service.test.ts +296 -0
- package/tests/unit/services/auth/token.service.test.ts +579 -0
- package/tests/unit/services/export/export.service.test.ts +241 -0
- package/tests/unit/services/export/formatters/csv.formatter.test.ts +270 -0
- package/tests/unit/services/export/formatters/json.formatter.test.ts +258 -0
- package/tests/unit/utils/.gitkeep +0 -0
- package/tsconfig.json +50 -0
|
@@ -0,0 +1,1270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Security Vulnerability Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects advanced AD vulnerabilities including ADCS, LAPS, Shadow Credentials, RBCD, DCSync, etc.
|
|
5
|
+
* Story 1.7: AD Vulnerability Detection Engine
|
|
6
|
+
* Story 1.1: SMB & LDAP Signing Detection
|
|
7
|
+
*
|
|
8
|
+
* Vulnerabilities detected (36):
|
|
9
|
+
* CRITICAL (5):
|
|
10
|
+
* - SHADOW_CREDENTIALS
|
|
11
|
+
* - RBCD_ABUSE
|
|
12
|
+
* - EXCHANGE_PRIV_ESC_PATH - Phase 4
|
|
13
|
+
* - LDAP_SIGNING_DISABLED - LDAP signing not required (Story 1.1)
|
|
14
|
+
* - SMB_SIGNING_DISABLED - SMB signing not required (Story 1.1)
|
|
15
|
+
*
|
|
16
|
+
* HIGH (11):
|
|
17
|
+
* - ESC1_VULNERABLE_TEMPLATE
|
|
18
|
+
* - ESC2_ANY_PURPOSE
|
|
19
|
+
* - ESC3_ENROLLMENT_AGENT
|
|
20
|
+
* - ESC4_VULNERABLE_TEMPLATE_ACL
|
|
21
|
+
* - ESC6_EDITF_ATTRIBUTESUBJECTALTNAME2
|
|
22
|
+
* - LAPS_PASSWORD_READABLE
|
|
23
|
+
* - REPLICATION_RIGHTS
|
|
24
|
+
* - DCSYNC_CAPABLE
|
|
25
|
+
* - MACHINE_ACCOUNT_QUOTA_HIGH - Quota > 10 (intentionally increased)
|
|
26
|
+
* - LDAP_CHANNEL_BINDING_DISABLED - LDAP channel binding not required
|
|
27
|
+
* - SMB_V1_ENABLED - SMBv1 protocol enabled
|
|
28
|
+
* - ADMIN_SD_HOLDER_MODIFIED - Phase 4
|
|
29
|
+
*
|
|
30
|
+
* MEDIUM (17):
|
|
31
|
+
* - ESC8_HTTP_ENROLLMENT
|
|
32
|
+
* - LAPS_NOT_DEPLOYED
|
|
33
|
+
* - LAPS_LEGACY_ATTRIBUTE
|
|
34
|
+
* - DUPLICATE_SPN
|
|
35
|
+
* - WEAK_PASSWORD_POLICY
|
|
36
|
+
* - WEAK_KERBEROS_POLICY
|
|
37
|
+
* - MACHINE_ACCOUNT_QUOTA_ABUSE
|
|
38
|
+
* - DELEGATION_PRIVILEGE
|
|
39
|
+
* - ADCS_WEAK_PERMISSIONS
|
|
40
|
+
* - DANGEROUS_LOGON_SCRIPTS
|
|
41
|
+
* - FOREIGN_SECURITY_PRINCIPALS
|
|
42
|
+
* - NTLM_RELAY_OPPORTUNITY
|
|
43
|
+
* - RECYCLE_BIN_DISABLED - AD Recycle Bin not enabled
|
|
44
|
+
* - ANONYMOUS_LDAP_ACCESS - Anonymous LDAP bind allowed
|
|
45
|
+
* - AUDIT_POLICY_WEAK - Audit policy incomplete
|
|
46
|
+
* - POWERSHELL_LOGGING_DISABLED - PowerShell logging not configured
|
|
47
|
+
* - DS_HEURISTICS_MODIFIED - Phase 4
|
|
48
|
+
*
|
|
49
|
+
* LOW (2):
|
|
50
|
+
* - LAPS_PASSWORD_SET
|
|
51
|
+
* - LAPS_PASSWORD_LEAKED
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
import { ADUser, ADComputer, ADDomain } from '../../../../types/ad.types';
|
|
55
|
+
import { Finding } from '../../../../types/finding.types';
|
|
56
|
+
import { toAffectedUserEntities } from '../../../../utils/entity-converter';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check for Shadow Credentials attack (msDS-KeyCredentialLink)
|
|
60
|
+
*/
|
|
61
|
+
export function detectShadowCredentials(users: ADUser[], includeDetails: boolean): Finding {
|
|
62
|
+
const affected = users.filter((u) => {
|
|
63
|
+
const keyLink = (u as any)['msDS-KeyCredentialLink'];
|
|
64
|
+
// Check that attribute exists, is not null, not empty string, and not empty array
|
|
65
|
+
return keyLink && (Array.isArray(keyLink) ? keyLink.length > 0 : keyLink !== '');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
type: 'SHADOW_CREDENTIALS',
|
|
70
|
+
severity: 'critical',
|
|
71
|
+
category: 'advanced',
|
|
72
|
+
title: 'Shadow Credentials',
|
|
73
|
+
description: 'msDS-KeyCredentialLink attribute configured. Allows Kerberos authentication bypass by adding arbitrary public keys.',
|
|
74
|
+
count: affected.length,
|
|
75
|
+
affectedEntities: includeDetails ? toAffectedUserEntities(affected) : undefined,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check for RBCD abuse (Resource-Based Constrained Delegation)
|
|
81
|
+
*/
|
|
82
|
+
export function detectRbcdAbuse(users: ADUser[], includeDetails: boolean): Finding {
|
|
83
|
+
const affected = users.filter((u) => {
|
|
84
|
+
const rbcdAttr = (u as any)['msDS-AllowedToActOnBehalfOfOtherIdentity'];
|
|
85
|
+
// Check that attribute exists, is not null, not empty string, and not empty array
|
|
86
|
+
return rbcdAttr && (Array.isArray(rbcdAttr) ? rbcdAttr.length > 0 : rbcdAttr !== '');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
type: 'RBCD_ABUSE',
|
|
91
|
+
severity: 'critical',
|
|
92
|
+
category: 'advanced',
|
|
93
|
+
title: 'RBCD Abuse',
|
|
94
|
+
description: 'msDS-AllowedToActOnBehalfOfOtherIdentity configured. Enables privilege escalation via resource-based delegation.',
|
|
95
|
+
count: affected.length,
|
|
96
|
+
affectedEntities: includeDetails ? toAffectedUserEntities(affected) : undefined,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check for ESC1 vulnerable certificate template
|
|
102
|
+
*/
|
|
103
|
+
export function detectEsc1VulnerableTemplate(templates: any[], includeDetails: boolean): Finding {
|
|
104
|
+
const affected = templates.filter((t) => {
|
|
105
|
+
const hasClientAuth = t.pKIExtendedKeyUsage?.includes('1.3.6.1.5.5.7.3.2');
|
|
106
|
+
const enrolleeSuppliesSubject = t['msPKI-Certificate-Name-Flag'] && (t['msPKI-Certificate-Name-Flag'] & 0x1) !== 0;
|
|
107
|
+
return hasClientAuth && enrolleeSuppliesSubject;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
type: 'ESC1_VULNERABLE_TEMPLATE',
|
|
112
|
+
severity: 'high',
|
|
113
|
+
category: 'advanced',
|
|
114
|
+
title: 'ESC1 Vulnerable Template',
|
|
115
|
+
description: 'ADCS template with client auth + enrollee supplies subject. Enables domain compromise by obtaining cert for any user.',
|
|
116
|
+
count: affected.length,
|
|
117
|
+
affectedEntities: includeDetails ? affected.map((t) => t.dn) : undefined,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check for ESC2 Any Purpose EKU
|
|
123
|
+
*/
|
|
124
|
+
export function detectEsc2AnyPurpose(templates: any[], includeDetails: boolean): Finding {
|
|
125
|
+
const affected = templates.filter((t) => {
|
|
126
|
+
const hasAnyPurpose = t.pKIExtendedKeyUsage?.includes('2.5.29.37.0');
|
|
127
|
+
const isEmpty = !t.pKIExtendedKeyUsage || t.pKIExtendedKeyUsage.length === 0;
|
|
128
|
+
return hasAnyPurpose || isEmpty;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
type: 'ESC2_ANY_PURPOSE',
|
|
133
|
+
severity: 'high',
|
|
134
|
+
category: 'advanced',
|
|
135
|
+
title: 'ESC2 Any Purpose',
|
|
136
|
+
description: 'ADCS template with Any Purpose EKU or no usage restriction. Certificate can be used for domain authentication.',
|
|
137
|
+
count: affected.length,
|
|
138
|
+
affectedEntities: includeDetails ? affected.map((t) => t.dn) : undefined,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Check for ESC3 enrollment agent
|
|
144
|
+
*/
|
|
145
|
+
export function detectEsc3EnrollmentAgent(templates: any[], includeDetails: boolean): Finding {
|
|
146
|
+
const affected = templates.filter((t) => {
|
|
147
|
+
return t.pKIExtendedKeyUsage?.includes('1.3.6.1.4.1.311.20.2.1');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
type: 'ESC3_ENROLLMENT_AGENT',
|
|
152
|
+
severity: 'high',
|
|
153
|
+
category: 'advanced',
|
|
154
|
+
title: 'ESC3 Enrollment Agent',
|
|
155
|
+
description: 'ADCS template with enrollment agent EKU. Can request certificates on behalf of other users.',
|
|
156
|
+
count: affected.length,
|
|
157
|
+
affectedEntities: includeDetails ? affected.map((t) => t.dn) : undefined,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Check for ESC4 vulnerable template ACL
|
|
163
|
+
*/
|
|
164
|
+
export function detectEsc4VulnerableTemplateAcl(templates: any[], includeDetails: boolean): Finding {
|
|
165
|
+
const affected = templates.filter((t) => {
|
|
166
|
+
return t.hasWeakAcl; // This would be populated by ACL analysis
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
type: 'ESC4_VULNERABLE_TEMPLATE_ACL',
|
|
171
|
+
severity: 'high',
|
|
172
|
+
category: 'advanced',
|
|
173
|
+
title: 'ESC4 Vulnerable Template ACL',
|
|
174
|
+
description: 'Certificate template with weak ACLs. Can modify template to make it vulnerable to ESC1/ESC2.',
|
|
175
|
+
count: affected.length,
|
|
176
|
+
affectedEntities: includeDetails ? affected.map((t) => t.dn) : undefined,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Check for ESC6 EDITF_ATTRIBUTESUBJECTALTNAME2
|
|
182
|
+
*/
|
|
183
|
+
export function detectEsc6EditfAttributeSubjectAltName2(cas: any[], includeDetails: boolean): Finding {
|
|
184
|
+
const affected = cas.filter((ca) => {
|
|
185
|
+
return ca.flags && (ca.flags & 0x40000) !== 0; // EDITF_ATTRIBUTESUBJECTALTNAME2
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
type: 'ESC6_EDITF_ATTRIBUTESUBJECTALTNAME2',
|
|
190
|
+
severity: 'high',
|
|
191
|
+
category: 'advanced',
|
|
192
|
+
title: 'ESC6 EDITF Flag',
|
|
193
|
+
description: 'ADCS CA with EDITF_ATTRIBUTESUBJECTALTNAME2 flag. Allows specifying arbitrary SAN in certificate requests.',
|
|
194
|
+
count: affected.length,
|
|
195
|
+
affectedEntities: includeDetails ? affected.map((ca) => ca.dn) : undefined,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Check for LAPS password readable by non-admins
|
|
201
|
+
*/
|
|
202
|
+
export function detectLapsPasswordReadable(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
203
|
+
const affected = computers.filter((c) => {
|
|
204
|
+
return (c as any).lapsPasswordReadableByNonAdmins; // This would be populated by ACL analysis
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
type: 'LAPS_PASSWORD_READABLE',
|
|
209
|
+
severity: 'high',
|
|
210
|
+
category: 'advanced',
|
|
211
|
+
title: 'LAPS Password Readable',
|
|
212
|
+
description: 'Non-admin users can read LAPS password attributes. Exposure of local admin passwords.',
|
|
213
|
+
count: affected.length,
|
|
214
|
+
affectedEntities: includeDetails ? affected.map((c) => c.dn) : undefined,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Check for accounts with replication rights (potential DCSync)
|
|
220
|
+
*/
|
|
221
|
+
export function detectReplicationRights(users: ADUser[], includeDetails: boolean): Finding {
|
|
222
|
+
const affected = users.filter((u) => {
|
|
223
|
+
// adminCount=1 but not in standard admin groups
|
|
224
|
+
if (u.adminCount !== 1) return false;
|
|
225
|
+
if (!u.memberOf) return true; // Has adminCount but no groups
|
|
226
|
+
|
|
227
|
+
const isInStandardAdminGroups = u.memberOf.some((dn) => {
|
|
228
|
+
return (
|
|
229
|
+
dn.includes('CN=Domain Admins') ||
|
|
230
|
+
dn.includes('CN=Enterprise Admins') ||
|
|
231
|
+
dn.includes('CN=Administrators')
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
return !isInStandardAdminGroups;
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
type: 'REPLICATION_RIGHTS',
|
|
240
|
+
severity: 'high',
|
|
241
|
+
category: 'advanced',
|
|
242
|
+
title: 'Replication Rights',
|
|
243
|
+
description: 'Account with adminCount=1 outside standard admin groups. May have replication rights (DCSync).',
|
|
244
|
+
count: affected.length,
|
|
245
|
+
affectedEntities: includeDetails ? toAffectedUserEntities(affected) : undefined,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check for DCSync capable accounts
|
|
251
|
+
*/
|
|
252
|
+
export function detectDcsyncCapable(users: ADUser[], includeDetails: boolean): Finding {
|
|
253
|
+
const affected = users.filter((u) => {
|
|
254
|
+
return (u as any).hasDcsyncRights; // This would be populated by ACL analysis
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
type: 'DCSYNC_CAPABLE',
|
|
259
|
+
severity: 'high',
|
|
260
|
+
category: 'advanced',
|
|
261
|
+
title: 'DCSync Capable',
|
|
262
|
+
description: 'Account with DS-Replication-Get-Changes and DS-Replication-Get-Changes-All rights. Can extract all password hashes.',
|
|
263
|
+
count: affected.length,
|
|
264
|
+
affectedEntities: includeDetails ? toAffectedUserEntities(affected) : undefined,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Check for ESC8 HTTP enrollment
|
|
270
|
+
*/
|
|
271
|
+
export function detectEsc8HttpEnrollment(cas: any[], includeDetails: boolean): Finding {
|
|
272
|
+
const affected = cas.filter((ca) => {
|
|
273
|
+
return ca.webEnrollment && ca.webEnrollment.protocol === 'http';
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
type: 'ESC8_HTTP_ENROLLMENT',
|
|
278
|
+
severity: 'medium',
|
|
279
|
+
category: 'advanced',
|
|
280
|
+
title: 'ESC8 HTTP Enrollment',
|
|
281
|
+
description: 'ADCS web enrollment via HTTP. Enables NTLM relay attacks against certificate enrollment.',
|
|
282
|
+
count: affected.length,
|
|
283
|
+
affectedEntities: includeDetails ? affected.map((ca) => ca.dn) : undefined,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Check for LAPS not deployed
|
|
289
|
+
*/
|
|
290
|
+
export function detectLapsNotDeployed(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
291
|
+
const affected = computers.filter((c) => {
|
|
292
|
+
return !('ms-Mcs-AdmPwd' in c) && !('msLAPS-Password' in c);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
type: 'LAPS_NOT_DEPLOYED',
|
|
297
|
+
severity: 'medium',
|
|
298
|
+
category: 'advanced',
|
|
299
|
+
title: 'LAPS Not Deployed',
|
|
300
|
+
description: 'LAPS not deployed on domain computers. Shared/static local admin passwords across workstations.',
|
|
301
|
+
count: affected.length,
|
|
302
|
+
affectedEntities: includeDetails ? affected.map((c) => c.dn) : undefined,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Check for legacy LAPS attribute
|
|
308
|
+
*/
|
|
309
|
+
export function detectLapsLegacyAttribute(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
310
|
+
const affected = computers.filter((c) => {
|
|
311
|
+
return 'ms-Mcs-AdmPwd' in c && !('msLAPS-Password' in c);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
type: 'LAPS_LEGACY_ATTRIBUTE',
|
|
316
|
+
severity: 'medium',
|
|
317
|
+
category: 'advanced',
|
|
318
|
+
title: 'LAPS Legacy Attribute',
|
|
319
|
+
description: 'Legacy LAPS attribute used instead of Windows LAPS. Less secure implementation.',
|
|
320
|
+
count: affected.length,
|
|
321
|
+
affectedEntities: includeDetails ? affected.map((c) => c.dn) : undefined,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Check for duplicate SPNs
|
|
327
|
+
*/
|
|
328
|
+
export function detectDuplicateSpn(users: ADUser[], includeDetails: boolean): Finding {
|
|
329
|
+
const spnMap = new Map<string, string[]>();
|
|
330
|
+
|
|
331
|
+
// Build SPN to user DN mapping
|
|
332
|
+
users.forEach((u) => {
|
|
333
|
+
const spns = (u as any).servicePrincipalName;
|
|
334
|
+
if (spns && Array.isArray(spns)) {
|
|
335
|
+
spns.forEach((spn: string) => {
|
|
336
|
+
if (!spnMap.has(spn)) {
|
|
337
|
+
spnMap.set(spn, []);
|
|
338
|
+
}
|
|
339
|
+
spnMap.get(spn)!.push(u.dn);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Find duplicate SPNs
|
|
345
|
+
const affected: string[] = [];
|
|
346
|
+
spnMap.forEach((dns, _spn) => {
|
|
347
|
+
if (dns.length > 1) {
|
|
348
|
+
affected.push(...dns);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
type: 'DUPLICATE_SPN',
|
|
354
|
+
severity: 'medium',
|
|
355
|
+
category: 'advanced',
|
|
356
|
+
title: 'Duplicate SPN',
|
|
357
|
+
description: 'Service Principal Name registered multiple times. Can cause Kerberos authentication failures.',
|
|
358
|
+
count: affected.length,
|
|
359
|
+
affectedEntities: includeDetails ? affected : undefined,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Check for weak password policy
|
|
365
|
+
*/
|
|
366
|
+
export function detectWeakPasswordPolicy(domain: ADDomain | null, includeDetails: boolean): Finding {
|
|
367
|
+
if (!domain) {
|
|
368
|
+
return {
|
|
369
|
+
type: 'WEAK_PASSWORD_POLICY',
|
|
370
|
+
severity: 'medium',
|
|
371
|
+
category: 'advanced',
|
|
372
|
+
title: 'Weak Password Policy',
|
|
373
|
+
description: 'Unable to check domain password policy.',
|
|
374
|
+
count: 0,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const minPwdLength = (domain as any).minPwdLength || 0;
|
|
379
|
+
const maxPwdAge = (domain as any).maxPwdAge || 0;
|
|
380
|
+
const pwdHistoryLength = (domain as any).pwdHistoryLength || 0;
|
|
381
|
+
|
|
382
|
+
const isWeak = minPwdLength < 14 || maxPwdAge > 90 || pwdHistoryLength < 24;
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
type: 'WEAK_PASSWORD_POLICY',
|
|
386
|
+
severity: 'medium',
|
|
387
|
+
category: 'advanced',
|
|
388
|
+
title: 'Weak Password Policy',
|
|
389
|
+
description: 'Domain password policy below minimum standards. Enables easier password cracking.',
|
|
390
|
+
count: isWeak ? 1 : 0,
|
|
391
|
+
affectedEntities: includeDetails && isWeak ? [domain.dn] : undefined,
|
|
392
|
+
details: isWeak
|
|
393
|
+
? {
|
|
394
|
+
minPwdLength,
|
|
395
|
+
maxPwdAge,
|
|
396
|
+
pwdHistoryLength,
|
|
397
|
+
}
|
|
398
|
+
: undefined,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Check for weak Kerberos policy
|
|
404
|
+
*/
|
|
405
|
+
export function detectWeakKerberosPolicy(domain: ADDomain | null, includeDetails: boolean): Finding {
|
|
406
|
+
if (!domain) {
|
|
407
|
+
return {
|
|
408
|
+
type: 'WEAK_KERBEROS_POLICY',
|
|
409
|
+
severity: 'medium',
|
|
410
|
+
category: 'advanced',
|
|
411
|
+
title: 'Weak Kerberos Policy',
|
|
412
|
+
description: 'Unable to check Kerberos policy.',
|
|
413
|
+
count: 0,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const maxTicketAge = (domain as any).maxTicketAge || 0;
|
|
418
|
+
const maxRenewAge = (domain as any).maxRenewAge || 0;
|
|
419
|
+
|
|
420
|
+
const isWeak = maxTicketAge > 10 || maxRenewAge > 7;
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
type: 'WEAK_KERBEROS_POLICY',
|
|
424
|
+
severity: 'medium',
|
|
425
|
+
category: 'advanced',
|
|
426
|
+
title: 'Weak Kerberos Policy',
|
|
427
|
+
description: 'Kerberos ticket lifetimes exceed recommended values. Longer window for ticket-based attacks.',
|
|
428
|
+
count: isWeak ? 1 : 0,
|
|
429
|
+
affectedEntities: includeDetails && isWeak ? [domain.dn] : undefined,
|
|
430
|
+
details: isWeak
|
|
431
|
+
? {
|
|
432
|
+
maxTicketAge,
|
|
433
|
+
maxRenewAge,
|
|
434
|
+
}
|
|
435
|
+
: undefined,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Check for machine account quota abuse
|
|
441
|
+
*/
|
|
442
|
+
export function detectMachineAccountQuotaAbuse(domain: ADDomain | null, includeDetails: boolean): Finding {
|
|
443
|
+
if (!domain) {
|
|
444
|
+
return {
|
|
445
|
+
type: 'MACHINE_ACCOUNT_QUOTA_ABUSE',
|
|
446
|
+
severity: 'medium',
|
|
447
|
+
category: 'advanced',
|
|
448
|
+
title: 'Machine Account Quota Abuse',
|
|
449
|
+
description: 'Unable to check machine account quota.',
|
|
450
|
+
count: 0,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const quota = (domain as any)['ms-DS-MachineAccountQuota'];
|
|
455
|
+
const isVulnerable = typeof quota === 'number' && quota > 0;
|
|
456
|
+
|
|
457
|
+
return {
|
|
458
|
+
type: 'MACHINE_ACCOUNT_QUOTA_ABUSE',
|
|
459
|
+
severity: 'medium',
|
|
460
|
+
category: 'advanced',
|
|
461
|
+
title: 'Machine Account Quota Abuse',
|
|
462
|
+
description: 'ms-DS-MachineAccountQuota > 0. Non-admin users can join computers to domain (potential Kerberos attacks).',
|
|
463
|
+
count: isVulnerable ? 1 : 0,
|
|
464
|
+
affectedEntities: includeDetails && isVulnerable ? [domain.dn] : undefined,
|
|
465
|
+
details: isVulnerable
|
|
466
|
+
? {
|
|
467
|
+
quota,
|
|
468
|
+
}
|
|
469
|
+
: undefined,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Check for machine account quota set higher than default
|
|
475
|
+
* Default is 10 - values > 10 indicate intentional increase
|
|
476
|
+
*/
|
|
477
|
+
export function detectMachineAccountQuotaHigh(domain: ADDomain | null, includeDetails: boolean): Finding {
|
|
478
|
+
if (!domain) {
|
|
479
|
+
return {
|
|
480
|
+
type: 'MACHINE_ACCOUNT_QUOTA_HIGH',
|
|
481
|
+
severity: 'high',
|
|
482
|
+
category: 'advanced',
|
|
483
|
+
title: 'Machine Account Quota Elevated',
|
|
484
|
+
description: 'Unable to check machine account quota.',
|
|
485
|
+
count: 0,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const quota = (domain as any)['ms-DS-MachineAccountQuota'];
|
|
490
|
+
const DEFAULT_QUOTA = 10;
|
|
491
|
+
const isElevated = typeof quota === 'number' && quota > DEFAULT_QUOTA;
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
type: 'MACHINE_ACCOUNT_QUOTA_HIGH',
|
|
495
|
+
severity: 'high',
|
|
496
|
+
category: 'advanced',
|
|
497
|
+
title: 'Machine Account Quota Elevated Above Default',
|
|
498
|
+
description:
|
|
499
|
+
'ms-DS-MachineAccountQuota is higher than the default (10). Someone intentionally increased this value, allowing non-admin users to join more computers to the domain.',
|
|
500
|
+
count: isElevated ? 1 : 0,
|
|
501
|
+
affectedEntities: includeDetails && isElevated ? [domain.dn] : undefined,
|
|
502
|
+
details: isElevated
|
|
503
|
+
? {
|
|
504
|
+
currentQuota: quota,
|
|
505
|
+
defaultQuota: DEFAULT_QUOTA,
|
|
506
|
+
recommendation: 'Set ms-DS-MachineAccountQuota to 0 to prevent non-admin domain joins.',
|
|
507
|
+
}
|
|
508
|
+
: undefined,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Check for delegation privilege
|
|
514
|
+
*/
|
|
515
|
+
export function detectDelegationPrivilege(users: ADUser[], includeDetails: boolean): Finding {
|
|
516
|
+
const affected = users.filter((u) => {
|
|
517
|
+
return (u as any).hasSeEnableDelegationPrivilege;
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
return {
|
|
521
|
+
type: 'DELEGATION_PRIVILEGE',
|
|
522
|
+
severity: 'medium',
|
|
523
|
+
category: 'advanced',
|
|
524
|
+
title: 'Delegation Privilege',
|
|
525
|
+
description: 'Account has SeEnableDelegationPrivilege. Can enable delegation on user/computer accounts.',
|
|
526
|
+
count: affected.length,
|
|
527
|
+
affectedEntities: includeDetails ? toAffectedUserEntities(affected) : undefined,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Check for foreign security principals
|
|
533
|
+
*/
|
|
534
|
+
export function detectForeignSecurityPrincipals(fsps: any[], includeDetails: boolean): Finding {
|
|
535
|
+
return {
|
|
536
|
+
type: 'FOREIGN_SECURITY_PRINCIPALS',
|
|
537
|
+
severity: 'medium',
|
|
538
|
+
category: 'advanced',
|
|
539
|
+
title: 'Foreign Security Principals',
|
|
540
|
+
description: 'Foreign security principals from external forests. Potential for cross-forest privilege escalation.',
|
|
541
|
+
count: fsps.length,
|
|
542
|
+
affectedEntities: includeDetails ? fsps.map((fsp) => fsp.dn) : undefined,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Check for NTLM relay opportunity
|
|
548
|
+
*/
|
|
549
|
+
export function detectNtlmRelayOpportunity(domain: ADDomain | null, includeDetails: boolean): Finding {
|
|
550
|
+
if (!domain) {
|
|
551
|
+
return {
|
|
552
|
+
type: 'NTLM_RELAY_OPPORTUNITY',
|
|
553
|
+
severity: 'medium',
|
|
554
|
+
category: 'advanced',
|
|
555
|
+
title: 'NTLM Relay Opportunity',
|
|
556
|
+
description: 'Unable to check LDAP signing configuration.',
|
|
557
|
+
count: 0,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const ldapSigningRequired = (domain as any).ldapSigningRequired;
|
|
562
|
+
const channelBindingRequired = (domain as any).channelBindingRequired;
|
|
563
|
+
|
|
564
|
+
const isVulnerable = !ldapSigningRequired || !channelBindingRequired;
|
|
565
|
+
|
|
566
|
+
return {
|
|
567
|
+
type: 'NTLM_RELAY_OPPORTUNITY',
|
|
568
|
+
severity: 'medium',
|
|
569
|
+
category: 'advanced',
|
|
570
|
+
title: 'NTLM Relay Opportunity',
|
|
571
|
+
description: 'LDAP signing or channel binding not enforced. Enables NTLM relay attacks.',
|
|
572
|
+
count: isVulnerable ? 1 : 0,
|
|
573
|
+
affectedEntities: includeDetails && isVulnerable ? [domain.dn] : undefined,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Check for LAPS password set (informational)
|
|
579
|
+
*/
|
|
580
|
+
export function detectLapsPasswordSet(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
581
|
+
const affected = computers.filter((c) => {
|
|
582
|
+
return 'ms-Mcs-AdmPwd' in c || 'msLAPS-Password' in c;
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
return {
|
|
586
|
+
type: 'LAPS_PASSWORD_SET',
|
|
587
|
+
severity: 'low',
|
|
588
|
+
category: 'advanced',
|
|
589
|
+
title: 'LAPS Password Set',
|
|
590
|
+
description: 'LAPS password successfully managed. Informational - indicates proper LAPS deployment.',
|
|
591
|
+
count: affected.length,
|
|
592
|
+
affectedEntities: includeDetails ? affected.map((c) => c.dn) : undefined,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Check for LAPS password leaked
|
|
598
|
+
*/
|
|
599
|
+
export function detectLapsPasswordLeaked(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
600
|
+
const affected = computers.filter((c) => {
|
|
601
|
+
return (c as any).lapsPasswordExcessiveReaders; // This would be populated by ACL analysis
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
return {
|
|
605
|
+
type: 'LAPS_PASSWORD_LEAKED',
|
|
606
|
+
severity: 'low',
|
|
607
|
+
category: 'advanced',
|
|
608
|
+
title: 'LAPS Password Leaked',
|
|
609
|
+
description: 'LAPS password visible to too many users. Reduces effectiveness of LAPS.',
|
|
610
|
+
count: affected.length,
|
|
611
|
+
affectedEntities: includeDetails ? affected.map((c) => c.dn) : undefined,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Check for ADCS weak permissions
|
|
617
|
+
*/
|
|
618
|
+
export function detectAdcsWeakPermissions(templates: any[], includeDetails: boolean): Finding {
|
|
619
|
+
const affected = templates.filter((t) => {
|
|
620
|
+
// Check if template has weak ACLs allowing enrollment by non-admins
|
|
621
|
+
return t.hasWeakEnrollmentAcl || t.hasGenericAllPermission;
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
return {
|
|
625
|
+
type: 'ADCS_WEAK_PERMISSIONS',
|
|
626
|
+
severity: 'medium',
|
|
627
|
+
category: 'advanced',
|
|
628
|
+
title: 'ADCS Weak Permissions',
|
|
629
|
+
description: 'Weak permissions on ADCS objects or certificate templates allow unauthorized enrollment.',
|
|
630
|
+
count: affected.length,
|
|
631
|
+
affectedEntities: includeDetails ? affected.map((t) => t.dn || t.name) : undefined,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Check for dangerous logon scripts with weak ACLs
|
|
637
|
+
*/
|
|
638
|
+
export function detectDangerousLogonScripts(users: ADUser[], includeDetails: boolean): Finding {
|
|
639
|
+
const affected = users.filter((u) => {
|
|
640
|
+
const scriptPath = (u as any).scriptPath;
|
|
641
|
+
// Check if user has logon script configured
|
|
642
|
+
// In real implementation, would check ACLs on script file/share
|
|
643
|
+
return scriptPath && (scriptPath.includes('\\\\') || scriptPath.startsWith('//'));
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
return {
|
|
647
|
+
type: 'DANGEROUS_LOGON_SCRIPTS',
|
|
648
|
+
severity: 'medium',
|
|
649
|
+
category: 'advanced',
|
|
650
|
+
title: 'Dangerous Logon Scripts',
|
|
651
|
+
description: 'Logon scripts with weak ACLs can be modified by attackers for code execution on user login.',
|
|
652
|
+
count: affected.length,
|
|
653
|
+
affectedEntities: includeDetails ? toAffectedUserEntities(affected) : undefined,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// ==================== PHASE 1B DETECTORS ====================
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Check if anonymous LDAP access is allowed
|
|
661
|
+
* This is tested during the audit via separate anonymous bind attempt
|
|
662
|
+
*/
|
|
663
|
+
export function detectAnonymousLdapAccess(
|
|
664
|
+
anonymousAccessAllowed: boolean,
|
|
665
|
+
domain: ADDomain | null,
|
|
666
|
+
includeDetails: boolean
|
|
667
|
+
): Finding {
|
|
668
|
+
return {
|
|
669
|
+
type: 'ANONYMOUS_LDAP_ACCESS',
|
|
670
|
+
severity: 'medium',
|
|
671
|
+
category: 'advanced',
|
|
672
|
+
title: 'Anonymous LDAP Access Allowed',
|
|
673
|
+
description:
|
|
674
|
+
'LDAP server accepts anonymous binds. Attackers can enumerate AD objects (users, groups, computers) without valid credentials.',
|
|
675
|
+
count: anonymousAccessAllowed ? 1 : 0,
|
|
676
|
+
affectedEntities: includeDetails && anonymousAccessAllowed && domain ? [domain.dn] : undefined,
|
|
677
|
+
details: anonymousAccessAllowed
|
|
678
|
+
? {
|
|
679
|
+
recommendation:
|
|
680
|
+
'Configure "Network security: LDAP client signing requirements" and restrict anonymous access via dsHeuristics.',
|
|
681
|
+
currentStatus: 'Anonymous bind allowed',
|
|
682
|
+
}
|
|
683
|
+
: undefined,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Check if AD Recycle Bin is disabled
|
|
689
|
+
* Recycle Bin allows recovery of deleted objects
|
|
690
|
+
*/
|
|
691
|
+
export function detectRecycleBinDisabled(domain: ADDomain | null, includeDetails: boolean): Finding {
|
|
692
|
+
if (!domain) {
|
|
693
|
+
return {
|
|
694
|
+
type: 'RECYCLE_BIN_DISABLED',
|
|
695
|
+
severity: 'medium',
|
|
696
|
+
category: 'advanced',
|
|
697
|
+
title: 'AD Recycle Bin Status Unknown',
|
|
698
|
+
description: 'Unable to determine AD Recycle Bin status.',
|
|
699
|
+
count: 0,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const recycleBinEnabled = (domain as any).recycleBinEnabled === true;
|
|
704
|
+
|
|
705
|
+
return {
|
|
706
|
+
type: 'RECYCLE_BIN_DISABLED',
|
|
707
|
+
severity: 'medium',
|
|
708
|
+
category: 'advanced',
|
|
709
|
+
title: 'AD Recycle Bin Not Enabled',
|
|
710
|
+
description:
|
|
711
|
+
'Active Directory Recycle Bin is not enabled. Deleted objects cannot be easily recovered, which complicates incident response and may lead to permanent data loss.',
|
|
712
|
+
count: recycleBinEnabled ? 0 : 1,
|
|
713
|
+
affectedEntities: includeDetails && !recycleBinEnabled ? [domain.dn] : undefined,
|
|
714
|
+
details: !recycleBinEnabled
|
|
715
|
+
? {
|
|
716
|
+
recommendation:
|
|
717
|
+
'Enable AD Recycle Bin feature. Note: This requires forest functional level 2008 R2 or higher and is irreversible.',
|
|
718
|
+
currentStatus: 'Disabled',
|
|
719
|
+
}
|
|
720
|
+
: undefined,
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* GPO Security Settings from SYSVOL
|
|
726
|
+
*/
|
|
727
|
+
export interface GpoSecuritySettings {
|
|
728
|
+
/** LDAP server signing requirement: 0=none, 1=negotiate, 2=require */
|
|
729
|
+
ldapServerIntegrity?: number;
|
|
730
|
+
/** LDAP channel binding: 0=never, 1=when supported, 2=always */
|
|
731
|
+
ldapChannelBinding?: number;
|
|
732
|
+
/** SMBv1 server enabled */
|
|
733
|
+
smbv1ServerEnabled?: boolean;
|
|
734
|
+
/** SMBv1 client enabled */
|
|
735
|
+
smbv1ClientEnabled?: boolean;
|
|
736
|
+
/** SMB Server signing required (RequireSecuritySignature) */
|
|
737
|
+
smbSigningRequired?: boolean;
|
|
738
|
+
/** SMB Client signing required */
|
|
739
|
+
smbClientSigningRequired?: boolean;
|
|
740
|
+
/** Audit policies configured */
|
|
741
|
+
auditPolicies?: {
|
|
742
|
+
category: string;
|
|
743
|
+
subcategory?: string;
|
|
744
|
+
success: boolean;
|
|
745
|
+
failure: boolean;
|
|
746
|
+
}[];
|
|
747
|
+
/** PowerShell logging settings */
|
|
748
|
+
powershellLogging?: {
|
|
749
|
+
moduleLogging: boolean;
|
|
750
|
+
scriptBlockLogging: boolean;
|
|
751
|
+
transcription: boolean;
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Check if LDAP signing is disabled
|
|
757
|
+
* Requires GPO settings from SYSVOL
|
|
758
|
+
*/
|
|
759
|
+
export function detectLdapSigningDisabled(
|
|
760
|
+
gpoSettings: GpoSecuritySettings | null,
|
|
761
|
+
domain: ADDomain | null,
|
|
762
|
+
includeDetails: boolean
|
|
763
|
+
): Finding {
|
|
764
|
+
// If we have GPO settings, use them
|
|
765
|
+
if (gpoSettings && gpoSettings.ldapServerIntegrity !== undefined) {
|
|
766
|
+
const signingDisabled = gpoSettings.ldapServerIntegrity === 0;
|
|
767
|
+
|
|
768
|
+
return {
|
|
769
|
+
type: 'LDAP_SIGNING_DISABLED',
|
|
770
|
+
severity: 'critical',
|
|
771
|
+
category: 'advanced',
|
|
772
|
+
title: 'LDAP Signing Not Required',
|
|
773
|
+
description:
|
|
774
|
+
'LDAP server signing is not required. This allows NTLM relay attacks and man-in-the-middle attacks against LDAP connections.',
|
|
775
|
+
count: signingDisabled ? 1 : 0,
|
|
776
|
+
affectedEntities: includeDetails && signingDisabled && domain ? [domain.dn] : undefined,
|
|
777
|
+
details: signingDisabled
|
|
778
|
+
? {
|
|
779
|
+
recommendation: 'Configure "Domain controller: LDAP server signing requirements" to "Require signing".',
|
|
780
|
+
currentSetting: gpoSettings.ldapServerIntegrity,
|
|
781
|
+
requiredSetting: 2,
|
|
782
|
+
}
|
|
783
|
+
: undefined,
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// If GPO settings not available, assume vulnerable (Windows defaults don't require LDAP signing)
|
|
788
|
+
return {
|
|
789
|
+
type: 'LDAP_SIGNING_DISABLED',
|
|
790
|
+
severity: 'critical',
|
|
791
|
+
category: 'advanced',
|
|
792
|
+
title: 'LDAP Signing Not Configured in GPO',
|
|
793
|
+
description:
|
|
794
|
+
'LDAP signing is not configured via Group Policy. Windows defaults do not require LDAP signing, making this environment vulnerable to LDAP relay attacks.',
|
|
795
|
+
count: 1,
|
|
796
|
+
affectedEntities: includeDetails && domain ? [domain.dn] : undefined,
|
|
797
|
+
details: {
|
|
798
|
+
recommendation:
|
|
799
|
+
'Configure "Domain controller: LDAP server signing requirements" to "Require signing" via Group Policy.',
|
|
800
|
+
note: 'No GPO security template found. Windows defaults do not require LDAP signing.',
|
|
801
|
+
},
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Check if LDAP channel binding is disabled
|
|
807
|
+
* Requires GPO settings from SYSVOL
|
|
808
|
+
*/
|
|
809
|
+
export function detectLdapChannelBindingDisabled(
|
|
810
|
+
gpoSettings: GpoSecuritySettings | null,
|
|
811
|
+
domain: ADDomain | null,
|
|
812
|
+
includeDetails: boolean
|
|
813
|
+
): Finding {
|
|
814
|
+
if (gpoSettings && gpoSettings.ldapChannelBinding !== undefined) {
|
|
815
|
+
const bindingDisabled = gpoSettings.ldapChannelBinding === 0;
|
|
816
|
+
|
|
817
|
+
return {
|
|
818
|
+
type: 'LDAP_CHANNEL_BINDING_DISABLED',
|
|
819
|
+
severity: 'high',
|
|
820
|
+
category: 'advanced',
|
|
821
|
+
title: 'LDAP Channel Binding Not Required',
|
|
822
|
+
description:
|
|
823
|
+
'LDAP channel binding is not required. This allows NTLM relay attacks against LDAPS connections even when signing is enabled.',
|
|
824
|
+
count: bindingDisabled ? 1 : 0,
|
|
825
|
+
affectedEntities: includeDetails && bindingDisabled && domain ? [domain.dn] : undefined,
|
|
826
|
+
details: bindingDisabled
|
|
827
|
+
? {
|
|
828
|
+
recommendation: 'Configure "Domain controller: LDAP server channel binding token requirements" to "Always".',
|
|
829
|
+
currentSetting: gpoSettings.ldapChannelBinding,
|
|
830
|
+
requiredSetting: 2,
|
|
831
|
+
}
|
|
832
|
+
: undefined,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
return {
|
|
837
|
+
type: 'LDAP_CHANNEL_BINDING_DISABLED',
|
|
838
|
+
severity: 'high',
|
|
839
|
+
category: 'advanced',
|
|
840
|
+
title: 'LDAP Channel Binding Configuration Unknown',
|
|
841
|
+
description: 'Unable to determine LDAP channel binding configuration. Manual review recommended.',
|
|
842
|
+
count: 0,
|
|
843
|
+
details: {
|
|
844
|
+
note: 'GPO/Registry settings not available. Check LdapEnforceChannelBinding registry value manually.',
|
|
845
|
+
},
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Check if SMBv1 is enabled
|
|
851
|
+
* Requires GPO settings from SYSVOL
|
|
852
|
+
*/
|
|
853
|
+
export function detectSmbV1Enabled(
|
|
854
|
+
gpoSettings: GpoSecuritySettings | null,
|
|
855
|
+
domain: ADDomain | null,
|
|
856
|
+
includeDetails: boolean
|
|
857
|
+
): Finding {
|
|
858
|
+
if (gpoSettings && (gpoSettings.smbv1ServerEnabled !== undefined || gpoSettings.smbv1ClientEnabled !== undefined)) {
|
|
859
|
+
const smbv1Enabled = gpoSettings.smbv1ServerEnabled === true || gpoSettings.smbv1ClientEnabled === true;
|
|
860
|
+
|
|
861
|
+
return {
|
|
862
|
+
type: 'SMB_V1_ENABLED',
|
|
863
|
+
severity: 'high',
|
|
864
|
+
category: 'advanced',
|
|
865
|
+
title: 'SMBv1 Protocol Enabled',
|
|
866
|
+
description:
|
|
867
|
+
'SMBv1 protocol is enabled. SMBv1 is deprecated and vulnerable to attacks like EternalBlue (WannaCry, NotPetya).',
|
|
868
|
+
count: smbv1Enabled ? 1 : 0,
|
|
869
|
+
affectedEntities: includeDetails && smbv1Enabled && domain ? [domain.dn] : undefined,
|
|
870
|
+
details: smbv1Enabled
|
|
871
|
+
? {
|
|
872
|
+
recommendation: 'Disable SMBv1 on all systems. Use SMBv2/v3 instead.',
|
|
873
|
+
smbv1Server: gpoSettings.smbv1ServerEnabled,
|
|
874
|
+
smbv1Client: gpoSettings.smbv1ClientEnabled,
|
|
875
|
+
}
|
|
876
|
+
: undefined,
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
return {
|
|
881
|
+
type: 'SMB_V1_ENABLED',
|
|
882
|
+
severity: 'high',
|
|
883
|
+
category: 'advanced',
|
|
884
|
+
title: 'SMBv1 Configuration Unknown',
|
|
885
|
+
description: 'Unable to determine SMBv1 configuration. Manual review recommended.',
|
|
886
|
+
count: 0,
|
|
887
|
+
details: {
|
|
888
|
+
note: 'GPO/Registry settings not available. Check SMB1 registry values and Windows features manually.',
|
|
889
|
+
},
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Check if SMB signing is disabled
|
|
895
|
+
* Requires GPO settings from SYSVOL
|
|
896
|
+
*
|
|
897
|
+
* SMB signing prevents man-in-the-middle attacks and NTLM relay attacks.
|
|
898
|
+
* Registry key: MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\RequireSecuritySignature
|
|
899
|
+
*/
|
|
900
|
+
export function detectSmbSigningDisabled(
|
|
901
|
+
gpoSettings: GpoSecuritySettings | null,
|
|
902
|
+
domain: ADDomain | null,
|
|
903
|
+
includeDetails: boolean
|
|
904
|
+
): Finding {
|
|
905
|
+
if (gpoSettings && gpoSettings.smbSigningRequired !== undefined) {
|
|
906
|
+
const signingDisabled = gpoSettings.smbSigningRequired === false;
|
|
907
|
+
|
|
908
|
+
return {
|
|
909
|
+
type: 'SMB_SIGNING_DISABLED',
|
|
910
|
+
severity: 'critical',
|
|
911
|
+
category: 'advanced',
|
|
912
|
+
title: 'SMB Signing Not Required',
|
|
913
|
+
description:
|
|
914
|
+
'SMB server signing is not required. This allows man-in-the-middle attacks and NTLM relay attacks against SMB connections.',
|
|
915
|
+
count: signingDisabled ? 1 : 0,
|
|
916
|
+
affectedEntities: includeDetails && signingDisabled && domain ? [domain.dn] : undefined,
|
|
917
|
+
details: signingDisabled
|
|
918
|
+
? {
|
|
919
|
+
recommendation: 'Configure "Microsoft network server: Digitally sign communications (always)" to Enabled.',
|
|
920
|
+
currentSetting: 'Not Required',
|
|
921
|
+
requiredSetting: 'Required',
|
|
922
|
+
smbServerSigning: gpoSettings.smbSigningRequired,
|
|
923
|
+
smbClientSigning: gpoSettings.smbClientSigningRequired,
|
|
924
|
+
}
|
|
925
|
+
: undefined,
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// If GPO settings not available, assume vulnerable (Windows defaults don't require signing)
|
|
930
|
+
return {
|
|
931
|
+
type: 'SMB_SIGNING_DISABLED',
|
|
932
|
+
severity: 'critical',
|
|
933
|
+
category: 'advanced',
|
|
934
|
+
title: 'SMB Signing Not Configured in GPO',
|
|
935
|
+
description:
|
|
936
|
+
'SMB signing is not configured via Group Policy. Windows defaults do not require SMB signing, making this environment vulnerable to NTLM relay attacks.',
|
|
937
|
+
count: 1,
|
|
938
|
+
affectedEntities: includeDetails && domain ? [domain.dn] : undefined,
|
|
939
|
+
details: {
|
|
940
|
+
recommendation:
|
|
941
|
+
'Configure "Microsoft network server: Digitally sign communications (always)" via Group Policy.',
|
|
942
|
+
note: 'No GPO security template found. Windows defaults do not require SMB signing.',
|
|
943
|
+
},
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Check if audit policy is weak/incomplete
|
|
949
|
+
* Requires GPO settings from SYSVOL
|
|
950
|
+
*/
|
|
951
|
+
export function detectAuditPolicyWeak(
|
|
952
|
+
gpoSettings: GpoSecuritySettings | null,
|
|
953
|
+
domain: ADDomain | null,
|
|
954
|
+
includeDetails: boolean
|
|
955
|
+
): Finding {
|
|
956
|
+
// Critical audit categories that should be enabled
|
|
957
|
+
const criticalAuditCategories = [
|
|
958
|
+
'Account Logon',
|
|
959
|
+
'Account Management',
|
|
960
|
+
'Logon/Logoff',
|
|
961
|
+
'Object Access',
|
|
962
|
+
'Policy Change',
|
|
963
|
+
'Privilege Use',
|
|
964
|
+
'System',
|
|
965
|
+
];
|
|
966
|
+
|
|
967
|
+
if (gpoSettings && gpoSettings.auditPolicies && gpoSettings.auditPolicies.length > 0) {
|
|
968
|
+
const configuredCategories = new Set(gpoSettings.auditPolicies.map((p) => p.category));
|
|
969
|
+
const missingCategories = criticalAuditCategories.filter((cat) => !configuredCategories.has(cat));
|
|
970
|
+
|
|
971
|
+
// Check if critical events are being audited
|
|
972
|
+
const hasWeakAudit = missingCategories.length > 0;
|
|
973
|
+
|
|
974
|
+
return {
|
|
975
|
+
type: 'AUDIT_POLICY_WEAK',
|
|
976
|
+
severity: 'medium',
|
|
977
|
+
category: 'advanced',
|
|
978
|
+
title: 'Audit Policy Incomplete',
|
|
979
|
+
description:
|
|
980
|
+
'Domain audit policy does not cover all critical security events. Attacks may go undetected.',
|
|
981
|
+
count: hasWeakAudit ? 1 : 0,
|
|
982
|
+
affectedEntities: includeDetails && hasWeakAudit && domain ? [domain.dn] : undefined,
|
|
983
|
+
details: hasWeakAudit
|
|
984
|
+
? {
|
|
985
|
+
recommendation: 'Configure Advanced Audit Policy to audit all critical security categories.',
|
|
986
|
+
missingCategories,
|
|
987
|
+
configuredCategories: Array.from(configuredCategories),
|
|
988
|
+
}
|
|
989
|
+
: undefined,
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
return {
|
|
994
|
+
type: 'AUDIT_POLICY_WEAK',
|
|
995
|
+
severity: 'medium',
|
|
996
|
+
category: 'advanced',
|
|
997
|
+
title: 'Audit Policy Configuration Unknown',
|
|
998
|
+
description: 'Unable to determine audit policy configuration. Manual review recommended.',
|
|
999
|
+
count: 0,
|
|
1000
|
+
details: {
|
|
1001
|
+
note: 'GPO audit settings not available. Check Advanced Audit Policy Configuration manually.',
|
|
1002
|
+
requiredCategories: criticalAuditCategories,
|
|
1003
|
+
},
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Check if PowerShell logging is disabled
|
|
1009
|
+
* Requires GPO settings from SYSVOL
|
|
1010
|
+
*/
|
|
1011
|
+
export function detectPowershellLoggingDisabled(
|
|
1012
|
+
gpoSettings: GpoSecuritySettings | null,
|
|
1013
|
+
domain: ADDomain | null,
|
|
1014
|
+
includeDetails: boolean
|
|
1015
|
+
): Finding {
|
|
1016
|
+
if (gpoSettings && gpoSettings.powershellLogging) {
|
|
1017
|
+
const logging = gpoSettings.powershellLogging;
|
|
1018
|
+
const loggingDisabled = !logging.moduleLogging && !logging.scriptBlockLogging;
|
|
1019
|
+
|
|
1020
|
+
return {
|
|
1021
|
+
type: 'POWERSHELL_LOGGING_DISABLED',
|
|
1022
|
+
severity: 'medium',
|
|
1023
|
+
category: 'advanced',
|
|
1024
|
+
title: 'PowerShell Logging Not Configured',
|
|
1025
|
+
description:
|
|
1026
|
+
'PowerShell script block logging and module logging are not enabled. Malicious PowerShell activity will not be logged.',
|
|
1027
|
+
count: loggingDisabled ? 1 : 0,
|
|
1028
|
+
affectedEntities: includeDetails && loggingDisabled && domain ? [domain.dn] : undefined,
|
|
1029
|
+
details: loggingDisabled
|
|
1030
|
+
? {
|
|
1031
|
+
recommendation:
|
|
1032
|
+
'Enable "Turn on PowerShell Script Block Logging" and "Turn on Module Logging" via GPO.',
|
|
1033
|
+
moduleLogging: logging.moduleLogging,
|
|
1034
|
+
scriptBlockLogging: logging.scriptBlockLogging,
|
|
1035
|
+
transcription: logging.transcription,
|
|
1036
|
+
}
|
|
1037
|
+
: undefined,
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
return {
|
|
1042
|
+
type: 'POWERSHELL_LOGGING_DISABLED',
|
|
1043
|
+
severity: 'medium',
|
|
1044
|
+
category: 'advanced',
|
|
1045
|
+
title: 'PowerShell Logging Configuration Unknown',
|
|
1046
|
+
description: 'Unable to determine PowerShell logging configuration. Manual review recommended.',
|
|
1047
|
+
count: 0,
|
|
1048
|
+
details: {
|
|
1049
|
+
note: 'GPO/Registry settings not available. Check PowerShell logging GPO settings manually.',
|
|
1050
|
+
},
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Advanced detector options
|
|
1056
|
+
*/
|
|
1057
|
+
/**
|
|
1058
|
+
* Detect modified dsHeuristics attribute
|
|
1059
|
+
*
|
|
1060
|
+
* dsHeuristics controls various AD behaviors. Non-default values may indicate
|
|
1061
|
+
* security weakening (e.g., allowing anonymous access, disabling list object mode).
|
|
1062
|
+
*
|
|
1063
|
+
* @param domain - Domain information
|
|
1064
|
+
* @param _includeDetails - Whether to include affected entity details
|
|
1065
|
+
* @returns Finding for DS_HEURISTICS_MODIFIED
|
|
1066
|
+
*/
|
|
1067
|
+
export function detectDsHeuristicsModified(
|
|
1068
|
+
domain: ADDomain | null,
|
|
1069
|
+
_includeDetails: boolean
|
|
1070
|
+
): Finding {
|
|
1071
|
+
// dsHeuristics is stored on CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration
|
|
1072
|
+
const dsHeuristics = domain ? (domain as Record<string, unknown>)['dsHeuristics'] as string | undefined : undefined;
|
|
1073
|
+
|
|
1074
|
+
// Default is empty or null - any value is a modification
|
|
1075
|
+
const isModified = dsHeuristics !== undefined && dsHeuristics !== null && dsHeuristics !== '';
|
|
1076
|
+
|
|
1077
|
+
// Check for specific dangerous settings
|
|
1078
|
+
const dangerousSettings: string[] = [];
|
|
1079
|
+
if (dsHeuristics) {
|
|
1080
|
+
// Position 7: fLDAPBlockAnonOps (0=block anonymous, 2=allow)
|
|
1081
|
+
if (dsHeuristics.length >= 7 && dsHeuristics[6] === '2') {
|
|
1082
|
+
dangerousSettings.push('Anonymous LDAP operations allowed (position 7)');
|
|
1083
|
+
}
|
|
1084
|
+
// Position 3: fDisableListObject (0=enabled, 1=disabled)
|
|
1085
|
+
if (dsHeuristics.length >= 3 && dsHeuristics[2] === '1') {
|
|
1086
|
+
dangerousSettings.push('List Object mode disabled (position 3)');
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
return {
|
|
1091
|
+
type: 'DS_HEURISTICS_MODIFIED',
|
|
1092
|
+
severity: 'medium',
|
|
1093
|
+
category: 'advanced',
|
|
1094
|
+
title: 'dsHeuristics Modified',
|
|
1095
|
+
description:
|
|
1096
|
+
'The dsHeuristics attribute has been modified from defaults. ' +
|
|
1097
|
+
'This may weaken AD security or enable dangerous features.',
|
|
1098
|
+
count: isModified ? 1 : 0,
|
|
1099
|
+
details: {
|
|
1100
|
+
currentValue: dsHeuristics || '(empty)',
|
|
1101
|
+
dangerousSettings: dangerousSettings.length > 0 ? dangerousSettings : undefined,
|
|
1102
|
+
recommendation:
|
|
1103
|
+
'Review dsHeuristics value and document any intentional modifications.',
|
|
1104
|
+
},
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* Detect modified AdminSDHolder permissions
|
|
1110
|
+
*
|
|
1111
|
+
* AdminSDHolder template is applied to protected accounts every 60 minutes.
|
|
1112
|
+
* Modified permissions on AdminSDHolder will propagate to all protected accounts.
|
|
1113
|
+
*
|
|
1114
|
+
* @param domain - Domain information
|
|
1115
|
+
* @param _includeDetails - Whether to include affected entity details
|
|
1116
|
+
* @returns Finding for ADMIN_SD_HOLDER_MODIFIED
|
|
1117
|
+
*/
|
|
1118
|
+
export function detectAdminSdHolderModified(
|
|
1119
|
+
domain: ADDomain | null,
|
|
1120
|
+
_includeDetails: boolean
|
|
1121
|
+
): Finding {
|
|
1122
|
+
// This would require reading the nTSecurityDescriptor of AdminSDHolder
|
|
1123
|
+
// For now, check if domain has indicators of modification
|
|
1124
|
+
// A proper check would compare against known-good template
|
|
1125
|
+
const adminSdHolderInfo = domain
|
|
1126
|
+
? (domain as Record<string, unknown>)['adminSDHolderModified'] as boolean | undefined
|
|
1127
|
+
: undefined;
|
|
1128
|
+
|
|
1129
|
+
return {
|
|
1130
|
+
type: 'ADMIN_SD_HOLDER_MODIFIED',
|
|
1131
|
+
severity: 'high',
|
|
1132
|
+
category: 'advanced',
|
|
1133
|
+
title: 'AdminSDHolder Review Required',
|
|
1134
|
+
description:
|
|
1135
|
+
'AdminSDHolder permissions should be reviewed. Modifications propagate to all protected accounts ' +
|
|
1136
|
+
'(Domain Admins, Enterprise Admins, etc.) via SDProp process.',
|
|
1137
|
+
count: adminSdHolderInfo ? 1 : 0,
|
|
1138
|
+
details: {
|
|
1139
|
+
recommendation:
|
|
1140
|
+
'Compare AdminSDHolder ACL against baseline. Look for non-standard principals with permissions.',
|
|
1141
|
+
checkCommand: 'Get-ADObject "CN=AdminSDHolder,CN=System,DC=..." -Properties nTSecurityDescriptor',
|
|
1142
|
+
},
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Detect Exchange privilege escalation paths
|
|
1148
|
+
*
|
|
1149
|
+
* Exchange security groups often have dangerous permissions that can be
|
|
1150
|
+
* abused for privilege escalation (WriteDacl on domain, etc.).
|
|
1151
|
+
*
|
|
1152
|
+
* @param users - Array of AD users
|
|
1153
|
+
* @param includeDetails - Whether to include affected entity details
|
|
1154
|
+
* @returns Finding for EXCHANGE_PRIV_ESC_PATH
|
|
1155
|
+
*/
|
|
1156
|
+
export function detectExchangePrivEscPath(
|
|
1157
|
+
users: ADUser[],
|
|
1158
|
+
includeDetails: boolean
|
|
1159
|
+
): Finding {
|
|
1160
|
+
// Exchange groups with dangerous permissions
|
|
1161
|
+
const exchangeGroups = [
|
|
1162
|
+
'Exchange Trusted Subsystem',
|
|
1163
|
+
'Exchange Windows Permissions',
|
|
1164
|
+
'Organization Management',
|
|
1165
|
+
'Exchange Servers',
|
|
1166
|
+
];
|
|
1167
|
+
|
|
1168
|
+
// Find users in Exchange groups that might indicate privilege escalation risk
|
|
1169
|
+
const affected = users.filter((u) => {
|
|
1170
|
+
if (!u.enabled || !u.memberOf) return false;
|
|
1171
|
+
return u.memberOf.some((dn) =>
|
|
1172
|
+
exchangeGroups.some((eg) => dn.toLowerCase().includes(eg.toLowerCase()))
|
|
1173
|
+
);
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
return {
|
|
1177
|
+
type: 'EXCHANGE_PRIV_ESC_PATH',
|
|
1178
|
+
severity: 'critical',
|
|
1179
|
+
category: 'advanced',
|
|
1180
|
+
title: 'Exchange Privilege Escalation Risk',
|
|
1181
|
+
description:
|
|
1182
|
+
'Users in Exchange security groups with potentially dangerous permissions. ' +
|
|
1183
|
+
'Exchange Trusted Subsystem has WriteDacl on domain by default (CVE-2019-1166).',
|
|
1184
|
+
count: affected.length,
|
|
1185
|
+
affectedEntities: includeDetails ? toAffectedUserEntities(affected) : undefined,
|
|
1186
|
+
details: {
|
|
1187
|
+
exchangeGroups: exchangeGroups,
|
|
1188
|
+
recommendation:
|
|
1189
|
+
'Review Exchange group permissions on domain head. Apply PrivExchange mitigations.',
|
|
1190
|
+
reference: 'CVE-2019-1166, PrivExchange',
|
|
1191
|
+
},
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
export interface AdvancedDetectorOptions {
|
|
1196
|
+
/** GPO security settings from SYSVOL */
|
|
1197
|
+
gpoSettings?: GpoSecuritySettings | null;
|
|
1198
|
+
/** Whether anonymous LDAP access is allowed */
|
|
1199
|
+
anonymousAccessAllowed?: boolean;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* Detect all advanced vulnerabilities
|
|
1204
|
+
*/
|
|
1205
|
+
export function detectAdvancedVulnerabilities(
|
|
1206
|
+
users: ADUser[],
|
|
1207
|
+
computers: ADComputer[],
|
|
1208
|
+
domain: ADDomain | null,
|
|
1209
|
+
templates: any[] = [],
|
|
1210
|
+
cas: any[] = [],
|
|
1211
|
+
fsps: any[] = [],
|
|
1212
|
+
includeDetails: boolean,
|
|
1213
|
+
options: AdvancedDetectorOptions = {}
|
|
1214
|
+
): Finding[] {
|
|
1215
|
+
const { gpoSettings = null, anonymousAccessAllowed = false } = options;
|
|
1216
|
+
|
|
1217
|
+
return [
|
|
1218
|
+
// Critical
|
|
1219
|
+
detectShadowCredentials(users, includeDetails),
|
|
1220
|
+
detectRbcdAbuse(users, includeDetails),
|
|
1221
|
+
// Critical (GPO-based) - Story 1.1
|
|
1222
|
+
detectLdapSigningDisabled(gpoSettings, domain, includeDetails),
|
|
1223
|
+
detectSmbSigningDisabled(gpoSettings, domain, includeDetails),
|
|
1224
|
+
// High
|
|
1225
|
+
detectEsc1VulnerableTemplate(templates, includeDetails),
|
|
1226
|
+
detectEsc2AnyPurpose(templates, includeDetails),
|
|
1227
|
+
detectEsc3EnrollmentAgent(templates, includeDetails),
|
|
1228
|
+
detectEsc4VulnerableTemplateAcl(templates, includeDetails),
|
|
1229
|
+
detectEsc6EditfAttributeSubjectAltName2(cas, includeDetails),
|
|
1230
|
+
detectLapsPasswordReadable(computers, includeDetails),
|
|
1231
|
+
detectReplicationRights(users, includeDetails),
|
|
1232
|
+
detectDcsyncCapable(users, includeDetails),
|
|
1233
|
+
// High (domain)
|
|
1234
|
+
detectMachineAccountQuotaHigh(domain, includeDetails),
|
|
1235
|
+
// High (GPO-based)
|
|
1236
|
+
detectLdapChannelBindingDisabled(gpoSettings, domain, includeDetails),
|
|
1237
|
+
detectSmbV1Enabled(gpoSettings, domain, includeDetails),
|
|
1238
|
+
// Medium
|
|
1239
|
+
detectEsc8HttpEnrollment(cas, includeDetails),
|
|
1240
|
+
detectLapsNotDeployed(computers, includeDetails),
|
|
1241
|
+
detectLapsLegacyAttribute(computers, includeDetails),
|
|
1242
|
+
detectDuplicateSpn(users, includeDetails),
|
|
1243
|
+
detectWeakPasswordPolicy(domain, includeDetails),
|
|
1244
|
+
detectWeakKerberosPolicy(domain, includeDetails),
|
|
1245
|
+
detectMachineAccountQuotaAbuse(domain, includeDetails),
|
|
1246
|
+
detectDelegationPrivilege(users, includeDetails),
|
|
1247
|
+
detectForeignSecurityPrincipals(fsps, includeDetails),
|
|
1248
|
+
detectNtlmRelayOpportunity(domain, includeDetails),
|
|
1249
|
+
detectAdcsWeakPermissions(templates, includeDetails),
|
|
1250
|
+
detectDangerousLogonScripts(users, includeDetails),
|
|
1251
|
+
// Medium (Phase 1B)
|
|
1252
|
+
detectRecycleBinDisabled(domain, includeDetails),
|
|
1253
|
+
detectAnonymousLdapAccess(anonymousAccessAllowed, domain, includeDetails),
|
|
1254
|
+
detectAuditPolicyWeak(gpoSettings, domain, includeDetails),
|
|
1255
|
+
detectPowershellLoggingDisabled(gpoSettings, domain, includeDetails),
|
|
1256
|
+
// Low
|
|
1257
|
+
detectLapsPasswordSet(computers, includeDetails),
|
|
1258
|
+
detectLapsPasswordLeaked(computers, includeDetails),
|
|
1259
|
+
// Phase 4: Advanced detections
|
|
1260
|
+
detectDsHeuristicsModified(domain, includeDetails),
|
|
1261
|
+
detectAdminSdHolderModified(domain, includeDetails),
|
|
1262
|
+
detectExchangePrivEscPath(users, includeDetails),
|
|
1263
|
+
].filter(
|
|
1264
|
+
(finding) =>
|
|
1265
|
+
finding.count > 0 ||
|
|
1266
|
+
// Always include critical signing findings (even with count=0) for visibility
|
|
1267
|
+
finding.type === 'SMB_SIGNING_DISABLED' ||
|
|
1268
|
+
finding.type === 'LDAP_SIGNING_DISABLED'
|
|
1269
|
+
);
|
|
1270
|
+
}
|