Prefacio: por qué la seguridad no es una opción, sino una higiene básica
\n\nImagine: está escribiendo un servicio web genial en C#. Todo funciona perfectamente: los usuarios se registran, dejan comentarios, buscan productos. Y luego llega el "hacker Vasya" (que en realidad solo copió un script de internet) y se roba toda la base de datos. O, peor aún, inserta en su página un script que roba las cookies de todos los visitantes.
\n\n¿Suena como el guión de una película de terror? Pues esto ocurre todos los días. Según las estadísticas de OWASP, las inyecciones (SQL, XSS, LDAP) se encuentran entre las tres vulnerabilidades más peligrosas de las aplicaciones web. Y C#, con toda su fiabilidad, no le otorga un escudo de inmunidad por defecto.
\n\nEn este artículo, analizaremos los dos ataques más candentes: inyecciones SQL y XSS (Cross-Site Scripting). No solo obtendrá teoría, sino técnicas de defensa prácticas en C# con ejemplos de código reales. ¡Allá vamos!
\n\nInyecciones SQL: cuando el usuario se convierte en hacker
\n\nCómo se ve en la práctica
\n\nSupongamos que tiene un inicio de sesión simple. El usuario ingresa su nombre de usuario y contraseña, y usted forma la consulta así:
\n\nstring login = txtLogin.Text;\nstring password = txtPassword.Text;\nstring query = "SELECT * FROM Users WHERE Login = '" + login + "' AND Password = '" + password + "'";\nSqlCommand cmd = new SqlCommand(query, connection);\n\n¿Parece lógico? ¡Error! Si un atacante ingresa en el campo de usuario: ' OR 1=1 --, la consulta se convierte en:
SELECT * FROM Users WHERE Login = '' OR 1=1 --' AND Password = '...'\n\nTodo lo que está después de -- es un comentario. La condición 1=1 siempre es verdadera. La consulta devolverá todos los usuarios y la autenticación se habrá completado. ¿Y si en lugar del comentario inserta DROP TABLE Users? Adiós, base de datos.
Protección n.º 1: consultas parametrizadas (su principal escudo)
\n\nLa mejor protección contra las inyecciones SQL es nunca concatenar cadenas manualmente. Use parámetros. El servidor SQL escapará los caracteres peligrosos por sí mismo, y su base de datos recibirá los datos como valores, no como código.
\n\nstring login = txtLogin.Text;\nstring password = txtPassword.Text;\n\nstring query = "SELECT * FROM Users WHERE Login = @Login AND Password = @Password";\n\nusing (SqlConnection conn = new SqlConnection(connectionString))\n{\n SqlCommand cmd = new SqlCommand(query, conn);\n cmd.Parameters.AddWithValue("@Login", login);\n cmd.Parameters.AddWithValue("@Password", password);\n \n conn.Open();\n SqlDataReader reader = cmd.ExecuteReader();\n // procesamiento del resultado\n}\n\nObserve: @Login y @Password son parámetros. Incluso si el usuario ingresa '; DROP TABLE Users; --, el DBMS lo interpretará como una cadena normal, no como código. Seguro y elegante.
Protección n.º 2: procedimientos almacenados
\n\nOtra herramienta potente son los procedimientos almacenados. No solo protegen contra inyecciones, sino que también mejoran el rendimiento (los planes de consulta se almacenan en caché).
\n\n-- SQL Server\nCREATE PROCEDURE GetUserByLogin\n @Login NVARCHAR(50)\nAS\nBEGIN\n SELECT * FROM Users WHERE Login = @Login\nEND\n\n// Código C#\nusing (SqlConnection conn = new SqlConnection(connectionString))\n{\n SqlCommand cmd = new SqlCommand("GetUserByLogin", conn);\n cmd.CommandType = CommandType.StoredProcedure;\n cmd.Parameters.AddWithValue("@Login", inputLogin);\n \n conn.Open();\n // ...\n}\n\nProtección n.º 3: Entity Framework y ORM
\n\nSi usa Entity Framework (o cualquier otro ORM), ya está parcialmente protegido. EF genera consultas parametrizadas automáticamente. Pero hay un detalle: las consultas SQL sin procesar a través de EF (método FromSqlRaw) son la misma concatenación. ¡Esté atento!
// MALO:\nvar users = context.Users.FromSqlRaw("SELECT * F