@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,1188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computers Security Vulnerability Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects computer-related vulnerabilities in AD.
|
|
5
|
+
* Story 1.7: AD Vulnerability Detection Engine
|
|
6
|
+
*
|
|
7
|
+
* Vulnerabilities detected (28):
|
|
8
|
+
* - COMPUTER_CONSTRAINED_DELEGATION (Critical)
|
|
9
|
+
* - COMPUTER_RBCD (Critical)
|
|
10
|
+
* - COMPUTER_IN_ADMIN_GROUP (Critical)
|
|
11
|
+
* - COMPUTER_DCSYNC_RIGHTS (Critical)
|
|
12
|
+
* - COMPUTER_UNCONSTRAINED_DELEGATION (Critical)
|
|
13
|
+
* - COMPUTER_OS_OBSOLETE_XP (Critical)
|
|
14
|
+
* - COMPUTER_OS_OBSOLETE_2003 (Critical)
|
|
15
|
+
* - COMPUTER_STALE_INACTIVE (High)
|
|
16
|
+
* - COMPUTER_PASSWORD_OLD (High)
|
|
17
|
+
* - COMPUTER_WITH_SPNS (High)
|
|
18
|
+
* - COMPUTER_NO_LAPS (High)
|
|
19
|
+
* - COMPUTER_ACL_ABUSE (High)
|
|
20
|
+
* - COMPUTER_OS_OBSOLETE_2008 (High)
|
|
21
|
+
* - COMPUTER_OS_OBSOLETE_VISTA (High)
|
|
22
|
+
* - DC_NOT_IN_DC_OU (High) - Phase 2C
|
|
23
|
+
* - COMPUTER_NO_BITLOCKER (High) - Phase 4
|
|
24
|
+
* - COMPUTER_DISABLED_NOT_DELETED (Medium)
|
|
25
|
+
* - COMPUTER_WRONG_OU (Medium)
|
|
26
|
+
* - COMPUTER_WEAK_ENCRYPTION (Medium)
|
|
27
|
+
* - COMPUTER_DESCRIPTION_SENSITIVE (Medium)
|
|
28
|
+
* - COMPUTER_PRE_WINDOWS_2000 (Medium)
|
|
29
|
+
* - COMPUTER_NEVER_LOGGED_ON (Medium)
|
|
30
|
+
* - COMPUTER_DUPLICATE_SPN (Medium) - Phase 2C
|
|
31
|
+
* - SERVER_NO_ADMIN_GROUP (Medium) - Phase 2C
|
|
32
|
+
* - COMPUTER_LEGACY_PROTOCOL (Medium) - Phase 4
|
|
33
|
+
* - COMPUTER_ADMIN_COUNT (Low)
|
|
34
|
+
* - COMPUTER_SMB_SIGNING_DISABLED (Low)
|
|
35
|
+
* - WORKSTATION_IN_SERVER_OU (Low) - Phase 2C
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { ADComputer } from '../../../../types/ad.types';
|
|
39
|
+
import { Finding } from '../../../../types/finding.types';
|
|
40
|
+
import { toAffectedComputerEntities, ldapAttrToString } from '../../../../utils/entity-converter';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Windows FILETIME epoch offset in milliseconds
|
|
44
|
+
* Difference between 1601-01-01 and 1970-01-01
|
|
45
|
+
*/
|
|
46
|
+
const FILETIME_EPOCH_OFFSET = 11644473600000;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Convert any date format to timestamp in milliseconds
|
|
50
|
+
* Handles: Date object, ISO string, FILETIME (number/string), undefined/null
|
|
51
|
+
*
|
|
52
|
+
* @param value - Date in any format
|
|
53
|
+
* @returns Timestamp in milliseconds, or null if invalid
|
|
54
|
+
*/
|
|
55
|
+
function toTimestamp(value: any): number | null {
|
|
56
|
+
if (!value) return null;
|
|
57
|
+
|
|
58
|
+
// Already a Date object
|
|
59
|
+
if (value instanceof Date) {
|
|
60
|
+
const time = value.getTime();
|
|
61
|
+
return isNaN(time) ? null : time;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// String value
|
|
65
|
+
if (typeof value === 'string') {
|
|
66
|
+
// Try ISO date first (e.g., "2024-01-15T10:30:00.000Z")
|
|
67
|
+
if (value.includes('-') && value.includes('T')) {
|
|
68
|
+
const date = new Date(value);
|
|
69
|
+
const time = date.getTime();
|
|
70
|
+
return isNaN(time) ? null : time;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Try LDAP generalizedTime format (e.g., "20260115123456.0Z" or "20260115123456Z")
|
|
74
|
+
const gtMatch = value.match(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/);
|
|
75
|
+
if (gtMatch && gtMatch[1] && gtMatch[2] && gtMatch[3] && gtMatch[4] && gtMatch[5] && gtMatch[6]) {
|
|
76
|
+
const date = new Date(
|
|
77
|
+
Date.UTC(
|
|
78
|
+
parseInt(gtMatch[1], 10),
|
|
79
|
+
parseInt(gtMatch[2], 10) - 1,
|
|
80
|
+
parseInt(gtMatch[3], 10),
|
|
81
|
+
parseInt(gtMatch[4], 10),
|
|
82
|
+
parseInt(gtMatch[5], 10),
|
|
83
|
+
parseInt(gtMatch[6], 10)
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
const time = date.getTime();
|
|
87
|
+
return isNaN(time) ? null : time;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Try as numeric FILETIME string
|
|
91
|
+
const parsed = parseInt(value, 10);
|
|
92
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
93
|
+
return filetimeToTimestamp(parsed);
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Number - could be FILETIME or Unix timestamp
|
|
99
|
+
if (typeof value === 'number') {
|
|
100
|
+
if (value <= 0) return null;
|
|
101
|
+
// FILETIME values are huge (> 100 trillion), Unix timestamps are ~1.7 trillion ms
|
|
102
|
+
if (value > 100000000000000) {
|
|
103
|
+
return filetimeToTimestamp(value);
|
|
104
|
+
}
|
|
105
|
+
// If it's reasonable (after year 2000 and before year 2100)
|
|
106
|
+
if (value > 946684800000 && value < 4102444800000) {
|
|
107
|
+
return value; // Already a Unix timestamp in ms
|
|
108
|
+
}
|
|
109
|
+
// Could be Unix seconds
|
|
110
|
+
if (value > 946684800 && value < 4102444800) {
|
|
111
|
+
return value * 1000;
|
|
112
|
+
}
|
|
113
|
+
// Assume FILETIME
|
|
114
|
+
return filetimeToTimestamp(value);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Convert Windows FILETIME to Unix timestamp in milliseconds
|
|
122
|
+
*/
|
|
123
|
+
function filetimeToTimestamp(filetime: number): number | null {
|
|
124
|
+
// Invalid or "Never" values
|
|
125
|
+
if (filetime === 0 || filetime >= Number.MAX_SAFE_INTEGER) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Convert 100-ns intervals to milliseconds and adjust epoch
|
|
130
|
+
const ms = filetime / 10000 - FILETIME_EPOCH_OFFSET;
|
|
131
|
+
|
|
132
|
+
// Validate reasonable range (year 1970 to 2100)
|
|
133
|
+
if (ms < 0 || ms > 4102444800000) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return ms;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check for computer with constrained delegation
|
|
142
|
+
*/
|
|
143
|
+
export function detectComputerConstrainedDelegation(
|
|
144
|
+
computers: ADComputer[],
|
|
145
|
+
includeDetails: boolean
|
|
146
|
+
): Finding {
|
|
147
|
+
const affected = computers.filter((c) => {
|
|
148
|
+
const delegateTo = (c as any)['msDS-AllowedToDelegateTo'];
|
|
149
|
+
// Check that attribute exists, is not null, not empty string, and not empty array
|
|
150
|
+
return delegateTo && (Array.isArray(delegateTo) ? delegateTo.length > 0 : delegateTo !== '');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
type: 'COMPUTER_CONSTRAINED_DELEGATION',
|
|
155
|
+
severity: 'critical',
|
|
156
|
+
category: 'computers',
|
|
157
|
+
title: 'Computer Constrained Delegation',
|
|
158
|
+
description: 'Computer with constrained Kerberos delegation. Can impersonate users to specified services.',
|
|
159
|
+
count: affected.length,
|
|
160
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Check for computer with RBCD configured
|
|
166
|
+
*/
|
|
167
|
+
export function detectComputerRbcd(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
168
|
+
const affected = computers.filter((c) => {
|
|
169
|
+
const rbcdAttr = (c as any)['msDS-AllowedToActOnBehalfOfOtherIdentity'];
|
|
170
|
+
// Check that attribute exists, is not null, not empty string, and not empty array
|
|
171
|
+
return rbcdAttr && (Array.isArray(rbcdAttr) ? rbcdAttr.length > 0 : rbcdAttr !== '');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
type: 'COMPUTER_RBCD',
|
|
176
|
+
severity: 'critical',
|
|
177
|
+
category: 'computers',
|
|
178
|
+
title: 'Computer RBCD',
|
|
179
|
+
description: 'Computer with Resource-Based Constrained Delegation. Enables privilege escalation via RBCD attack.',
|
|
180
|
+
count: affected.length,
|
|
181
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Check for computer in admin groups
|
|
187
|
+
*/
|
|
188
|
+
export function detectComputerInAdminGroup(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
189
|
+
const adminGroups = ['Domain Admins', 'Enterprise Admins'];
|
|
190
|
+
|
|
191
|
+
const affected = computers.filter((c) => {
|
|
192
|
+
const memberOf = (c as any).memberOf;
|
|
193
|
+
if (!memberOf) return false;
|
|
194
|
+
return memberOf.some((dn: string) => adminGroups.some((group) => dn.includes(`CN=${group}`)));
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
type: 'COMPUTER_IN_ADMIN_GROUP',
|
|
199
|
+
severity: 'critical',
|
|
200
|
+
category: 'computers',
|
|
201
|
+
title: 'Computer in Admin Group',
|
|
202
|
+
description: 'Computer account in Domain Admins or Enterprise Admins. Computer compromise leads to domain admin access.',
|
|
203
|
+
count: affected.length,
|
|
204
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Check for computer with DCSync rights
|
|
210
|
+
*/
|
|
211
|
+
export function detectComputerDcsyncRights(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
212
|
+
// Note: This requires ACL analysis which would be done in permissions detector
|
|
213
|
+
// For now, we detect computers with the replication rights attribute
|
|
214
|
+
const affected = computers.filter((c) => {
|
|
215
|
+
return 'replicationRights' in c && (c as any).replicationRights;
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
type: 'COMPUTER_DCSYNC_RIGHTS',
|
|
220
|
+
severity: 'critical',
|
|
221
|
+
category: 'computers',
|
|
222
|
+
title: 'Computer DCSync Rights',
|
|
223
|
+
description: 'Computer with DCSync replication rights. Can extract all domain password hashes.',
|
|
224
|
+
count: affected.length,
|
|
225
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Check for computer with unconstrained delegation
|
|
231
|
+
*/
|
|
232
|
+
export function detectComputerUnconstrainedDelegation(
|
|
233
|
+
computers: ADComputer[],
|
|
234
|
+
includeDetails: boolean
|
|
235
|
+
): Finding {
|
|
236
|
+
const affected = computers.filter((c) => {
|
|
237
|
+
const uac = (c as any).userAccountControl;
|
|
238
|
+
if (!uac) return false;
|
|
239
|
+
return (uac & 0x80000) !== 0; // TRUSTED_FOR_DELEGATION
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
type: 'COMPUTER_UNCONSTRAINED_DELEGATION',
|
|
244
|
+
severity: 'critical',
|
|
245
|
+
category: 'computers',
|
|
246
|
+
title: 'Computer Unconstrained Delegation',
|
|
247
|
+
description: 'Computer with unconstrained delegation enabled. Servers can be used for privilege escalation attacks.',
|
|
248
|
+
count: affected.length,
|
|
249
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Check for stale/inactive computers (90+ days)
|
|
255
|
+
* Note: Computers that have NEVER logged on are handled by COMPUTER_NEVER_LOGGED_ON
|
|
256
|
+
*/
|
|
257
|
+
export function detectComputerStaleInactive(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
258
|
+
const now = Date.now();
|
|
259
|
+
const ninetyDaysAgo = now - 90 * 24 * 60 * 60 * 1000;
|
|
260
|
+
|
|
261
|
+
const affected = computers.filter((c) => {
|
|
262
|
+
// Only check enabled computers
|
|
263
|
+
if (!c.enabled) return false;
|
|
264
|
+
|
|
265
|
+
// Try lastLogon first, then lastLogonTimestamp (replicated, more reliable)
|
|
266
|
+
const lastLogonTime =
|
|
267
|
+
toTimestamp(c.lastLogon) ?? toTimestamp((c as any)['lastLogonTimestamp']);
|
|
268
|
+
|
|
269
|
+
// Skip if no logon time (handled by COMPUTER_NEVER_LOGGED_ON)
|
|
270
|
+
if (!lastLogonTime) return false;
|
|
271
|
+
|
|
272
|
+
return lastLogonTime < ninetyDaysAgo;
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
type: 'COMPUTER_STALE_INACTIVE',
|
|
277
|
+
severity: 'high',
|
|
278
|
+
category: 'computers',
|
|
279
|
+
title: 'Computer Stale/Inactive',
|
|
280
|
+
description: 'Computer inactive for 90+ days. Orphaned computer accounts could be exploited without detection.',
|
|
281
|
+
count: affected.length,
|
|
282
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Check for computer with old password (>90 days)
|
|
288
|
+
*/
|
|
289
|
+
export function detectComputerPasswordOld(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
290
|
+
const now = Date.now();
|
|
291
|
+
const ninetyDaysAgo = now - 90 * 24 * 60 * 60 * 1000;
|
|
292
|
+
|
|
293
|
+
// Debug: track why computers are filtered out
|
|
294
|
+
let debugStats = { total: 0, disabled: 0, noPwdLastSet: 0, recent: 0, old: 0 };
|
|
295
|
+
|
|
296
|
+
const affected = computers.filter((c) => {
|
|
297
|
+
debugStats.total++;
|
|
298
|
+
// Only check enabled computers
|
|
299
|
+
if (!c.enabled) {
|
|
300
|
+
debugStats.disabled++;
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Try pwdLastSet first, then passwordLastSet
|
|
305
|
+
const pwdLastSet = toTimestamp((c as any).pwdLastSet) ?? toTimestamp((c as any).passwordLastSet);
|
|
306
|
+
if (!pwdLastSet) {
|
|
307
|
+
debugStats.noPwdLastSet++;
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (pwdLastSet < ninetyDaysAgo) {
|
|
312
|
+
debugStats.old++;
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
debugStats.recent++;
|
|
316
|
+
return false;
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
type: 'COMPUTER_PASSWORD_OLD',
|
|
321
|
+
severity: 'high',
|
|
322
|
+
category: 'computers',
|
|
323
|
+
title: 'Computer Password Old',
|
|
324
|
+
description: 'Computer password not changed for 90+ days. Increases risk of password-based attacks.',
|
|
325
|
+
count: affected.length,
|
|
326
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
327
|
+
details: {
|
|
328
|
+
debug: debugStats,
|
|
329
|
+
threshold: '90 days',
|
|
330
|
+
checkDate: new Date(ninetyDaysAgo).toISOString(),
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Check for computer with SPNs (Kerberoastable)
|
|
337
|
+
*/
|
|
338
|
+
export function detectComputerWithSpns(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
339
|
+
const affected = computers.filter((c) => {
|
|
340
|
+
const spns = (c as any).servicePrincipalName;
|
|
341
|
+
return spns && spns.length > 0;
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
type: 'COMPUTER_WITH_SPNS',
|
|
346
|
+
severity: 'high',
|
|
347
|
+
category: 'computers',
|
|
348
|
+
title: 'Computer with SPNs',
|
|
349
|
+
description: 'Computer with Service Principal Names. Enables Kerberoasting attack against computer account.',
|
|
350
|
+
count: affected.length,
|
|
351
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Check for computer without LAPS
|
|
357
|
+
* Checks for both legacy LAPS (ms-Mcs-AdmPwd) and Windows LAPS (msLAPS-Password)
|
|
358
|
+
*
|
|
359
|
+
* Note: Password attributes may not be readable without LAPS admin rights,
|
|
360
|
+
* so we also check expiration time attributes which are more accessible.
|
|
361
|
+
* If LAPS schema is not extended, ALL computers are flagged.
|
|
362
|
+
*/
|
|
363
|
+
export function detectComputerNoLaps(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
364
|
+
// Stats for debugging
|
|
365
|
+
const total = computers.length;
|
|
366
|
+
const disabled = computers.filter((c) => !c.enabled).length;
|
|
367
|
+
const enabled = total - disabled;
|
|
368
|
+
|
|
369
|
+
// Count DCs (SERVER_TRUST_ACCOUNT = 0x2000)
|
|
370
|
+
const domainControllers = computers.filter((c) => {
|
|
371
|
+
const uac = (c as any).userAccountControl;
|
|
372
|
+
return uac && (uac & 0x2000) !== 0;
|
|
373
|
+
}).length;
|
|
374
|
+
|
|
375
|
+
// Check if any computer has LAPS attributes (indicates schema is extended)
|
|
376
|
+
let hasLegacyLapsSchema = false;
|
|
377
|
+
let hasWindowsLapsSchema = false;
|
|
378
|
+
let withLegacyLaps = 0;
|
|
379
|
+
let withWindowsLaps = 0;
|
|
380
|
+
|
|
381
|
+
const affected = computers.filter((c) => {
|
|
382
|
+
// Only check enabled computers (workstations and servers, not DCs)
|
|
383
|
+
if (!c.enabled) return false;
|
|
384
|
+
|
|
385
|
+
// Skip Domain Controllers (they don't use LAPS)
|
|
386
|
+
const uac = (c as any).userAccountControl;
|
|
387
|
+
if (uac && (uac & 0x2000) !== 0) return false; // SERVER_TRUST_ACCOUNT = DC
|
|
388
|
+
|
|
389
|
+
const comp = c as Record<string, unknown>;
|
|
390
|
+
|
|
391
|
+
// Check for legacy LAPS - password or expiration time
|
|
392
|
+
// Note: LDAP may return [] for non-existent attributes, so check for actual values
|
|
393
|
+
const legacyLaps = comp['ms-Mcs-AdmPwd'];
|
|
394
|
+
const legacyLapsExpiry = comp['ms-Mcs-AdmPwdExpirationTime'];
|
|
395
|
+
|
|
396
|
+
// Helper to check if value is a real LAPS value (not empty/null/undefined/[])
|
|
397
|
+
const isValidLapsValue = (val: unknown): boolean => {
|
|
398
|
+
if (val === undefined || val === null || val === '') return false;
|
|
399
|
+
if (Array.isArray(val) && val.length === 0) return false;
|
|
400
|
+
if (val === '0' || val === 0) return false;
|
|
401
|
+
return true;
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// Track if schema attributes exist with actual values
|
|
405
|
+
if (isValidLapsValue(legacyLaps) || isValidLapsValue(legacyLapsExpiry)) {
|
|
406
|
+
hasLegacyLapsSchema = true;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const hasLegacyLaps = isValidLapsValue(legacyLaps) || isValidLapsValue(legacyLapsExpiry);
|
|
410
|
+
if (hasLegacyLaps) withLegacyLaps++;
|
|
411
|
+
|
|
412
|
+
// Check for Windows LAPS - password or expiration time
|
|
413
|
+
const windowsLaps = comp['msLAPS-Password'];
|
|
414
|
+
const windowsLapsExpiry = comp['msLAPS-PasswordExpirationTime'];
|
|
415
|
+
|
|
416
|
+
// Track if schema attributes exist with actual values
|
|
417
|
+
if (isValidLapsValue(windowsLaps) || isValidLapsValue(windowsLapsExpiry)) {
|
|
418
|
+
hasWindowsLapsSchema = true;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const hasWindowsLaps = isValidLapsValue(windowsLaps) || isValidLapsValue(windowsLapsExpiry);
|
|
422
|
+
if (hasWindowsLaps) withWindowsLaps++;
|
|
423
|
+
|
|
424
|
+
// No LAPS if neither legacy nor Windows LAPS is configured
|
|
425
|
+
return !hasLegacyLaps && !hasWindowsLaps;
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// Determine severity based on schema availability
|
|
429
|
+
const schemaExtended = hasLegacyLapsSchema || hasWindowsLapsSchema;
|
|
430
|
+
const eligibleComputers = enabled - domainControllers;
|
|
431
|
+
|
|
432
|
+
return {
|
|
433
|
+
type: 'COMPUTER_NO_LAPS',
|
|
434
|
+
severity: !schemaExtended ? 'critical' : 'high', // Critical if schema not extended
|
|
435
|
+
category: 'computers',
|
|
436
|
+
title: !schemaExtended ? 'LAPS Not Deployed (Schema Not Extended)' : 'Computer No LAPS',
|
|
437
|
+
description: !schemaExtended
|
|
438
|
+
? 'LAPS schema is not extended in Active Directory. ALL local admin passwords are unmanaged and likely shared across computers.'
|
|
439
|
+
: 'Computer without LAPS deployed. Shared/static local admin passwords across workstations.',
|
|
440
|
+
count: affected.length,
|
|
441
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
442
|
+
details: {
|
|
443
|
+
debug: {
|
|
444
|
+
total,
|
|
445
|
+
enabled,
|
|
446
|
+
disabled,
|
|
447
|
+
domainControllers,
|
|
448
|
+
eligibleComputers,
|
|
449
|
+
withLegacyLaps,
|
|
450
|
+
withWindowsLaps,
|
|
451
|
+
withoutLaps: affected.length,
|
|
452
|
+
schemaExtended,
|
|
453
|
+
hasLegacyLapsSchema,
|
|
454
|
+
hasWindowsLapsSchema,
|
|
455
|
+
},
|
|
456
|
+
recommendation: !schemaExtended
|
|
457
|
+
? 'Install LAPS (legacy or Windows LAPS) and extend the AD schema. Then deploy via GPO.'
|
|
458
|
+
: 'Deploy LAPS to remaining computers via GPO.',
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Check for computer with ACL abuse potential
|
|
465
|
+
*/
|
|
466
|
+
export function detectComputerAclAbuse(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
467
|
+
// Note: This requires ACL analysis which would be done in permissions detector
|
|
468
|
+
// For now, we detect computers with suspicious ACL attributes
|
|
469
|
+
const affected = computers.filter((c) => {
|
|
470
|
+
return 'dangerousAcl' in c && (c as any).dangerousAcl;
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
type: 'COMPUTER_ACL_ABUSE',
|
|
475
|
+
severity: 'high',
|
|
476
|
+
category: 'computers',
|
|
477
|
+
title: 'Computer ACL Abuse',
|
|
478
|
+
description: 'Computer with dangerous ACL permissions. Can modify computer object properties and escalate privileges.',
|
|
479
|
+
count: affected.length,
|
|
480
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Check for disabled computers not deleted (>30 days)
|
|
486
|
+
*/
|
|
487
|
+
export function detectComputerDisabledNotDeleted(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
488
|
+
const now = Date.now();
|
|
489
|
+
const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1000;
|
|
490
|
+
|
|
491
|
+
// Debug stats
|
|
492
|
+
const debugStats: {
|
|
493
|
+
total: number;
|
|
494
|
+
enabled: number;
|
|
495
|
+
noWhenChanged: number;
|
|
496
|
+
recent: number;
|
|
497
|
+
old: number;
|
|
498
|
+
sampleDates: {
|
|
499
|
+
name: string;
|
|
500
|
+
raw: unknown;
|
|
501
|
+
rawType: string;
|
|
502
|
+
rawStringified: string;
|
|
503
|
+
parsed: number | null;
|
|
504
|
+
asIso: string | null;
|
|
505
|
+
whenCreated: unknown;
|
|
506
|
+
}[];
|
|
507
|
+
} = { total: 0, enabled: 0, noWhenChanged: 0, recent: 0, old: 0, sampleDates: [] };
|
|
508
|
+
|
|
509
|
+
const affected = computers.filter((c) => {
|
|
510
|
+
debugStats.total++;
|
|
511
|
+
if (c.enabled) {
|
|
512
|
+
debugStats.enabled++;
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Use toTimestamp for robust date handling
|
|
517
|
+
const rawWhenChanged = (c as any).whenChanged;
|
|
518
|
+
const whenChangedTime = toTimestamp(rawWhenChanged);
|
|
519
|
+
|
|
520
|
+
// Capture sample dates for debugging (first 5 disabled computers)
|
|
521
|
+
if (debugStats.sampleDates.length < 5) {
|
|
522
|
+
debugStats.sampleDates.push({
|
|
523
|
+
name: c.sAMAccountName || 'unknown',
|
|
524
|
+
raw: rawWhenChanged,
|
|
525
|
+
rawType: typeof rawWhenChanged,
|
|
526
|
+
rawStringified: JSON.stringify(rawWhenChanged),
|
|
527
|
+
parsed: whenChangedTime,
|
|
528
|
+
asIso: whenChangedTime ? new Date(whenChangedTime).toISOString() : null,
|
|
529
|
+
whenCreated: (c as any).whenCreated, // Also capture whenCreated for comparison
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (!whenChangedTime) {
|
|
534
|
+
debugStats.noWhenChanged++;
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (whenChangedTime < thirtyDaysAgo) {
|
|
539
|
+
debugStats.old++;
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
debugStats.recent++;
|
|
543
|
+
return false;
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
return {
|
|
547
|
+
type: 'COMPUTER_DISABLED_NOT_DELETED',
|
|
548
|
+
severity: 'medium',
|
|
549
|
+
category: 'computers',
|
|
550
|
+
title: 'Computer Disabled Not Deleted',
|
|
551
|
+
description: 'Disabled computer not deleted (>30 days). Clutters AD, potential security oversight.',
|
|
552
|
+
count: affected.length,
|
|
553
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
554
|
+
details: {
|
|
555
|
+
debug: debugStats,
|
|
556
|
+
threshold: '30 days',
|
|
557
|
+
checkDate: new Date(thirtyDaysAgo).toISOString(),
|
|
558
|
+
nowDate: new Date(now).toISOString(),
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Check for computer in default Computers container (not organized into OUs)
|
|
565
|
+
*
|
|
566
|
+
* Computers in "CN=Computers,DC=..." are in the default container, not an OU.
|
|
567
|
+
* This indicates they haven't been organized and may not receive proper GPOs.
|
|
568
|
+
*
|
|
569
|
+
* Note: This is different from PingCastle's "Computer_Wrong_OU" which may
|
|
570
|
+
* check for different criteria. We check for computers in default container.
|
|
571
|
+
*/
|
|
572
|
+
export function detectComputerWrongOu(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
573
|
+
const affected = computers.filter((c) => {
|
|
574
|
+
// Check if computer is directly in the default Computers container
|
|
575
|
+
// DN format: CN=COMPUTER$,CN=Computers,DC=domain,DC=com
|
|
576
|
+
const dnLower = c.dn.toLowerCase();
|
|
577
|
+
|
|
578
|
+
// Check if it's in CN=Computers (not OU=)
|
|
579
|
+
// This catches: CN=PC01$,CN=Computers,DC=example,DC=com
|
|
580
|
+
const isInDefaultContainer = dnLower.includes(',cn=computers,dc=');
|
|
581
|
+
|
|
582
|
+
return isInDefaultContainer;
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
return {
|
|
586
|
+
type: 'COMPUTER_WRONG_OU',
|
|
587
|
+
severity: 'medium',
|
|
588
|
+
category: 'computers',
|
|
589
|
+
title: 'Computer in Default Container',
|
|
590
|
+
description:
|
|
591
|
+
'Computer in default Computers container instead of an organizational OU. ' +
|
|
592
|
+
'May not receive proper Group Policy and indicates lack of organization.',
|
|
593
|
+
count: affected.length,
|
|
594
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Check for computer with weak encryption (DES/RC4 only)
|
|
600
|
+
*/
|
|
601
|
+
export function detectComputerWeakEncryption(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
602
|
+
const affected = computers.filter((c) => {
|
|
603
|
+
const encTypes = (c as any)['msDS-SupportedEncryptionTypes'];
|
|
604
|
+
if (typeof encTypes !== 'number') return false;
|
|
605
|
+
// Check if only DES/RC4 (no AES)
|
|
606
|
+
return (encTypes & 0x18) === 0 && (encTypes & 0x7) !== 0;
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
return {
|
|
610
|
+
type: 'COMPUTER_WEAK_ENCRYPTION',
|
|
611
|
+
severity: 'medium',
|
|
612
|
+
category: 'computers',
|
|
613
|
+
title: 'Computer Weak Encryption',
|
|
614
|
+
description: 'Computer with weak encryption types (DES/RC4 only). Vulnerable to Kerberos downgrade attacks.',
|
|
615
|
+
count: affected.length,
|
|
616
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Check for computer description with sensitive data
|
|
622
|
+
*/
|
|
623
|
+
export function detectComputerDescriptionSensitive(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
624
|
+
const sensitivePatterns = [
|
|
625
|
+
/password|passwd|pwd/i,
|
|
626
|
+
/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/, // IP addresses
|
|
627
|
+
/admin|root|sa/i,
|
|
628
|
+
];
|
|
629
|
+
|
|
630
|
+
const affected = computers.filter((c) => {
|
|
631
|
+
const rawDesc = (c as any).description;
|
|
632
|
+
const description = ldapAttrToString(rawDesc);
|
|
633
|
+
if (!description) return false;
|
|
634
|
+
return sensitivePatterns.some((pattern) => pattern.test(description));
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
return {
|
|
638
|
+
type: 'COMPUTER_DESCRIPTION_SENSITIVE',
|
|
639
|
+
severity: 'medium',
|
|
640
|
+
category: 'computers',
|
|
641
|
+
title: 'Computer Description Sensitive',
|
|
642
|
+
description: 'Computer description contains sensitive data (passwords, IPs, etc.). Information disclosure.',
|
|
643
|
+
count: affected.length,
|
|
644
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Check for Pre-Windows 2000 computer accounts
|
|
650
|
+
*/
|
|
651
|
+
export function detectComputerPreWindows2000(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
652
|
+
const affected = computers.filter((c) => {
|
|
653
|
+
const os = ldapAttrToString(c.operatingSystem);
|
|
654
|
+
if (!os) return false;
|
|
655
|
+
return /Windows NT|Windows 2000|Windows 95|Windows 98/i.test(os);
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
return {
|
|
659
|
+
type: 'COMPUTER_PRE_WINDOWS_2000',
|
|
660
|
+
severity: 'medium',
|
|
661
|
+
category: 'computers',
|
|
662
|
+
title: 'Pre-Windows 2000 Computer',
|
|
663
|
+
description: 'Pre-Windows 2000 compatible computer. Weak security settings, potential compatibility exploits.',
|
|
664
|
+
count: affected.length,
|
|
665
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Check for computer with adminCount attribute
|
|
671
|
+
*/
|
|
672
|
+
export function detectComputerAdminCount(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
673
|
+
const affected = computers.filter((c) => {
|
|
674
|
+
const adminCount = (c as any).adminCount;
|
|
675
|
+
return adminCount === 1;
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
return {
|
|
679
|
+
type: 'COMPUTER_ADMIN_COUNT',
|
|
680
|
+
severity: 'low',
|
|
681
|
+
category: 'computers',
|
|
682
|
+
title: 'Computer adminCount Set',
|
|
683
|
+
description: 'Computer with adminCount attribute set to 1. May indicate current or former administrative privileges.',
|
|
684
|
+
count: affected.length,
|
|
685
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Check for computer with SMB signing disabled
|
|
691
|
+
*/
|
|
692
|
+
export function detectComputerSmbSigningDisabled(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
693
|
+
// Note: This would typically require querying computer configuration
|
|
694
|
+
// For now, we check for an attribute that would be set if SMB signing is disabled
|
|
695
|
+
const affected = computers.filter((c) => {
|
|
696
|
+
return 'smbSigningDisabled' in c && (c as any).smbSigningDisabled;
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
return {
|
|
700
|
+
type: 'COMPUTER_SMB_SIGNING_DISABLED',
|
|
701
|
+
severity: 'low',
|
|
702
|
+
category: 'computers',
|
|
703
|
+
title: 'Computer SMB Signing Disabled',
|
|
704
|
+
description: 'Computer with SMB signing disabled. Vulnerable to SMB relay attacks (informational finding).',
|
|
705
|
+
count: affected.length,
|
|
706
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Obsolete OS patterns for detection
|
|
712
|
+
*/
|
|
713
|
+
const OBSOLETE_OS_PATTERNS = [
|
|
714
|
+
{
|
|
715
|
+
pattern: /Windows XP/i,
|
|
716
|
+
type: 'COMPUTER_OS_OBSOLETE_XP',
|
|
717
|
+
severity: 'critical' as const,
|
|
718
|
+
osName: 'Windows XP',
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
pattern: /Server 2003/i,
|
|
722
|
+
type: 'COMPUTER_OS_OBSOLETE_2003',
|
|
723
|
+
severity: 'critical' as const,
|
|
724
|
+
osName: 'Windows Server 2003',
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
pattern: /Server 2008(?!\s*R2)/i, // 2008 but not 2008 R2
|
|
728
|
+
type: 'COMPUTER_OS_OBSOLETE_2008',
|
|
729
|
+
severity: 'high' as const,
|
|
730
|
+
osName: 'Windows Server 2008',
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
pattern: /Windows Vista/i,
|
|
734
|
+
type: 'COMPUTER_OS_OBSOLETE_VISTA',
|
|
735
|
+
severity: 'high' as const,
|
|
736
|
+
osName: 'Windows Vista',
|
|
737
|
+
},
|
|
738
|
+
];
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Check for computers running obsolete operating systems
|
|
742
|
+
* Returns multiple findings, one per OS type detected
|
|
743
|
+
*/
|
|
744
|
+
export function detectComputerObsoleteOS(
|
|
745
|
+
computers: ADComputer[],
|
|
746
|
+
includeDetails: boolean
|
|
747
|
+
): Finding[] {
|
|
748
|
+
return OBSOLETE_OS_PATTERNS.map(({ pattern, type, severity, osName }) => {
|
|
749
|
+
const affected = computers.filter((c) => {
|
|
750
|
+
const os = ldapAttrToString(c.operatingSystem);
|
|
751
|
+
return os && pattern.test(os);
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
return {
|
|
755
|
+
type,
|
|
756
|
+
severity,
|
|
757
|
+
category: 'computers' as const,
|
|
758
|
+
title: `Obsolete OS: ${osName}`,
|
|
759
|
+
description: `Computers running ${osName}, an unsupported operating system. No security patches available, making these systems highly vulnerable to exploitation.`,
|
|
760
|
+
count: affected.length,
|
|
761
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
762
|
+
};
|
|
763
|
+
}).filter((f) => f.count > 0);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Check for computers that have never logged on
|
|
768
|
+
* Enabled computers with no lastLogon date may indicate orphaned or unused accounts
|
|
769
|
+
*
|
|
770
|
+
* Checks both lastLogon (local to DC) and lastLogonTimestamp (replicated).
|
|
771
|
+
*/
|
|
772
|
+
export function detectComputerNeverLoggedOn(
|
|
773
|
+
computers: ADComputer[],
|
|
774
|
+
includeDetails: boolean
|
|
775
|
+
): Finding {
|
|
776
|
+
const affected = computers.filter((c) => {
|
|
777
|
+
if (!c.enabled) return false;
|
|
778
|
+
|
|
779
|
+
// Check both lastLogon and lastLogonTimestamp
|
|
780
|
+
const lastLogonTime = toTimestamp(c.lastLogon) ?? toTimestamp((c as any)['lastLogonTimestamp']);
|
|
781
|
+
|
|
782
|
+
// No logon time means never logged on
|
|
783
|
+
return !lastLogonTime;
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
return {
|
|
787
|
+
type: 'COMPUTER_NEVER_LOGGED_ON',
|
|
788
|
+
severity: 'medium',
|
|
789
|
+
category: 'computers',
|
|
790
|
+
title: 'Computer Never Logged On',
|
|
791
|
+
description:
|
|
792
|
+
'Enabled computer accounts that have never authenticated to the domain. These may be orphaned accounts from failed deployments or unused systems that should be cleaned up.',
|
|
793
|
+
count: affected.length,
|
|
794
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Check for pre-created computer accounts (disabled + never logged on)
|
|
800
|
+
* These are staging accounts that were created but never used.
|
|
801
|
+
* PingCastle: Computer_Pre_Created
|
|
802
|
+
*/
|
|
803
|
+
export function detectComputerPreCreated(
|
|
804
|
+
computers: ADComputer[],
|
|
805
|
+
includeDetails: boolean
|
|
806
|
+
): Finding {
|
|
807
|
+
const affected = computers.filter((c) => {
|
|
808
|
+
// Disabled computer that has never logged on
|
|
809
|
+
if (c.enabled) return false;
|
|
810
|
+
|
|
811
|
+
// Check if it has never logged on
|
|
812
|
+
const lastLogonTime = toTimestamp(c.lastLogon) ?? toTimestamp((c as any)['lastLogonTimestamp']);
|
|
813
|
+
return !lastLogonTime;
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
return {
|
|
817
|
+
type: 'COMPUTER_PRE_CREATED',
|
|
818
|
+
severity: 'medium',
|
|
819
|
+
category: 'computers',
|
|
820
|
+
title: 'Computer Pre-Created (Staging)',
|
|
821
|
+
description:
|
|
822
|
+
'Disabled computer accounts that have never logged on. These are staging accounts that were created but never deployed. Should be reviewed and cleaned up.',
|
|
823
|
+
count: affected.length,
|
|
824
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// ==================== PHASE 2C DETECTORS ====================
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Detect Domain Controllers not in Domain Controllers OU
|
|
832
|
+
* DCs should always be in the default Domain Controllers OU
|
|
833
|
+
*/
|
|
834
|
+
export function detectDcNotInDcOu(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
835
|
+
const dcPatterns = [/^DC\d*/i, /domain controller/i];
|
|
836
|
+
|
|
837
|
+
const affected = computers.filter((c) => {
|
|
838
|
+
// Check if it's a domain controller
|
|
839
|
+
const dnsName = typeof c.dNSHostName === 'string' ? c.dNSHostName : (Array.isArray(c.dNSHostName) ? c.dNSHostName[0] : '');
|
|
840
|
+
const isDC =
|
|
841
|
+
dcPatterns.some((p) => p.test(c.sAMAccountName || '')) ||
|
|
842
|
+
(dnsName && dnsName.toLowerCase().includes('dc')) ||
|
|
843
|
+
((c.userAccountControl ?? 0) & 0x2000) !== 0; // SERVER_TRUST_ACCOUNT flag
|
|
844
|
+
|
|
845
|
+
if (!isDC) return false;
|
|
846
|
+
|
|
847
|
+
// Check if it's in the Domain Controllers OU
|
|
848
|
+
const isInDCOU = c.dn.toLowerCase().includes('ou=domain controllers');
|
|
849
|
+
|
|
850
|
+
return !isInDCOU;
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
return {
|
|
854
|
+
type: 'DC_NOT_IN_DC_OU',
|
|
855
|
+
severity: 'high',
|
|
856
|
+
category: 'computers',
|
|
857
|
+
title: 'Domain Controller Not in Domain Controllers OU',
|
|
858
|
+
description:
|
|
859
|
+
'Domain Controllers found outside the Domain Controllers OU. This may indicate misconfiguration or an attempt to hide a rogue DC.',
|
|
860
|
+
count: affected.length,
|
|
861
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
862
|
+
details:
|
|
863
|
+
affected.length > 0
|
|
864
|
+
? {
|
|
865
|
+
recommendation:
|
|
866
|
+
'Move all Domain Controllers to the Domain Controllers OU for proper GPO application and management.',
|
|
867
|
+
risks: [
|
|
868
|
+
'GPOs targeting Domain Controllers OU may not apply',
|
|
869
|
+
'May indicate rogue or compromised DC',
|
|
870
|
+
'Security baselines may not be applied correctly',
|
|
871
|
+
],
|
|
872
|
+
}
|
|
873
|
+
: undefined,
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Detect duplicate SPNs across computers
|
|
879
|
+
* Duplicate SPNs cause authentication failures
|
|
880
|
+
*/
|
|
881
|
+
export function detectComputerDuplicateSpn(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
882
|
+
const spnMap = new Map<string, ADComputer[]>();
|
|
883
|
+
|
|
884
|
+
// Build SPN to computer mapping
|
|
885
|
+
for (const computer of computers) {
|
|
886
|
+
const spns = computer['servicePrincipalName'];
|
|
887
|
+
if (!spns || !Array.isArray(spns)) continue;
|
|
888
|
+
|
|
889
|
+
for (const spn of spns) {
|
|
890
|
+
const normalizedSpn = (spn as string).toLowerCase();
|
|
891
|
+
if (!spnMap.has(normalizedSpn)) {
|
|
892
|
+
spnMap.set(normalizedSpn, []);
|
|
893
|
+
}
|
|
894
|
+
spnMap.get(normalizedSpn)!.push(computer);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// Find duplicates
|
|
899
|
+
const duplicateComputers = new Set<ADComputer>();
|
|
900
|
+
const duplicateSpns: { spn: string; computers: string[] }[] = [];
|
|
901
|
+
|
|
902
|
+
for (const [spn, computersList] of spnMap.entries()) {
|
|
903
|
+
if (computersList.length > 1) {
|
|
904
|
+
duplicateSpns.push({
|
|
905
|
+
spn,
|
|
906
|
+
computers: computersList.map((c) => c.sAMAccountName || c.dn),
|
|
907
|
+
});
|
|
908
|
+
computersList.forEach((c) => duplicateComputers.add(c));
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
return {
|
|
913
|
+
type: 'COMPUTER_DUPLICATE_SPN',
|
|
914
|
+
severity: 'medium',
|
|
915
|
+
category: 'computers',
|
|
916
|
+
title: 'Duplicate SPNs Detected',
|
|
917
|
+
description:
|
|
918
|
+
'Multiple computers share the same Service Principal Name. This causes Kerberos authentication failures.',
|
|
919
|
+
count: duplicateComputers.size,
|
|
920
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(Array.from(duplicateComputers)) : undefined,
|
|
921
|
+
details:
|
|
922
|
+
duplicateSpns.length > 0
|
|
923
|
+
? {
|
|
924
|
+
duplicateSpns: duplicateSpns.slice(0, 10), // Show first 10
|
|
925
|
+
totalDuplicates: duplicateSpns.length,
|
|
926
|
+
recommendation:
|
|
927
|
+
'Remove duplicate SPNs using setspn -D. Ensure each SPN is unique across the domain.',
|
|
928
|
+
}
|
|
929
|
+
: undefined,
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Detect servers without local admin groups properly configured
|
|
935
|
+
* Servers should have documented local administrators
|
|
936
|
+
*/
|
|
937
|
+
export function detectServerNoAdminGroup(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
938
|
+
const serverPatterns = [/server/i, /^srv/i, /^sql/i, /^web/i, /^app/i, /^db/i, /^file/i];
|
|
939
|
+
const serverOsPatterns = [/server/i];
|
|
940
|
+
|
|
941
|
+
const affected = computers.filter((c) => {
|
|
942
|
+
// Check if it's a server
|
|
943
|
+
const os = ldapAttrToString(c.operatingSystem);
|
|
944
|
+
const isServer =
|
|
945
|
+
serverPatterns.some((p) => p.test(c.sAMAccountName || '')) ||
|
|
946
|
+
(os && serverOsPatterns.some((p) => p.test(os)));
|
|
947
|
+
|
|
948
|
+
if (!isServer) return false;
|
|
949
|
+
|
|
950
|
+
// Check if it's enabled
|
|
951
|
+
if (!c.enabled) return false;
|
|
952
|
+
|
|
953
|
+
// Check if there's a corresponding admin group (naming convention: ServerName-Admins or similar)
|
|
954
|
+
// This is a heuristic - we flag servers that might not have managed admin groups
|
|
955
|
+
// In practice, this would need to be verified against a CMDB or documented standard
|
|
956
|
+
|
|
957
|
+
// Flag if it's a server with description indicating it's unmanaged
|
|
958
|
+
const rawDesc = c['description'];
|
|
959
|
+
const description = (typeof rawDesc === 'string' ? rawDesc : (Array.isArray(rawDesc) ? rawDesc[0] : '') || '').toLowerCase();
|
|
960
|
+
const isUnmanaged =
|
|
961
|
+
description.includes('unmanaged') ||
|
|
962
|
+
description.includes('legacy') ||
|
|
963
|
+
description.includes('deprecated');
|
|
964
|
+
|
|
965
|
+
return isUnmanaged;
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
return {
|
|
969
|
+
type: 'SERVER_NO_ADMIN_GROUP',
|
|
970
|
+
severity: 'medium',
|
|
971
|
+
category: 'computers',
|
|
972
|
+
title: 'Server Without Managed Admin Group',
|
|
973
|
+
description:
|
|
974
|
+
'Servers identified as unmanaged or without proper administrative group documentation. Local admin access may not be properly controlled.',
|
|
975
|
+
count: affected.length,
|
|
976
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
977
|
+
details:
|
|
978
|
+
affected.length > 0
|
|
979
|
+
? {
|
|
980
|
+
recommendation:
|
|
981
|
+
'Create dedicated admin groups for each server (e.g., SRV01-Admins) and document access.',
|
|
982
|
+
risks: [
|
|
983
|
+
'Unknown administrators may have access',
|
|
984
|
+
'Audit trail for admin actions may be incomplete',
|
|
985
|
+
'Compliance violations for access management',
|
|
986
|
+
],
|
|
987
|
+
}
|
|
988
|
+
: undefined,
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Detect workstations in server OUs
|
|
994
|
+
* Workstations should be in workstation OUs, not server OUs
|
|
995
|
+
*/
|
|
996
|
+
export function detectWorkstationInServerOu(computers: ADComputer[], includeDetails: boolean): Finding {
|
|
997
|
+
const serverOuPatterns = [/ou=servers/i, /ou=server/i, /ou=datacenter/i, /ou=production/i];
|
|
998
|
+
const workstationOsPatterns = [/windows 10/i, /windows 11/i, /windows 7/i, /windows 8/i];
|
|
999
|
+
const workstationNamePatterns = [/^ws/i, /^pc/i, /^laptop/i, /^desktop/i, /^nb/i];
|
|
1000
|
+
|
|
1001
|
+
const affected = computers.filter((c) => {
|
|
1002
|
+
// Check if it's in a server OU
|
|
1003
|
+
const isInServerOU = serverOuPatterns.some((p) => p.test(c.dn));
|
|
1004
|
+
if (!isInServerOU) return false;
|
|
1005
|
+
|
|
1006
|
+
// Check if it's actually a workstation (not a server)
|
|
1007
|
+
const os = ldapAttrToString(c.operatingSystem);
|
|
1008
|
+
const isWorkstation =
|
|
1009
|
+
workstationNamePatterns.some((p) => p.test(c.sAMAccountName || '')) ||
|
|
1010
|
+
(os && workstationOsPatterns.some((p) => p.test(os)));
|
|
1011
|
+
|
|
1012
|
+
return isWorkstation;
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
return {
|
|
1016
|
+
type: 'WORKSTATION_IN_SERVER_OU',
|
|
1017
|
+
severity: 'low',
|
|
1018
|
+
category: 'computers',
|
|
1019
|
+
title: 'Workstation in Server OU',
|
|
1020
|
+
description:
|
|
1021
|
+
'Workstation computers found in server OUs. This causes incorrect GPO application and may indicate organizational issues.',
|
|
1022
|
+
count: affected.length,
|
|
1023
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
1024
|
+
details:
|
|
1025
|
+
affected.length > 0
|
|
1026
|
+
? {
|
|
1027
|
+
recommendation: 'Move workstations to appropriate workstation OUs for proper GPO targeting.',
|
|
1028
|
+
impact: 'Server-targeted GPOs may apply to workstations causing configuration issues.',
|
|
1029
|
+
}
|
|
1030
|
+
: undefined,
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Detect computers without BitLocker encryption
|
|
1036
|
+
*
|
|
1037
|
+
* Computers without disk encryption are vulnerable to physical attacks
|
|
1038
|
+
* where hard drives can be removed and read.
|
|
1039
|
+
*
|
|
1040
|
+
* @param computers - Array of AD computers
|
|
1041
|
+
* @param includeDetails - Whether to include affected entity details
|
|
1042
|
+
* @returns Finding for COMPUTER_NO_BITLOCKER
|
|
1043
|
+
*/
|
|
1044
|
+
export function detectComputerNoBitlocker(
|
|
1045
|
+
computers: ADComputer[],
|
|
1046
|
+
includeDetails: boolean
|
|
1047
|
+
): Finding {
|
|
1048
|
+
// BitLocker status is stored in ms-FVE-RecoveryInformation objects under the computer
|
|
1049
|
+
// We can check for msDS-isGC or look for recovery info attributes
|
|
1050
|
+
// For now, we check servers (not workstations) that likely need encryption
|
|
1051
|
+
const serversWithoutBitlocker = computers.filter((c) => {
|
|
1052
|
+
if (!c.enabled) return false;
|
|
1053
|
+
const os = ldapAttrToString(c.operatingSystem).toLowerCase();
|
|
1054
|
+
const isServer = os.includes('server');
|
|
1055
|
+
// Check if BitLocker recovery info exists (would need separate query)
|
|
1056
|
+
// For now, flag servers that might need BitLocker review
|
|
1057
|
+
const hasBitlockerInfo = (c as Record<string, unknown>)['ms-FVE-RecoveryInformation'] !== undefined;
|
|
1058
|
+
return isServer && !hasBitlockerInfo;
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
return {
|
|
1062
|
+
type: 'COMPUTER_NO_BITLOCKER',
|
|
1063
|
+
severity: 'high',
|
|
1064
|
+
category: 'computers',
|
|
1065
|
+
title: 'BitLocker Not Detected',
|
|
1066
|
+
description:
|
|
1067
|
+
'Servers without BitLocker recovery information in AD. ' +
|
|
1068
|
+
'Unencrypted disks are vulnerable to physical theft and offline attacks.',
|
|
1069
|
+
count: serversWithoutBitlocker.length,
|
|
1070
|
+
affectedEntities: includeDetails
|
|
1071
|
+
? toAffectedComputerEntities(serversWithoutBitlocker)
|
|
1072
|
+
: undefined,
|
|
1073
|
+
details: {
|
|
1074
|
+
recommendation:
|
|
1075
|
+
'Enable BitLocker on all servers and configure AD backup of recovery keys.',
|
|
1076
|
+
note: 'This detection checks for ms-FVE-RecoveryInformation in AD. Standalone BitLocker may not be detected.',
|
|
1077
|
+
},
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Detect computers using legacy protocols
|
|
1083
|
+
*
|
|
1084
|
+
* Computers configured to use legacy/insecure protocols like SMBv1,
|
|
1085
|
+
* NTLMv1, or LM are vulnerable to various attacks.
|
|
1086
|
+
*
|
|
1087
|
+
* @param computers - Array of AD computers
|
|
1088
|
+
* @param includeDetails - Whether to include affected entity details
|
|
1089
|
+
* @returns Finding for COMPUTER_LEGACY_PROTOCOL
|
|
1090
|
+
*/
|
|
1091
|
+
export function detectComputerLegacyProtocol(
|
|
1092
|
+
computers: ADComputer[],
|
|
1093
|
+
includeDetails: boolean
|
|
1094
|
+
): Finding {
|
|
1095
|
+
// Check for computers with legacy OS that likely use legacy protocols
|
|
1096
|
+
const legacyOsPatterns = [
|
|
1097
|
+
/Windows XP/i,
|
|
1098
|
+
/Windows 2000/i,
|
|
1099
|
+
/Windows NT/i,
|
|
1100
|
+
/Server 2003/i,
|
|
1101
|
+
/Windows Vista/i,
|
|
1102
|
+
];
|
|
1103
|
+
|
|
1104
|
+
// Also check msDS-SupportedEncryptionTypes for weak encryption
|
|
1105
|
+
const affected = computers.filter((c) => {
|
|
1106
|
+
if (!c.enabled) return false;
|
|
1107
|
+
const os = ldapAttrToString(c.operatingSystem);
|
|
1108
|
+
|
|
1109
|
+
// Legacy OS definitely uses legacy protocols
|
|
1110
|
+
const hasLegacyOs = legacyOsPatterns.some((p) => p.test(os));
|
|
1111
|
+
if (hasLegacyOs) return true;
|
|
1112
|
+
|
|
1113
|
+
// Check supported encryption types (if only DES/RC4)
|
|
1114
|
+
const encTypes = (c as Record<string, unknown>)['msDS-SupportedEncryptionTypes'] as number | undefined;
|
|
1115
|
+
if (encTypes !== undefined) {
|
|
1116
|
+
// If only DES (0x1, 0x2) or RC4 (0x4) are supported, it's legacy
|
|
1117
|
+
const onlyLegacy = (encTypes & 0x18) === 0; // No AES128 (0x8) or AES256 (0x10)
|
|
1118
|
+
if (onlyLegacy && encTypes > 0) return true;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
return false;
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
return {
|
|
1125
|
+
type: 'COMPUTER_LEGACY_PROTOCOL',
|
|
1126
|
+
severity: 'medium',
|
|
1127
|
+
category: 'computers',
|
|
1128
|
+
title: 'Legacy Protocol Support',
|
|
1129
|
+
description:
|
|
1130
|
+
'Computers configured to use legacy protocols (SMBv1, NTLMv1, DES/RC4 only). ' +
|
|
1131
|
+
'These are vulnerable to relay attacks, credential theft, and encryption downgrade.',
|
|
1132
|
+
count: affected.length,
|
|
1133
|
+
affectedEntities: includeDetails ? toAffectedComputerEntities(affected) : undefined,
|
|
1134
|
+
details: {
|
|
1135
|
+
recommendation:
|
|
1136
|
+
'Upgrade legacy systems or disable legacy protocols. Enable AES encryption support.',
|
|
1137
|
+
protocols: ['SMBv1', 'NTLMv1', 'DES', 'RC4'],
|
|
1138
|
+
},
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/**
|
|
1143
|
+
* Detect all computer-related vulnerabilities
|
|
1144
|
+
*/
|
|
1145
|
+
export function detectComputersVulnerabilities(
|
|
1146
|
+
computers: ADComputer[],
|
|
1147
|
+
includeDetails: boolean
|
|
1148
|
+
): Finding[] {
|
|
1149
|
+
// Get obsolete OS findings (returns array)
|
|
1150
|
+
const obsoleteOsFindings = detectComputerObsoleteOS(computers, includeDetails);
|
|
1151
|
+
|
|
1152
|
+
return [
|
|
1153
|
+
...obsoleteOsFindings,
|
|
1154
|
+
detectComputerNeverLoggedOn(computers, includeDetails),
|
|
1155
|
+
detectComputerPreCreated(computers, includeDetails),
|
|
1156
|
+
detectComputerConstrainedDelegation(computers, includeDetails),
|
|
1157
|
+
detectComputerRbcd(computers, includeDetails),
|
|
1158
|
+
detectComputerInAdminGroup(computers, includeDetails),
|
|
1159
|
+
detectComputerDcsyncRights(computers, includeDetails),
|
|
1160
|
+
detectComputerUnconstrainedDelegation(computers, includeDetails),
|
|
1161
|
+
detectComputerStaleInactive(computers, includeDetails),
|
|
1162
|
+
detectComputerPasswordOld(computers, includeDetails),
|
|
1163
|
+
detectComputerWithSpns(computers, includeDetails),
|
|
1164
|
+
detectComputerNoLaps(computers, includeDetails),
|
|
1165
|
+
detectComputerAclAbuse(computers, includeDetails),
|
|
1166
|
+
detectComputerDisabledNotDeleted(computers, includeDetails),
|
|
1167
|
+
detectComputerWrongOu(computers, includeDetails),
|
|
1168
|
+
detectComputerWeakEncryption(computers, includeDetails),
|
|
1169
|
+
detectComputerDescriptionSensitive(computers, includeDetails),
|
|
1170
|
+
detectComputerPreWindows2000(computers, includeDetails),
|
|
1171
|
+
detectComputerAdminCount(computers, includeDetails),
|
|
1172
|
+
detectComputerSmbSigningDisabled(computers, includeDetails),
|
|
1173
|
+
// Phase 2C: Enhanced detections
|
|
1174
|
+
detectDcNotInDcOu(computers, includeDetails),
|
|
1175
|
+
detectComputerDuplicateSpn(computers, includeDetails),
|
|
1176
|
+
detectServerNoAdminGroup(computers, includeDetails),
|
|
1177
|
+
detectWorkstationInServerOu(computers, includeDetails),
|
|
1178
|
+
// Phase 4: Advanced detections
|
|
1179
|
+
detectComputerNoBitlocker(computers, includeDetails),
|
|
1180
|
+
detectComputerLegacyProtocol(computers, includeDetails),
|
|
1181
|
+
].filter((finding) => {
|
|
1182
|
+
// Include findings with count > 0
|
|
1183
|
+
if (finding.count > 0) return true;
|
|
1184
|
+
// Also include findings with debug details (for troubleshooting)
|
|
1185
|
+
if (finding.details?.['debug']) return true;
|
|
1186
|
+
return false;
|
|
1187
|
+
});
|
|
1188
|
+
}
|