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

The admin terminal in Hawt.io does not require authentication, which allows remote attackers to execute arbitrary commands via the k parameter.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-287
cwe_name: Improper Authentication
description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
extended_description: 
            
			
         
         

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-05 10:48:13-05:00
msg: Ensure we secure hawtio-karaf-terminal's /term context
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: pom.xml
change_type: ModificationType.MODIFY
diff: @@ -30,6 +30,12 @@
       <version>${project.version}</version>
     </dependency>
 
+    <dependency>
+      <groupId>io.hawt</groupId>
+      <artifactId>hawtio-system</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
     <dependency>
       <groupId>javax.servlet</groupId>
       <artifactId>servlet-api</artifactId>

programming_language: HTML
filename: KarafTerminalContextListener.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,38 @@
+package io.hawt.web.plugin.karaf.terminal;
+
+import io.hawt.system.ConfigManager;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+/**
+ * @author Stan Lewis
+ */
+public class KarafTerminalContextListener implements ServletContextListener {
+
+  private ConfigManager configManager = new ConfigManager();
+
+  @Override
+  public void contextInitialized(ServletContextEvent sce) {
+    try {
+      configManager.init();
+    } catch (Exception e) {
+      throw createServletException(e);
+    }
+    sce.getServletContext().setAttribute("ConfigManager", configManager);
+  }
+
+  @Override
+  public void contextDestroyed(ServletContextEvent sce) {
+    try {
+      configManager.destroy();
+    } catch (Exception e) {
+      throw createServletException(e);
+    }
+
+  }
+  protected RuntimeException createServletException(Exception e) {
+    return new RuntimeException(e);
+  }
+
+}

programming_language: Java
filename: TerminalServlet.java
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,6 @@
 package io.hawt.web.plugin.karaf.terminal;
 
+import io.hawt.system.Helpers;
 import org.apache.felix.service.command.CommandProcessor;
 import org.apache.felix.service.command.CommandSession;
 import org.apache.felix.service.threadio.ThreadIO;
@@ -7,12 +8,16 @@ 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;
 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;
 
 /**
@@ -38,12 +43,32 @@ public class TerminalServlet extends HttpServlet {
 
     @Override
     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) {
+            Helpers.doForbidden(response);
+            return;
+          }
+          session = request.getSession(true);
+          session.setAttribute("subject", subject);
+        } else {
+          Subject subject = (Subject) session.getAttribute("subject");
+          if (subject == null) {
+            session.invalidate();
+            Helpers.doForbidden(response);
+            return;
+          }
+        }
+
         String encoding = request.getHeader("Accept-Encoding");
         boolean supportsGzip = (encoding != null && encoding.toLowerCase().indexOf("gzip") > -1);
-        SessionTerminal st = (SessionTerminal) request.getSession(true).getAttribute("terminal");
+        SessionTerminal st = (SessionTerminal) session.getAttribute("terminal");
         if (st == null || st.isClosed()) {
             st = new SessionTerminal(getCommandProcessor(), getThreadIO());
-            request.getSession().setAttribute("terminal", st);
+            session.setAttribute("terminal", st);
         }
         String str = request.getParameter("k");
         String f = request.getParameter("f");

programming_language: Java
filename: web.xml
change_type: ModificationType.MODIFY
diff: @@ -8,6 +8,44 @@
   <description>hawtio</description>
   <display-name>hawtio Karaf terminal plugin</display-name>
 
+  <env-entry>
+    <description>Enable/disable hawtio's authentication filter, value is really a boolean</description>
+    <env-entry-name>hawtio/authenticationEnabled</env-entry-name>
+    <env-entry-type>java.lang.String</env-entry-type>
+    <env-entry-value>false</env-entry-value>
+  </env-entry>
+
+  <env-entry>
+    <description>Authorized user role, empty string disables authorization</description>
+    <env-entry-name>hawtio/role</env-entry-name>
+    <env-entry-type>java.lang.String</env-entry-type>
+    <env-entry-value></env-entry-value>
+  </env-entry>
+
+  <env-entry>
+    <description>JAAS classname that would contain the role principal, empty string disables authorization</description>
+    <env-entry-name>hawtio/rolePrincipalClasses</env-entry-name>
+    <env-entry-type>java.lang.String</env-entry-type>
+    <env-entry-value></env-entry-value>
+  </env-entry>
+
+  <env-entry>
+    <description>JAAS realm used to authenticate users</description>
+    <env-entry-name>hawtio/realm</env-entry-name>
+    <env-entry-type>java.lang.String</env-entry-type>
+    <env-entry-value>*</env-entry-value>
+  </env-entry>
+
+  <filter>
+    <filter-name>AuthenticationFilter</filter-name>
+    <filter-class>io.hawt.web.AuthenticationFilter</filter-class>
+  </filter>
+  <filter-mapping>
+    <filter-name>AuthenticationFilter</filter-name>
+    <url-pattern>/term/*</url-pattern>
+  </filter-mapping>
+
+
   <servlet>
     <servlet-name>TerminalServlet</servlet-name>
     <servlet-class>io.hawt.web.plugin.karaf.terminal.TerminalServlet</servlet-class>
@@ -18,5 +56,9 @@
     <url-pattern>/term/*</url-pattern>
   </servlet-mapping>
 
+  <listener>
+    <listener-class>io.hawt.web.plugin.karaf.terminal.KarafTerminalContextListener</listener-class>
+  </listener>
+
 </web-app>
 

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

programming_language: JavaScript
filename: gogoPlugin.js
change_type: ModificationType.MODIFY
diff: @@ -11,7 +11,7 @@ var Gogo = (function() {
         when('/gogo', {
             templateUrl: 'hawtio-karaf-terminal/app/html/gogo.html'
           });
-    }).directive('gogoTerminal', function(log) {
+    }).directive('gogoTerminal', function(log, userDetails) {
       return {
         restrict: 'A',
         link: function(scope, element, attrs) {
@@ -50,7 +50,9 @@ var Gogo = (function() {
             'min-height': cssHeight
           });
 
-          gogo.Terminal(element.get(0), width, height);
+          var authHeader = Core.getBasicAuthHeader(userDetails.username, userDetails.password);
+
+          gogo.Terminal(element.get(0), width, height, authHeader);
 
           scope.$on("$destroy", function(e) {
             document.onkeypress = null;

programming_language: JavaScript
filename: AuthenticationFilter.java
change_type: ModificationType.MODIFY
diff: @@ -105,9 +105,7 @@ public class AuthenticationFilter implements Filter {
             }
         }
 
-        boolean doAuthenticate = path.startsWith("/auth") ||
-                path.startsWith("/jolokia") ||
-                path.startsWith("/upload");
+        boolean doAuthenticate = true;
 
         if (doAuthenticate) {
             LOG.debug("Doing authentication and authorization for path {}", path);
@@ -129,7 +127,7 @@ public class AuthenticationFilter implements Filter {
                     break;
             }
         } else {
-            LOG.debug("No authentication needed for path {}", path);
+            LOG.warn("No authentication needed for path {}", path);
             chain.doFilter(request, response);
         }
     }

programming_language: Java