171 lines
7.4 KiB
Plaintext
171 lines
7.4 KiB
Plaintext
<div class="widget">
|
|
<div class="widget-header">
|
|
<div class="title">
|
|
<span class="fs1" aria-hidden="true" data-icon=""></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> |