CVE-2014-0168 (Java, published 2014-10-06T14:55Z)
Description: value: Cross-site request forgery (CSRF) vulnerability in Jolokia before 1.2.1 allows remote attackers to hijack the authentication of users for requests that execute MBeans methods via a crafted web page.
Severity: MEDIUM (CVSS v3 base score: )
Weakness: CWE-352 \u2014 Cross-Site Request Forgery (CSRF)
The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
Repository: https://github.com/rhuss/jolokia
Files changed:
agent/core/src/main/java/org/jolokia/backend/BackendManager.java
agent/core/src/main/java/org/jolokia/http/AgentServlet.java
agent/core/src/main/java/org/jolokia/http/HttpRequestHandler.java
agent/core/src/main/java/org/jolokia/restrictor/AbstractConstantRestrictor.java
agent/core/src/main/java/org/jolokia/restrictor/PolicyRestrictor.java
agent/core/src/main/java/org/jolokia/restrictor/Restrictor.java
agent/core/src/main/java/org/jolokia/restrictor/policy/CorsChecker.java
agent/core/src/test/java/org/jolokia/backend/BackendManagerTest.java
agent/core/src/test/java/org/jolokia/http/AgentServletTest.java
agent/core/src/test/java/org/jolokia/http/HttpRequestHandlerTest.java
agent/core/src/test/java/org/jolokia/restrictor/PolicyBasedRestrictorTest.java
agent/core/src/test/resources/allow-origin2.xml
agent/core/src/test/resources/allow-origin4.xml
agent/jvm/src/main/java/org/jolokia/jvmagent/JolokiaHttpHandler.java
agent/osgi/src/main/java/org/jolokia/osgi/DelegatingRestrictor.java
agent/osgi/src/test/java/org/jolokia/osgi/DelegatingRestrictorTest.java
src/changes/changes.xml
src/docbkx/security.xml
Commit message: Enchanced policy wr to origin handling. The Origin: can now be checked on the server side, too.


Vulnerable code:
     * @return true if icors access is allowed
    public boolean isCorsAccessAllowed(String pOrigin) {
        return restrictor.isCorsAccessAllowed(pOrigin);
            requestHandler.checkClientIPAccess(pReq.getRemoteHost(),pReq.getRemoteAddr());
        if (pOrigin != null && backendManager.isCorsAccessAllowed(pOrigin)) {
    public void checkClientIPAccess(String pHost, String pAddress) {
     * Check whether for the given host is a cross-browser request allowed. This check is deligated to the
            if (backendManager.isCorsAccessAllowed(origin)) {
    public boolean isCorsAccessAllowed(String pOrigin) {
    public boolean isCorsAccessAllowed(String pOrigin) {
        return corsChecker.check(pOrigin);
     * @param pOrigin the "Origin:" URL provided within the request
    boolean isCorsAccessAllowed(String pOrigin);
                    assertNodeName(node,"allow-origin");
                    String p = node.getTextContent().trim().toLowerCase();
                    p = Pattern.quote(p).replace("*","\\E.*\\Q");
                    patterns.add(Pattern.compile("^" + p + "$"));
            if (pattern.matcher(pArg).matches()) {
        assertTrue(backendManager.isCorsAccessAllowed("http://bla.com"));
        expect(config.getServletContext()).andReturn(context).anyTimes();
        expect(config.getServletName()).andReturn("jolokia").anyTimes();
            String url = "http://pirx:9876/jolokia";
                        expect(request.getHeader("Origin")).andReturn(null);
                        expect(request.getHeader("Origin")).andReturn(in);
        expectLastCall().anyTimes();
        expect(config.getServletContext()).andReturn(context).anyTimes();
        expect(config.getServletName()).andReturn("jolokia").anyTimes();
        expectLastCall().anyTimes();
                expect(request.getHeader("Origin")).andReturn(null);
        handler.checkClientIPAccess("localhost","127.0.0.1");
        handler.checkClientIPAccess("localhost","127.0.0.1");
        expect(backend.isCorsAccessAllowed(origin)).andReturn(true);
        expect(backend.isCorsAccessAllowed(origin)).andReturn(false);
        assertTrue(restrictor.isCorsAccessAllowed("http://bla.com"));
        assertFalse(restrictor.isCorsAccessAllowed("http://www.jolokia.org"));
        assertTrue(restrictor.isCorsAccessAllowed("https://www.consol.de"));
        assertTrue(restrictor.isCorsAccessAllowed("http://bla.com"));
        assertTrue(restrictor.isCorsAccessAllowed("http://www.jolokia.org"));
        assertTrue(restrictor.isCorsAccessAllowed("http://www.consol.de"));
        assertTrue(restrictor.isCorsAccessAllowed("http://bla.com"));
        assertTrue(restrictor.isCorsAccessAllowed("http://www.jolokia.org"));
        assertTrue(restrictor.isCorsAccessAllowed("http://www.consol.de"));
        assertTrue(restrictor.isCorsAccessAllowed("http://bla.com"));
        assertTrue(restrictor.isCorsAccessAllowed("http://www.jolokia.org"));
        assertTrue(restrictor.isCorsAccessAllowed("https://www.consol.de"));
            requestHandler.checkClientIPAccess(address.getHostName(),address.getAddress().getHostAddress());

            return restrictor.isCorsAccessAllowed((String) args[0]);
    public boolean isCorsAccessAllowed(String pOrigin) {
        return checkRestrictorService(CORS_CHECK,pOrigin);
        assertTrue(restrictor.isCorsAccessAllowed("http://bla.com"));
        public boolean isCorsAccessAllowed(String pOrigin) {
      </section>
      <programlisting language="xml"><![CDATA[

Fixed code:
     * @param pStrictChecking whether to a strict check (i.e. server side check)
     * @return true if if cors access is allowed
    public boolean isOriginAllowed(String pOrigin,boolean pStrictChecking) {
        return restrictor.isOriginAllowed(pOrigin, pStrictChecking);
            requestHandler.checkAccess(pReq.getRemoteHost(), pReq.getRemoteAddr(),
                                       getOriginOrReferer(pReq));
    private String getOriginOrReferer(HttpServletRequest pReq) {
        String origin = pReq.getHeader("Origin");
        if (origin == null) {
            origin = pReq.getHeader("Referer");
        }
        return origin != null ? origin.replaceAll("[\\n\\r]*","") : null;
    }


        if (pOrigin != null && backendManager.isOriginAllowed(pOrigin,false)) {
     * @param pOrigin (optional) origin header to check also.
    public void checkAccess(String pHost, String pAddress, String pOrigin) {
        if (pOrigin != null && !backendManager.isOriginAllowed(pOrigin,true)) {
            throw new SecurityException("Origin " + pOrigin + " is not allowed to call this agent");
        }
     * Check whether for the given host is a cross-browser request allowed. This check is delegated to the
            if (backendManager.isOriginAllowed(origin,false)) {
    public boolean isOriginAllowed(String pOrigin, boolean pIsStrictCheck) {
    public boolean isOriginAllowed(String pOrigin, boolean pIsStrictCheck) {
        return corsChecker.check(pOrigin,pIsStrictCheck);
     * @param pOrigin the "Origin:" header provided within the request
     * @param pIsStrictCheck whether doing a strict check
    boolean isOriginAllowed(String pOrigin, boolean pIsStrictCheck);
    private boolean strictChecking = false;

     *
     *       &lt;strict-checking/&gt;
                    assertNodeName(node,"allow-origin","strict-checking");
                    if (node.getNodeName().equals("allow-origin")) {
                        String p = node.getTextContent().trim().toLowerCase();
                        p = Pattern.quote(p).replace("*", "\\E.*\\Q");
                        patterns.add(Pattern.compile("^" + p + "$"));
                    } else if (node.getNodeName().equals("strict-checking")) {
                        strictChecking = true;
                    }
        return check(pArg,false);
    }

    public boolean check(String pOrigin, boolean pIsStrictCheck) {
        // Method called during strict checking but we have not configured that
        // So the check passes always.
        if (pIsStrictCheck && !strictChecking) {
            return true;
        }

            if (pattern.matcher(pOrigin).matches()) {
        assertTrue(backendManager.isOriginAllowed("http://bla.com",false));
        expect(config.getServletContext()).andStubReturn(context);
        expect(config.getServletName()).andStubReturn("jolokia");
            String url = "http://10.9.11.1:9876/jolokia";
                        expect(request.getHeader("Origin")).andStubReturn(null);
                        expect(request.getHeader("Referer")).andStubReturn(null);
                        expect(request.getHeader("Origin")).andStubReturn(in);
        expectLastCall().asStub();

        expect(config.getServletContext()).andStubReturn(context);
        expect(config.getServletName()).andStubReturn("jolokia");
        expectLastCall().asStub();
                expect(request.getHeader("Origin")).andStubReturn(null);
                expect(request.getHeader("Referer")).andStubReturn(null);
        handler.checkAccess("localhost", "127.0.0.1",null);
        handler.checkAccess("localhost", "127.0.0.1",null);
    @Test(expectedExceptions = { SecurityException.class })
    public void accessDeniedViaOrigin() {
        expect(backend.isRemoteAccessAllowed("localhost","127.0.0.1")).andReturn(true);
        expect(backend.isOriginAllowed("www.jolokia.org",true)).andReturn(false);
        replay(backend);

        handler.checkAccess("localhost", "127.0.0.1","www.jolokia.org");
    }


        expect(backend.isOriginAllowed(origin,false)).andReturn(true);
        expect(backend.isOriginAllowed(origin,false)).andReturn(false);
        InputStream is = getClass().getResourceAsStream("/allow-origin4.xml");
        PolicyRestrictor restrictor = new PolicyRestrictor(is);

        for (boolean strict : new boolean[] {true, false}) {
            assertTrue(restrictor.isOriginAllowed("http://bla.com", strict));
            assertFalse(restrictor.isOriginAllowed("http://www.jolokia.org", strict));
            assertTrue(restrictor.isOriginAllowed("https://www.consol.de", strict));
        }
    }

    @Test
    public void corsStrictCheckingOff() {
        // Allways true since we want a strict check but strict checking is off.
        assertTrue(restrictor.isOriginAllowed("http://bla.com", true));
        assertTrue(restrictor.isOriginAllowed("http://www.jolokia.org", true));
        assertTrue(restrictor.isOriginAllowed("https://www.consol.de", true));
        assertTrue(restrictor.isOriginAllowed("http://bla.com", false));
        assertTrue(restrictor.isOriginAllowed("http://www.jolokia.org", false));
        assertTrue(restrictor.isOriginAllowed("http://www.consol.de", false));
        assertTrue(restrictor.isOriginAllowed("http://bla.com", false));
        assertTrue(restrictor.isOriginAllowed("http://www.jolokia.org", false));
        assertTrue(restrictor.isOriginAllowed("http://www.consol.de", false));
        assertTrue(restrictor.isOriginAllowed("http://bla.com", false));
        assertTrue(restrictor.isOriginAllowed("http://www.jolokia.org", false));
        assertTrue(restrictor.isOriginAllowed("https://www.consol.de", false));

<?xml version="1.0" encoding="UTF-8"?>

<!--
  ~ Copyright 2009-2013 Roland Huss
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~       http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<restrict>

  <cors>
    <allow-origin>http://bla.com</allow-origin>
    <allow-origin>http://blub.com</allow-origin>
    <allow-origin>*://*.consol.de</allow-origin>
    <strict-checking/>
  </cors>
</restrict>
            requestHandler.checkAccess(address.getHostName(), address.getAddress().getHostAddress(),
                                       extractOriginOrReferer(pExchange));

    // Used for checking origin or referer is an origin policy is enabled
    private String extractOriginOrReferer(HttpExchange pExchange) {
        Headers headers = pExchange.getRequestHeaders();
        String origin = headers.getFirst("Origin");
        if (origin == null) {
            origin = headers.getFirst("Referer");
        }
        return origin != null ? origin.replaceAll("[\\n\\r]*","") : null;
    }

            return restrictor.isOriginAllowed((String) args[0], (Boolean) args[1]);
    public boolean isOriginAllowed(String pOrigin, boolean pIsStrictCheck) {
        return checkRestrictorService(CORS_CHECK,pOrigin,pIsStrictCheck);
        assertTrue(restrictor.isOriginAllowed("http://bla.com", false));
        public boolean isOriginAllowed(String pOrigin, boolean pIsStrictCheck) {
      <action dev="roland" type="add" date="2014-04-24">
        Added "strict-checking" for jolokia-access.xml in the CORS
        section in order to enable server side origin checking, too.
      </action>
        <programlisting language="xml"><![CDATA[

   <!-- Check for the proper origin on the server side, too -->
   <strict-checking/>
        <para>
          If the option <literal>&lt;strict-checking/&gt;</literal> is given in this section, too, then the given pattern
          are not only used for CORS checking but also every request ist checked on th server side whether the
          <literal>Origin:</literal> or <literal>Referer:</literal> header matches one of the given patterns.
          This useful for protecting against Cross-Site Request Forgery.
        </para>
      </section>