• OWASP nice examples of attack and vulnerability XSS there you can see first two links:

https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet

  • html image src could be stored:
<IMG SRC=/ onerror="console.log('cao. bolje da eskejpujete sadrzaj');location.assign('http://www.iznajmljivanjeprojektoranovisad.in.rs')"></img>
  • inspect post/patch request in chrome developer tools on Network tab, right click on request and “Copy as cURL”, and update fields to insert your html

  • in rails, copy as curl, for xhr post, I need to add --request PATCH and --data @data.txt and to join all lines with & in data.txt from Copy all Data.

Prevent injection with https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#Output_Encoding_Rules_Summary

  • html entity: & to &amp;, < to &lt;, > to &gt;, quotation mark " to &quot; (&#22; hex &34; dec), apostrophe ' to &x27; (&39; decimal), / to &#x2f; https://www.w3schools.com/html/html_charset.asp
  • html attribute: escape all characters except aphanumeric with html entity &#xHH; and space &#x;
  • url encoding: use percent encoding %XX only for parametar values, not the url of path fragments. Space is replaced with + or %20, " to %22, # to %23, $ to %24, % to %25, & to %26, ' to %27, ( to %28, ) to %29, . to %2E, / to %2F, : to %3A
  • javascript: escape all chards with \uXXXX unicode escaping format
  • css hex encoding

In ruby you can escape query params

  require 'uri'

  enc_uri = URI.escape("http://example.com/?a=\11\15")
  p enc_uri
  # => "http://example.com/?a=%09%0D"

  p URI.unescape(enc_uri)
  # => "http://example.com/?a=\t\r"

Some common escaped characters are: blank, slash URI.escape " /" # %20%2f but escape does not convert / to %2f

DOS attack

  • sudo hping3 -c 10000 -d 120 -S -w 64 -p 21 --flood --rand-source myapp.herokuapp.com
  • you can generate authnentication key for http basic authentication with
auth=`echo -n 'username:password' | openssl base64`

Siege

Run 25 concurent users 1 minute with basic http authorization:

siege myapp.herokuapp.com -c25 -t1M -H "Authorization: Basic
bmalrb2xhb2I6bmlrb2xhamVjYXI="

AB

Run 500 requests in 5 threads:

ab -n 500 -c 5 -H 'Authorization:Basic barb2xhb2I6bmlrb2xhamVjYXI=' http://yourapp.com/
ab -kc 10 -t 10 url # 10 seconds in 10 threads

Rack Attack

You can use rack-attack gem to prevent dos attacks. It will allow all requests from saveflist and exit. Otherwise it will ban all requests from blocklist and exit. Otherwise it will check for throttle. Example configuration

echo 'rack-attack' >> Gemfile
bundle
sed -i config/application.rb -e '/^  end$/i \
    config.middleware.use Rack::Attack'
# config/initializers/rack_attack.rb
module Rack
  class Attack
    # https://github.com/kickstarter/rack-attack/wiki/Example-Configuration
    THROTTLED_BLOCKED_INTERVAL = 1.hour

    # Key: "rack::attack:#{Time.now.to_i/:period}:req/ip:#{req.ip}"
    throttle('req/ip', limit: 300, period: 5.minutes) do |req|
      req.ip unless req.path.starts_with?('/assets')
    end

    # Key: "rack::attack:#{Time.now.to_i/:period}:logins/email:#{req.ip}"
    throttle('logins/email', limit: 5, period: 20.seconds) do |req|
      req.params['user'].try(:[], "email") if req.path == '/users/sign_in' &&
                                              req.post?
    end

    blocklist('blocklist_ips 1.2.3.4') do |req|
      Rails.cache.fetch("blocklist_ips #{req.ip}").present?
    end

    OFFICE_IP = '1.2.3.4'
    blocklist('bad_admin_ip') do |req|
      req.path.start_with?('/admin') && req.ip != OFFICE_IP
    end

    safelist('safelist_ips 1.2.3.4') do |req|
      # if req.ip == "127.0.0.1"
      #   true
      # else
      #   Rails.cache.fetch("safelist_ips #{req.ip}").present?
      # end
      Rails.cache.fetch("safelist_ips #{req.ip}").present?
    end

    self.blocklisted_response = lambda do |env|
      ip = find_ip_from_env(env)
      unless Rails.cache.fetch("notification_block #{ip}").present?
        Rails.cache.write("notification_block #{ip}", true, expires_in: 1.hour)
        Notify.exception_with_env(
          "ip_blocklist",
          env: env,
          email_prefix: "just to notify (in this hour) that #{ip} tried again")
      end
      # Using 503 because it may make attacker think that they have successfully
      # DOSed the site. Rack::Attack returns 403 for blocklists by default
      [503, {}, [
        "Your IP address (#{ip}) has been blocked. If you think that this a "\
        "mistake please write to info@xceednet.com"]]
    end

    self.throttled_response = lambda do |env|
      ip = find_ip_from_env(env)
      if ip.present?
        Rails.cache.write("blocklist_ips #{ip}", true,
                          expires_in: THROTTLED_BLOCKED_INTERVAL)
        Rails.logger.info "throttled response: temporary adding #{ip} to "\
        "blocklist_ips"
        Notify.exception_with_env(
          "ip_throttle",
          env: env,
          email_prefix: "just info that #{ip} is on blocklist for some time",
          data: {
            help_text: "You can remove this temporary block in rails console "\
            "with this command (note that throttle still works):
            rake rack_attack:remove_from_blocklist_ips[#{ip}]
            ')" }
        )
      else
        Rails.logger.info "throttled response, but do not know ip address"
      end

      # Using 503 because it may make attacker think that they have successfully
      # DOSed the site. Rack::Attack returns 429 for throttling by default
      # [ 503, {}, [body]]
      [503, {}, ["Try later"]]
    end

    def self.find_ip_from_env(env)
      if env["REMOTE_ADDR"].present?
        env["REMOTE_ADDR"]
      else
        # heroku
        env["HTTP_X_FORWARDED_FOR"]
      end
    end
  end
end

You can test with command:

ab -n 350 http://localhost:3000

Default rails cache is file, so you need to rm -rf tmp/cache if you want to test clean situation.

If you receive too much 404 in logs, you can ban ip addreses http://marianposaceanu.com/articles/keep-your-rails-logs-free-of-unwanted-noise For example block ip address for one day if request is made to /{wp-admin|wp-login}

# After 1 blocked requests in 10 minutes, block all requests from that IP for 1 day.
Rack::Attack.blocklist('fail2ban pentesters') do |req|
  # `filter` returns truthy value if request fails, or if it's from a previously banned IP so the request is blocked
  Rack::Attack::Fail2Ban.filter(
    "pentesters-#{req.ip}",
    maxretry: 1,
    findtime: 10.minutes,
    bantime: 1.day
  ) do
    # The count for the IP is incremented if the return value is truthy
    req.path.include?('wp-admin') ||
    req.path.include?('wp-login')
  end
end

SQL Injection

  • rails-sqli list of common injections
  • external params should be sanitized where("id = ?", params[:id]) or where("id = :id", id: params[:id])
  • with order we can not use sanitizations ? or :id method. So do not use string interpolation since anybody can do anything (drop table, get access). You can try with adding limit 1# (# is %23 encoded) for example ?sort=created_at&direction=asc+limit+1%23. Solution is to use methods that returns only column names

    # app/controllers/application_controller.rb
    def sort_column
      %w[id name created_at].include?(params[:sort])) ? params[:sort] : "id"
    end
    
    def sort_direction
      %w[asc desc].include?(params[:direction]) ?  params[:direction] : "desc"
    end
    
    def sort_query
      "#{sort_column} #{sort_direction}"
    end
    
    # use like: User.order(sort_query)
    

SSL

Add free ssl

  • Test if it is working https://www.ssllabs.com/ssltest/ try config with Mozzila ssl config generator https://mozilla.github.io/server-side-tls/ssl-config-generator/

  • Add your site to hsts list https://hstspreload.org/ so major browser will use https instead http (can’t use http).
  • If not using https than ISP or Browser can easilly inject javascript into the page and show adds.
  • If you have password field on a page, browsers will require https or show a “Not Secure Warning”.
  • It is mandatory for HTTP 2.0.
  • Some browsers features are only available over HTTPS: geolocation, service workers, fullscreen…

CSP

https://www.randomerrata.com/articles/2017/rails-security/ CSP content security policy is header that tells browser where assets can be loaded from. Rails initialy gives us protection from: CSRF, XSS, Injection (SQL, HTML, Javascript). Default headers: X-Frame-Options, X-XSS-Protection, X-Content-Type-Options.

Gem https://github.com/twitter/secureheaders can be added (just add to your Gemfile gem 'secure_headers' and create configuration

# config/initializers/secure_headers.rb
SecureHeaders::Configuration.default do |config|
end
Content-Security-Policy:
# allow only scripts from the same origin as the page
script-src 'self'
# allow only scripts from the same origin as the page and google
script-src 'self' https://apis.google.com
# allow all sources but disallow unsafe inline assets
script-src *
# allow all and allow inline
script-src * 'unsafe-inline'

# reporting to
report-uri https://xceednet.report-uri.io/r/default/csp/reportOnly

Content-Security-Policy-Report-Only

https://github.com/gbataille/csp_report You can deploy to production using Report only so you can see what assets are used and add them one by one.

You can test if browser is injecting js https://gist.github.com/danharper/8364399

Tutorial https://chase.pursu.es/simple-intro-to-csp-for-rails.html

Iptables

There are 3 types of chains: input, forward and output (some services like ssh use both input and output chains). Default policy (for example ACCEPT) is used for unmatched traffic. Response could be Accept, Drop (do not notify the source) and Reject (source knows that it was rejected).

# list all rules
iptables -L
# block all connections from 10.10.10.10
iptables -A INPUT -s 10.10.10.10 -j DROP
# block all connections from 10.10.10.10 for ssh
iptables -A INPUT -p tcp --dport ssh -s 10.10.10.10 -j DROP

# SSH connections FROM 10.10.10.10 are permitted, but SSH TO 10.10.10.10 are not
iptables -A INPUT -p tcp --dport ssh -s 10.10.10.10 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -p tcp --sport 22 -d 10.10.10.10 -m state --state ESTABLISHED -j ACCEPT

iptables-save to save changes.

Example for WordPress useragent verifying pingback from ddos attack https://www.christophe-casalegno.com/under-wordpress-pingback-ddos-attack-use-iptables/

Monit

https://www.youtube.com/watch?v=wiRt3mY7Rrw

Tips

  • use scoped find, instead of Order.find params[:id] use current_user.orders find params[:id]
  • use rate limiting, for example facebook password recovery number is 6 digits, and beta.facebook did not have rate limit, so you can try 1 milin numbers very quickly
  • do not trust the mobile app, for example mobile app make a failed payment and than change the payment response to be success and send that to server
  • if your server is doing curl requests for some user url, than user can set up sftp or smtp requests to any server and hide itself
  • use SHA 256 instead md5 and SHA 1

https://github.com/eliotsykes/rails-security-checklist https://github.com/eliotsykes/real-world-rails