Michael Lubas, 2024-04-30
Phoenix LiveView has seen incredible adoption in the past few years, providing developers with the ability to ship real time web applications with minimal latency and bandwidth usage. Paraxial.io is the only security product with a strong Elixir focus, so a natural question is “Does Paraxial.io support LiveView”? The answer is yes!
LiveView uses HEEx, a templating language with files that end in .html.heex
. Last year I noticed that these files were not being scanned for cross site scripting (XSS) issues with Sobelow, and submitted a PR to fix this. The SAST component of Paraxial.io uses Sobelow, and these files are currently being scanned correctly.
Imagine a bad client opening several hundred LiveView connections per second for some malicious purpose, such as running a bot to break into user accounts. Each connection starts as a normal HTTP request, so Paraxial.io bot defense can detect the unusual traffic and ban the IP address.
This feature has always existed in Paraxial.io. You may be wondering: What if the bad guy establishes one connection, but sends hundreds of websocket messages per second over the connection, to avoid detection?
Today Paraxial.io can now guard against a malicious client sending too many websocket requests over a LiveView connection. Consider the following:
def handle_event("login", %{"user" => user_params}, socket) do
case Accounts.login(user_params) do
{:ok, user} ->
{:noreply,
socket
|> assign(current_user: user)
|> put_flash(:info, "Login successful")}
{:error, changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
With Paraxial.io you can rate limit the number of login attempts send over one websocket connection. Note that for the below code to work you must use the :peer_data
option with socket connect_info, then put the address in the socket assigns.
def handle_event("login", %{"user" => user_params}, socket) do
ip_string = socket.assigns.address |> :inet.ntoa() |> to_string()
key = "user-login-#{ip_string}"
seconds = 5
count = 5
ban_length = "hour"
ip = socket.assigns.address
msg = "`> 5 requests in 5 seconds to login from #{ip_string}`"
case Paraxial.check_rate(key, seconds, count, ban_length, ip, msg) do
{:allow, _} ->
do_handle_login(user_params)
{:deny, _} ->
conn
|> put_resp_content_type("text/html")
|> send_resp(429, "Rate limited")
end
end
def do_handle_login(user_params) do
case Accounts.login(user_params) do
{:ok, user} ->
{:noreply,
socket
|> assign(current_user: user)
|> put_flash(:info, "Login successful")}
{:error, changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
The banned IPs will be tracked in the Paraxial.io backend, and you will receive an alert via the Paraxial.io Slack App.
See the documentation page LiveView Bot Defense for more information.
Paraxial.io stops data breaches by helping developers ship secure applications. Get a demo or start for free.
Subscribe to stay up to date on new posts.