Ruby

Recommendations for OWASP TOP 10 2017

A1- Injection

SQL Injection

  • Use Active Records library which uses parameterized queries to interact with the database.
  • Non Compliant Code:
        User.where("email = '#{email}'") 
        User.where("email = '%{email}'" % { email: email })
  • Compliant Code:
    User.where(email: email)
    User.where("email = ?", email)
    User.where("email = :email", email: email)
  • We could also use ActiveRecord::sanitize method which will sanitize the string explicitly.

  • Use Dynamic Attribute-Based Finders

    • Non Compliant Code: User.find_by(name: params[:name]) # Traditional

    • Compliant Code: User.find_by_name(name) # dynamic finder

Reference:

OS Command Injection

  • Use Shellwords Module which escapes a string so that it can be safely used in a Bourne shell command line.
    • Compliant Code:
open("| grep #{Shellwords.escape(pattern)} file") { |pipe|
    # ...
}
  • Using correct syntax for system function

    • Non Complaint Code: system("ls -a -l -@ -1 #{path}")
    • Complaint Code: system("ls", "-a", "-l", "-@", "-1", path)
  • When using Open Redirection

    • When using open redirection in the code use open3 library to properly handle output redirection
    • Non Complaint Code: `ls -a -l -@ -1 #{path} 2>&1`
    • Complaint Code: stdout_and_stderr, status = Open3.capture2e("ls", "-a", "-l", "-@", "-1", path)

References:

A2 - Broken Authentication & Session Management

Broken Authentication

  • Use devise or authlogic plugins to store encrypted passwords. Rails 3.1 uses similar built in function has_secure_password
  • Where possible, implement multi-factor authentication to prevent automated, credential stuffing, brute force, and stolen credential re-use attacks.
  • Ensure registration, credential recovery, and API pathways are hardened against account enumeration attacks by using the same messages for all outcomes.
  • Limit or increasingly delay failed login attempts. Log all failures and alert administrators when credential stuffing, brute force, or other attacks are detected.
  • Minimum passwords length should be at least eight (8) characters long. Combining this length with complexity makes a password difficult to guess and/or brute force.
  • Do not ship or deploy with any default credentials, particularly for admin users.
  • The application may should different HTTP Error code depending on the authentication attempt response.
  • Use a side-channel to communicate the method to reset their password.
  • Ensure that generated tokens or codes in forgot/reset password are:
    • Randomly generated using a cryptographically safe algorithm.
    • Sufficiently long to protect against brute-force attacks.
    • Stored securely.
    • Single use and expire after an appropriate period.
  • A stronger password can be enforced using the regex below, which requires at least 8 character password with numbers and both lowercase and uppercase letters.
    • var PASS_RE =/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$/;

Session Management

  • Use ActiveRecordStore mechanism to store session hashes. ActiveRecordStore keeps the session id and hash in a database table and saves and retrieves the hash on every request.
  • Session IDs should not be exposed in the URL.
  • Session IDs should timeout. User sessions or authentication tokens should get properly invalidated during logout.
  • Use the popular RestfulAuthentication plugin for user management, add reset_session to the SessionsController#create action. Note that this removes any value from the session, you have to transfer them to the new session. This helps in mitigating Session Fixation Attacks.
  • Passwords, session IDs, and other credentials should not be sent over unencrypted connections.
  • Session IDs should never be under the control of users and clients to create. They should all be generated, controlled, and secured centrally by the authentication and authorization mechanism.
  • It is important to expire the session after maximum of 20 minutes. It is important to add a created_at column in the database to expire sessions that were created before specific time.
    • Compliant Code:
    class Session < ApplicationRecord
        def self.sweep(time = 1.hour)
            if time.is_a?(String)
                time = time.split.inject { |count, unit| count.to_i.send(unit) }
            end
            delete_all "updated_at < '#{time.ago.to_s(:db)}' OR created_at < '#{2.days.ago.to_s(:db)}'"
        end
    end

References:

A3 Sensitive Data Exposure

  • Classify data processed, stored or transmitted by an application. Identify which data is sensitive according to privacy laws, regulatory requirements, or business needs.
  • Apply controls as per the classification.
  • Don’t store sensitive data unnecessarily. Discard it as soon as possible or use PCI DSS compliant tokenization or even truncation. Data that is not retained cannot be stolen.
  • Ensure up-to-date and strong standard algorithms, protocols, and keys are in place; use proper key management.
  • Encrypt all data in transit with secure protocols such as TLS with perfect forward secrecy (PFS) ciphers, cipher prioritization by the server, and secure parameters. Enforce encryption using directives like HTTP Strict Transport Security (HSTS)
    • Enable ssl globally in the application.rb by adding the below code:
    //force HTTPS on all environments
    config.force_ssl = true
  • Encrypt all sensitive data at rest
    • Compliant Code:
    data = "Very, very confidential data"
//Encrypting Data 
    cipher = OpenSSL::Cipher::AES.new(128, :CBC)
    cipher.encrypt
    key = cipher.random_key
    iv = cipher.random_iv

    encrypted = cipher.update(data) + cipher.final
...
//Decrypting Data 
    decipher = OpenSSL::Cipher::AES.new(128, :CBC)
    decipher.decrypt
    decipher.key = key
    decipher.iv = iv

    plain = decipher.update(encrypted) + decipher.final

    puts data == plain #=> true
  • Store passwords using strong adaptive and salted hashing functions with a work factor (delay factor), such as Argon2, scrypt, bcrypt or PBKDF2.
    • Compliant Code:
//Using bcrypt hashing
    require 'bcrypt'
    BCrypt::Password.create("test")
    // "$2a$10$zccXimeuNdA083RSDFy7VeVgs538d5XRQurRbqjdEd3h0kU7Q0j2e"

//Using Argon2 hashing
    crypt_newhash 'password', id: 'argon2i'
    // => "$argon2i$v=19$m=4096,t=3,p=1$b9AqucWUJADOdNMW8fW+0A$s3+Yno9+X7rpA2AsaG7KnoBtjQiE+AUevLvT7u1lXeA"

    crypt_checkpass? 'password', '$argon2i$v=19$m=4096,t=3,p=1$b9AqucWUJADOdNMW8fW+0A$s3+Yno9+X7rpA2AsaG7KnoBtjQiE+AUevLvT7u1lXeA'
    // => true 

References:

A4 XML External Entities (XXE)

  • Don’t parse XML if it’s not an application requirement.
  • Don’t use a library that has supports entity replacement (LibXML). Use the built in default instead; REXML
  • Ensure entity replacement is disabled. New versions of LibXML make it hard to enable entity replacement. You may still be vulnerable to a DoS attack when using LibXML.
    • LibXML::XML.default_substitute_entities
    • false
  • Whitelist known external entities

References:

A5 Broken Access Control

  • Require multi-factor authentication for users to log in.
  • Use pre-built libraries for authentication such as omniauth & devise.
  • Disable web server directory listing and ensure file metadata (e.g. .git) and backup files are not present within web roots.
  • Access decisions should be made by checking if the current user has the permission associated with the requested application action.
  • Implementing an account lockout from too many failed attempts can prevent brute force attacks!
  • Limit the sessions.
  • Do not allow concurrent sessions.
  • Keep session IDs secure by encrypting them.
  • If your application has some default username/password combinations, ensure they are deleted from the database.

A6 Security Misconfiguration

  • Use latest stable version of ruby and different gem libraries. Keep a watch on published vulnerabilities of these.
  • Do not run application with root privileges. It may seem necessary to run as root user to access privileged ports such as 80. However, this can achieved either by starting server as root and then downgrading the non-privileged user after listening on port 80 is established, or using a separate proxy, or using port mapping.
  • It is recommended to configure the security headers under <ruby_directory>/lib/action_dispatch/railtie.rb which is a part of the ActionDispatch module.
  • It is recommended to configure the CORS for the application. Ruby provides a DSL that allows you to configure a Content Security Policy for your application. You can configure a global default policy and then override it on a per-resource basis.
    • Compliant Code:
    //Edit the <ruby_directory>/config/initializers/content_security_policy.rb
    Rails.application.config.content_security_policy do |policy|
        policy.default_src :self, :https
        policy.font_src    :self, :https, :data
        policy.img_src     :self, :https, :data
        policy.object_src  :none
        policy.script_src  :self, :https
        policy.style_src   :self, :https

        //Specify URI for violation reports
        policy.report_uri "/csp-violation-report-endpoint"
    end

References:

A7 Cross Site Scripting (XSS)

  • Ruby uses ERB::Util#html_escape function to escape HTML entities. This particular function does the substitution based on the following hash:
{ '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
  • Use html_safe function to create ActiveSupport::SafeBuffer as a response when called. In the below code the params[:input] will be HTML-encoded before appending into other SafeBuffer, therefore it would be safe.
  • Compliant Code:
Hello <%=params[:name].html_safe%> 
  • Non Compliant Code:
Hello <%= "<b>#{params[:name]}</b>".html_safe%>
  • When passing numbers to JavaScript, we should make sure that we are passing just an integer, nothing else. We can acheive this by type casting the user input.
  • Compliant Code:
<script>current_user_id = <%=param[:id].to_i%></script>
  • Non Compliant Code:
<script>current_user_id = <%=param[:id]%></script>
  • Ruby uses ERB::Util#json_escape function to escape JSON characters. This particular function does the substitution based on the following hash:
{ '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
  • Compliant Code:
There is some JSON: <%= json_escape (User.last.to_json) %>
  • When using escape_javascript function, it allows a developer to fix the newline XSS payloads by breaking the JavaScript code and resulting in syntax errors. We have to be careful of HTML Encoding the escaped JavaScript code.
  • Non Compliant Code:
JavaScript Escaping: "<%= escape_javascript whatever %>"
  • Compliant Code:
JavaScript Escaping: "<%= raw escape_javascript whatever %>"

References:

Cross-Site Request Forgery (CSRF)

  • Enable the in-built CSRF protection in the application_controller.rb file by
protect_from_forgery with: :exception
  • Also add the below code to application.html.erb
<%= csrf_meta_tags %>
  • Use POST HTTP method instead of GET.
  • Always use SameSite Cookie Attribute for session cookies
  • Consider implementing 2 factor authentication for highly sensitive operations
  • Remember that any Cross-Site Scripting (XSS) can be used to defeat all CSRF mitigation techniques.
  • CSRF can be avoided by creating a unique token in a hidden field which would be sent in the body of the HTTP request rather than in an URL, which is more prone to exposure.

References:

A9 Using Components with known vulnerabilities

  • Run application with the least privilege user
  • Prefer packages that contain comprehensive unit tests and review tests for the functions our application uses
  • Review code for any unexpected file or database access
  • Research about how popular the package is, what other packages use it, if any other packages are written by the author, etc
  • Use the latest stable version of the packages.
  • Watch Github repositories for notifications. This will inform us if any vulnerabilities are discovered in the package in future
  • Remove unused dependencies, unnecessary features, components, files, and documentation.
  • Continuously inventory the versions of both client-side and server-side components (e.g. frameworks, libraries) and their dependencies using tools like versions, DependencyCheck, retire.js, etc. Continuously monitor sources like CVE and NVD for vulnerabilities in the components. Use software composition analysis tools to automate the process. Subscribe to email alerts for security vulnerabilities related to components you use.
  • Only obtain components from official sources over secure links. Prefer signed packages to reduce the chance of including a modified, malicious component.
  • Monitor for libraries and components that are unmaintained or do not create security patches for older versions. If patching is not possible, consider deploying a virtual patch to monitor, detect, or protect against the discovered issue.
  • Every organization must ensure that there is an ongoing plan for monitoring, triaging, and applying updates or configuration changes for the lifetime of the application or portfolio.

Reference:

A10 Insufficient Logging and Monitoring

  • Ensure all login, access control failures, and server-side input validation failures can be logged with sufficient user context to identify suspicious or malicious accounts, and held for sufficient time to allow delayed forensic analysis.
  • Ensure that logs are generated in a format that can be easily consumed by a centralized log management solutions.
  • Ensure high-value transactions have an audit trail with integrity controls to prevent tampering or deletion, such as append-only database tables or similar.
  • Establish effective monitoring and alerting such that suspicious activities are detected and responded to in a timely fashion.
  • Establish or adopt an incident response and recovery plan, such as NIST 800-61 rev 2 or later. There are commercial and open source application protection frameworks such as OWASP AppSensor, web application firewalls such as ModSecurity with the OWASP ModSecurity Core Rule Set, and log correlation software with custom dashboards and alerting.

Reference: