Convert file indentation to spaces
This commit is contained in:
@@ -17,8 +17,8 @@
|
||||
<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.
|
||||
</p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -32,50 +32,50 @@
|
||||
</div>
|
||||
<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.
|
||||
</p>
|
||||
<p>
|
||||
Within app/controllers/benefits_controller.rb:
|
||||
</p>
|
||||
<pre class="ruby">
|
||||
def upload
|
||||
<span style="background:yellow">file = params[:benefits][:upload]</span>
|
||||
if file
|
||||
flash[:success] = "File Successfully Uploaded!"
|
||||
<span style="background:yellow">Benefits.save(file, params[:benefits][:backup])</span>
|
||||
else
|
||||
flash[:error] = "Something went wrong"
|
||||
end
|
||||
redirect_to user_benefit_forms_path(:user_id => current_user.user_id)
|
||||
end
|
||||
</pre>
|
||||
<p>
|
||||
Within app/models/benefits.rb:
|
||||
</p>
|
||||
<pre class="ruby">
|
||||
class Benefits < ActiveRecord::Base
|
||||
attr_accessor :backup
|
||||
<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.
|
||||
</p>
|
||||
<p>
|
||||
Within app/controllers/benefits_controller.rb:
|
||||
</p>
|
||||
<pre class="ruby">
|
||||
def upload
|
||||
<span style="background:yellow">file = params[:benefits][:upload]</span>
|
||||
if file
|
||||
flash[:success] = "File Successfully Uploaded!"
|
||||
<span style="background:yellow">Benefits.save(file, params[:benefits][:backup])</span>
|
||||
else
|
||||
flash[:error] = "Something went wrong"
|
||||
end
|
||||
redirect_to user_benefit_forms_path(:user_id => current_user.user_id)
|
||||
end
|
||||
</pre>
|
||||
<p>
|
||||
Within app/models/benefits.rb:
|
||||
</p>
|
||||
<pre class="ruby">
|
||||
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.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.now.to_i}_#{<span style="background:yellow">file.original_filename</span>}")
|
||||
end
|
||||
def self.make_backup(file, data_path, full_file_name)
|
||||
system("cp #{full_file_name} #{data_path}/bak#{Time.now.to_i}_#{<span style="background:yellow">file.original_filename</span>}")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
</pre>
|
||||
<p class="desc">
|
||||
The command injection vulnerability is introduced when the user-supplied input (name of file) is interpolated or mixed in with a system command.
|
||||
</p>
|
||||
</pre>
|
||||
<p class="desc">
|
||||
The command injection vulnerability is introduced when the user-supplied input (name of file) is interpolated or mixed in with a system command.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,54 +90,54 @@
|
||||
<div class="accordion-body collapse" id="collapseEleven" style="height: 0px;">
|
||||
<div class="accordion-inner">
|
||||
<p><b>Command Injection - ATTACK</b></p>
|
||||
<p class="desc">
|
||||
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 ).
|
||||
</p>
|
||||
<pre class='ruby'>
|
||||
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
|
||||
<p class="desc">
|
||||
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 ).
|
||||
</p>
|
||||
<pre class='ruby'>
|
||||
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="utf8"
|
||||
|
||||
â
|
||||
----------54316025
|
||||
Content-Disposition: form-data; name="authenticity_token"
|
||||
â
|
||||
----------54316025
|
||||
Content-Disposition: form-data; name="authenticity_token"
|
||||
|
||||
zKnXZO1PGcM+rFweczO7H8IDQ6NHmc8Siud2ypM6ZeA=
|
||||
----------54316025
|
||||
Content-Disposition: form-data; name="benefits[backup]"
|
||||
zKnXZO1PGcM+rFweczO7H8IDQ6NHmc8Siud2ypM6ZeA=
|
||||
----------54316025
|
||||
Content-Disposition: form-data; name="benefits[backup]"
|
||||
|
||||
<span style="background:yellow">true</span>
|
||||
----------54316025
|
||||
Content-Disposition: form-data; name="benefits[upload]"; <span style="background:yellow">filename="test.rb;+mkdir+thisisatest "</span>
|
||||
Content-Type: text/x-ruby-script
|
||||
</pre>
|
||||
<p><b>Command Injection - SOLUTION</b></p>
|
||||
<p class="desc">
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
As an example:
|
||||
</p>
|
||||
<pre class="ruby">
|
||||
def self.make_backup(file, data_path, full_file_name)
|
||||
FileUtils.cp "#{full_file_name}", "#{data_path}/bak#{Time.now.to_i}_#{file.original_filename}"
|
||||
end
|
||||
</pre>
|
||||
<span style="background:yellow">true</span>
|
||||
----------54316025
|
||||
Content-Disposition: form-data; name="benefits[upload]"; <span style="background:yellow">filename="test.rb;+mkdir+thisisatest "</span>
|
||||
Content-Type: text/x-ruby-script
|
||||
</pre>
|
||||
<p><b>Command Injection - SOLUTION</b></p>
|
||||
<p class="desc">
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
As an example:
|
||||
</p>
|
||||
<pre class="ruby">
|
||||
def self.make_backup(file, data_path, full_file_name)
|
||||
FileUtils.cp "#{full_file_name}", "#{data_path}/bak#{Time.now.to_i}_#{file.original_filename}"
|
||||
end
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<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">
|
||||
@@ -151,6 +151,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -17,8 +17,8 @@
|
||||
<div class="accordion-body in collapse" id="collapseOne" style="height: auto;">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -32,30 +32,30 @@
|
||||
</div>
|
||||
<div class="accordion-body collapse" id="collapseTwo" style="height: 0px;">
|
||||
<div class="accordion-inner">
|
||||
<p class="desc">
|
||||
<p class="desc">
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
Within app/controllers/users_controller.rb
|
||||
</p>
|
||||
<pre class="ruby">
|
||||
def update
|
||||
message = false
|
||||
<span style="background-color:yellow"> user = User.find(:first, :conditions => "user_id = '#{params[:user][:user_id]}'")</span>
|
||||
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
|
||||
</pre>
|
||||
<p class="desc">
|
||||
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.
|
||||
</p>
|
||||
</p>
|
||||
<p>
|
||||
Within app/controllers/users_controller.rb
|
||||
</p>
|
||||
<pre class="ruby">
|
||||
def update
|
||||
message = false
|
||||
<span style="background-color:yellow"> user = User.find(:first, :conditions => "user_id = '#{params[:user][:user_id]}'")</span>
|
||||
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
|
||||
</pre>
|
||||
<p class="desc">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,77 +70,77 @@
|
||||
<div class="accordion-body collapse" id="collapseThree" style="height: 0px;">
|
||||
<div class="accordion-inner">
|
||||
<p><b>SQL Injection - ATTACK</b></p>
|
||||
<p class="desc">
|
||||
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:
|
||||
<p>
|
||||
<pre class="ruby">
|
||||
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=&<span style="background-color: yellow"> user[user_id]=5</span>&user[email]=ken@metacorp.com&user[first_name]=Ken&user[last_name]=Johnson&user[password]=testtest&user[password_confirmation]=testtest
|
||||
</pre>
|
||||
<p class="desc">
|
||||
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. <b> 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".</b>
|
||||
</p>
|
||||
<pre class="ruby">
|
||||
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
|
||||
<p class="desc">
|
||||
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:
|
||||
<p>
|
||||
<pre class="ruby">
|
||||
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=&<span style="background-color: yellow"> user[user_id]=5</span>&user[email]=ken@metacorp.com&user[first_name]=Ken&user[last_name]=Johnson&user[password]=testtest&user[password_confirmation]=testtest
|
||||
</pre>
|
||||
<p class="desc">
|
||||
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. <b> 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".</b>
|
||||
</p>
|
||||
<pre class="ruby">
|
||||
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=&<span style="background:yellow">user[user_id]=5') OR admin = 't' --'")</span>&user[password]=testtest1&user[password_confirmation]=testtest1
|
||||
</pre>
|
||||
<p><b>SQL Injection - SOLUTION</b></p>
|
||||
<p class="desc">
|
||||
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.<br/><br/>
|
||||
</p>
|
||||
<pre class="ruby">
|
||||
def update
|
||||
message = false
|
||||
<span style="background-color:yellow">user = current_user</span>
|
||||
utf8=â&_method=put&authenticity_token=GXhLKKhfBXdFx5i6iqHEd5E32Kebn1+G35eA87RW1tU=&<span style="background:yellow">user[user_id]=5') OR admin = 't' --'")</span>&user[password]=testtest1&user[password_confirmation]=testtest1
|
||||
</pre>
|
||||
<p><b>SQL Injection - SOLUTION</b></p>
|
||||
<p class="desc">
|
||||
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.<br/><br/>
|
||||
</p>
|
||||
<pre class="ruby">
|
||||
def update
|
||||
message = false
|
||||
<span style="background-color:yellow">user = current_user</span>
|
||||
|
||||
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
|
||||
</pre>
|
||||
<p class="desc">
|
||||
...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:
|
||||
</p>
|
||||
<pre class="ruby">
|
||||
user = User.find(:first, :conditions => ["user_id = ?", "#{params[:user][:user_id]}"])
|
||||
</pre>
|
||||
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
|
||||
</pre>
|
||||
<p class="desc">
|
||||
...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:
|
||||
</p>
|
||||
<pre class="ruby">
|
||||
user = User.find(:first, :conditions => ["user_id = ?", "#{params[:user][:user_id]}"])
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a style="background-color: rgb(181, 121, 158)" href="#collapseFour" data-parent="#accordion1" data-toggle="collapse" class="accordion-toggle">
|
||||
<i class="icon-aid icon-white">
|
||||
@@ -154,6 +154,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -17,14 +17,14 @@
|
||||
<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>
|
||||
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>
|
||||
</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>
|
||||
@@ -38,41 +38,41 @@
|
||||
</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
|
||||
<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>
|
||||
<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
|
||||
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>
|
||||
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>
|
||||
@@ -87,72 +87,72 @@
|
||||
<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"]
|
||||
<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 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>
|
||||
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-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">
|
||||
@@ -166,6 +166,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user