├── 01 - SQL Injection ├── sqli-fix.py └── sqli.py ├── 02 - XSS ├── dom-xss-fix.py ├── dom-xss.py ├── xss-reflected-fix.py ├── xss-reflected.py ├── xss-stored-fix.py └── xss-stored.py ├── 03 - SSRF ├── ssrf-fix.py └── ssrf.py ├── 04 - RCE ├── rce-fix.py └── rce.py ├── 05 - IDOR ├── idor-fix.py └── idor.py ├── 06 - Directory Traversal ├── directorytraversal-fix.py ├── directorytraversal.py └── files │ ├── file1.txt │ └── file2.txt ├── 07 - XXE ├── xxe-fix.py └── xxe.py ├── 08 - SSTI ├── ssti-fix.py └── ssti.py ├── 09 - LFI ├── lfi-fix.py ├── lfi.py └── templates │ ├── contact.html │ └── index.html ├── README.md └── requirements.txt /01 - SQL Injection/sqli-fix.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from flask import Flask, request 3 | 4 | app = Flask(__name__) 5 | 6 | def init_db(): 7 | conn = sqlite3.connect('example.db') 8 | c = conn.cursor() 9 | c.execute('''CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, username TEXT, password TEXT)''') 10 | c.execute("INSERT INTO users (username, password) VALUES ('razor', 'argentinacampeondelmundoperro')") 11 | conn.commit() 12 | conn.close() 13 | 14 | @app.route('/login', methods=['GET','POST']) 15 | def login(): 16 | if request.method == 'POST': 17 | username = request.form.get('username') 18 | password = request.form.get('password') 19 | 20 | conn = sqlite3.connect('example.db') 21 | c = conn.cursor() 22 | 23 | # Para remediar la vulnerabilidad utilizamos consultas parametrizadas 24 | fixquery = "SELECT * FROM users WHERE username = ? AND password = ?" # consultas parametrizadas 25 | print("") 26 | print("Consulta generada:", fixquery) 27 | print("") 28 | try: 29 | c.execute(fixquery, (username, password)) # ejecutar la consulta parametrizada 30 | result = c.fetchone() 31 | conn.close() 32 | 33 | if result: 34 | return "Login exitoso!" 35 | else: 36 | return "Credenciales inválidas" 37 | except Exception as e: 38 | print(f"Error: {e}") 39 | return "Error en la consulta SQL" 40 | 41 | # Si es GET, devolvemos un formulario simple para iniciar sesión 42 | return ''' 43 |
44 |
45 |

SQL Injection Lab

46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | 56 |
57 |
58 |
59 | ''' 60 | 61 | if __name__ == '__main__': 62 | init_db() 63 | app.run(debug=True) 64 | 65 | 66 | -------------------------------------------------------------------------------- /01 - SQL Injection/sqli.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from flask import Flask, request 3 | 4 | app = Flask(__name__) 5 | 6 | def init_db(): 7 | conn = sqlite3.connect('example.db') 8 | c = conn.cursor() 9 | c.execute('''CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, username TEXT, password TEXT)''') 10 | c.execute("INSERT INTO users (username, password) VALUES ('razor', 'argentinacampeondelmundoperro')") 11 | conn.commit() 12 | conn.close() 13 | 14 | @app.route('/login', methods=['GET','POST']) 15 | def login(): 16 | if request.method == 'POST': 17 | username = request.form.get('username') 18 | password = request.form.get('password') 19 | 20 | conn = sqlite3.connect('example.db') 21 | c = conn.cursor() 22 | 23 | # Vulnerable to SQL injection 24 | query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'" 25 | print("Consulta generada:", query) # Imprime la consulta completa 26 | 27 | try: 28 | c.execute(query) 29 | result = c.fetchone() 30 | conn.close() 31 | 32 | if result: 33 | return "Login exitoso!" 34 | else: 35 | return "Credenciales inválidas" 36 | except Exception as e: 37 | print(f"Error: {e}") 38 | return "Error en la consulta SQL" 39 | 40 | # Si es GET, devolvemos un formulario simple para iniciar sesión 41 | return ''' 42 |
43 |
44 |

SQL Injection Lab

45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 55 |
56 |
57 |
58 | ''' 59 | 60 | if __name__ == '__main__': 61 | init_db() 62 | app.run(debug=True) 63 | 64 | 65 | # Para probar la vulnerabilidad de inyección SQL, puedes seguir estos pasos: 66 | # 1. python3 sqli.py 67 | # 2. curl -X POST http://localhost:5000/login -d "username=razor' OR ''='--" 68 | 69 | # Referencias: https://r4z0r.gitbook.io/las-notas-de-r4z0r/web-app-pentest/sql-nosql-injection/sql-injection -------------------------------------------------------------------------------- /02 - XSS/dom-xss-fix.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template_string 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route('/') 6 | def index(): 7 | # Se obtiene el parámetro "texto" de la URL 8 | return render_template_string(''' 9 | 10 | 11 | 12 | DOM XSS Example 13 | 14 | 15 |

DOM XSS Example

16 |

Ingresa un mensaje en el parámetro "texto" de la URL.

17 |

Ejemplo: http://127.0.0.1:5000/?texto=test

18 |

19 | 20 | 26 | 27 | 28 | ''') 29 | 30 | if __name__ == '__main__': 31 | app.run(debug=True) -------------------------------------------------------------------------------- /02 - XSS/dom-xss.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template_string 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route('/') 6 | def index(): 7 | # Se obtiene el parámetro "texto" de la URL 8 | return render_template_string(''' 9 | 10 | 11 | 12 | DOM XSS Lab 13 | 14 | 15 |

DOM XSS Lab

16 |

Ingresa un mensaje en el parámetro "texto" de la URL.

17 |

Ejemplo: http://127.0.0.1:5000/?texto=test

18 |

19 | 20 | 26 | 27 | 28 | ''') 29 | 30 | if __name__ == '__main__': 31 | app.run(debug=True) 32 | 33 | 34 | ''' 35 | Algunas funciones JavaScript vulnerables a DOM XSS: 36 | innerHTML 37 | outerHTML 38 | document.write() 39 | document.writeln() 40 | insertAdjacentHTML() 41 | eval() 42 | location.href 43 | location.assign() 44 | location.replace() 45 | window.location 46 | window.location.search 47 | window.name 48 | postMessage() (si el contenido no está validado y sanitizado) 49 | ''' 50 | 51 | # Referencias: https://r4z0r.gitbook.io/las-notas-de-r4z0r/web-app-pentest/cross-site-scripting-xss 52 | -------------------------------------------------------------------------------- /02 - XSS/xss-reflected-fix.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, render_template_string 2 | from markupsafe import escape 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/') 7 | def index(): 8 | # usamos escape para evitar XSS 9 | name = escape(request.args.get('name', '">')) 10 | 11 | return render_template_string('

Hola, {}!

'.format(name)) 12 | 13 | if __name__ == '__main__': 14 | app.run(debug=True) -------------------------------------------------------------------------------- /02 - XSS/xss-reflected.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, render_template_string 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route('/') 6 | def index(): 7 | name = request.args.get('name', 'mundo') # en mundo meter el payload malicioso 8 | #name = request.args.get('name', '') 9 | 10 | # Vulnerable a XSS 11 | return render_template_string('

Hola, {}!

'.format(name)) 12 | 13 | if __name__ == '__main__': 14 | app.run(debug=True) 15 | 16 | # Referencias: https://r4z0r.gitbook.io/las-notas-de-r4z0r/web-app-pentest/cross-site-scripting-xss -------------------------------------------------------------------------------- /02 - XSS/xss-stored-fix.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from flask import Flask, request, render_template_string, redirect 3 | from markupsafe import escape 4 | 5 | app = Flask(__name__) 6 | 7 | def init_db(): 8 | conn = sqlite3.connect('example.db') 9 | c = conn.cursor() 10 | c.execute('''CREATE TABLE IF NOT EXISTS comments (id INTEGER PRIMARY KEY, content TEXT)''') 11 | conn.commit() 12 | conn.close() 13 | 14 | @app.route('/') 15 | def index(): 16 | conn = sqlite3.connect('example.db') 17 | c = conn.cursor() 18 | c.execute('SELECT content FROM comments') 19 | comments = c.fetchall() 20 | conn.close() 21 | 22 | comment_section = ''.join([f"

{comment[0]}

" for comment in comments]) 23 | 24 | return render_template_string(''' 25 |

Deja un comentario

26 |
27 | 28 | 29 |
30 |

Comentarios:

31 | {} 32 | '''.format(comment_section)) 33 | 34 | @app.route('/add_comment', methods=['POST']) 35 | def add_comment(): 36 | content = escape(request.form.get('content')) # Sanitizamos el contenido para evitar XSS 37 | 38 | conn = sqlite3.connect('example.db') 39 | c = conn.cursor() 40 | c.execute('INSERT INTO comments (content) VALUES (?)', (content,)) 41 | conn.commit() 42 | conn.close() 43 | 44 | return redirect('/') 45 | 46 | if __name__ == '__main__': 47 | init_db() 48 | app.run(debug=True) 49 | -------------------------------------------------------------------------------- /02 - XSS/xss-stored.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from flask import Flask, request, render_template_string, redirect 3 | 4 | app = Flask(__name__) 5 | 6 | def init_db(): 7 | conn = sqlite3.connect('example.db') 8 | c = conn.cursor() 9 | c.execute('''CREATE TABLE IF NOT EXISTS comments (id INTEGER PRIMARY KEY, content TEXT)''') 10 | conn.commit() 11 | conn.close() 12 | 13 | @app.route('/') 14 | def index(): 15 | conn = sqlite3.connect('example.db') 16 | c = conn.cursor() 17 | c.execute('SELECT content FROM comments') 18 | comments = c.fetchall() 19 | conn.close() 20 | 21 | comment_section = ''.join([f"

{comment[0]}

" for comment in comments]) 22 | 23 | return render_template_string(''' 24 |

Deja un comentario

25 |
26 | 27 | 28 |
29 |

Comentarios:

30 | {} 31 | '''.format(comment_section)) 32 | 33 | @app.route('/add_comment', methods=['POST']) 34 | def add_comment(): 35 | content = request.form.get('content') 36 | 37 | # Vulnerabilidad de XSS Stored: No se sanitiza el contenido antes de guardarlo 38 | conn = sqlite3.connect('example.db') 39 | c = conn.cursor() 40 | c.execute('INSERT INTO comments (content) VALUES (?)', (content,)) 41 | conn.commit() 42 | conn.close() 43 | 44 | return redirect('/') 45 | 46 | if __name__ == '__main__': 47 | init_db() 48 | app.run(debug=True) 49 | 50 | # Referencias: https://r4z0r.gitbook.io/las-notas-de-r4z0r/web-app-pentest/cross-site-scripting-xss -------------------------------------------------------------------------------- /03 - SSRF/ssrf-fix.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from flask import Flask, request, abort 3 | from urllib.parse import urlparse 4 | 5 | app = Flask(__name__) 6 | 7 | # Lista blanca de dominios permitidos 8 | ALLOWED_DOMAINS = ["r4z0r.gitbook.io", "juankaenel.com"] 9 | 10 | def is_url_allowed(url): 11 | try: 12 | parsed_url = urlparse(url) 13 | domain = parsed_url.netloc 14 | return any(domain.endswith(allowed) for allowed in ALLOWED_DOMAINS) 15 | except Exception: 16 | return False 17 | 18 | @app.route('/fetch', methods=['GET', 'POST']) 19 | def fetch(): 20 | if request.method == 'GET': 21 | # Mostrar el formulario 22 | return ''' 23 |
24 |
25 |

SSRF Lab - Fix

26 |
27 |
28 |
29 |
30 |
31 | 32 |
33 |
34 |
35 | ''' 36 | 37 | if request.method == 'POST': 38 | url = request.form.get('url') 39 | 40 | if not is_url_allowed(url): 41 | abort(403, "URL no permitida") 42 | 43 | try: 44 | response = requests.get(url, timeout=5) 45 | return response.text 46 | except Exception as e: 47 | return f"Error obteniendo URL: {e}" 48 | 49 | if __name__ == '__main__': 50 | app.run(debug=True) 51 | -------------------------------------------------------------------------------- /03 - SSRF/ssrf.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from flask import Flask, request 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/fetch', methods=['GET', 'POST']) 7 | def fetch(): 8 | if request.method == 'GET': 9 | return ''' 10 |
11 |
12 |

SSRF Lab

13 |
14 |
15 |
16 |
17 |
18 | 19 |
20 |
21 |
22 | ''' 23 | 24 | if request.method == 'POST': 25 | url = request.form.get('url') 26 | try: 27 | response = requests.get(url) # Sin ningún tipo de validación, vulnerable a SSRF 28 | return response.text 29 | except Exception as e: 30 | return f"Error obteniendo una URL: {e}" 31 | 32 | if __name__ == '__main__': 33 | app.run(debug=True) 34 | 35 | # Visitar: http://127.0.0.1:5000/fetch y meter una url, la petición se hará internamente desde el servidor víctima. 36 | 37 | # Referencias: https://r4z0r.gitbook.io/las-notas-de-r4z0r/web-app-pentest/server-side-request-forgery-ssrf -------------------------------------------------------------------------------- /04 - RCE/rce-fix.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from flask import Flask, request 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/ejecutar', methods=['GET', 'POST']) 7 | def execute(): 8 | if request.method == 'GET': 9 | 10 | return ''' 11 |
12 |
13 |

RCE Lab - Fix

14 |
15 |
16 |
17 |
18 |
19 | 20 |
21 |
22 |
23 | ''' 24 | # Lista blanca de comandos 25 | ALLOWED_COMMANDS = ['whoami','id'] 26 | if request.method == 'POST': 27 | command = request.form.get('command').strip() 28 | 29 | # Solo permite 'whoami' y 'id' 30 | try: 31 | 32 | if command in ALLOWED_COMMANDS: 33 | result = subprocess.check_output(command, shell=True).decode() 34 | else: 35 | return "
Comando no permitido
" 36 | return f"
{result}
" 37 | except Exception as e: 38 | return f"Error ejecutando el comando: {e}" 39 | 40 | if __name__ == '__main__': 41 | app.run(debug=True) -------------------------------------------------------------------------------- /04 - RCE/rce.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from flask import Flask, request 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/ejecutar', methods=['GET', 'POST']) 7 | def execute(): 8 | if request.method == 'GET': 9 | return ''' 10 |
11 |
12 |

RCE Lab

13 |
14 |
15 |
16 |
17 |
18 | 19 |
20 |
21 |
22 | ''' 23 | 24 | if request.method == 'POST': 25 | try: 26 | command = request.form.get('command') 27 | result = subprocess.check_output(command, shell=True).decode() 28 | return f"
{result}
" 29 | except Exception as e: 30 | return f"Error ejecutando el comando: {e}" 31 | 32 | if __name__ == '__main__': 33 | app.run(debug=True) -------------------------------------------------------------------------------- /05 - IDOR/idor-fix.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app = Flask(__name__) 4 | 5 | users = { 6 | '1': {'username': 'R4z0r', 'email': 'razor@codigovulnerable.xyz', 'dni': '30.000.000', 'phone': '3712112233', 'address': 'Calle Falsa 123, Buenos Aires'}, 7 | '2': {'username': 'Uzumaki Naruto', 'email': 'naruto@codigovulnerable.xyz', 'dni': '30.000.001', 'phone': '3712112234', 'address': 'Avenida Siempreviva 742, Buenos Aires'}, 8 | '3': {'username': 'Rock Lee', 'email': 'rocklee@codigovulnerable.xyz', 'dni': '30.000.002', 'phone': '3712112235', 'address': 'Calle Los Álamos 456, Buenos Aires'}, 9 | '4': {'username': 'Gaara de la Arena', 'email': 'gaara@codigovulnerable.xyz', 'dni': '30.000.003', 'phone': '3712112236', 'address': 'Pasaje del Sol 789, Buenos Aires'}, 10 | '5': {'username': 'Uchiha Sasuke', 'email': 'sasuke@codigovulnerable.xyz', 'dni': '30.000.004', 'phone': '3712112237', 'address': 'Boulevard de los Sueños Rotos 101, Buenos Aires'} 11 | } 12 | 13 | @app.route('/users/', methods=['GET']) 14 | def profile(user_id): 15 | # Simulación del usuario autenticado (en un caso real, se obtendría de la cookie de sesión o token. 16 | current_user_id = '1' # Usuario autenticado: R4z0r 17 | 18 | if user_id == current_user_id: 19 | user_data = users[user_id] 20 | return f"

Perfil de {user_data['username']}

Email: {user_data['email']}
DNI: {user_data['dni']}
Teléfono: {user_data['phone']}
Dirección: {user_data['address']}

Estás autenticado como *R4z0r*

" 21 | else: 22 | return "

Acceso denegado. No tienes permiso para ver este perfil.

" 23 | 24 | if __name__ == '__main__': 25 | app.run(debug=True) -------------------------------------------------------------------------------- /05 - IDOR/idor.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app = Flask(__name__) 4 | 5 | users = { 6 | '1': {'username': 'R4z0r', 'email': 'razor@codigovulnerable.xyz', 'dni': '30.000.000', 'phone': '3712112233', 'address': 'Calle Falsa 123, Buenos Aires'}, 7 | '2': {'username': 'Uzumaki Naruto', 'email': 'naruto@codigovulnerable.xyz', 'dni': '30.000.001', 'phone': '3712112234', 'address': 'Avenida Siempreviva 742, Buenos Aires'}, 8 | '3': {'username': 'Rock Lee', 'email': 'rocklee@codigovulnerable.xyz', 'dni': '30.000.002', 'phone': '3712112235', 'address': 'Calle Los Álamos 456, Buenos Aires'}, 9 | '4': {'username': 'Gaara de la Arena', 'email': 'gaara@codigovulnerable.xyz', 'dni': '30.000.003', 'phone': '3712112236', 'address': 'Pasaje del Sol 789, Buenos Aires'}, 10 | '5': {'username': 'Uchiha Sasuke', 'email': 'sasuke@codigovulnerable.xyz', 'dni': '30.000.004', 'phone': '3712112237', 'address': 'Boulevard de los Sueños Rotos 101, Buenos Aires'} 11 | } 12 | 13 | @app.route('/users/', methods=['GET']) 14 | def profile(user_id): 15 | 16 | if user_id in users: 17 | user_data = users[user_id] 18 | return f"

Perfil de {user_data['username']}

Email: {user_data['email']}
DNI: {user_data['dni']}
Teléfono: {user_data['phone']}
Dirección: {user_data['address']}

Estás autenticado como *R4z0r*

" 19 | else: 20 | return "

Usuario no encontrado

" 21 | 22 | if __name__ == '__main__': 23 | app.run(debug=True) 24 | 25 | # Referencias: https://r4z0r.gitbook.io/las-notas-de-r4z0r/web-app-pentest/insecure-direct-object-reference-idor -------------------------------------------------------------------------------- /06 - Directory Traversal/directorytraversal-fix.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, send_file 2 | import os 3 | 4 | app = Flask(__name__) 5 | 6 | BASE_DIR = "./files" 7 | 8 | @app.route('/download', methods=['GET']) 9 | def download_file(): 10 | filename = request.args.get('file') 11 | try: 12 | file_path = os.path.abspath(os.path.join(BASE_DIR, os.path.normpath(filename))) 13 | # Validar que el archivo esté dentro del directorio permitido y no permitir caracteres maliciosos para Path Traversal 14 | if not file_path.startswith(os.path.abspath(BASE_DIR)) or filename.startswith('/') or '//' in filename or '\\' in filename or '%' in filename or '..' in os.path.normpath(filename): 15 | return "

Acceso denegado. Ruta no permitida.

" 16 | return send_file(file_path) 17 | except FileNotFoundError: 18 | return "

Archivo no encontrado, prueba con file1.txt

" 19 | except Exception as e: 20 | return f"

Error: {e}

" 21 | 22 | if __name__ == '__main__': 23 | os.makedirs(BASE_DIR, exist_ok=True) 24 | with open(os.path.join(BASE_DIR, 'file1.txt'), 'w') as f: 25 | f.write("Contenido del archivo 1") 26 | with open(os.path.join(BASE_DIR, 'file2.txt'), 'w') as f: 27 | f.write("Contenido del archivo 2") 28 | app.run(debug=True) 29 | 30 | -------------------------------------------------------------------------------- /06 - Directory Traversal/directorytraversal.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, send_file 2 | import os 3 | 4 | app = Flask(__name__) 5 | 6 | BASE_DIR = "./files" 7 | 8 | @app.route('/download', methods=['GET']) 9 | def download_file(): 10 | filename = request.args.get('file') 11 | # No se realiza ninguna validación sobre el contenido de 'filename' 12 | try: 13 | return send_file(f'./files/{filename}') 14 | except FileNotFoundError: 15 | return "

Archivo no encontrado

" 16 | except Exception as e: 17 | return f"

Error: {e}

" 18 | 19 | if __name__ == '__main__': 20 | os.makedirs(BASE_DIR, exist_ok=True) 21 | # Crear archivos de ejemplo 22 | with open(os.path.join(BASE_DIR, 'file1.txt'), 'w') as f: 23 | f.write("Contenido del archivo 1") 24 | with open(os.path.join(BASE_DIR, 'file2.txt'), 'w') as f: 25 | f.write("Contenido del archivo 2") 26 | app.run(debug=True) 27 | 28 | 29 | # En este caso, la vulnerabilidad es un Directory Traversal, ya que el usuario puede manipular el parámetro 'file' para acceder a archivos fuera del directorio permitido. 30 | # Si ejecutamos: http://127.0.0.1:5000/download?file=../../../../../../etc/passwd, nos descargaremos el archivo /etc/passwd ya que no se está validando lo que el usuario ingresa 31 | 32 | # Referencias: https://r4z0r.gitbook.io/las-notas-de-r4z0r/web-app-pentest/directory-traversal-path-traversal -------------------------------------------------------------------------------- /06 - Directory Traversal/files/file1.txt: -------------------------------------------------------------------------------- 1 | Contenido del archivo 1 -------------------------------------------------------------------------------- /06 - Directory Traversal/files/file2.txt: -------------------------------------------------------------------------------- 1 | Contenido del archivo 2 -------------------------------------------------------------------------------- /07 - XXE/xxe-fix.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request 2 | from lxml import etree 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/xml', methods=['POST']) 7 | def xml_parser(): 8 | try: 9 | xml_data = request.data.decode() 10 | # Si no se resuelven entidades externas, no se puede hacer XXE, a menos que se pueda utilizar XInclude, cosa que acá no está habilitada 11 | parser = etree.XMLParser(resolve_entities=False) 12 | root = etree.fromstring(xml_data, parser) 13 | response = f"

Root tag: {root.tag}

" 14 | return response 15 | except etree.XMLSyntaxError as e: 16 | return f"Error al procesar XML: {str(e)}" 17 | 18 | if __name__ == '__main__': 19 | app.run(debug=True) 20 | -------------------------------------------------------------------------------- /07 - XXE/xxe.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request 2 | from lxml import etree 3 | 4 | app = Flask(__name__) 5 | 6 | @app.route('/xml', methods=['POST']) 7 | def xml_parse(): 8 | xml_data = request.data 9 | try: 10 | # Se está resolviendo entidades externas, lo que permite XXE 11 | parser = etree.XMLParser(resolve_entities=True) 12 | tree = etree.fromstring(xml_data, parser) 13 | return f"XML: {etree.tostring(tree, pretty_print=True).decode()}" 14 | except etree.XMLSyntaxError as e: 15 | return f"Error al procesar XML: {str(e)}" 16 | 17 | if __name__ == '__main__': 18 | app.run(debug=True) 19 | 20 | # Intenta con: curl -X POST -H "Content-Type: application/xml" -d ']>&xxe;' http://127.0.0.1:5000/xml ─╯ 21 | -------------------------------------------------------------------------------- /08 - SSTI/ssti-fix.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, render_template_string 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route('/saludar', methods=['GET', 'POST']) 6 | def greet(): 7 | if request.method == 'GET': 8 | return ''' 9 |

Saludar al Usuario

10 |
11 |
12 |

13 | 14 |
15 | ''' 16 | 17 | if request.method == 'POST': 18 | name = request.form.get('name') 19 | template = "Hola, {{ name }}!" 20 | return render_template_string(template, name=name) 21 | 22 | if __name__ == '__main__': 23 | app.run(debug=True) 24 | 25 | '''En Jinja2, la sintaxis {{ name }} es un placeholder seguro que representa una variable pasada a la plantilla. 26 | En el código anterior, template = f"Hola, {name}!" permitía que el valor de name se inyectara directamente en la plantilla, lo cual es peligroso, ya que permitía ejecutar código malicioso. 27 | En el código corregido, "Hola, {{ name }}!" es una plantilla segura donde {{ name }} es reemplazado con el valor de name de manera controlada y escapada, evitando así la ejecución de código malicioso.''' 28 | 29 | -------------------------------------------------------------------------------- /08 - SSTI/ssti.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, render_template_string 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route('/saludar', methods=['GET', 'POST']) 6 | def greet(): 7 | if request.method == 'GET': 8 | return ''' 9 |

Saludar al Usuario

10 |
11 |
12 |

13 | 14 |
15 | ''' 16 | 17 | if request.method == 'POST': 18 | name = request.form.get('name') 19 | # Vulnerable a SSTI 20 | template = f"Hola, {name}!" 21 | return render_template_string(template) 22 | 23 | if __name__ == '__main__': 24 | app.run(debug=True) 25 | 26 | # Intenta con: curl -X POST -d "name={{ config }}" http://127.0.0.1:5000/saludar -------------------------------------------------------------------------------- /09 - LFI/lfi-fix.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, render_template_string, send_file 2 | import os 3 | 4 | app = Flask(__name__) 5 | BASE_DIR = "./templates" 6 | 7 | @app.route('/view', methods=['GET']) 8 | def view_file(): 9 | filename = request.args.get('file', 'index.html') 10 | try: 11 | file_path = os.path.abspath(os.path.join(BASE_DIR, filename)) 12 | # Validamos que el archivo esté dentro del directorio permitido y no permitir caracteres maliciosos para que use Path Traversal 13 | if not file_path.startswith(os.path.abspath(BASE_DIR)) or '..' in filename or '%' in filename or filename.startswith('/'): 14 | return "

Acceso denegado. Ruta no permitida.

" 15 | 16 | with open(file_path, 'r') as file: 17 | content = file.read() 18 | return content 19 | except FileNotFoundError: 20 | return "

Archivo no encontrado

" 21 | except Exception as e: 22 | return f"

Error: {e}

" 23 | 24 | if __name__ == '__main__': 25 | os.makedirs(BASE_DIR, exist_ok=True) 26 | # Crear archivos de ejemplo 27 | with open(os.path.join(BASE_DIR, 'index.html'), 'w') as f: 28 | f.write("

Inicio

Esta es la página de de Inicio.

") 29 | with open(os.path.join(BASE_DIR, 'contact.html'), 'w') as f: 30 | f.write("

Contacto

Esta es la página de Contacto.

") 31 | app.run(debug=True) 32 | -------------------------------------------------------------------------------- /09 - LFI/lfi.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, render_template_string, send_file 2 | import os 3 | 4 | app = Flask(__name__) 5 | BASE_DIR = "./templates" 6 | 7 | @app.route('/view', methods=['GET']) 8 | def view_file(): 9 | filename = request.args.get('file') 10 | # Vulnerable a LFI - Permite cargar archivos locales sin validación 11 | try: 12 | file_path = os.path.join(BASE_DIR, filename if filename else 'index.html') 13 | with open(file_path, 'r') as file: 14 | content = file.read() 15 | return render_template_string(content) 16 | except FileNotFoundError: 17 | return "

Archivo no encontrado

" 18 | except Exception as e: 19 | return f"

Error: {e}

" 20 | 21 | if __name__ == '__main__': 22 | os.makedirs(BASE_DIR, exist_ok=True) 23 | # Crear archivos de ejemplo 24 | with open(os.path.join(BASE_DIR, 'index.html'), 'w') as f: 25 | f.write("

Inicio

Esta es la página de de Inicio.

") 26 | with open(os.path.join(BASE_DIR, 'contact.html'), 'w') as f: 27 | f.write("

Contacto

Esta es la página de Contacto.

") 28 | app.run(debug=True) 29 | 30 | # Intenta con http://127.0.0.1:5000/view?file=../../../../../etc/passwd 31 | # Recuerda que al ser LFI, a diferencia de un Path Traversal aquí puedes cargar y ejecutar archivos locales, en un Path Traversal solo puedes leer archivos locales. 32 | 33 | # Referencias: https://r4z0r.gitbook.io/las-notas-de-r4z0r/web-app-pentest/local-file-inclusion-lfi -------------------------------------------------------------------------------- /09 - LFI/templates/contact.html: -------------------------------------------------------------------------------- 1 |

Contacto

Esta es la página de Contacto.

-------------------------------------------------------------------------------- /09 - LFI/templates/index.html: -------------------------------------------------------------------------------- 1 |

Inicio

Esta es la página de de Inicio.

-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InsecureCodeLab 2 | 3 | Laboratorios prácticos para realizar Secure Code Review en Python. Este proyecto explora no solo vulnerabilidades comunes del OWASP Top Ten, sino también muchas otras que no se incluyen en este TOP. Todo esto, mediante aplicaciones diseñadas intencionalmente inseguras. Cada código vulnerable incluye una solución propuesta, pero se recomienda que primero intentes desarrollar tu propia remediación antes de revisar las soluciones proporcionadas. Recuerda, no hay un único camino para mitigar estas vulnerabilidades; existen múltiples enfoques para resolver cada problema. 4 | 5 | --- 6 | 7 | ## Requisitos: 8 | 9 | - Python 3.10 o superior 10 | - Virtualenv (opcional, pero recomendado) 11 | - Flask 12 | 13 | --- 14 | 15 | ## Instalación: 16 | 17 | ```bash 18 | git clone https://github.com/medicenr4z0r/InsecureCodeLab.git 19 | 20 | cd InsecureCodeLab 21 | 22 | python3 -m venv venv 23 | 24 | source venv/bin/activate # Linux/Mac 25 | .\venv\Scripts\activate # Windows 26 | 27 | pip install -r requirements.txt 28 | ``` 29 | --- 30 | ## Cómo ejecutar los laboratorios: 31 | Cada laboratorio está desarrollado en un archivo independiente. Para ejecutar un laboratorio específico, simplemente corre el archivo correspondiente. Por ejemplo: 32 | 33 | ```bash 34 | python3 sqli.py 35 | ``` 36 | --- 37 | ## Objetivo 38 | 39 | - Identificar vulnerabilidades en aplicaciones. 40 | - Comprender cómo funcionan las vulnerabilidades. 41 | - Aprender a mitigar y corregir cada vulnerabilidad. 42 | --- 43 | 44 | ## Advertencia 45 | 46 | Este proyecto es únicamente para fines educativos y no debe ser utilizado en entornos de producción o sistemas no autorizados. 47 | 48 | --- 49 | 50 | ### Aporta o aparta 51 | 52 | Tu apoyo sería un gran favor a la comunidad, estamos acá para dejar un mundo mejor, acuérdate de tu yo de hace 5 años, siempre podemos ayudar a los demás, stay hack. 53 | 54 | Con amor ~ R4z0r. 55 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | blinker==1.9.0 2 | certifi==2025.4.26 3 | charset-normalizer==3.4.2 4 | click==8.1.8 5 | Flask==3.1.0 6 | idna==3.10 7 | itsdangerous==2.2.0 8 | Jinja2==3.1.6 9 | lxml==5.4.0 10 | MarkupSafe==3.0.2 11 | requests==2.32.3 12 | urllib3==2.4.0 13 | Werkzeug==3.1.3 14 | --------------------------------------------------------------------------------