关于 boxlang-security
boxlang-security 是面向 BoxLang 应用开发者的安全技能,介绍运行时控制与常见漏洞防护方法,并涵盖密钥保护和生产环境加固。
主要功能
- 01运行时安全配置
- 02防止 SQL 注入
- 03按上下文编码输出
- 04CSRF 与上传防护
- 05密钥和身份验证实践
- 06生产环境加固清单
适用场景
- 01检查 BoxLang 应用安全性
- 02保护表单、上传和远程函数
- 03为生产环境部署 BoxLang 应用做准备
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ ortus-boxlang/ skills --skill boxlang-security安装后在 Claude Code 中输入/boxlang-security确认已加载。
详细介绍
BoxLang 安全
BoxLang 是一个 AI 原生软件生产力平台,可用于构建、现代化改造和运行应用程序,让开发者与 AI Agent 协同工作。
概述
BoxLang 继承了 ColdFusion/CFML 和 JVM 的强大功能,也继承了它们历史遗留的攻击面。本技能介绍安全模式和运行时控制措施,帮助从头开始构建安全的应用程序。
运行时安全配置(boxlang.json)
"security": {
// Regex patterns — prevent dangerous Java class access
"disallowedImports": [
"java\\.lang\\.(ProcessBuilder|Runtime)",
"java\\.io\\.(FileWriter|PrintWriter)",
"java\\.lang\\.reflect\\."
],
// BIFs that should be disabled in production web apps
"disallowedBifs": [
"createObject",
"systemExecute",
"getSystemInfo"
],
// Components that should be disabled in production
"disallowedComponents": [
"execute"
],
// Prevent system properties from leaking into server scope
"populateServerSystemScope": false,
// Explicit upload whitelist (overrides disallowed list)
"allowedFileOperationExtensions": [ "jpg", "png", "pdf", "docx" ],
// Dangerous executable extensions blocked on file upload and copy/move
"disallowedFileOperationExtensions": [
"exe", "bat", "sh", "bx", "bxm", "bxs", "php", "jsp", "jar", "dll"
]
}
注入防护
SQL 注入
绝不要通过字符串拼接来构建 SQL。始终使用 QueryParam:
// BAD — SQL injection vulnerability
var result = queryExecute(
"SELECT * FROM users WHERE email = '#email#'"
)
// GOOD — parameterized query (prevents SQL injection)
var result = queryExecute(
"SELECT * FROM users WHERE email = :email",
{ email: { value: arguments.email, cfsqltype: "cf_sql_varchar" } }
)
// GOOD — struct shorthand
var result = queryExecute(
"SELECT * FROM users WHERE id = :id AND status = :status",
{
id: { value: arguments.id, cfsqltype: "cf_sql_integer" },
status: { value: arguments.status, cfsqltype: "cf_sql_varchar" }
}
)
跨站脚本攻击(XSS)
在 HTML 上下文中对所有用户提供的输出进行编码:
// BAD — raw user input rendered in HTML
<bx:output>#userComment#</bx:output>
// GOOD — HTML encode before output
<bx:output>#encodeForHTML( userComment )#</bx:output>
// For HTML attribute context
<input value="#encodeForHTMLAttribute( userInput )#">
// For JavaScript context
<script>var name = "#encodeForJavaScript( userName )#";</script>
// For URL context
<a href="/search?q=#encodeForURL( searchTerm )#">Search</a>
根据上下文使用正确的编码函数:
| 上下文 | 函数 |
|---|---|
| HTML 内容 | encodeForHTML() |
| HTML 属性 | encodeForHTMLAttribute() |
| JavaScript 字符串 | encodeForJavaScript() |
| URL 参数 | encodeForURL() |
| CSS 值 | encodeForCSS() |
跨站请求伪造(CSRF)
// Generate a token per session/form
var token = generateSecureToken()
session.csrfToken = token
// In the form template
<input type="hidden" name="csrfToken" value="#session.csrfToken#">
// Validate on POST
function onRequestStart( required string targetPage ) {
if ( cgi.request_method == "POST" ) {
if ( !structKeyExists( form, "csrfToken" ) ||
form.csrfToken != session.csrfToken ) {
throw( type="SecurityException", message="CSRF validation failed" )
}
}
return true
}
文件上传安全
处理文件上传之前,务必先进行验证:
function handleUpload( required string fieldName ) {
// Restrict to explicit allowed types and extensions
var upload = fileUpload(
destination = getTempDirectory(),
filefield = arguments.fieldName,
accept = "image/jpeg,image/png,application/pdf",
nameconflict = "makeunique"
)
// Validate extension explicitly regardless of MIME type
var allowedExtensions = [ "jpg", "jpeg", "png", "pdf" ]
if ( !allowedExtensions.contains( lCase( upload.serverFileExt ) ) ) {
fileDelete( upload.serverDirectory & "/" & upload.serverFile )
throw( type="SecurityException", message="Disallowed file type" )
}
// Validate file size
var maxSizeBytes = 5 * 1024 * 1024 // 5 MB
if ( upload.fileSize > maxSizeBytes ) {
fileDelete( upload.serverDirectory & "/" & upload.serverFile )
throw( type="SecurityException", message="File too large" )
}
// Store outside the webroot, serve via handler — never serve raw uploads
var safePath = expandPath( "/private/uploads/" ) & createUUID() & "." & upload.serverFileExt
fileMove( upload.serverDirectory & "/" & upload.serverFile, safePath )
return safePath
}
密钥管理
绝不要将密钥硬编码。通过 BoxLang 配置使用环境变量:
// boxlang.json — uses env var substitution
"datasources": {
"mainDB": {
"driver": "postgresql",
"host": "${env.DB_HOST:localhost}",
"username": "${env.DB_USERNAME:app}",
"password": "${env.DB_PASSWORD}" // No default — fail fast if missing
}
}
在代码中通过环境变量访问(不要硬编码):
// GOOD — read from environment
var apiKey = server.system.environment.PAYMENT_API_KEY ?: ""
var jwtSecret = server.system.environment.JWT_SECRET ?: ""
if ( apiKey.isEmpty() ) {
throw( type="ConfigException", message="PAYMENT_API_KEY is not configured" )
}
身份验证模式
密码哈希
// GOOD — bcrypt-style hash (use bx-bcrypt module or GeneratePBKDFKey)
var salt = generateSecureToken( 32 )
var hashed = hash( arguments.password & salt, "SHA-512" )
// Even better — use a dedicated BCrypt module
// install bx-bcrypt
var bcrypt = new BCrypt()
var hashed = bcrypt.hashpw( plainPassword, bcrypt.gensalt() )
// Verify
var isMatch = bcrypt.checkpw( loginPassword, storedHash )
JWT / 令牌验证
// Always validate before trusting any token data
function validateToken( required string token ) {
try {
// Use the bx-jwt module
var claims = jwtService.decode( arguments.token )
// Validate expiry
if ( claims.exp < epochSecond() ) {
throw( type="AuthException", message="Token expired" )
}
return claims
} catch ( "JWT" e ) {
throw( type="AuthException", message="Invalid token" )
}
}
会话安全
在 Application.bx 中配置会话设置:
class {
this.name = "MyApp"
this.sessionManagement = true
this.sessionTimeout = createTimeSpan( 0, 2, 0, 0 ) // 2 hours
// Rotate session ID after login (session fixation prevention)
function onLogin( required struct user ) {
var oldData = duplicate( session )
sessionRotate() // BoxLang BIF to regenerate session ID
structDelete( session, "ALL" )
structAppend( session, oldData )
session.userId = user.id
session.isLoggedIn = true
}
}
路径遍历防护
验证所有用户提供的文件路径:
function readFile( required string filename ) {
var safeBase = expandPath( "/app/uploads/" )
var fullPath = safeBase & arguments.filename
var cleanPath = createObject( "java", "java.io.File" ).init( fullPath ).getCanonicalPath()
// Ensure the resolved path is within the allowed directory
if ( !cleanPath.startsWith( safeBase ) ) {
throw( type="SecurityException", message="Path traversal detected" )
}
return fileRead( cleanPath )
}
输入验证
// Validate and sanitize input at the boundary
function processContactForm( required struct form ) {
var errors = []
// Length limits
if ( form.name.len() > 100 ) errors.append( "Name too long" )
// Email format
if ( !isValid( "email", form.email ) ) errors.append( "Invalid email" )
// Numeric ranges
if ( !isNumeric( form.age ) || form.age < 0 || form.age > 150 ) {
errors.append( "Invalid age" )
}
// Allowlist for enum-style fields
var validTopics = [ "support", "sales", "billing" ]
if ( !validTopics.contains( lCase( form.topic ) ) ) {
errors.append( "Invalid topic" )
}
if ( !errors.isEmpty() ) {
throw( type="ValidationException", message=errors.toList( ", " ) )
}
}
远程函数公开(Web 服务)
将类函数公开为可通过 Web 访问的端点时,应明确控制访问权限:
class {
remote struct function getUser( required numeric id ) returnformat="json" {
// Always authenticate remote calls
if ( !isAuthenticated() ) {
httpSetResponseStatus( 401 )
return { error: "Unauthorized" }
}
// Validate and sanitize the id parameter
if ( !isValid( "integer", arguments.id ) || arguments.id < 1 ) {
httpSetResponseStatus( 400 )
return { error: "Invalid user ID" }
}
return userService.getById( arguments.id )
}
}
生产环境加固清单
- 除非必要,否则将
populateServerSystemScope: false - 配置
disallowedBifs,移除危险的 BIF(如不需要,可移除systemExecute、createObject) - 配置
disallowedImports,限制对 Java 类的访问 - 设置
disallowedComponents: ["execute"],防止执行操作系统命令 - 所有密钥均通过环境变量提供——代码或提交的配置中不得包含密钥
- 所有 SQL 均使用参数化查询(
queryParam) - 使用适合相应上下文的编码函数,对所有用户输出进行 HTML 编码
- 所有会更改状态的表单均使用 CSRF 令牌
- 文件上传存储在 Web 根目录之外,并按扩展名和大小进行验证
- 配置会话超时;权限提升时轮换会话
- 在生产环境中启用
trustedCache: true和classResolverCache: true - 在反向代理或容器层强制使用 HTTPS
- 保持依赖模块为最新版本(检查
box outdated)
评论
支持 Markdown