CVE-2014-0120 (Java, published 2017-12-29T22:29Z)
Description: value: 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 v3 base score: 8.8)
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/hawtio/hawtio
Files changed:
hawtio-karaf-terminal/src/main/java/io/hawt/web/plugin/karaf/terminal/TerminalServlet.java
hawtio-karaf-terminal/src/main/webapp/WEB-INF/web.xml
hawtio-karaf-terminal/src/main/webapp/app/js/gogo.js
hawtio-karaf-terminal/src/main/webapp/app/js/gogoPlugin.js
hawtio-system/src/main/java/io/hawt/web/LoginServlet.java
hawtio-system/src/main/java/io/hawt/web/LoginTokenServlet.java
Commit message: 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.


Vulnerable code:
import javax.security.auth.Subject;
import java.security.AccessControlContext;
import java.security.AccessController;
        if (session == null) {
          AccessControlContext acc = AccessController.getContext();
          Subject subject = Subject.getSubject(acc);
          if (subject == null) {
          }
          session = request.getSession(true);
          session.setAttribute("subject", subject);
        } else {
          Subject subject = (Subject) session.getAttribute("subject");
          if (subject == null) {
            session.invalidate();
          }
gogo.Terminal_ctor = function(div, width, height, authHeader) {
           r.setRequestHeader('Authorization', authHeader);
gogo.Terminal = function(div, width, height, authHeader) {
   return new this.Terminal_ctor(div, width, height, authHeader);
          gogo.Terminal(element.get(0), width, height, authHeader);

          scope.$on("$destroy", function(e) {
            document.onkeypress = null;
            document.onkeydown = null;
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;
    Converters converters = new Converters();
    JsonConvertOptions options = JsonConvertOptions.DEFAULT;
    ConfigManager config;
            returnPrincipals(subject, out);
        returnPrincipals(subject, out);
    private void returnPrincipals(Subject subject, PrintWriter out) {

Fixed code:
import io.hawt.web.LoginTokenServlet;
        String token = request.getHeader(LoginTokenServlet.LOGIN_TOKEN);

        if (token == null || session == null) {
        }

        String sessionToken = (String) session.getAttribute(LoginTokenServlet.LOGIN_TOKEN);
        if (!token.equals(sessionToken)) {
  <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>
gogo.Terminal_ctor = function(div, width, height, token) {
           r.setRequestHeader('LoginToken', token);
gogo.Terminal = function(div, width, height, token) {
   return new this.Terminal_ctor(div, width, height, token);
          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 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);
            }
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.*;
    protected Converters converters = new Converters();
    protected JsonConvertOptions options = JsonConvertOptions.DEFAULT;
    protected ConfigManager config;
                return;
            sendResponse(session, subject, out);
        sendResponse(session, subject, out);
    protected void sendResponse(HttpSession session, Subject subject, PrintWriter out) {
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);
    }

}