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 Insecure Direct Object Reference 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 /railsgoat/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://owaspbwa/railsgoat/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][email protected]&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 [email protected]'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.

				POST /railsgoat/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://owaspbwa/railsgoat/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?
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.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://owaspbwa/railsgoat/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.now.to_i}_#{file.original_filename}"
					 end
			   
Let's create a backup when uploading a file, wonder how they are naming it?