mcpField
B
boxlang-security

boxlang-security

github.com/ortus-boxlang安全045 次安装6 天前更新
代码仓库

关于 boxlang-security

boxlang-security 是面向 BoxLang 应用开发者的安全技能,介绍运行时控制与常见漏洞防护方法,并涵盖密钥保护和生产环境加固。

主要功能

  1. 01运行时安全配置
  2. 02防止 SQL 注入
  3. 03按上下文编码输出
  4. 04CSRF 与上传防护
  5. 05密钥和身份验证实践
  6. 06生产环境加固清单

适用场景

  1. 01检查 BoxLang 应用安全性
  2. 02保护表单、上传和远程函数
  3. 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