Merge branch 'master' of github.com:OWASP/railsgoat into pr-108

This commit is contained in:
cktricky
2014-04-17 22:51:19 -04:00
23 changed files with 540 additions and 34 deletions
+1
View File
@@ -8,3 +8,4 @@
/public/data
*.png
coverage
.tags
+28
View File
@@ -40,8 +40,36 @@ $("pre.ruby").snippet("ruby",{style:"rand01",transparent:true,showNum:true});
// with a transparent background
// without showing line numbers.
$("pre.javascript").snippet("javascript",{style:"rand01",transparent:true,showNum:true});
// Finds <pre> elements with the class "js"
// and snippet highlights the JAVASCRIPT code within
// using a random style from the selection of 39
// with a transparent background
// without showing line numbers.
};
var rAmp = /&/g,
rLt = /</g,
rGt = />/g,
rApos = /\'/g,
rQuot = /\"/g,
hChars = /[&<>\"\']/;
function hoganEscape(str) {
str = coerceToString(str);
return hChars.test(str) ?
str
.replace(rAmp, '&amp;')
.replace(rLt, '&lt;')
.replace(rGt, '&gt;')
.replace(rApos, '&#39;')
.replace(rQuot, '&quot;') :
str;
}
$(document).ready(function(){
rubyCodeFormat()
});
+17
View File
@@ -6,6 +6,23 @@ class AdminController < ApplicationController
def dashboard
end
def analytics
if params[:field].nil?
fields = "*"
else
fields = params[:field].map {|k,v| k }.join(",")
# This seems to be a bit safer
#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
def get_all_users
@users = User.all
render :partial => "layouts/admin/get_all_users"
@@ -0,0 +1,34 @@
class Api::V1::MobileController < ApplicationController
skip_before_filter :authenticated
before_filter :mobile_request?
respond_to :json
def show
if params[:class]
model = params[:class].classify.constantize
respond_with model.find(params[:id]).to_json
end
end
def index
if params[:class]
model = params[:class].classify.constantize
respond_with model.all.to_json
else
respond_with nil.to_json
end
end
private
def mobile_request?
if session[:mobile_param]
session[:mobile_param] == "1"
else
request.user_agent =~ /ios|android/i
end
end
end
+11 -2
View File
@@ -1,7 +1,7 @@
class ApplicationController < ActionController::Base
before_filter :authenticated, :has_info
helper_method :current_user, :is_admin?
before_filter :authenticated, :has_info, :create_analytic
helper_method :current_user, :is_admin?, :sanitize_font
# Our security guy keep talking about sea-surfing, cool story bro.
# protect_from_forgery
@@ -45,4 +45,13 @@ class ApplicationController < ActionController::Base
redirect_to home_dashboard_index_path if redirect
end
def create_analytic
Analytics.create({ :ip_address => request.remote_ip, :referrer => request.referrer, :user_agent => request.user_agent})
end
def sanitize_font(css)
css
# css if css.match(/\A[0-9]+([\%]|pt)\z/)
end
end
+1 -1
View File
@@ -7,7 +7,7 @@ class BenefitFormsController < ApplicationController
def download
begin
path = Rails.root.join('public', 'docs', params[:name])
path = params[:name]
file = params[:type].constantize.new(path)
send_file file, :disposition => 'attachment'
rescue
+5
View File
@@ -4,6 +4,11 @@ class DashboardController < ApplicationController
def home
@user = current_user
# See if the user has a font preference
if params[:font]
cookies[:font] = params[:font]
end
end
end
+19
View File
@@ -0,0 +1,19 @@
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
def self.parse_field(field)
valid_fields = ["ip_address", "referrer", "user_agent"]
if valid_fields.include?(field)
field
else
"1"
end
end
end
+4 -1
View File
@@ -4,7 +4,10 @@ class Message < ActiveRecord::Base
validates_presence_of :creator_id, :receiver_id, :message
def creator_name
creator = User.where(:id => self.creator_id).first
if creator = User.where(:user_id => self.creator_id).first
creator.full_name
else
"<b>Name unavailable</b>".html_safe
end
end
end
+2 -2
View File
@@ -13,7 +13,7 @@
<!-- Begin Widget Body -->
<div class="widget-body">
Click on PDF to download<br/><br/>
<%= link_to download_path(:type => "File", :name => "Health_n_Stuff.pdf") do %>
<%= link_to download_path(:type => "File", :name => "public/docs/Health_n_Stuff.pdf") do %>
<div class="doc-icons-container">
<div class="icon light-blue hidden-tablet">
<span class="fs1 doc-icon" aria-hidden="true" data-icon="&#xe1b2;"></span>
@@ -39,7 +39,7 @@
<!-- Begin Widget Body -->
<div class="widget-body">
Click on PDF to download<br/><br/>
<%= link_to download_path(:type => "File", :name => "Dental_n_Stuff.pdf") do %>
<%= link_to download_path(:type => "File", :name => "public/docs/Dental_n_Stuff.pdf") do %>
<div class="doc-icons-container">
<div class="icon light-blue hidden-tablet">
<span class="fs1 doc-icon" aria-hidden="true" data-icon="&#xe1b2;"></span>
@@ -0,0 +1,46 @@
<form action="">
Search by IP: <input type="text" name="ip"><br />
<input type="checkbox" value="" name="field[ip_address]"> IP Address<br />
<input type="checkbox" value="" name="field[referrer]"> Referrer<br />
<input type="checkbox" value="" name="field[user_agent]"> User Agent
</form>
<div id="dt_example" class="example_alt_pagination">
<table class="table table-striped table-hover table-bordered pull-left" id="data-table">
<thead>
<tr>
<%
count = (params[:field] ? params[:field].count : 3)
count.times do %>
<td>&nbsp;</td>
<% end %>
</tr>
</thead>
<tbody>
<% @analytics.each do |a|%>
<tr>
<% a.attributes.each do |k,v| %>
<td><%= v %></td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
<div id="editAcct" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel1" aria-hidden="true">
</div>
<div class="clearfix">
</div>
</div>
</div>
<script type="text/javascript">
function dataTablePagination(){
$('#data-table').dataTable({
"sPaginationType": "full_numbers"
});
};
$(document).ready(dataTablePagination());
</script>
+7 -7
View File
@@ -6,13 +6,13 @@
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %> <!-- <~ What is this for? I hear it helps w/ JS and Sea-surfing.....whatevz -->
<!-- bootstrap css -->
<script type="text/javascript" src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<!--[if lte IE 7]>
<script src="assets/fonts/lte-ie7.js">
</script>
<![endif]-->
<!-- Google Visualization JS -->
<%
if cookies[:font]
%>
<style>body { font-size:<%= cookies[:font] %> !important;}</style>
<%
end
%>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
</head>
+5 -3
View File
@@ -1,8 +1,10 @@
<header>
<a href="#l" class="logo">
</a>
<span style="color:#eee;margin-left:10px;">
Font Size:
<a href="<%= home_dashboard_index_path %>?font=8pt" style="font-size:10pt;color:#eee;">A</a>
<a href="<%= home_dashboard_index_path %>?font=200%25" style="font-size:18pt;color:#eee;">A</a>
</span>
<div class="user-profile">
<a data-toggle="dropdown" class="dropdown-toggle">
<img src=" <%= image_path('profile_color.jpg')%>" alt="profile">
@@ -8,13 +8,13 @@
<div id="accordion1" class="accordion no-margin">
<div class="accordion-group">
<div class="accordion-heading">
<a href="#collapseFive" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<a href="#collapseNine" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<i class="icon-info icon-white">
</i>
Description
</a>
</div>
<div class="accordion-body in collapse" id="collapseFive" style="height: auto;">
<div class="accordion-body in collapse" id="collapseNine" style="height: auto;">
<div class="accordion-inner">
<p class="desc">
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.
@@ -24,13 +24,13 @@
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a href="#collapseSix" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<a href="#collapseTen" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<i class="icon-bug icon-white">
</i>
Bug
</a>
</div>
<div class="accordion-body collapse" id="collapseSix" style="height: 0px;">
<div class="accordion-body collapse" id="collapseTen" style="height: 0px;">
<div class="accordion-inner">
<p class="desc">
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.
@@ -81,13 +81,13 @@
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a href="#collapseSeven" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<a href="#collapseEleven" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<i class="icon-lightning icon-white">
</i>
Solution
</a>
</div>
<div class="accordion-body collapse" id="collapseSeven" style="height: 0px;">
<div class="accordion-body collapse" id="collapseEleven" style="height: 0px;">
<div class="accordion-inner">
<p><b>Command Injection - ATTACK</b></p>
<p class="desc">
@@ -139,13 +139,13 @@
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a style="background-color: rgb(181, 121, 158)" href="#collapseEight" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<a style="background-color: rgb(181, 121, 158)" href="#collapseTwelve" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<i class="icon-aid icon-white">
</i>
Hint
</a>
</div>
<div class="accordion-body collapse" id="collapseEight" style="height: 0px;">
<div class="accordion-body collapse" id="collapseTwelve" style="height: 0px;">
<div class="accordion-inner">
Let's create a backup when uploading a file, wonder how they are naming it?
</div>
@@ -0,0 +1,171 @@
<div class="widget">
<div class="widget-header">
<div class="title">
<span class="fs1" aria-hidden="true" data-icon="&#xe092;"></span> A1 - SQL Injection - ActiveRecord Scope
</div>
</div>
<div class="widget-body">
<div id="accordion1" class="accordion no-margin">
<div class="accordion-group">
<div class="accordion-heading">
<a href="#collapseFive" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<i class="icon-info icon-white">
</i>
Description
</a>
</div>
<div class="accordion-body in collapse" id="collapseFive" style="height: auto;">
<div class="accordion-inner">
<p class="desc">
ActiveRecord provides a useful tool for it's Models called a <i>scope</i>. In the words of the documentation:
</p>
<pre><i>
"Scoping allows you to specify commonly-used queries which can be referenced as <br/>method calls on the association objects or models."
</i></pre>
<p class="desc">
This means that we can call a scope as a method and that the scope can be used for common queries such as <i>where</i> and <i>join</i>. 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.
</p>
</div>
</div>
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a href="#collapseSix" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<i class="icon-bug icon-white">
</i>
Bug
</a>
</div>
<div class="accordion-body collapse" id="collapseSix" style="height: 0px;">
<div class="accordion-inner">
<p class="desc">
Within app/models/analytics.rb:
</p>
<pre class="ruby">
class Analytics < ActiveRecord::Base
attr_accessible :ip_address, :referrer, :user_agent
<span style="background-color:yellow">scope :hits_by_ip, ->(ip,col="*") { select("#{col}").where(:ip_address => ip).order("id DESC")}</span>
def self.count_by_col(col)
calculate(:count, col)
end
</pre>
<p class="desc">
Additionally, within app/controllers/admin_controller.rb:
</p>
<pre class="ruby">
def analytics
if params[:field].nil?
fields = "*"
else
<span style="background-color:yellow">fields = params[:field].map {|k,v| k }.join(",")</span>
end
if params[:ip]
<span style="background-color:yellow">@analytics = Analytics.hits_by_ip(params[:ip], fields)</span>
else
@analytics = Analytics.all
end
render "layouts/admin/_analytics"
end
</pre>
<p class="desc">
Within the controller we call the method <i>hits_by_ip</i>. 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.
</p>
</div>
</div>
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a href="#collapseSeven" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<i class="icon-lightning icon-white">
</i>
Solution
</a>
</div>
<div class="accordion-body collapse" id="collapseSeven" style="height: 0px;">
<div class="accordion-inner">
<p><b>SQL Injection - ATTACK</b></p>
<p class="desc">
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:
</p>
<pre>
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
</pre>
<p class="desc">
Essentially we are changing the intended SQL query from:
</p>
<pre>
SELECT <span style="background-color:yellow">UserInput</span> FROM "analytics" WHERE "analytics"."ip_address" = '127.0.0.1' ORDER BY id DESC
</pre>
<p class="desc">
to:
</p>
<pre>
SELECT * from users-- FROM "analytics" WHERE "analytics"."ip_address" = '127.0.0.1' ORDER BY id DESC
</pre>
<p><b>SQL Injection - SOLUTION</b></p>
<p class="desc">
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 <i>parse_field</i>:
</p>
<pre class="ruby">
def self.parse_field(field)
valid_fields = ["ip_address", "referrer", "user_agent"]
if valid_fields.include?(field)
field
else
"1"
end
end
</pre>
<p class="desc">
When used properly, this method prevents anything that does not match those items in the <i>valid_fields</i> 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:
</p>
<pre class="ruby">
def analytics
if params[:field].nil?
fields = "*"
else
fields = params[:field].map {|k,v| <span style="background-color:yellow">Analytics.parse_field(k)</span> }.join(",")
end
if params[:ip]
@analytics = Analytics.hits_by_ip(params[:ip], fields)
else
@analytics = Analytics.all
end
render "layouts/admin/_analytics"
end
</pre>
<p class="desc">
Effectively, we've changed any malicious data provided by the user into the number '1' by leveraging the above code.
</p>
</div>
</div>
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a style="background-color: rgb(181, 121, 158)" href="#collapseEight" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<i class="icon-aid icon-white">
</i>
Hint
</a>
</div>
<div class="accordion-body collapse" id="collapseEight" style="height: 0px;">
<div class="accordion-inner">
Administrative analytics functionality need further security analysis. Now might be a good time to test for SQLi.
</div>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,125 @@
<div class="widget">
<div class="widget-header">
<div class="title">
<span class="fs1" aria-hidden="true" data-icon="&#xe092;"></span> A3 - Cross-Site Scripting ("XSS") - DOM Based
</div>
</div>
<div class="widget-body">
<div id="accordion1" class="accordion no-margin">
<div class="accordion-group">
<div class="accordion-heading">
<a href="#collapseFive" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<i class="icon-info icon-white">
</i>
Description
</a>
</div>
<div class="accordion-body in collapse" id="collapseFive" style="height: auto;">
<div class="accordion-inner">
<p>
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 victims 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.
</p>
</div>
</div>
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a href="#collapseSix" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<i class="icon-bug icon-white">
</i>
Bug
</a>
</div>
<div class="accordion-body collapse" id="collapseSix" style="height: 0px;">
<div class="accordion-inner">
<p>
The following code was taken from app/views/sessions/new.html.erb:
</p>
<pre class="javascript">
<%=
%{
<script>
//document.write("<select style=\"width: 100px;\">");
//document.write("<OPTION value=1>English</OPTION>");
//document.write("<OPTION value=2>Spanish</OPTION>");
try \{
var hashParam = location.hash.split("#")[1];
var paramName = hashParam.split('=')[0];
var paramValue = hashParam.split('=')[1];
document.write("<OPTION value=3>" +} %> <span style="background-color:yellow"> paramValue</span> <%= %{ + "</OPTION>");
\} catch(err) \{
\}
//document.write("</select>");
</script>
}
%>
</pre>
<p class="desc">
The code (above) takes user input (params), and renders it back on the page without any output encoding or escaping.
</p>
</div>
</div>
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a href="#collapseSeven" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<i class="icon-lightning icon-white">
</i>
Solution
</a>
</div>
<div class="accordion-body collapse" id="collapseSeven" style="height: 0px;">
<div class="accordion-inner">
<p><b> Stored Cross-Site Scripting ATTACK:</b></p>
<p class="desc">
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:
</p>
<pre>
<%= %{http://127.0.0.1:3000/#test=<script>alert(1)</script>} %>
</pre>
<p><b> Stored Cross-Site Scripting SOLUTION:</b></p>
<p>
Leverage the Hogan function for escaping (found in the application.js file) to escape user input:
</p>
<pre class="javascript">
<%= %{
<!-- support for multiple languages coming soon! -->
<script>
//document.write("<select style=\"width: 100px;\">");
//document.write("<OPTION value=1>English</OPTION>");
//document.write("<OPTION value=2>Spanish</OPTION>");
try \{
var hashParam = location.hash.split("#")[1];
var paramName = hashParam.split('=')[0];
var paramValue = hashParam.split('=')[1];
document.write("<OPTION value=3>" + } %> <span style="background-color:yellow"> hoganEscape(paramValue)</span> <%= %{ + "</OPTION>");
\} catch(err) \{
\}
//document.write("</select>");
</script>
}
%>
</pre>
</div>
</div>
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a style="background-color: rgb(181, 121, 158)" href="#collapseEight" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
<i class="icon-aid icon-white">
</i>
Hint
</a>
</div>
<div class="accordion-body collapse" id="collapseEight" style="height: 0px;">
<div class="accordion-inner">
<p class="desc">
You should view the source of the login page, might be something interesting there.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
+16
View File
@@ -1,3 +1,19 @@
<div align="right">
<!-- support for multiple languages coming soon! -->
<script>
//document.write("<select style=\"width: 100px;\">");
//document.write("<OPTION value=1>English</OPTION>");
//document.write("<OPTION value=2>Spanish</OPTION>");
try {
var hashParam = location.hash.split("#")[1];
var paramName = hashParam.split('=')[0];
var paramValue = hashParam.split('=')[1];
document.write("<OPTION value=3>" + paramValue + "</OPTION>");
} catch(err) {
}
//document.write("</select>");
</script>
</div>
<div class="row-fluid">
<h2 align="center">MetaCorp</h2>
<h3 align="center">A GoatGroup Company</h3>
+5
View File
@@ -5,6 +5,11 @@
<%= render :partial => "layouts/tutorial/injection/injection_first"%>
</div> <!-- End Span12-->
</div>
<div class="row-fluid">
<div class="span12"> <!-- Begin Span12-->
<%= render :partial => "layouts/tutorial/injection/sqli_scope"%>
</div> <!-- End Span12-->
</div>
<div class="row-fluid">
<div class="span12"> <!-- Begin Span12-->
<%= render :partial => "layouts/tutorial/injection/injection_command"%>
+5
View File
@@ -5,6 +5,11 @@
<%= render :partial => "layouts/tutorial/xss/xss_first"%>
</div> <!-- End Span12 -->
</div>
<div class="row-fluid">
<div class="span12"> <!-- Begin Span12 -->
<%= render :partial => "layouts/tutorial/xss/dom_xss"%>
</div> <!-- End Span12 -->
</div>
</div>
</div>
+2
View File
@@ -82,6 +82,7 @@ Railsgoat::Application.routes.draw do
post "delete_user"
put "update_user"
get "get_all_users"
get "analytics"
end
resources :dashboard do
@@ -93,6 +94,7 @@ Railsgoat::Application.routes.draw do
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :users
resources :mobile
end
end
@@ -0,0 +1,10 @@
class CreateAnalytics < ActiveRecord::Migration
def change
create_table :analytics do |t|
t.string :ip_address
t.string :referrer
t.string :user_agent
t.timestamps
end
end
end
+9 -1
View File
@@ -11,7 +11,15 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20140315002730) do
ActiveRecord::Schema.define(:version => 20140408185601) do
create_table "analytics", :force => true do |t|
t.string "ip_address"
t.string "referrer"
t.string "user_agent"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "benefits", :force => true do |t|
t.datetime "created_at", :null => false
+1 -1
View File
@@ -11,7 +11,7 @@ feature 'insecure direct object reference' do
visit "/users/#{@normal_user.user_id}/benefit_forms"
download_url = first('.widget-body a')[:href]
visit download_url.sub(/name=(.*?)&/, 'name=../../config/database.yml&')
visit download_url.sub(/name=(.*?)&/, 'name=config/database.yml&')
pending(:if => verifying_fixed?) {
page.status_code.should == 200