CVE-2016-0721 (Ruby, published 2017-04-21T15:59Z)
Description: value: Session fixation vulnerability in pcsd in pcs before 0.9.157.
Severity: MEDIUM (CVSS v3 base score: 8.1)
Weakness: CWE-384 \u2014 Session Fixation
Authenticating a user, or otherwise establishing a new user session, without invalidating any existing session identifier gives an attacker the opportunity to steal authenticated sessions.
Repository: https://github.com/ClusterLabs/pcs
Files changed:
pcsd/pcsd.rb
pcsd/public/js/nodes-ember.js
pcsd/public/js/pcsd.js
pcsd/views/_dialogs.erb
pcsd/views/permissions.erb
Commit message: web UI auth: add js/ajax login dialog


Vulnerable code:
      if !gui_request and
        request.env['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest'
      then
  get '/logout' do 
    erb :login, :layout => :main
      redirect '/manage'
      session["bad_login_name"] = params['username']
      redirect '/login?badlogin=1'
      $.ajax({
    $.ajax({
    $.ajax({
    $.ajax({
    $.ajax({
        $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
      "#node_start", get_cluster_remote_url() +"cluster_start", "start"
  $.ajax({
      var message = "Unable to stop node '" + node + " " + ajax_simple_error(
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
      $.ajax({
  $.ajax({
      $.ajax({
    $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({
  $.ajax({

Fixed code:
  def is_ajax?
    return request.env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'
  end

      if !gui_request and !is_ajax? then
  get '/logout' do
    redirect '/login'
      if is_ajax?
        halt [200, "OK"]
      else
        redirect '/manage'
      end
      if is_ajax?
        halt [401, '{"notauthorized":"true"}']
      else
        session["bad_login_name"] = params['username']
        redirect '/login?badlogin=1'
      end
      ajax_wrapper({
    ajax_wrapper({
    ajax_wrapper({
    ajax_wrapper({
    ajax_wrapper({
var login_dialog_opened = false;
var ajax_queue = Array();
        ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
      "#node_start", get_cluster_remote_url() + "cluster_start", "start"
  ajax_wrapper({
      var message = "Unable to stop node '" + node + "' " + ajax_simple_error(
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
      ajax_wrapper({
  ajax_wrapper({
      ajax_wrapper({
    ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
function ajax_wrapper(options) {
  // get original callback functions
  var error_original = function(xhr, status, error) {};
  if (options.error) {
    error_original = options.error;
  }
  var complete_original = function(xhr, status) {};
  if (options.complete) {
    complete_original = options.complete;
  }

  // prepare new callback functions
  var options_new = $.extend(true, {}, options);
  // display login dialog on error
  options_new.error = function(xhr, status, error) {
    if (xhr.status == 401) {
      ajax_queue.push(options);
      if (!login_dialog_opened) {
        login_dialog(function() {
          var item;
          while (ajax_queue.length > 0) {
            item = ajax_queue.shift();
            ajax_wrapper(item);
          }
        });
      }
    }
    else {
      error_original(xhr, status, error);
    }
  }
  // Do not run complete function if login dialog is open.
  // Once user is logged in again, the original complete function will be run
  // in repeated ajax call run by login dialog on success.
  options_new.complete = function(xhr, status) {
    if (xhr.status == 401) {
      return;
    }
    else {
      complete_original(xhr, status);
    }
  }

  // run ajax request or put it into a queue
  if (login_dialog_opened) {
    ajax_queue.push(options);
  }
  else {
    $.ajax(options_new);
  }
}

function login_dialog(on_success) {
  var ok_button_id = "login_form_ok";
  var ok_button_selector = "#" + ok_button_id;
  var buttons = [
    {
      text: "Log In",
      id: ok_button_id,
      click: function() {
        var me = $(this);
        var my_dialog = $(this).dialog()
        my_dialog.find("#login_form_denied").hide();
        $(ok_button_selector).button("option", "disabled", true);
        $.ajax({
          type: "POST",
          url: "/login",
          data: my_dialog.find("#login_form").serialize(),
          complete: function() {
            $(ok_button_selector).button("option", "disabled", false);
          },
          success: function() {
            my_dialog.find("#login_form_username").val("");
            my_dialog.find("#login_form_password").val("");
            me.dialog("destroy");
            login_dialog_opened = false;
            on_success();
          },
          error: function(xhr, status, error) {
            if (xhr.status == 401) {
              my_dialog.find("#login_form_denied").show();
              my_dialog.find("#login_form_password").val("");
            }
            else {
              alert("Login error " + ajax_simple_error(xhr, status, error));
            }
          },
        });
      },
    },
    {
      text: "Cancel",
      id: "login_form_cancel",
      // cancel will close the dialog the same way as X button does
      click: function() {
        $(this).dialog("close");
      },
    },
  ];
  var dialog_obj = $("#dialog_login").dialog({
    title: "Log In",
    modal: true,
    resizable: true,
    width: 400,
    buttons: buttons,
    open: function(event, ui) {
      login_dialog_opened = true;
    },
    create: function(event, ui) {
      login_dialog_opened = true;
    },
    // make sure to logout the user on dialog close
    close: function(event, ui) {
      login_dialog_opened = false;
      location = "/logout";
    },
  });
  dialog_obj.find("#login_form_denied").hide();
  // submit on enter
  dialog_obj.keypress(function(e) {
    if (
      e.keyCode == $.ui.keyCode.ENTER
      &&
      !dialog_obj.parent().find(ok_button_selector).button("option", "disabled")
    ) {
      dialog_obj.parent().find(ok_button_selector).trigger("click");
      return false;
    }
  });
}

  ajax_wrapper({
  ajax_wrapper({
  ajax_wrapper({
<div id="dialog_login" style="display:none;">
  <form id="login_form">
    <div>Your session has expired. Log in again, please.</div>
    <div id="login_form_denied" style="color:red; display:none;">
      Bad username or password
    </div>
    <table>
      <tr>
        <td><label for="login_form_username">Username:</label></td>
        <td><input type="text" id="login_form_username" name="username"></td>
      </tr>
      <tr>
        <td><label for="login_form_password">Password:</label></td>
        <td><input type="password" id="login_form_password" name="password"></td>
      </tr>
    </table>
  </form>
</div>

<%= erb :_dialogs %>