Neonify

É hora de uma brilhante revelação de um nunca antes visto "neonificador" de texto. Venha testar nosso mais novo website e faça qualquer texto brilhar como um tubo de neon de lo-fi.

Esse desafio consiste numa aplicação web em Ruby que estiliza a entrada do usuário para que ela receba um efeito Neon.

Reconhecimento

A aplicação possui apenas um campo para entrada de dados e um botão para enviar o texto informado.

Como o desafio fornece o código-fonte, podemos realizar uma análise para entender seu funcionamento. No index, perceba que a aplicação usa um template para carregar o texto informado pelo usuário:

index.erb
...SNIP
<h1 class="title">Amazing Neonify Generator</h1>
<form action="/" method="post">
    <p>Enter Text to Neonify</p><br>
    <input type="text" name="neon" value="">
    <input type="submit" value="Submit">
</form>
<h1 class="glow"><%= @neon %></h1> <!-- observe essa linha -->
SNIP...

Esse template é alimentado pelo arquivo neon.erb, que por sua vez recebe a entrada do usuário por meio de uma requisição POST, realiza uma validação e renderiza na página.

neon.erb
class NeonControllers < Sinatra::Base

  configure do
    set :views, "app/views"
    set :public_dir, "public"
  end

  get '/' do
    @neon = "Glow With The Flow"
    erb :'index'
  end

  post '/' do
    if params[:neon] =~ /^[0-9a-z ]+$/i
      @neon = ERB.new(params[:neon]).result(binding)
    else
      @neon = "Malicious Input Detected"
    end
    erb :'index'
  end

end

Esse código valida o parâmetro POST neon por meio de regular expression e identifica a presença de caracteres que não sejam numerais ou letras de A a Z. Logo a seguir, esse parâmetro é retornado para alimentar o template na página index.

Exploração

Pesquisando, descobri que é possível o bypass da expressão regular informando um payload de Server-Side Template Injection logo antes de uma quebra de linha. Dessa forma, a aplicação não consegue validar o payload malicioso e permite a injeção de templates na página.

Prova de Conceito

O payload final ficou da seguinte forma:

<%25= File.open('flag.txt').read %25>
bypassed

Atente-se à quebra de linha antes do bypassed, é ela que permite o funcionamento do payload. Interceptando o envio da requisição e definindo esse payload, obtemos o seguinte resultado:

Atualizado