CVE-2014-0168: Cross-Site Request Forgery (CSRF) (CWE-352)
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.
CWE description: 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.
Published: 2014-10-06T14:55Z
Repository: https://github.com/rhuss/jolokia
Language: Java
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 date: 2014-04-24 17:13:24 +0200
Commit message: Enchanced policy wr to origin handling. The Origin: can now be checked on the server side, too.

Diff: diff --git a/agent/core/src/main/java/org/jolokia/backend/BackendManager.java b/agent/core/src/main/java/org/jolokia/backend/BackendManager.java
index 31f4de8b..d69e6f8d 100644
--- a/agent/core/src/main/java/org/jolokia/backend/BackendManager.java
+++ b/agent/core/src/main/java/org/jolokia/backend/BackendManager.java
@@ -221,14 +221,15 @@ public class BackendManager implements AgentDetailsHolder {
 
     /**
      * Check whether CORS access is allowed for the given origin.
      *
      * @param pOrigin origin URL which needs to be checked
-     * @return true if icors access is allowed
+     * @param pStrictChecking whether to a strict check (i.e. server side check)
+     * @return true if if cors access is allowed
      */
-    public boolean isCorsAccessAllowed(String pOrigin) {
-        return restrictor.isCorsAccessAllowed(pOrigin);
+    public boolean isOriginAllowed(String pOrigin,boolean pStrictChecking) {
+        return restrictor.isOriginAllowed(pOrigin, pStrictChecking);
     }
 
     /**
      * Log at info level
      *
diff --git a/agent/core/src/main/java/org/jolokia/http/AgentServlet.java b/agent/core/src/main/java/org/jolokia/http/AgentServlet.java
index 932e8950..52ad03c6 100755
--- a/agent/core/src/main/java/org/jolokia/http/AgentServlet.java
+++ b/agent/core/src/main/java/org/jolokia/http/AgentServlet.java
@@ -276,11 +276,12 @@ public class AgentServlet extends HttpServlet {
     @SuppressWarnings({ "PMD.AvoidCatchingThrowable", "PMD.AvoidInstanceofChecksInCatchClause" })
     private void handle(ServletRequestHandler pReqHandler,HttpServletRequest pReq, HttpServletResponse pResp) throws IOException {
         JSONAware json = null;
         try {
             // Check access policy
-            requestHandler.checkClientIPAccess(pReq.getRemoteHost(),pReq.getRemoteAddr());
+            requestHandler.checkAccess(pReq.getRemoteHost(), pReq.getRemoteAddr(),
+                                       getOriginOrReferer(pReq));
 
             // Remember the agent URL upon the first request. Needed for discovery
             updateAgentUrlIfNeeded(pReq);
 
             // Dispatch for the proper HTTP request method
@@ -302,10 +303,19 @@ public class AgentServlet extends HttpServlet {
                 sendResponse(pResp, getMimeType(pReq),answer);
             }
         }
     }
 
+    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;
+    }
+
+
     // Update the agent URL in the agent details if not already done
     private void updateAgentUrlIfNeeded(HttpServletRequest pReq) {
         // Lookup the Agent URL if needed
         if (initAgentUrlFromRequest) {
             updateAgentUrl(NetworkUtil.sanitizeLocalUrl(pReq.getRequestURL().toString()), extractServletPath(pReq),pReq.getAuthType() != null);
diff --git a/agent/core/src/main/java/org/jolokia/http/HttpRequestHandler.java b/agent/core/src/main/java/org/jolokia/http/HttpRequestHandler.java
index 29c8b63c..63c41df7 100644
--- a/agent/core/src/main/java/org/jolokia/http/HttpRequestHandler.java
+++ b/agent/core/src/main/java/org/jolokia/http/HttpRequestHandler.java
@@ -151,11 +151,11 @@ public class HttpRequestHandler {
      * @param pRequestHeaders extra headers to check against
      * @return headers to set
      */
     public Map<String, String> handleCorsPreflightRequest(String pOrigin, String pRequestHeaders) {
         Map<String,String> ret = new HashMap<String, String>();
-        if (pOrigin != null && backendManager.isCorsAccessAllowed(pOrigin)) {
+        if (pOrigin != null && backendManager.isOriginAllowed(pOrigin,false)) {
             // CORS is allowed, we set exactly the origin in the header, so there are no problems with authentication
             ret.put("Access-Control-Allow-Origin","null".equals(pOrigin) ? "*" : pOrigin);
             if (pRequestHeaders != null) {
                 ret.put("Access-Control-Allow-Headers",pRequestHeaders);
             }
@@ -275,30 +275,34 @@ public class HttpRequestHandler {
     /**
      * Check whether the given host and/or address is allowed to access this agent.
      *
      * @param pHost host to check
      * @param pAddress address to check
+     * @param pOrigin (optional) origin header to check also.
      */
-    public void checkClientIPAccess(String pHost, String pAddress) {
+    public void checkAccess(String pHost, String pAddress, String pOrigin) {
         if (!backendManager.isRemoteAccessAllowed(pHost,pAddress)) {
             throw new SecurityException("No access from client " + pAddress + " allowed");
         }
+        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 deligated to the
+     * Check whether for the given host is a cross-browser request allowed. This check is delegated to the
      * backendmanager which is responsible for the security configuration.
      * Also, some sanity checks are applied.
      *
      * @param pOrigin the origin URL to check against
      * @return the origin to put in the response header or null if none is to be set
      */
     public String extractCorsOrigin(String pOrigin) {
         if (pOrigin != null) {
             // Prevent HTTP response splitting attacks
             String origin  = pOrigin.replaceAll("[\\n\\r]*","");
-            if (backendManager.isCorsAccessAllowed(origin)) {
+            if (backendManager.isOriginAllowed(origin,false)) {
                 return "null".equals(origin) ? "*" : origin;
             } else {
                 return null;
             }
         }
diff --git a/agent/core/src/main/java/org/jolokia/restrictor/AbstractConstantRestrictor.java b/agent/core/src/main/java/org/jolokia/restrictor/AbstractConstantRestrictor.java
index d0aa4bcc..2c65c8c4 100644
--- a/agent/core/src/main/java/org/jolokia/restrictor/AbstractConstantRestrictor.java
+++ b/agent/core/src/main/java/org/jolokia/restrictor/AbstractConstantRestrictor.java
@@ -71,9 +71,9 @@ public abstract class AbstractConstantRestrictor implements Restrictor {
     public final boolean isRemoteAccessAllowed(String... pHostOrAddress) {
         return isAllowed;
     }
 
     /** {@inheritDoc} */
-    public boolean isCorsAccessAllowed(String pOrigin) {
+    public boolean isOriginAllowed(String pOrigin, boolean pIsStrictCheck) {
         return isAllowed;
     }
 }
diff --git a/agent/core/src/main/java/org/jolokia/restrictor/PolicyRestrictor.java b/agent/core/src/main/java/org/jolokia/restrictor/PolicyRestrictor.java
index f9e25ede..6bcae163 100644
--- a/agent/core/src/main/java/org/jolokia/restrictor/PolicyRestrictor.java
+++ b/agent/core/src/main/java/org/jolokia/restrictor/PolicyRestrictor.java
@@ -97,12 +97,12 @@ public class PolicyRestrictor implements Restrictor {
     public boolean isRemoteAccessAllowed(String ... pHostOrAddress) {
         return networkChecker.check(pHostOrAddress);
     }
 
     /** {@inheritDoc} */
-    public boolean isCorsAccessAllowed(String pOrigin) {
-        return corsChecker.check(pOrigin);
+    public boolean isOriginAllowed(String pOrigin, boolean pIsStrictCheck) {
+        return corsChecker.check(pOrigin,pIsStrictCheck);
     }
 
     /** {@inheritDoc} */
     public boolean isAttributeReadAllowed(ObjectName pName, String pAttribute) {
         return check(RequestType.READ,pName,pAttribute);
diff --git a/agent/core/src/main/java/org/jolokia/restrictor/Restrictor.java b/agent/core/src/main/java/org/jolokia/restrictor/Restrictor.java
index 7f64e84c..3dbc6afc 100644
--- a/agent/core/src/main/java/org/jolokia/restrictor/Restrictor.java
+++ b/agent/core/src/main/java/org/jolokia/restrictor/Restrictor.java
@@ -88,10 +88,11 @@ public interface Restrictor {
     /**
      * Check whether cross browser access via CORS is allowed. See the
      * <a href="https://developer.mozilla.org/en/http_access_control">CORS</a> specification
      * for details
      *
-     * @param pOrigin the "Origin:" URL provided within the request
+     * @param pOrigin the "Origin:" header provided within the request
+     * @param pIsStrictCheck whether doing a strict check
      * @return true if this cross browser request allowed, false otherwise
      */
-    boolean isCorsAccessAllowed(String pOrigin);
+    boolean isOriginAllowed(String pOrigin, boolean pIsStrictCheck);
 }
diff --git a/agent/core/src/main/java/org/jolokia/restrictor/policy/CorsChecker.java b/agent/core/src/main/java/org/jolokia/restrictor/policy/CorsChecker.java
index 308c2049..1d3139e2 100644
--- a/agent/core/src/main/java/org/jolokia/restrictor/policy/CorsChecker.java
+++ b/agent/core/src/main/java/org/jolokia/restrictor/policy/CorsChecker.java
@@ -28,19 +28,23 @@ import org.w3c.dom.*;
  * @author roland
  * @since 07.04.12
  */
 public class CorsChecker extends AbstractChecker<String> {
 
+    private boolean strictChecking = false;
+
     private List<Pattern> patterns;
 
     /**
      * Constructor buiilding up this checker from the XML document provided.
      * CORS sections look like
      * <pre>
      *     &lt;cors&gt;
      *       &lt;allow-origin&gt;http://jolokia.org&lt;allow-origin&gt;
      *       &lt;allow-origin&gt;*://*.jmx4perl.org&gt;
+     *
+     *       &lt;strict-checking/&gt;
      *     &lt;/cors&gt;
      * </pre>
      *
      * @param pDoc the overall policy documents
      */
@@ -54,27 +58,41 @@ public class CorsChecker extends AbstractChecker<String> {
                 for (int j = 0;j <nodes.getLength();j++) {
                     Node node = nodes.item(j);
                     if (node.getNodeType() != Node.ELEMENT_NODE) {
                         continue;
                     }
-                    assertNodeName(node,"allow-origin");
-                    String p = node.getTextContent().trim().toLowerCase();
-                    p = Pattern.quote(p).replace("*","\\E.*\\Q");
-                    patterns.add(Pattern.compile("^" + p + "$"));
+                    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;
+                    }
                 }
             }
         }
     }
 
     /** {@inheritDoc} */
     @Override
     public boolean check(String pArg) {
+        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 (patterns == null || patterns.size() == 0) {
             return true;
         }
         for (Pattern pattern : patterns) {
-            if (pattern.matcher(pArg).matches()) {
+            if (pattern.matcher(pOrigin).matches()) {
                 return true;
             }
         }
         return false;
     }
diff --git a/agent/core/src/test/java/org/jolokia/backend/BackendManagerTest.java b/agent/core/src/test/java/org/jolokia/backend/BackendManagerTest.java
index 577ce72d..c34ca677 100755
--- a/agent/core/src/test/java/org/jolokia/backend/BackendManagerTest.java
+++ b/agent/core/src/test/java/org/jolokia/backend/BackendManagerTest.java
@@ -148,11 +148,11 @@ public class BackendManagerTest {
     }
 
     @Test
     public void corsAccessCheck() {
         BackendManager backendManager = new BackendManager(config,log);
-        assertTrue(backendManager.isCorsAccessAllowed("http://bla.com"));
+        assertTrue(backendManager.isOriginAllowed("http://bla.com",false));
         backendManager.destroy();
     }
 
     @Test
     public void convertError() throws MalformedObjectNameException {
diff --git a/agent/core/src/test/java/org/jolokia/http/AgentServletTest.java b/agent/core/src/test/java/org/jolokia/http/AgentServletTest.java
index 36631726..0fe8e339 100755
--- a/agent/core/src/test/java/org/jolokia/http/AgentServletTest.java
+++ b/agent/core/src/test/java/org/jolokia/http/AgentServletTest.java
@@ -123,12 +123,12 @@ public class AgentServletTest {
         context = createMock(ServletContext.class);
 
         HttpTestUtil.prepareServletConfigMock(config, new String[]{ConfigKey.LOGHANDLER_CLASS.getKeyValue(), CustomLogHandler.class.getName()});
         HttpTestUtil.prepareServletContextMock(context,null);
 
-        expect(config.getServletContext()).andReturn(context).anyTimes();
-        expect(config.getServletName()).andReturn("jolokia").anyTimes();
+        expect(config.getServletContext()).andStubReturn(context);
+        expect(config.getServletName()).andStubReturn("jolokia");
         replay(config, context);
 
         servlet.init(config);
         servlet.destroy();
 
@@ -189,11 +189,11 @@ public class AgentServletTest {
         prepareStandardInitialisation(ConfigKey.DISCOVERY_ENABLED.getKeyValue(), "true");
         try {
             StringWriter sw = initRequestResponseMocks();
             expect(request.getPathInfo()).andReturn(HttpTestUtil.HEAP_MEMORY_GET_REQUEST);
             expect(request.getParameter(ConfigKey.MIME_TYPE.getKeyValue())).andReturn("text/plain");
-            String url = "http://pirx:9876/jolokia";
+            String url = "http://10.9.11.1:9876/jolokia";
             StringBuffer buf = new StringBuffer();
             buf.append(url).append(HttpTestUtil.HEAP_MEMORY_GET_REQUEST);
             expect(request.getRequestURL()).andReturn(buf);
             expect(request.getRequestURI()).andReturn(buf.toString());
             expect(request.getContextPath()).andReturn("/jolokia");
@@ -257,11 +257,12 @@ public class AgentServletTest {
     public void simpleGetWithUnsupportedGetParameterMapCall() throws ServletException, IOException {
         prepareStandardInitialisation();
         StringWriter sw = initRequestResponseMocks(
                 new Runnable() {
                     public void run() {
-                        expect(request.getHeader("Origin")).andReturn(null);
+                        expect(request.getHeader("Origin")).andStubReturn(null);
+                        expect(request.getHeader("Referer")).andStubReturn(null);
                         expect(request.getRemoteHost()).andReturn("localhost");
                         expect(request.getRemoteAddr()).andReturn("127.0.0.1");
                         expect(request.getRequestURI()).andReturn("/jolokia/");
                         expect(request.getPathInfo()).andReturn(HttpTestUtil.HEAP_MEMORY_GET_REQUEST);
                         expect(request.getParameterMap()).andThrow(new UnsupportedOperationException(""));
@@ -366,11 +367,11 @@ public class AgentServletTest {
         prepareStandardInitialisation();
 
         StringWriter sw = initRequestResponseMocks(
                 new Runnable() {
                     public void run() {
-                        expect(request.getHeader("Origin")).andReturn(in);
+                        expect(request.getHeader("Origin")).andStubReturn(in);
                         expect(request.getRemoteHost()).andReturn("localhost");
                         expect(request.getRemoteAddr()).andReturn("127.0.0.1");
                         expect(request.getRequestURI()).andReturn("/jolokia/");
                         expect(request.getParameterMap()).andReturn(null);
                     }
@@ -462,12 +463,13 @@ public class AgentServletTest {
         context.log(find("Path-Info:"));
         context.log(find("Request:"));
         context.log(find("time:"));
         context.log(find("Response:"));
         context.log(find("TestDetector"),isA(RuntimeException.class));
-        expectLastCall().anyTimes();
+        expectLastCall().asStub();
         replay(config, context);
+
         servlet.init(config);
 
         StringWriter sw = initRequestResponseMocks();
         expect(request.getPathInfo()).andReturn(HttpTestUtil.HEAP_MEMORY_GET_REQUEST);
         expect(request.getParameter(ConfigKey.MIME_TYPE.getKeyValue())).andReturn(null);
@@ -501,23 +503,23 @@ public class AgentServletTest {
         params[params.length - 1] = "true";
         HttpTestUtil.prepareServletConfigMock(config,params);
         HttpTestUtil.prepareServletContextMock(context, pContextParams);
 
 
-        expect(config.getServletContext()).andReturn(context).anyTimes();
-        expect(config.getServletName()).andReturn("jolokia").anyTimes();
+        expect(config.getServletContext()).andStubReturn(context);
+        expect(config.getServletName()).andStubReturn("jolokia");
         if (pExceptionClass != null) {
             context.log(find(pLogRegexp),isA(pExceptionClass));
         } else {
             if (pLogRegexp != null) {
                 context.log(find(pLogRegexp));
             } else {
                 context.log((String) anyObject());
             }
         }
         context.log((String) anyObject());
-        expectLastCall().anyTimes();
+        expectLastCall().asStub();
         context.log(find("TestDetector"),isA(RuntimeException.class));
     }
 
     private StringWriter initRequestResponseMocks() throws IOException {
         return initRequestResponseMocks(
@@ -567,11 +569,12 @@ public class AgentServletTest {
     }
 
     private Runnable getStandardRequestSetup() {
         return new Runnable() {
             public void run() {
-                expect(request.getHeader("Origin")).andReturn(null);
+                expect(request.getHeader("Origin")).andStubReturn(null);
+                expect(request.getHeader("Referer")).andStubReturn(null);
                 expect(request.getRemoteHost()).andReturn("localhost");
                 expect(request.getRemoteAddr()).andReturn("127.0.0.1");
                 expect(request.getRequestURI()).andReturn("/jolokia/");
                 expect(request.getParameterMap()).andReturn(null);
             }
diff --git a/agent/core/src/test/java/org/jolokia/http/HttpRequestHandlerTest.java b/agent/core/src/test/java/org/jolokia/http/HttpRequestHandlerTest.java
index 3a01e14e..b7e8f943 100644
--- a/agent/core/src/test/java/org/jolokia/http/HttpRequestHandlerTest.java
+++ b/agent/core/src/test/java/org/jolokia/http/HttpRequestHandlerTest.java
@@ -63,21 +63,31 @@ public class HttpRequestHandlerTest {
     @Test
     public void accessAllowed() {
         expect(backend.isRemoteAccessAllowed("localhost","127.0.0.1")).andReturn(true);
         replay(backend);
 
-        handler.checkClientIPAccess("localhost","127.0.0.1");
+        handler.checkAccess("localhost", "127.0.0.1",null);
     }
 
     @Test(expectedExceptions = { SecurityException.class })
     public void accessDenied() {
         expect(backend.isRemoteAccessAllowed("localhost","127.0.0.1")).andReturn(false);
         replay(backend);
 
-        handler.checkClientIPAccess("localhost","127.0.0.1");
+        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");
+    }
+
+
     @Test
     public void get() throws InstanceNotFoundException, IOException, ReflectionException, AttributeNotFoundException, MBeanException {
         JSONObject resp = new JSONObject();
         expect(backend.handleRequest(isA(JmxReadRequest.class))).andReturn(resp);
         replay(backend);
@@ -150,22 +160,22 @@ public class HttpRequestHandlerTest {
 
     @Test
     public void preflightCheck() {
         String origin = "http://bla.com";
         String headers ="X-Data: Test";
-        expect(backend.isCorsAccessAllowed(origin)).andReturn(true);
+        expect(backend.isOriginAllowed(origin,false)).andReturn(true);
         replay(backend);
 
         Map<String,String> ret =  handler.handleCorsPreflightRequest(origin, headers);
         assertEquals(ret.get("Access-Control-Allow-Origin"),origin);
     }
 
     @Test
     public void preflightCheckNegative() {
         String origin = "http://bla.com";
         String headers ="X-Data: Test";
-        expect(backend.isCorsAccessAllowed(origin)).andReturn(false);
+        expect(backend.isOriginAllowed(origin,false)).andReturn(false);
         replay(backend);
 
         Map<String,String> ret =  handler.handleCorsPreflightRequest(origin, headers);
         assertNull(ret.get("Access-Control-Allow-Origin"));
     }
diff --git a/agent/core/src/test/java/org/jolokia/restrictor/PolicyBasedRestrictorTest.java b/agent/core/src/test/java/org/jolokia/restrictor/PolicyBasedRestrictorTest.java
index 7711698d..8b9609f0 100644
--- a/agent/core/src/test/java/org/jolokia/restrictor/PolicyBasedRestrictorTest.java
+++ b/agent/core/src/test/java/org/jolokia/restrictor/PolicyBasedRestrictorTest.java
@@ -220,45 +220,58 @@ public class PolicyBasedRestrictorTest {
         }
     }
 
     @Test
     public void cors() {
+        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() {
         InputStream is = getClass().getResourceAsStream("/allow-origin1.xml");
         PolicyRestrictor restrictor = new PolicyRestrictor(is);
 
-        assertTrue(restrictor.isCorsAccessAllowed("http://bla.com"));
-        assertFalse(restrictor.isCorsAccessAllowed("http://www.jolokia.org"));
-        assertTrue(restrictor.isCorsAccessAllowed("https://www.consol.de"));
+        // 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));
     }
 
     @Test
     public void corsWildCard() {
         InputStream is = getClass().getResourceAsStream("/allow-origin2.xml");
         PolicyRestrictor restrictor = new PolicyRestrictor(is);
 
-        assertTrue(restrictor.isCorsAccessAllowed("http://bla.com"));
-        assertTrue(restrictor.isCorsAccessAllowed("http://www.jolokia.org"));
-        assertTrue(restrictor.isCorsAccessAllowed("http://www.consol.de"));
+        assertTrue(restrictor.isOriginAllowed("http://bla.com", false));
+        assertTrue(restrictor.isOriginAllowed("http://www.jolokia.org", false));
+        assertTrue(restrictor.isOriginAllowed("http://www.consol.de", false));
     }
 
     @Test
     public void corsEmpty() {
         InputStream is = getClass().getResourceAsStream("/allow-origin3.xml");
         PolicyRestrictor restrictor = new PolicyRestrictor(is);
 
-        assertTrue(restrictor.isCorsAccessAllowed("http://bla.com"));
-        assertTrue(restrictor.isCorsAccessAllowed("http://www.jolokia.org"));
-        assertTrue(restrictor.isCorsAccessAllowed("http://www.consol.de"));
+        assertTrue(restrictor.isOriginAllowed("http://bla.com", false));
+        assertTrue(restrictor.isOriginAllowed("http://www.jolokia.org", false));
+        assertTrue(restrictor.isOriginAllowed("http://www.consol.de", false));
     }
 
     @Test
     public void corsNoTags() {
         InputStream is = getClass().getResourceAsStream("/access-sample1.xml");
         PolicyRestrictor restrictor = new PolicyRestrictor(is);
 
-        assertTrue(restrictor.isCorsAccessAllowed("http://bla.com"));
-        assertTrue(restrictor.isCorsAccessAllowed("http://www.jolokia.org"));
-        assertTrue(restrictor.isCorsAccessAllowed("https://www.consol.de"));
+        assertTrue(restrictor.isOriginAllowed("http://bla.com", false));
+        assertTrue(restrictor.isOriginAllowed("http://www.jolokia.org", false));
+        assertTrue(restrictor.isOriginAllowed("https://www.consol.de", false));
     }
 
 
 }
diff --git a/agent/core/src/test/resources/allow-origin2.xml b/agent/core/src/test/resources/allow-origin2.xml
index 72fea378..d5286a45 100644
--- a/agent/core/src/test/resources/allow-origin2.xml
+++ b/agent/core/src/test/resources/allow-origin2.xml
@@ -18,8 +18,9 @@
 
 <restrict>
 
   <cors>
     <allow-origin>*</allow-origin>
+
   </cors>
 
 </restrict>
diff --git a/agent/core/src/test/resources/allow-origin4.xml b/agent/core/src/test/resources/allow-origin4.xml
new file mode 100644
index 00000000..e019c788
--- /dev/null
+++ b/agent/core/src/test/resources/allow-origin4.xml
@@ -0,0 +1,27 @@
+<?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>
diff --git a/agent/jvm/src/main/java/org/jolokia/jvmagent/JolokiaHttpHandler.java b/agent/jvm/src/main/java/org/jolokia/jvmagent/JolokiaHttpHandler.java
index 866e1d27..cf5e15aa 100644
--- a/agent/jvm/src/main/java/org/jolokia/jvmagent/JolokiaHttpHandler.java
+++ b/agent/jvm/src/main/java/org/jolokia/jvmagent/JolokiaHttpHandler.java
@@ -163,11 +163,12 @@ public class JolokiaHttpHandler implements HttpHandler {
         URI uri = pExchange.getRequestURI();
         ParsedUri parsedUri = new ParsedUri(uri,context);
         try {
             // Check access policy
             InetSocketAddress address = pExchange.getRemoteAddress();
-            requestHandler.checkClientIPAccess(address.getHostName(),address.getAddress().getHostAddress());
+            requestHandler.checkAccess(address.getHostName(), address.getAddress().getHostAddress(),
+                                       extractOriginOrReferer(pExchange));
             String method = pExchange.getRequestMethod();
 
             // Dispatch for the proper HTTP request method
             if ("GET".equalsIgnoreCase(method)) {
                 setHeaders(pExchange);
@@ -188,11 +189,10 @@ public class JolokiaHttpHandler implements HttpHandler {
         }
     }
 
     // ========================================================================
 
-
     private Restrictor createRestrictor(Configuration pConfig) {
         String location = pConfig.get(ConfigKey.POLICY_LOCATION);
         try {
             Restrictor ret = RestrictorFactory.lookupPolicyRestrictor(location);
             if (ret != null) {
@@ -207,10 +207,21 @@ public class JolokiaHttpHandler implements HttpHandler {
                              ". Denying all access to MBeans for security reasons. Exception: " + e, e);
             return new DenyAllRestrictor();
         }
     }
 
+
+    // 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;
+    }
+
     private JSONAware executeGetRequest(ParsedUri parsedUri) {
         return requestHandler.handleGetRequest(parsedUri.getUri().toString(),parsedUri.getPathInfo(), parsedUri.getParameterMap());
     }
 
     private JSONAware executePostRequest(HttpExchange pExchange, ParsedUri pUri) throws MalformedObjectNameException, IOException {
diff --git a/agent/osgi/src/main/java/org/jolokia/osgi/DelegatingRestrictor.java b/agent/osgi/src/main/java/org/jolokia/osgi/DelegatingRestrictor.java
index ed2fa868..aaa6cb2f 100644
--- a/agent/osgi/src/main/java/org/jolokia/osgi/DelegatingRestrictor.java
+++ b/agent/osgi/src/main/java/org/jolokia/osgi/DelegatingRestrictor.java
@@ -164,17 +164,17 @@ class DelegatingRestrictor implements Restrictor {
     // ====================================================================
 
     private static final RestrictorCheck CORS_CHECK = new RestrictorCheck() {
         /** {@inheritDoc} */
         public boolean check(Restrictor restrictor, Object... args) {
-            return restrictor.isCorsAccessAllowed((String) args[0]);
+            return restrictor.isOriginAllowed((String) args[0], (Boolean) args[1]);
         }
     };
 
     /** {@inheritDoc} */
-    public boolean isCorsAccessAllowed(String pOrigin) {
-        return checkRestrictorService(CORS_CHECK,pOrigin);
+    public boolean isOriginAllowed(String pOrigin, boolean pIsStrictCheck) {
+        return checkRestrictorService(CORS_CHECK,pOrigin,pIsStrictCheck);
     }
 
     // =======================================================================================================
 
     /**
diff --git a/agent/osgi/src/test/java/org/jolokia/osgi/DelegatingRestrictorTest.java b/agent/osgi/src/test/java/org/jolokia/osgi/DelegatingRestrictorTest.java
index 360387de..17920f6e 100644
--- a/agent/osgi/src/test/java/org/jolokia/osgi/DelegatingRestrictorTest.java
+++ b/agent/osgi/src/test/java/org/jolokia/osgi/DelegatingRestrictorTest.java
@@ -81,11 +81,11 @@ public class DelegatingRestrictorTest {
         assertFalse(restrictor.isTypeAllowed(RequestType.EXEC));
         assertTrue(restrictor.isAttributeReadAllowed(new ObjectName("java.lang:type=Memory"), "HeapMemoryUsage"));
         assertFalse(restrictor.isAttributeWriteAllowed(new ObjectName("java.lang:type=Memory"), "HeapMemoryUsage"));
         assertTrue(restrictor.isOperationAllowed(new ObjectName("java.lang:type=Memory"), "gc"));
         assertFalse(restrictor.isRemoteAccessAllowed("localhost", "127.0.0.1"));
-        assertTrue(restrictor.isCorsAccessAllowed("http://bla.com"));
+        assertTrue(restrictor.isOriginAllowed("http://bla.com", false));
     }
 
     @Test(expectedExceptions = IllegalArgumentException.class,expectedExceptionsMessageRegExp = ".*Impossible.*")
     public void invalidSyntax() throws InvalidSyntaxException {
         expect(context.getServiceReferences("org.jolokia.restrictor.Restrictor", null)).andThrow(new InvalidSyntaxException("", null));
@@ -130,10 +130,10 @@ public class DelegatingRestrictorTest {
 
         public boolean isRemoteAccessAllowed(String... pHostOrAddress) {
             return remote;
         }
 
-        public boolean isCorsAccessAllowed(String pOrigin) {
+        public boolean isOriginAllowed(String pOrigin, boolean pIsStrictCheck) {
             return cors;
         }
     }
 }
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index ad880d5b..fccf3cca 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -22,10 +22,14 @@
     <title>Jolokia</title>
     <author email="roland@jolokia.org">Roland Huß</author>
   </properties>
   <body>
     <release version="1.2.1" description="Release 1.2.1" date="2014-02-24">
+      <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>
       <action dev="roland" type="fix" date="2014-04-10" issue="123">
         Fixed issue with JBoss 4.2.3 and JNDI lookups
       </action>
       <action dev="cymantic" type="update" date="2014-04-10" issue="122">
         Update Java client for usage with Apache Http Components >= 4.3
diff --git a/src/docbkx/security.xml b/src/docbkx/security.xml
index d4deab21..8fe70e5d 100644
--- a/src/docbkx/security.xml
+++ b/src/docbkx/security.xml
@@ -210,20 +210,29 @@
           <literal>&lt;cors&gt;</literal> sections. This tags can
           contain the origin URL provided by browsers with the
           <literal>Origin:</literal> header literally or a
           wildcard specification with <literal>*</literal>.          
         </para>
-      </section>
-      <programlisting language="xml"><![CDATA[
+        <programlisting language="xml"><![CDATA[
 <cors>
    <!-- Allow cross origin access from www.jolokia.org ... -->
    <allow-origin>http://www.jolokia.org</allow-origin>
 
    <!-- ... and all servers from jmx4perl.org with any protocol ->
    <allow-origin>*://*.jmx4perl.org</allow-origin>
+
+   <!-- Check for the proper origin on the server side, too -->
+   <strict-checking/>
 </cors>
 ]]></programlisting>
+        <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>
       <section>
         <title>Example for a security policy</title>
         <para>
           The following complete example applies various access
           restrictions: 

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>