diff --git a/.gitignore b/.gitignore index 6e10082..fef3086 100755 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,9 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. -# -# If you find yourself ignoring temporary files generated by your text editor -# or operating system, you probably want to add a global ignore instead: -# git config --global core.excludesfile ~/.gitignore_global - -# Ignore bundler config /.bundle - -# Ignore the default SQLite database. +/bin /db/*.sqlite3 - -# Ignore all logfiles and tempfiles. /log/*.log /tmp .elasticbeanstalk/ - -# Ignore Mac folder settings .DS_Store - -# Ignore data directory -/public/data \ No newline at end of file +/public/data +*.png \ No newline at end of file diff --git a/Gemfile b/Gemfile index 72031de..b175ad7 100755 --- a/Gemfile +++ b/Gemfile @@ -11,19 +11,23 @@ gem 'foreman' group :development do gem 'brakeman' - gem 'guard-brakeman' - gem 'guard-rspec' - gem 'rb-fsevent' - gem 'guard-shell' gem 'bundler-audit' + gem 'guard-brakeman' gem 'guard-livereload' + gem 'guard-rspec' + gem 'guard-shell' + gem 'pry' gem 'rack-livereload' + gem 'rb-fsevent' gem 'travis-lint' end gem 'gauntlt' group :development, :test do + gem 'capybara' + gem 'database_cleaner' + gem 'poltergeist' gem 'rspec-rails' end @@ -55,7 +59,7 @@ gem 'jquery-rails' gem 'powder' gem 'aruba' -gem 'minitest', '~> 4.0', :require=> "minitest/autorun" +#gem 'minitest', '~> 4.0', :require=> "minitest/autorun" #gem 'minitest' @@ -64,3 +68,7 @@ gem 'minitest', '~> 4.0', :require=> "minitest/autorun" # To use debugger # gem 'debugger' + +gem 'execjs' +gem 'therubyracer' + diff --git a/Gemfile.lock b/Gemfile.lock index e82bd83..dc27f3d 100755 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -48,8 +48,15 @@ GEM builder (3.0.4) bundler-audit (0.1.2) bundler (~> 1.2) + capybara (2.1.0) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) childprocess (0.3.9) ffi (~> 1.0, >= 1.0.11) + cliver (0.2.2) coderay (1.0.9) coffee-rails (3.2.2) coffee-script (>= 2.2.0) @@ -63,6 +70,7 @@ GEM diff-lcs (>= 1.1.3) gherkin (~> 2.12.0) multi_json (~> 1.3) + database_cleaner (1.1.1) diff-lcs (1.2.4) em-websocket (0.5.0) eventmachine (>= 0.12.9) @@ -116,6 +124,7 @@ GEM thor (>= 0.14, < 2.0) json (1.7.7) kgio (2.8.0) + libv8 (3.16.14.3) listen (0.7.3) lumberjack (1.0.3) mail (2.5.3) @@ -124,9 +133,13 @@ GEM treetop (~> 1.4.8) method_source (0.8.1) mime-types (1.22) - minitest (4.7.5) multi_json (1.7.2) nokogiri (1.5.10) + poltergeist (1.4.1) + capybara (~> 2.1.0) + cliver (~> 0.2.1) + multi_json (~> 1.0) + websocket-driver (>= 0.2.0) polyglot (0.3.3) powder (0.2.0) thor (>= 0.11.5) @@ -163,6 +176,7 @@ GEM rb-fsevent (0.9.3) rdoc (3.12.2) json (~> 1.4) + ref (1.0.5) rspec (2.14.1) rspec-core (~> 2.14.0) rspec-expectations (~> 2.14.0) @@ -201,6 +215,9 @@ GEM sqlite3 (1.3.7) temple (0.6.3) terminal-table (1.4.5) + therubyracer (0.12.0) + libv8 (~> 3.16.14.0) + ref thor (0.18.1) tilt (1.3.7) travis-lint (1.7.0) @@ -217,6 +234,9 @@ GEM kgio (~> 2.6) rack raindrops (~> 0.7) + websocket-driver (0.3.0) + xpath (2.0.0) + nokogiri (~> 1.3) PLATFORMS ruby @@ -226,7 +246,10 @@ DEPENDENCIES bcrypt-ruby brakeman bundler-audit + capybara coffee-rails (~> 3.2.1) + database_cleaner + execjs foreman gauntlt guard-brakeman @@ -235,14 +258,16 @@ DEPENDENCIES guard-shell jquery-fileupload-rails jquery-rails - minitest (~> 4.0) + poltergeist powder + pry rack-livereload rails (= 3.2.13) rb-fsevent rspec-rails sass-rails (~> 3.2.3) sqlite3 + therubyracer travis-lint uglifier (>= 1.0.3) unicorn diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 09701a3..d5ae600 100755 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -9,10 +9,7 @@ class UsersController < ApplicationController def create user = User.new(params[:user]) - user.build_retirement(POPULATE_RETIREMENTS.shuffle.first) - user.build_paid_time_off(POPULATE_PAID_TIME_OFF.shuffle.first).schedule.build(POPULATE_SCHEDULE.shuffle.first) - user.build_work_info(POPULATE_WORK_INFO.shuffle.first) - user.performance.build(POPULATE_PERFORMANCE.shuffle.first) + user.build_benefits_data if user.save session[:user_id] = user.user_id redirect_to home_dashboard_index_path @@ -36,7 +33,7 @@ class UsersController < ApplicationController user = User.find(:first, :conditions => "user_id = '#{params[:user][:user_id]}'") user.skip_user_id_assign = true - user.update_attributes(params[:user].reject { |k| k == ("password" || "password_confirmation") || "user_id" }) + user.update_attributes(params[:user].reject { |k| %w(password password_confirmation user_id).include? k }) pass = params[:user][:password] user.password = pass if !(pass.blank?) message = true if user.save! diff --git a/app/models/performance.rb b/app/models/performance.rb index b1df992..f6785b1 100644 --- a/app/models/performance.rb +++ b/app/models/performance.rb @@ -3,7 +3,7 @@ class Performance < ActiveRecord::Base belongs_to :user def reviewer_name - u = User.find_by_id(self.reviewer) - u.full_name if u.respond_to?('full_name') + u = User.find_by_id(self.reviewer) + u.full_name if u.respond_to?('fullname') end end diff --git a/app/models/performance.rb.orig b/app/models/performance.rb.orig new file mode 100644 index 0000000..772aaf0 --- /dev/null +++ b/app/models/performance.rb.orig @@ -0,0 +1,14 @@ +class Performance < ActiveRecord::Base + attr_accessible :comments, :date_submitted, :reviewer, :score + belongs_to :user + + def reviewer_name +<<<<<<< HEAD + u = User.find_by_id(self.reviewer) + u.full_name if u.respond_to?('full_name') +======= + u = User.find_by_id(self.reviewer) + u.full_name if u.respond_to?('fullname') +>>>>>>> 289716b24c7c4a1d72fcf1cf16fdc003e96e728c + end +end diff --git a/app/models/user.rb b/app/models/user.rb index bec4cdd..a1d8b72 100755 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -16,8 +16,15 @@ class User < ActiveRecord::Base has_one :paid_time_off, :foreign_key => :user_id, :primary_key => :user_id, :dependent => :destroy has_one :work_info, :foreign_key => :user_id, :primary_key => :user_id, :dependent => :destroy has_many :performance, :foreign_key => :user_id, :primary_key => :user_id, :dependent => :destroy - - + + + def build_benefits_data + build_retirement(POPULATE_RETIREMENTS.shuffle.first) + build_paid_time_off(POPULATE_PAID_TIME_OFF.shuffle.first).schedule.build(POPULATE_SCHEDULE.shuffle.first) + build_work_info(POPULATE_WORK_INFO.shuffle.first) + performance.build(POPULATE_PERFORMANCE.shuffle.first) + end + private def full_name diff --git a/public/data/.gitkeep b/public/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/spec/features/broken_auth_spec.rb b/spec/features/broken_auth_spec.rb new file mode 100644 index 0000000..d1f7e6e --- /dev/null +++ b/spec/features/broken_auth_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +feature 'broken_auth' do + before do + UserFixture.reset_all_users + @normal_user = UserFixture.normal_user + end + + scenario 'TMI during login', :js => true do + visit '/' + within('.signup') do + fill_in 'email', :with => @normal_user.email + 'not' + fill_in 'password', :with => @normal_user.clear_password + end + click_on 'Login' + find('div#flash_notice').text.should == "#{@normal_user.email}not doesn't exist!" + + within('.signup') do + fill_in 'email', :with => @normal_user.email + fill_in 'password', :with => @normal_user.clear_password + 'not' + end + click_on 'Login' + find('div#flash_notice').text.should == 'Incorrect Password!' + end +end \ No newline at end of file diff --git a/spec/features/command_injection_spec.rb b/spec/features/command_injection_spec.rb new file mode 100644 index 0000000..e1ef311 --- /dev/null +++ b/spec/features/command_injection_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' +require 'tmpdir' + +feature 'command injection' do + before do + UserFixture.reset_all_users + @normal_user = UserFixture.normal_user + end + + scenario 'injection attack on file upload', :js => true do + login(@normal_user) + + legit_file = File.join(Rails.root, 'public', 'data', 'legit.txt') + File.open(legit_file, 'w') { |f| f.puts 'totes legit' } + + visit "/users/#{@normal_user.user_id}/benefit_forms" + Dir.mktmpdir do |dir| + hackety_file = File.join(dir, '; cd public && cd data && rm -f * ;') + File.open(hackety_file, 'w') { |f| f.print 'mwahaha' } + within('.new_benefits') do + attach_file 'benefits_upload', hackety_file + find(:xpath, "//input[@id='benefits_backup']", :visible => false).set 'true' + end + save_screenshot('screenshot.before.upload.png') + click_on 'Start Upload' + end + save_screenshot('screenshot.after.upload.png') + File.exists?(legit_file).should be_false + end +end \ No newline at end of file diff --git a/spec/features/insecure_dor_spec.rb b/spec/features/insecure_dor_spec.rb new file mode 100644 index 0000000..b0ac570 --- /dev/null +++ b/spec/features/insecure_dor_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +feature 'insecure direct object reference' do + before do + UserFixture.reset_all_users + @normal_user = UserFixture.normal_user + end + + scenario 'download production configuration' do + login(@normal_user) + + visit "/users/#{@normal_user.user_id}/benefit_forms" + download_url = first('.widget-body a')[:href] + visit download_url.sub(/name=(.*?)&/, 'name=../../config/database.yml&') + + page.status_code.should == 200 + page.response_headers['Content-Disposition'].should include('database.yml') + page.response_headers['Content-Length'].should == '576' + end + + scenario 'view any user work_info' do + login(@normal_user) + + @normal_user.user_id.should_not == 2 + visit '/users/2/work_info' + + first('td').text.should == 'Jack Mannino' + end +end \ No newline at end of file diff --git a/spec/features/sql_injection_spec.rb b/spec/features/sql_injection_spec.rb new file mode 100644 index 0000000..9553fc4 --- /dev/null +++ b/spec/features/sql_injection_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +feature 'sql injection' do + before do + UserFixture.reset_all_users + @normal_user = UserFixture.normal_user + @admin_user = User.where("admin='t'").first + end + + scenario 'injection attack on account_settings' do + @admin_user.admin.should be_true + + login(@normal_user) + + visit "/users/#{@normal_user.user_id}/account_settings" + within('#account_edit') do + fill_in 'Email', :with => 'joe.admin@schmoe.com' + fill_in 'user_password', :with => 'hacketyhack' + fill_in 'user_password_confirmation', :with => 'hacketyhack' + + # this is a hidden field, so cannot use fill_in to access it. + find(:xpath, "//input[@id='user_user_id']", :visible => false).set "8' OR admin='t') --" + end + click_on 'Submit' + + @admin_user = User.where("admin='t'").first + @admin_user.email.should == 'joe.admin@schmoe.com' + @admin_user.admin.should == true + end +end \ No newline at end of file diff --git a/spec/features/xss_spec.rb b/spec/features/xss_spec.rb new file mode 100644 index 0000000..a5bea9f --- /dev/null +++ b/spec/features/xss_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +feature 'xss' do + before do + UserFixture.reset_all_users + @normal_user = UserFixture.normal_user + end + + scenario 'xss attack on account_settings', :js => true do + login @normal_user + + visit "/users/#{@normal_user.user_id}/account_settings" + within('#account_edit') do + fill_in 'First name', :with => "B" + + # password gets screwed up if you don't re-submit - need to fix + fill_in 'user_password', :with => @normal_user.clear_password + fill_in 'user_password_confirmation', :with => @normal_user.clear_password + end + click_on 'Submit' + save_screenshot('screenshot.post.submit.png') + + visit '/' + + find('form.button_to input.btn.btn-primary').value.should == 'RailsGoat h4x0r3d' + + # might be nice to demonstrate posting cookie contents or somesuch, but + # this at least shows the vulnerability still exists. + end +end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d2cbea7..417153f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,6 +3,9 @@ ENV["RAILS_ENV"] ||= 'test' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'rspec/autorun' +require 'capybara/rails' +require 'capybara/poltergeist' +require 'database_cleaner' # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. @@ -23,7 +26,7 @@ RSpec.configure do |config| # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. - config.use_transactional_fixtures = true + config.use_transactional_fixtures = false # Capybara Poltergeist driver requires this # If true, the base class of anonymous controllers will be inferred # automatically. This will be the default behavior in future versions of @@ -35,4 +38,16 @@ RSpec.configure do |config| # the seed, which is printed after each run. # --seed 1234 config.order = "random" + + config.before(:each) do + DatabaseCleaner.start + end + + config.after(:each) do + DatabaseCleaner.clean + end end + +Capybara.javascript_driver = :poltergeist + +DatabaseCleaner.strategy = :truncation \ No newline at end of file diff --git a/spec/support/capybara_shared.rb b/spec/support/capybara_shared.rb new file mode 100644 index 0000000..aeeb960 --- /dev/null +++ b/spec/support/capybara_shared.rb @@ -0,0 +1,8 @@ +def login(user) + visit '/' + within('.signup') do + fill_in 'email', :with => user.email + fill_in 'password', :with => user.clear_password + end + click_on 'Login' +end diff --git a/spec/support/user_fixture.rb b/spec/support/user_fixture.rb new file mode 100644 index 0000000..8a5f182 --- /dev/null +++ b/spec/support/user_fixture.rb @@ -0,0 +1,18 @@ +class UserFixture + def self.reset_all_users + User.delete_all + Rails.application.load_seed + end + + def self.normal_user + password = 'aoeuaoeu' + user = User.new(:first_name => 'Joe', :last_name => 'Schmoe', + :email => 'joe@schmoe.com', :password => password, :password_confirmation => password) + def user.clear_password + 'aoeuaoeu' + end + user.build_benefits_data + user.save! + user + end +end \ No newline at end of file