├── 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------