关于 instance-security
面向 ServiceNow 实例安全管理的技能,涵盖认证、授权、会话与安全加固,并提供 ES5 示例及相关 MCP 工具工作流。
主要功能
- 01密码策略与 MFA 检查
- 02会话安全检查
- 03输入校验与注入防护
- 04安全属性与事件审计
- 05安全健康检查
适用场景
- 01检查实例密码与会话设置
- 02审查 ACL 和安全事件
- 03测试安全脚本与健康检查
MCP 相关
文档介绍了通过 snow_property_manage、snow_execute_script 和 snow_query_table 查询安全设置、测试脚本及审查 ACL。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ serac-labs/ serac --skill instance-security安装后在 Claude Code 中输入/instance-security确认已加载。
详细介绍
ServiceNow 实例安全
实例安全涵盖身份验证、授权和安全加固。
安全层
Network Security
↓
Authentication (SSO, MFA)
↓
Session Management
↓
Authorization (ACLs, Roles)
↓
Data Protection
↓
Audit & Logging
关键表
| 表 | 用途 |
|---|---|
sys_user | 用户帐户 |
sys_user_role | 角色 |
sys_security_acl | 访问控制 |
sysevent_log | 安全事件 |
sys_properties | 安全设置 |
身份验证安全(ES5)
密码策略配置
// Check password strength (ES5 ONLY!)
function validatePasswordStrength(password) {
var issues = []
// Minimum length
var minLength = parseInt(gs.getProperty("glide.security.password.min_length", "8"), 10)
if (password.length < minLength) {
issues.push("Password must be at least " + minLength + " characters")
}
// Complexity requirements
if (!/[A-Z]/.test(password)) {
issues.push("Password must contain uppercase letter")
}
if (!/[a-z]/.test(password)) {
issues.push("Password must contain lowercase letter")
}
if (!/[0-9]/.test(password)) {
issues.push("Password must contain number")
}
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
issues.push("Password must contain special character")
}
return {
valid: issues.length === 0,
issues: issues,
}
}
会话安全
// Check session validity (ES5 ONLY!)
function isSessionSecure() {
var session = gs.getSession()
// Check session age
var maxAge = parseInt(gs.getProperty("glide.security.session.timeout", "60"), 10)
var sessionAge = session.getSessionAge() / 60000 // Convert to minutes
if (sessionAge > maxAge) {
return { valid: false, reason: "Session expired" }
}
// Check for session fixation
var clientIP = gs.getSession().getClientIP()
var originalIP = session.getValue("original_ip")
if (originalIP && clientIP !== originalIP) {
return { valid: false, reason: "IP address changed" }
}
return { valid: true }
}
// Force re-authentication
function requireReauthentication(reason) {
gs.getSession().invalidate()
gs.addErrorMessage(reason)
// Redirect to login
response.sendRedirect("/login.do")
}
MFA 实现(ES5)
检查 MFA 状态
// Check if user has MFA enabled (ES5 ONLY!)
function hasMFAEnabled(userSysId) {
var user = new GlideRecord("sys_user")
if (!user.get(userSysId)) {
return false
}
// Check MFA settings
var mfa = new GlideRecord("sys_user_mfa")
mfa.addQuery("user", userSysId)
mfa.addQuery("active", true)
mfa.query()
return mfa.hasNext()
}
// Enforce MFA for sensitive operations
function requireMFA(operation) {
var userId = gs.getUserID()
if (!hasMFAEnabled(userId)) {
gs.addErrorMessage("MFA required for " + operation)
return false
}
// Check if MFA verified this session
var session = gs.getSession()
var mfaVerified = session.getValue("mfa_verified")
if (mfaVerified !== "true") {
// Trigger MFA challenge
gs.eventQueue("user.mfa.challenge", null, userId, operation)
return false
}
return true
}
输入验证(ES5)
防止 XSS
// Sanitize user input (ES5 ONLY!)
function sanitizeInput(input) {
if (!input) return ""
// Encode HTML entities
var sanitized = input
.toString()
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'")
return sanitized
}
// Validate and sanitize before storage
function validateUserInput(tableName, fieldName, value) {
// Check field type
var dict = new GlideRecord("sys_dictionary")
dict.addQuery("name", tableName)
dict.addQuery("element", fieldName)
dict.query()
if (!dict.next()) {
return { valid: false, error: "Field not found" }
}
var fieldType = dict.getValue("internal_type")
// Validate based on type
if (fieldType === "string" || fieldType === "html") {
// Check for script injection
if (/<script/i.test(value)) {
return { valid: false, error: "Script tags not allowed" }
}
// Check for event handlers
if (/on\w+\s*=/i.test(value)) {
return { valid: false, error: "Event handlers not allowed" }
}
}
return { valid: true, sanitized: sanitizeInput(value) }
}
防止 SQL 注入
// Safe query building (ES5 ONLY!)
// NEVER concatenate user input directly into queries
// BAD - Vulnerable to injection
// gr.addEncodedQuery('name=' + userInput);
// GOOD - Use parameterized queries
function safeQuery(tableName, fieldName, value) {
var gr = new GlideRecord(tableName)
// GlideRecord methods handle escaping
gr.addQuery(fieldName, value)
gr.query()
return gr
}
// For encoded queries, validate input
function safeEncodedQuery(tableName, userQuery) {
// Whitelist allowed fields
var allowedFields = ["number", "short_description", "state", "priority"]
// Parse and validate query
var parts = userQuery.split("^")
var safeQuery = []
for (var i = 0; i < parts.length; i++) {
var part = parts[i]
var match = part.match(/^(\w+)(=|!=|LIKE|CONTAINS)(.+)$/)
if (match) {
var field = match[1]
if (allowedFields.indexOf(field) !== -1) {
safeQuery.push(part)
}
}
}
var gr = new GlideRecord(tableName)
if (safeQuery.length > 0) {
gr.addEncodedQuery(safeQuery.join("^"))
}
gr.query()
return gr
}
安全属性(ES5)
关键安全设置
// Get security-related properties (ES5 ONLY!)
function getSecuritySettings() {
return {
// Session settings
session_timeout: gs.getProperty("glide.security.session.timeout"),
session_cookie_secure: gs.getProperty("glide.security.session.cookie.secure"),
// Password settings
password_min_length: gs.getProperty("glide.security.password.min_length"),
password_history: gs.getProperty("glide.security.password.history"),
password_expiry: gs.getProperty("glide.security.password.expiry"),
// Login settings
max_failed_logins: gs.getProperty("glide.security.password.max_failed_logins"),
lockout_duration: gs.getProperty("glide.security.password.lockout_duration"),
// General security
csrf_protection: gs.getProperty("glide.security.csrf.strict_validation"),
xss_protection: gs.getProperty("glide.security.xss.strict"),
}
}
// Update security property
function setSecurityProperty(name, value, requireRestart) {
gs.setProperty(name, value)
if (requireRestart) {
gs.warn("Security property changed: " + name + ". Restart may be required.")
}
// Log security change
gs.eventQueue("security.property.changed", null, name, value)
}
安全审计(ES5)
记录安全事件
// Log security event (ES5 ONLY!)
function logSecurityEvent(eventType, details) {
var log = new GlideRecord("syslog")
log.initialize()
log.setValue("level", "warning")
log.setValue("source", "Security")
log.setValue("message", eventType + ": " + JSON.stringify(details))
log.insert()
// Also queue for security monitoring
gs.eventQueue("security.event", null, eventType, JSON.stringify(details))
}
// Track failed login attempts
function trackFailedLogin(username, ipAddress) {
logSecurityEvent("failed_login", {
username: username,
ip: ipAddress,
timestamp: new GlideDateTime().getDisplayValue(),
})
// Check for brute force
var recentFailures = countRecentFailures(username, 5) // Last 5 minutes
var maxFailures = parseInt(gs.getProperty("glide.security.password.max_failed_logins", "5"), 10)
if (recentFailures >= maxFailures) {
lockAccount(username)
logSecurityEvent("account_locked", {
username: username,
reason: "Too many failed login attempts",
failures: recentFailures,
})
}
}
安全运行状况检查
// Check instance security health (ES5 ONLY!)
function securityHealthCheck() {
var issues = []
// Check for default admin password
var admin = new GlideRecord("sys_user")
if (admin.get("user_name", "admin")) {
if (admin.getValue("password") === gs.getProperty("glide.security.default.admin.password")) {
issues.push({ severity: "critical", issue: "Default admin password not changed" })
}
}
// Check session timeout
var timeout = parseInt(gs.getProperty("glide.security.session.timeout", "0"), 10)
if (timeout === 0 || timeout > 60) {
issues.push({ severity: "high", issue: "Session timeout too long or disabled" })
}
// Check password policy
var minLength = parseInt(gs.getProperty("glide.security.password.min_length", "0"), 10)
if (minLength < 12) {
issues.push({ severity: "medium", issue: "Password minimum length less than 12" })
}
// Check HTTPS enforcement
if (gs.getProperty("glide.security.session.cookie.secure") !== "true") {
issues.push({ severity: "high", issue: "Secure cookies not enforced" })
}
return {
healthy: issues.length === 0,
issues: issues,
}
}
MCP 工具集成
可用工具
| 工具 | 用途 |
|---|---|
snow_property_manage (action=get) | 检查安全属性 |
snow_execute_script | 测试安全脚本 |
snow_query_table | 审查 ACL |
示例工作流
// 1. Check security properties
await snow_property_manage({
action: "get",
name: "glide.security.session.timeout",
})
// 2. Run security health check
await snow_execute_script({
script: `
var health = securityHealthCheck();
gs.info(JSON.stringify(health));
`,
})
// 3. Review access controls
// truncate_output is REQUIRED here: `script` is on snow_query_table's
// truncation list, so without it every ACL script comes back as its first
// 200 characters plus "... [truncated, N chars total]".
await snow_query_table({
table: "sys_security_acl",
query: "active=true^admin_overrides=true",
fields: ["name", "operation", "type", "script"],
truncate_output: false,
})
如需完整读取一个 ACL 脚本,且完全不受截断规则影响,请使用
snow_get_by_sysid({ table: "sys_security_acl", sys_id })——该方法不会进行截断。关于 Table API 读取会返回和不会返回的其他内容,请参阅
table-api-reads 技能。
最佳实践
- 强密码 - 强制实施复杂度要求
- MFA - 为特权用户启用
- 会话超时 - 15-30 分钟
- 输入验证 - 对所有输入进行清理
- 最小权限 - 仅分配最低限度的角色
- 审计日志记录 - 记录安全事件
- 定期审查 - 开展安全评估
- 仅使用 ES5 - 不使用现代 JavaScript 语法
评论
支持 Markdown