Initial commit (history cleared)
CI / test (3.4.1) (push) Has been cancelled

This commit is contained in:
2026-04-29 11:21:39 +01:00
commit 298610b5f6
277 changed files with 30877 additions and 0 deletions
@@ -0,0 +1,6 @@
# frozen_string_literal: true
# require 'spec_helper'
#
# describe Api::V1::UsersController do
#
# end
@@ -0,0 +1,2 @@
# frozen_string_literal: true
require "spec_helper"
@@ -0,0 +1,2 @@
# frozen_string_literal: true
require "spec_helper"
+6
View File
@@ -0,0 +1,6 @@
# frozen_string_literal: true
# require 'spec_helper'
#
# describe PayController do
#
# end
@@ -0,0 +1,308 @@
# frozen_string_literal: true
require "spec_helper"
RSpec.describe TutorialsController, type: :controller do
describe "ReDoS vulnerabilities (Rails 8)" do
describe "POST #redos_email" do
context "with valid email" do
it "validates email successfully" do
post :redos_email, params: { email: "test@example.com" }
expect(response).to have_http_status(:success)
json_response = JSON.parse(response.body)
expect(json_response["valid"]).to be true
expect(json_response["message"]).to eq("Email validation completed")
end
it "completes validation in reasonable time" do
post :redos_email, params: { email: "test@example.com" }
json_response = JSON.parse(response.body)
expect(json_response["time_elapsed"]).to be < 0.1 # Should be nearly instant
end
end
context "with potentially malicious ReDoS input" do
it "handles potentially malicious input" do
# Input that could cause catastrophic backtracking in less optimized regex engines
# Note: Ruby 3.3's regex engine is well-optimized and may not timeout with this input
malicious_email = "a" * 30 + "@" + "a" * 30
post :redos_email, params: { email: malicious_email }
# Response may be success (if regex completes) or bad_request (if timeout)
# Both are acceptable outcomes demonstrating the vulnerability
expect(response).to have_http_status(:success).or have_http_status(:bad_request)
json_response = JSON.parse(response.body)
# If it times out, check error message
if response.status == 400
expect(json_response["error"]).to eq("Timeout")
expect(json_response["message"]).to include("ReDoS")
end
end
it "demonstrates the vulnerable pattern exists" do
# This test documents that the pattern is theoretically vulnerable
# even if Ruby 3.3's engine handles it efficiently
malicious_email = "test@example.com"
post :redos_email, params: { email: malicious_email }
expect(response).to have_http_status(:success)
json_response = JSON.parse(response.body)
expect(json_response).to have_key("time_elapsed")
end
end
end
describe "POST #redos_username" do
context "with valid username" do
it "validates username matching pattern" do
post :redos_username, params: { username: "aaaa" }
expect(response).to have_http_status(:success)
json_response = JSON.parse(response.body)
expect(json_response["valid"]).to be true
end
end
context "with potentially malicious ReDoS input" do
it "demonstrates the classic ReDoS pattern (a+)+" do
# This is the classic ReDoS pattern: (a+)+
# Ruby 3.3's engine is optimized but the pattern is still considered vulnerable
malicious_username = "a" * 30 + "!"
post :redos_username, params: { username: malicious_username }
# Ruby 3.3 handles this efficiently, but the pattern is still bad practice
expect(response).to have_http_status(:success).or have_http_status(:bad_request)
json_response = JSON.parse(response.body)
# If it times out, verify the timeout message
if response.status == 400
expect(json_response["error"]).to eq("Timeout")
expect(json_response["time_elapsed"]).to be >= 0.9
end
end
it "demonstrates Rails 8 timeout protection exists" do
malicious_username = "a" * 30 + "!"
# With Rails 8's Regexp.timeout, this won't hang indefinitely
# (In older Ruby versions without timeout, this could hang)
expect {
post :redos_username, params: { username: malicious_username }
}.not_to raise_error # Should not hang, should return response
end
end
end
describe "POST #redos_email_safe" do
context "with valid email" do
it "validates email using safe regex" do
post :redos_email_safe, params: { email: "test@example.com" }
expect(response).to have_http_status(:success)
json_response = JSON.parse(response.body)
expect(json_response["valid"]).to be true
expect(json_response["message"]).to include("safe method")
end
end
context "with potentially malicious input" do
it "handles malicious input safely without timeout" do
malicious_email = "a" * 100 + "@" + "a" * 100 + ".com"
post :redos_email_safe, params: { email: malicious_email }
# Should complete quickly without timeout
expect(response).to have_http_status(:success)
json_response = JSON.parse(response.body)
expect(json_response["time_elapsed"]).to be < 0.1 # Fast even with long input
end
end
end
end
describe "Comparison: Vulnerable vs Safe" do
it "demonstrates the difference between vulnerable and safe patterns" do
# Test vulnerable endpoint with potentially malicious input
post :redos_username, params: { username: "aaaa" }
vulnerable_response = JSON.parse(response.body)
# Test safe endpoint with same type of input
post :redos_email_safe, params: { email: "test@example.com" }
safe_response = JSON.parse(response.body)
# Both should complete (Ruby 3.3 is well-optimized)
expect(vulnerable_response).to have_key("time_elapsed")
expect(safe_response).to have_key("time_elapsed")
# Safe endpoint should use Ruby's built-in URI regex
expect(safe_response["message"]).to include("safe method")
end
it "shows that timeout protection is available" do
# Demonstrates that Regexp.timeout is configured
# This prevents potential hangs even if catastrophic backtracking occurs
expect(Regexp.timeout).to eq(1.0)
end
end
describe "A03:2025 - Software Supply Chain Failures (Rails 8)" do
describe "GET #supply_chain" do
it "returns comprehensive supply chain vulnerability information" do
get :supply_chain
expect(response).to have_http_status(:success)
json_response = JSON.parse(response.body)
expect(json_response).to have_key("vulnerabilities")
expect(json_response).to have_key("demo")
expect(json_response).to have_key("secure_example")
end
it "documents missing SRI vulnerability" do
get :supply_chain
json_response = JSON.parse(response.body)
vulnerabilities = json_response["vulnerabilities"]
sri_vuln = vulnerabilities.find { |v| v["type"] == "Missing Subresource Integrity (SRI)" }
expect(sri_vuln).not_to be_nil
expect(sri_vuln["location"]).to eq("app/views/layouts/application.html.erb")
expect(sri_vuln["description"]).to include("CDN assets loaded without integrity checks")
expect(sri_vuln["impact"]).to include("compromised")
end
it "documents outdated dependencies vulnerability" do
get :supply_chain
json_response = JSON.parse(response.body)
vulnerabilities = json_response["vulnerabilities"]
dep_vuln = vulnerabilities.find { |v| v["type"] == "Outdated Dependencies" }
expect(dep_vuln).not_to be_nil
expect(dep_vuln["mitigation"]).to include("bundle audit")
end
it "documents missing SBOM vulnerability" do
get :supply_chain
json_response = JSON.parse(response.body)
vulnerabilities = json_response["vulnerabilities"]
sbom_vuln = vulnerabilities.find { |v| v["type"] == "No Software Bill of Materials (SBOM)" }
expect(sbom_vuln).not_to be_nil
expect(sbom_vuln["mitigation"]).to include("CycloneDX or SPDX")
end
it "provides secure vs vulnerable examples" do
get :supply_chain
json_response = JSON.parse(response.body)
secure_example = json_response["secure_example"]
expect(secure_example["vulnerable"]).not_to include("integrity=")
expect(secure_example["secure"]).to include("integrity=")
expect(secure_example["secure"]).to include("crossorigin=")
end
it "includes real-world CVE example" do
get :supply_chain
json_response = JSON.parse(response.body)
vulnerabilities = json_response["vulnerabilities"]
sri_vuln = vulnerabilities.find { |v| v["type"] == "Missing Subresource Integrity (SRI)" }
expect(sri_vuln["cve_example"]).to include("British Airways")
expect(sri_vuln["cve_example"]).to include("Magecart")
end
end
describe "GET #check_dependencies" do
it "returns dependency scanning simulation" do
get :check_dependencies
expect(response).to have_http_status(:success)
json_response = JSON.parse(response.body)
expect(json_response["status"]).to eq("scan_complete")
expect(json_response).to have_key("message")
expect(json_response).to have_key("example_vulnerabilities")
expect(json_response).to have_key("recommended_tools")
end
it "provides example vulnerability data" do
get :check_dependencies
json_response = JSON.parse(response.body)
vulnerabilities = json_response["example_vulnerabilities"]
expect(vulnerabilities).to be_an(Array)
expect(vulnerabilities.length).to be >= 2
# Check Rails example
rails_vuln = vulnerabilities.find { |v| v["gem"] == "rails" }
expect(rails_vuln).not_to be_nil
expect(rails_vuln["version"]).to eq("8.0.4")
end
it "recommends security scanning tools" do
get :check_dependencies
json_response = JSON.parse(response.body)
tools = json_response["recommended_tools"]
expect(tools).to be_an(Array)
expect(tools.any? { |t| t.include?("bundle-audit") }).to be true
expect(tools.any? { |t| t.include?("Dependabot") }).to be true
expect(tools.any? { |t| t.include?("Snyk") }).to be true
end
it "includes instructions for manual checking" do
get :check_dependencies
json_response = JSON.parse(response.body)
expect(json_response["note"]).to include("bundle audit")
end
it "handles errors gracefully" do
# Simulate an error by stubbing JSON.parse to raise an error
allow_any_instance_of(TutorialsController).to receive(:render).and_call_original
# The endpoint should handle errors and return 500
# This is more of a structural test to ensure error handling exists
get :check_dependencies
# Should return successful response under normal conditions
expect(response).to have_http_status(:success)
end
end
describe "Integration: Supply Chain Attack Surface" do
it "demonstrates complete attack surface" do
# Check supply chain vulnerabilities
get :supply_chain
supply_response = JSON.parse(response.body)
# Check dependency scanning
get :check_dependencies
dep_response = JSON.parse(response.body)
# Both endpoints should provide complementary information
expect(supply_response["vulnerabilities"].length).to be >= 5
expect(dep_response["recommended_tools"].length).to be >= 4
# Supply chain should reference the tools mentioned in dependency check
expect(supply_response["vulnerabilities"].any? { |v| v["mitigation"]&.include?("bundle audit") }).to be true
end
end
end
end
+2
View File
@@ -0,0 +1,2 @@
# frozen_string_literal: true
require "spec_helper"
+24
View File
@@ -0,0 +1,24 @@
# frozen_string_literal: true
require "spec_helper"
require_relative "../../lib/encryption"
describe Encryption do
let(:value) {
allow(Encryption).to receive(:key).and_return(SecureRandom.bytes(32))
allow(Encryption).to receive(:iv).and_return(SecureRandom.bytes(16))
"OMG PII"
}
it "encrypts values" do
encrypted = Encryption.encrypt_sensitive_value(value)
expect(Base64.decode64(encrypted)).not_to eq(value)
end
it "decrypts values" do
encrypted = Encryption.encrypt_sensitive_value(value)
decrypted = Encryption.decrypt_sensitive_value(encrypted)
expect(decrypted).to eq(value)
end
end
+2
View File
@@ -0,0 +1,2 @@
# frozen_string_literal: true
require "spec_helper"
+25
View File
@@ -0,0 +1,25 @@
# frozen_string_literal: true
require "spec_helper.rb"
describe Benefits do
before(:all) do
UserFixture.reset_all_users
DatabaseCleaner.strategy = :transaction
end
after(:all) do
DatabaseCleaner.strategy = :truncation
end
it "can be instantiated" do
expect(Benefits.new).to be_an_instance_of(Benefits)
end
it "name can be updated" do
new_name = "Bobby"
user = User.all.first
user.first_name = new_name
user.save!
expect(User.all.first.first_name).to eq(new_name)
end
end
+2
View File
@@ -0,0 +1,2 @@
# frozen_string_literal: true
require "spec_helper"
+2
View File
@@ -0,0 +1,2 @@
# frozen_string_literal: true
require "spec_helper"
+38
View File
@@ -0,0 +1,38 @@
# frozen_string_literal: true
require "spec_helper.rb"
describe User do
before(:all) do
UserFixture.reset_all_users
DatabaseCleaner.strategy = :transaction
end
after(:all) do
DatabaseCleaner.strategy = :truncation
end
it "can be instantiated" do
expect(User.new).to be_an_instance_of(User)
end
it "should require a email" do
expect(User.new(email: "")).not_to be_valid
end
it "should require valid email" do
expect(User.new(email: "@gmail.com")).not_to be_valid
end
it "should require unique email" do
user = User.all.first
expect(User.new(email: user.email)).not_to be_valid
end
it "name can be updated" do
new_name = "Bobby"
user = User.all.first
user.first_name = new_name
user.save!
expect(User.all.first.first_name).to eq(new_name)
end
end
+67
View File
@@ -0,0 +1,67 @@
# frozen_string_literal: true
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= "test"
# To use simplecov, do this: COVERAGE=true rake
require "simplecov"
SimpleCov.start if ENV["COVERAGE"]
require File.expand_path("../../config/environment", __FILE__)
require "rspec/rails"
require "capybara/rails"
require "selenium-webdriver"
require "database_cleaner"
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
RSpec.configure do |config|
# ## Mock Framework
#
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
#
# config.mock_with :mocha
# config.mock_with :flexmock
# config.mock_with :rr
# 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 = 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
# rspec-rails.
config.infer_base_class_for_anonymous_controllers = false
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# 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
# rspec-rails 3 will no longer automatically infer an example group's spec type
# from the file location. You can explicitly opt-in to the feature using this
# config option.
# To explicitly tag specs without using automatic inference, set the `:type`
# metadata manually:
#
# describe ThingsController, :type => :controller do
# # Equivalent to being in spec/controllers
# end
config.infer_spec_type_from_file_location!
end
# Driver is configured in spec/support/capybara_shared.rb
# to use :poltergeist (PhantomJS) which is more reliable across platforms
DatabaseCleaner.strategy = :truncation
+58
View File
@@ -0,0 +1,58 @@
# frozen_string_literal: true
# By default this will return true, and thus all of the Capybara specs will
# fail until a developer using the site for training has patched up all of
# the vulnerabilities.
#
# However, RailsGoat maintainers need the Capybara features to pass to indicate
# changes to the site have not inadvertently removed or fixed any vulnerabilities
# since the whole point is to provide a site for a developer to fix.
$displayed_spec_notice = false
def verifying_fixed?
maintainer_env_name = "RAILSGOAT_MAINTAINER"
result = !ENV[maintainer_env_name]
if !$displayed_spec_notice && result
puts <<-NOTICE
******************************************************************************
You are running the RailsGoat Capybara Specs in Training mode. These specs
are supposed to fail, indicating vulnerabilities exist. They contain spoilers,
so do not read the code in spec/vulnerabilities if your goal is to learn more
about patching the vulnerabilities. You should fix the vulnerabilities in the
application in order to get these specs to pass**. You can use them to measure
your progress.
These same specs will pass if you set the #{maintainer_env_name} ENV variable.
**NOTE: The RSpec skip feature is used to toggle the outcome of these specs
between Training mode and RailsGoat Maintainer mode. When the vulnerabilities
are removed, the specs will pass instead. Try to get a fully passing suite.
******************************************************************************
NOTICE
$displayed_spec_notice = true
end
result
end
def login(user)
visit "/"
fill_in "email", with: user.email
fill_in "password", with: user.clear_password
find("input[type='submit'][value='Login']").click
end
# Configure Selenium with headless Chrome for JavaScript testing
# This works across macOS, Linux, and Windows without requiring Firefox
Capybara.register_driver :selenium_chrome_headless do |app|
options = Selenium::WebDriver::Chrome::Options.new
options.add_argument("--headless")
options.add_argument("--disable-gpu")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--window-size=1920,1080")
Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end
Capybara.javascript_driver = :selenium_chrome_headless
+21
View File
@@ -0,0 +1,21 @@
# frozen_string_literal: true
class UserFixture
def self.reset_all_users
User.delete_all
Rails.application.load_seed
end
def self.normal_user
password = "thi$ 1s cOmplExEr"
User.create!(first_name: "Joe", last_name: "Schmoe", email: "joe@schmoe.com",
password: password, password_confirmation: password).tap do |user|
def user.clear_password
"thi$ 1s cOmplExEr"
end
end
end
def self.admin_user
User.where(admin: true).first
end
end
+32
View File
@@ -0,0 +1,32 @@
# frozen_string_literal: true
require "spec_helper"
feature "broken_auth" do
let(:normal_user) { UserFixture.normal_user }
before do
UserFixture.reset_all_users
skip unless verifying_fixed?
end
scenario "one\nTutorial: https://github.com/OWASP/railsgoat/wiki/A2-Credential-Enumeration" do
wrong_email = normal_user.email + "not"
visit "/"
fill_in "email", with: wrong_email
fill_in "password", with: normal_user.clear_password
find("input[type='submit'][value='Login']").click
expect(find("div#flash_notice").text).not_to include(wrong_email)
end
scenario "two\nTutorial: https://github.com/OWASP/railsgoat/wiki/A2-Credential-Enumeration" do
visit "/"
fill_in "email", with: normal_user.email
fill_in "password", with: normal_user.clear_password + "not"
find("input[type='submit'][value='Login']").click
expect(find("div#flash_notice").text).not_to include("Incorrect Password!")
end
end
@@ -0,0 +1,32 @@
# frozen_string_literal: true
require "spec_helper"
require "tmpdir"
feature "command injection" do
let(:normal_user) { UserFixture.normal_user }
before do
UserFixture.reset_all_users
skip unless verifying_fixed?
end
scenario "attack\nTutorial: https://github.com/OWASP/railsgoat/wiki/A1-Command-Injection", 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.id}/benefit_forms"
Dir.mktmpdir do |dir|
hackety_file = File.join(dir, "test; 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
click_on "Start Upload"
end
expect(File.exist?(legit_file)).to be_truthy
end
end
+47
View File
@@ -0,0 +1,47 @@
# frozen_string_literal: true
require "spec_helper"
require "tmpdir"
feature "csrf" do
let(:normal_user) { UserFixture.normal_user }
before(:each) do
UserFixture.reset_all_users
skip unless verifying_fixed?
end
scenario "attack\nTutorial: https://github.com/OWASP/railsgoat/wiki/R4-A8-CSRF", js: true do
visit "/"
# TODO: is there a way to get this without visiting root first?
base_url = current_url
login(normal_user)
Dir.mktmpdir do |dir|
hackety_file = File.join(dir, "form.on.bad.guy.site.html")
post_url = "#{base_url}schedule.json"
File.open(hackety_file, "w") do |f|
f.print <<-HTML
<html>
<body>
<form id='submit_me' action="#{post_url}" method="POST">
<input type="hidden" name="schedule&#91;event&#95;name&#93;" value="Bad&#32;Guy" />
<input type="hidden" name="schedule&#91;event&#95;type&#93;" value="pto" />
<input type="hidden" name="schedule&#91;event&#95;desc&#93;" value="Fun&#32;Fun" />
<input type="hidden" name="date&#95;range1" value="06&#47;08&#47;2013&#32;&#45;&#32;06&#47;09&#47;2013" />
<input type="submit" value="Submit request" />
</form>
</body>
</html>
HTML
end
page.driver.visit "file://#{hackety_file}"
within("#submit_me") do
click_on "Submit request"
end
end
expect(normal_user.reload.paid_time_off.schedule.last.event_name).not_to eq("Bad Guy")
end
end
+34
View File
@@ -0,0 +1,34 @@
# frozen_string_literal: true
require "spec_helper"
feature "insecure direct object reference" do
let(:normal_user) { UserFixture.normal_user }
let(:another_user) { User.find_by(id: 2) }
before do
UserFixture.reset_all_users
skip unless verifying_fixed?
end
scenario "attack one" do
login(normal_user)
visit "/users/#{normal_user.id}/benefit_forms"
download_url = first(".widget-body a")[:href]
visit download_url.sub(/name=(.*?)&/, "name=config/database.yml&")
expect(page.status_code).not_to eq(200)
expect(page.response_headers["Content-Disposition"].to_a).not_to include("database.yml")
end
scenario "attack two\nTutorial: https://github.com/OWASP/railsgoat/wiki/A4-Insecure-Direct-Object-Reference" do
login(normal_user)
expect(normal_user.id).not_to eq(another_user.id)
visit "/users/#{another_user.id}/work_info"
expect(first("td").text).not_to include(another_user.full_name)
expect(first("td").text).to include(normal_user.full_name)
end
end
@@ -0,0 +1,38 @@
# frozen_string_literal: true
require "spec_helper"
feature "mass assignment" do
let(:normal_user) { UserFixture.normal_user }
before do
UserFixture.reset_all_users
skip unless verifying_fixed?
end
scenario "attack one" do
expect(normal_user.admin).to be_falsey
login(normal_user)
params = { user: { admin: "t",
id: normal_user.id,
password: normal_user.clear_password,
password_confirmation: normal_user.clear_password }}
page.driver.put "/users/#{normal_user.id}.json", params
expect(normal_user.reload.admin).to be_falsy
end
scenario "attack two, Tutorial: https://github.com/OWASP/railsgoat/wiki/R4-Extras-Mass-Assignment-Admin-Role" do
params = { user: { admin: "t",
email: "hackety@h4x0rs.c0m",
first_name: "hackety",
last_name: "hax",
password: "foobarewe",
password_confirmation: "foobarewe" }}
page.driver.post "/users", params
expect(User.find_by(email: "hackety@h4x0rs.c0m").admin).to be_falsy
end
end
@@ -0,0 +1,26 @@
# frozen_string_literal: true
require "spec_helper"
feature "password complexity" do
let(:normal_user) { UserFixture.normal_user }
before do
UserFixture.reset_all_users
skip unless verifying_fixed?
end
scenario "one\nTutorial: https://github.com/OWASP/railsgoat/wiki/A2-Lack-of-Password-Complexity" do
new_user_email = normal_user.email + "two"
visit "/signup"
fill_in "email", with: new_user_email
fill_in "first_name", with: normal_user.first_name
fill_in "last_name", with: normal_user.last_name + "not"
fill_in "password", with: "password"
fill_in "password_confirmation", with: "password"
click_on "Create Account"
expect(User.find_by(email: new_user_email)).to be_nil
expect(current_path).to eq("/signup")
end
end
@@ -0,0 +1,26 @@
# frozen_string_literal: true
require "spec_helper"
feature "improper password hashing" do
let(:normal_user) { UserFixture.normal_user }
before do
UserFixture.reset_all_users
end
scenario "with just md5\nTutorial: https://github.com/OWASP/railsgoat/wiki/A6-Sensitive-Data-Exposure-Insecure-Password-Storage" do
new_pass = "testPassw0rd!"
normal_user.password = new_pass
normal_user.password_confirmation = new_pass
normal_user.save!
if verifying_fixed?
# Training mode: expect BCrypt (not MD5) - test should fail because vulnerability exists
expect(normal_user.password).not_to eq(Digest::MD5.hexdigest(new_pass))
else
# Maintainer mode: expect MD5 to verify vulnerability still exists - test should pass
expect(normal_user.password).to eq(Digest::MD5.hexdigest(new_pass))
end
end
end
@@ -0,0 +1,24 @@
# frozen_string_literal: true
require "spec_helper"
feature "sensitive data exposure" do
let(:normal_user) { UserFixture.normal_user }
let(:user_ssn) { "999-99-9999" }
before do
UserFixture.reset_all_users
normal_user.work_info.update(:SSN, user_ssn)
skip unless verifying_fixed?
end
# this won't work with javascript_driver, as it'll apply the javascript
# function to mask this value and the source will be overwritten.
scenario "attack\nTutorial: https://github.com/OWASP/railsgoat/wiki/A6-Sensitive-Data-Exposure-Cleartext-Storage-SSNs" do
login(normal_user)
visit "/users/#{normal_user.id}/work_info"
expect(page.source).not_to include(user_ssn)
end
end
@@ -0,0 +1,32 @@
# frozen_string_literal: true
require "spec_helper"
feature "sql injection" do
let(:normal_user) { UserFixture.normal_user }
let(:admin_user) { User.where(admin: true).first }
before do
UserFixture.reset_all_users
skip unless verifying_fixed?
end
scenario "attack\nTutorial: https://github.com/OWASP/railsgoat/wiki/R5-A1-SQL-Injection-Concatentation" do
expect(admin_user.admin).to be_truthy
login(normal_user)
visit "/users/#{normal_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_id']", visible: false).set "8' OR 1 == 1) --"
end
click_on "Submit"
admin_user = User.where(admin: true).first
expect(admin_user.email).not_to eq("joe.admin@schmoe.com")
end
end
@@ -0,0 +1,22 @@
# frozen_string_literal: true
require "spec_helper"
feature "unvalidated redirect" do
let(:normal_user) { UserFixture.normal_user }
before do
UserFixture.reset_all_users
skip unless verifying_fixed?
end
scenario "attack\nTutorial: https://github.com/OWASP/railsgoat/wiki/A10-Unvalidated-Redirects-and-Forwards-(redirect_to)", js: true do
visit "/?url=http://example.com/do/evil/things"
fill_in "email", with: normal_user.email
fill_in "password", with: normal_user.clear_password
find("input[type='submit'][value='Login']").click
expect(current_url).to start_with("http://127.0.0.1")
expect(current_path).to eq("/dashboard/home")
end
end
+20
View File
@@ -0,0 +1,20 @@
# frozen_string_literal: true
require "spec_helper"
feature "url access" do
let(:normal_user) { UserFixture.normal_user }
before do
UserFixture.reset_all_users
skip unless verifying_fixed?
end
scenario "attack\nTutorial: https://github.com/OWASP/railsgoat/wiki/A7-Missing-Function-Level-Access-Control--(Admin-Controller)", js: true do
login(normal_user)
visit "/admin/1/dashboard"
expect(current_path).to eq("/dashboard/home")
end
end
+36
View File
@@ -0,0 +1,36 @@
# frozen_string_literal: true
require "spec_helper"
feature "xss" do
let(:normal_user) { UserFixture.normal_user }
before(:each) do
UserFixture.reset_all_users
skip unless verifying_fixed?
end
scenario "attack\nTutorial: https://github.com/OWASP/railsgoat/wiki/A3-Cross-Site-Scripting", js: true do
login(normal_user)
visit "/users/#{normal_user.id}/account_settings"
within("#account_edit") do
fill_in "First name", with: "<script>$(function() { $('div input.btn').val('RailsGoat h4x0r3d') } )</script>"
# 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"
sleep(1)
visit "/users/#{normal_user.id}/account_settings"
expect(find("#submit_button").value).not_to include("RailsGoat h4x0r3d")
# might be nice to demonstrate posting cookie contents or somesuch, but
# this at least shows the vulnerability still exists.
end
end