CVE-2014-0120 (published 2017-12-29T22:29Z)

Cross-site request forgery (CSRF) vulnerability in the admin terminal in Hawt.io allows remote attackers to hijack the authentication of arbitrary users for requests that run commands on the Karaf server, as demonstrated by running "shutdown -f."

Severity: MEDIUM
CVSS v2 base score: 6.8 (AV:N/AC:M/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 8.8, HIGH (CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-352
cwe_name: Cross-Site Request Forgery (CSRF)
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.
extended_description: When a web server is designed to receive a request from a client without any mechanism for verifying that it was intentionally sent, then it might be possible for an attacker to trick a client into making an unintentional request to the web server which will be treated as an authentic request. This can be done via a URL, image load, XMLHttpRequest, etc. and can result in exposure of data or unintended code execution.
         

Fix information:
repo_url: https://github.com/hawtio/hawtio
commit_details: repo_url: https://github.com/hawtio/hawtio
author: Stan Lewis
author_date: 2014-03-06 12:11:42-05:00
msg: Add a LoginTokenServlet that plugins can use to fetch a token, and let's use it in hawtio-karaf-terminal.  Also handle cases where the terminal scope gets created a couple times.
repository: repo_url: https://github.com/hawtio/hawtio
repo_name: hawtio/hawtio
description: Hawtio web console helps you manage your JVM stuff and stay cool!
repo_language: Java
owner: hawtio
file_changes: filename: TerminalServlet.java
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,7 @@
 package io.hawt.web.plugin.karaf.terminal;
 
 import io.hawt.system.Helpers;
+import io.hawt.web.LoginTokenServlet;
 import org.apache.felix.service.command.CommandProcessor;
 import org.apache.felix.service.command.CommandSession;
 import org.apache.felix.service.threadio.ThreadIO;
@@ -8,7 +9,6 @@ import org.apache.karaf.shell.console.jline.Console;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.security.auth.Subject;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
@@ -16,8 +16,6 @@ import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 import java.io.*;
 import java.lang.reflect.Constructor;
-import java.security.AccessControlContext;
-import java.security.AccessController;
 import java.util.zip.GZIPOutputStream;
 
 /**
@@ -45,22 +43,17 @@ public class TerminalServlet extends HttpServlet {
     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 
         HttpSession session = request.getSession(false);
-        if (session == null) {
-          AccessControlContext acc = AccessController.getContext();
-          Subject subject = Subject.getSubject(acc);
-          if (subject == null) {
+        String token = request.getHeader(LoginTokenServlet.LOGIN_TOKEN);
+
+        if (token == null || session == null) {
             Helpers.doForbidden(response);
             return;
-          }
-          session = request.getSession(true);
-          session.setAttribute("subject", subject);
-        } else {
-          Subject subject = (Subject) session.getAttribute("subject");
-          if (subject == null) {
-            session.invalidate();
+        }
+
+        String sessionToken = (String) session.getAttribute(LoginTokenServlet.LOGIN_TOKEN);
+        if (!token.equals(sessionToken)) {
             Helpers.doForbidden(response);
             return;
-          }
         }
 
         String encoding = request.getHeader("Accept-Encoding");

programming_language: Java
filename: web.xml
change_type: ModificationType.MODIFY
diff: @@ -16,7 +16,28 @@
     <filter-name>AuthenticationFilter</filter-name>
     <url-pattern>/term/*</url-pattern>
   </filter-mapping>
+  <filter-mapping>
+    <filter-name>AuthenticationFilter</filter-name>
+    <url-pattern>/auth/*</url-pattern>
+  </filter-mapping>
+
+  <servlet>
+    <servlet-name>login</servlet-name>
+    <servlet-class>io.hawt.web.LoginTokenServlet</servlet-class>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>login</servlet-name>
+    <url-pattern>/auth/login/*</url-pattern>
+  </servlet-mapping>
 
+  <servlet>
+    <servlet-name>logout</servlet-name>
+    <servlet-class>io.hawt.web.LogoutServlet</servlet-class>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>logout</servlet-name>
+    <url-pattern>/auth/logout/*</url-pattern>
+  </servlet-mapping>
 
   <servlet>
     <servlet-name>TerminalServlet</servlet-name>

programming_language: PowerShell
filename: gogo.js
change_type: ModificationType.MODIFY
diff: @@ -6,7 +6,7 @@
 
 gogo = { };
 
-gogo.Terminal_ctor = function(div, width, height, authHeader) {
+gogo.Terminal_ctor = function(div, width, height, token) {
 
    var query0 = "w=" + width + "&h=" + height;
    var query1 = query0 + "&k=";
@@ -47,7 +47,7 @@ gogo.Terminal_ctor = function(div, width, height, authHeader) {
                force = 0;
            }
            r.open("POST", "hawtio-karaf-terminal/term", true);
-           r.setRequestHeader('Authorization', authHeader);
+           r.setRequestHeader('LoginToken', token);
            r.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            r.onreadystatechange = function () {
                if (r.readyState == 4) {
@@ -224,7 +224,7 @@ gogo.Terminal_ctor = function(div, width, height, authHeader) {
 
 }
 
-gogo.Terminal = function(div, width, height, authHeader) {
-   return new this.Terminal_ctor(div, width, height, authHeader);
+gogo.Terminal = function(div, width, height, token) {
+   return new this.Terminal_ctor(div, width, height, token);
 }
 

programming_language: JavaScript
filename: gogoPlugin.js
change_type: ModificationType.MODIFY
diff: @@ -16,6 +16,34 @@ var Gogo = (function() {
         restrict: 'A',
         link: function(scope, element, attrs) {
 
+          scope.$on("$destroy", function(e) {
+            scope.destroyed = true;
+            document.onkeypress = null;
+            document.onkeydown = null;
+            if (!('term' in scope)) {
+              return;
+            }
+            var url = "hawtio-karaf-terminal/auth/logout/";
+            delete scope.term;
+            $.ajax(url, {
+              type: "POST",
+              success: function (response) {
+                log.debug("logged out of terminal");
+                Core.$apply(scope);
+              },
+              error: function (xhr, textStatus, error) {
+                log.info("Failed to log out of terminal: ", error);
+              },
+              beforeSend: function (xhr) {
+                xhr.setRequestHeader('Authorization', authHeader);
+              }
+            })
+          });
+
+          if (scope.destroyed) {
+            return;
+          }
+
           var width = 120;
           var height = 39;
 
@@ -52,11 +80,32 @@ var Gogo = (function() {
 
           var authHeader = Core.getBasicAuthHeader(userDetails.username, userDetails.password);
 
-          gogo.Terminal(element.get(0), width, height, authHeader);
-
-          scope.$on("$destroy", function(e) {
-            document.onkeypress = null;
-            document.onkeydown = null;
+          var url = "hawtio-karaf-terminal/auth/login/";
+
+          $.ajax(url, {
+            type: "POST",
+            success: function (response) {
+              if (scope.destroyed) {
+                log.debug("Scope's been destroyed since we made our request, let's not create a terminal instance");
+                return;
+              }
+              log.debug("got back response: ", response);
+              if ('term' in scope) {
+                log.debug("Previous terminal created, let's clean it up");
+                document.onkeypress = null;
+                document.onkeydown = null;
+                delete scope.term;
+              }
+              scope.term = gogo.Terminal(element.get(0), width, height, response['token']);
+              Core.$apply(scope);
+
+            },
+            error: function (xhr, textStatus, error) {
+              log.warn("Failed to log into terminal: ", error);
+            },
+            beforeSend: function (xhr) {
+              xhr.setRequestHeader('Authorization', authHeader);
+            }
           });
 
         }

programming_language: JavaScript
filename: LoginServlet.java
change_type: ModificationType.MODIFY
diff: @@ -1,16 +1,12 @@
 package io.hawt.web;
 
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import io.hawt.system.ConfigManager;
+import io.hawt.system.Helpers;
+import org.jolokia.converter.Converters;
+import org.jolokia.converter.json.JsonConvertOptions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import javax.security.auth.Subject;
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
@@ -18,13 +14,12 @@ import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
-
-import io.hawt.system.ConfigManager;
-import io.hawt.system.Helpers;
-import org.jolokia.converter.Converters;
-import org.jolokia.converter.json.JsonConvertOptions;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.Principal;
+import java.util.*;
 
 /**
  *
@@ -34,9 +29,9 @@ public class LoginServlet extends HttpServlet {
     private static final long serialVersionUID = 1L;
     private static final transient Logger LOG = LoggerFactory.getLogger(LoginServlet.class);
 
-    Converters converters = new Converters();
-    JsonConvertOptions options = JsonConvertOptions.DEFAULT;
-    ConfigManager config;
+    protected Converters converters = new Converters();
+    protected JsonConvertOptions options = JsonConvertOptions.DEFAULT;
+    protected ConfigManager config;
     private Integer timeout;
 
     @Override
@@ -74,8 +69,9 @@ public class LoginServlet extends HttpServlet {
                 LOG.warn("No security subject stored in existing session, invalidating");
                 session.invalidate();
                 Helpers.doForbidden(resp);
+                return;
             }
-            returnPrincipals(subject, out);
+            sendResponse(session, subject, out);
             return;
         }
 
@@ -112,10 +108,10 @@ public class LoginServlet extends HttpServlet {
             LOG.debug("Http session timeout for user {} is {} sec.", username, session.getMaxInactiveInterval());
         }
 
-        returnPrincipals(subject, out);
+        sendResponse(session, subject, out);
     }
 
-    private void returnPrincipals(Subject subject, PrintWriter out) {
+    protected void sendResponse(HttpSession session, Subject subject, PrintWriter out) {
 
         Map<String, Object> answer = new HashMap<String, Object>();
 

programming_language: Java
filename: LoginTokenServlet.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,42 @@
+package io.hawt.web;
+
+import org.apache.commons.codec.binary.Base64;
+
+import javax.security.auth.Subject;
+import javax.servlet.http.HttpSession;
+import java.io.PrintWriter;
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * @author Stan Lewis
+ */
+public class LoginTokenServlet extends LoginServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    public static final String LOGIN_TOKEN = "LoginToken";
+
+    @Override
+    protected void sendResponse(HttpSession session, Subject subject, PrintWriter out) {
+
+        String token = (String) session.getAttribute(LOGIN_TOKEN);
+
+        if ( token == null) {
+            byte[] seed = (subject.toString() + new Long(System.currentTimeMillis()).toString()).getBytes();
+            SecureRandom random = new SecureRandom(seed);
+            byte[] tokenBytes = new byte[128];
+            random.nextBytes(tokenBytes);
+            token = Base64.encodeBase64String(tokenBytes);
+            session.setAttribute(LOGIN_TOKEN, token);
+        }
+
+        Map<String, String> answer = new HashMap<String, String>();
+        answer.put("token", token);
+
+        ServletHelpers.writeObject(converters, options, out, answer);
+    }
+
+}

programming_language: Java