Subscribe to access all episodes. View plans →

#149: Bot Defense with Paraxial.io

Published August 1, 2022

Note: video uses Paraxial version 0.0.7 but the code also works for version 0.0.8

Paraxial.io

Getting started with Paraxial docs

Paraxial blog post on credential stuffing


Here we have a Phoenix web application that has a sign-in page for users to authenticate and access the site. While our site works great for the user - we have a problem. There was a data breach from a large site that leaked user login data. That data is now in the hands of a bad actor.

To better understand what the bad actor wants to do we’ve got a script here that simulates a credential stuffing attack - it loops over the compromised login credentials, attempting to use them to gain access to our site, and works when someone on our site used the same username/password combination on the site that suffered the data breach. This is known as “credential stuffing” and is a very effective technique for stealing user accounts.

In fact, let’s simulate the attack. We’ll go to the command line and run the attack script - we can see it’s posting the data to our site with no problem. And looking at the development logs we even see a successful login.

$ mix run attack.exs
...

We don’t currently have any protection on our site against this type of attack. What we need is some type of bot detection for our account login - and any other pages that could be exploited.

To help us prevent these attacks, we’ll use Paraxial.io. Paraxial.io is a bot detection and prevention service built specifically for Elixir web applications. While other services might require you to configure your DNS or install extra JavaScript - Paraxial.io is just a Hex package. And unlike CDN services, where your data passes through their servers - you have total control over your data with Paraxial.io.

Let’s open Paraxial.io’s installation docs and outlined here are all the steps we’ll need to get Paraxial.io setup in our application. But, before we get started let’s go to the command line and run our tests.

$ mix test
...

Great everything passes, so let’s go back to the install guide to get started we’ll grab the paraxial package and add it to our list of dependencies.

mix.exs

...
defp deps do
  [
    ...
    {:paraxial, "~> 0.0.7"},
    ...
  ]
end
...

Then let’s add our config. We’ll copy the example, minus the “bulk” or “trusted_domains” options, which are used to help prevent email spam. Since our application doesn’t send any emails we won’t include them for this demo. With that, we can open our config/dev.ex and paste it into the config.

config/dev.exs

...

config :paraxial,
  paraxial_api_key: "PARAXIAL_API_KEY",
  paraxial_url: "https://app.paraxial.io",
  fetch_cloud_ips: true

...

Now to complete our config we need a paraxial_api_key. To get that we’ll go to Paraxial.io - and I’ll sign in - but if you don’t have an account with Paraxial.io, you’ll need to register before continuing.

Once we’ve signed in we’ll need to create a new site so let’s do that. I’ll give this the name of “dev.elixircasts.io” and select our timezone. Great our site was created and here we’ll get an overview of all the requests made to our application.

To get our API key we’ll click on the site settings tab and here at the top we have our Site API key, let’s copy this, and then we’ll paste it into our dev config.

With that, we can go to the command line and install paraxial with mix deps.get.

$ mix deps.get
...

Great let’s go back to the install guide and Paraxial.io has a few plugs we need to add. There’s the RemoteIp plug which you’ll want if your application is deployed behind a proxy - for example, if you’re using fly.io or Gigalixir - your conn.remote_ip is probably different from the real IP of the client and the RemoteIp plug will fix that.

We won’t worry about that for this example, but we will want to add the Paraxial.AllowedPlug and Paraxial.RecordPlug before our Router plug and then the Paraxial.RecordPlug after our Router. Let’s open our app’s endpoint.ex and add those plugs.

lib/teacher_web/endpoint.ex

...
plug Paraxial.AllowedPlug
plug Paraxial.RecordPlug
plug TeacherWeb.Router
plug Paraxial.RecordPlug
...

Now that we’ve added paraxial to our app let’s run our tests again. When we do we’re now getting some failing tests. This is because while we added paraxial config to our dev config, we still need to add it to our test config.

config/test.exs

...

config :paraxial,
  paraxial_api_key: "PARAXIAL_API_KEY",
  paraxial_url: "https://app.paraxial.io",
  fetch_cloud_ips: true

...

Let’s copy over our dev config to test and we’ll also want to create a new API key for “test” to keep any requests to Paraxial.io separate. We’ll go back to Paraxial.io and create a site just like before, only this time I’ll prefix it with “test.elixircasts.io” we’ll grab the key and then add it to our test config. With that let’s run our tests again - and great they all pass! So let’s go ahead and start our server.

$ mix phx.server
...

Then if we simulate the attack again with our script we get the same responses as before.

$ mix run attack.exs
...

But if we go back to Paraxial.io we see there were 10 requests to our application. Great, our app is configured to use Paraxial.io. Now we just need to create a rule in Paraxial.io to mitigate the attack.

Let’s go to the “Rules” tab and create a rule for our site. Let’s call it “Block credential stuffing” and here you can customize the rule to fit your application. For our’s let’s set the number of requests allowed to “5” and we’ll use 10 seconds for the time period.

Then we can specify the path, for this rule we’ll want to use our login path - “/users/log_in” and then the HTTP method of “post”. For our trigger, we’ll select “Create alert and ban the IP”. Now that our rule has been created, let’s go back to the command line and simulate another attack. Our first few requests look the same … but then we start seeing a different response status code.

$ mix run attack.exs
...

Let’s go back to Paraxial.io and perfect - there were 4 blocked requests, this is exactly what we wanted to happen.

Paraxial.io banned the IP once our rule was triggered. And if we go to “Rule events” we see an event was created. Our attacker’s IP address was placed on a ban list. This is great and exactly what we wanted to happen. Paraxial.io will even give us a nice summary of the event and we can easily update our banned IPs. We were able to configure our application to use Paraxial.io and set it up to block a bad actor in hardly any time.

There are a lot of other great features in Paraxial.io like Cloud IP matching and if you want to learn more about credential stuffing there’s a great post on the Paraxial.io blog that I’ll link to in the episode notes. It demonstrates how to test a Phoenix application to see if credential stuffing from one IP address is possible.

© 2024 HEXMONSTER LLC