¿Por qué incluso los desarrolladores de Go caen en las trampas de los hackers?
\n\nGo es famoso por su velocidad, simplicidad y herramientas de seguridad integradas. Pero consolarse con la frase «escribo en Go, por lo tanto, estoy seguro» es una ilusión peligrosa. Las inyecciones SQL y los ataques XSS no son reliquias del pasado. Todavía hoy derriban sitios web y filtran bases de datos a diario, y Go no es una excepción.
\n\nEl problema es que Go le brinda al desarrollador herramientas potentes, pero no las usa por usted. Puede accidentalmente concatenar una consulta SQL usando fmt.Sprintf o mostrar un comentario de usuario sin escape, y un atacante obtendrá acceso a su base de datos o robará las cookies de los visitantes.
En este artículo, analizaremos los dos tipos de ataques más peligrosos: inyecciones SQL y Cross-Site Scripting (XSS), y mostraremos cómo prevenirlos usando Go nativo y bibliotecas populares. Obtendrá no solo teoría, sino plantillas de código listas para usar que puede incorporar a su proyecto hoy mismo.
\n\nInyecciones SQL en Go: cómo surgen y cómo eliminarlas de raíz
\n\nError típico: concatenación de cadenas
\n\nImagine que tiene un endpoint para obtener un usuario por ID. La forma más ingenua (y peligrosa) de escribir la consulta es así:
\n\npackage main\n\nimport (\n "database/sql"\n "fmt"\n "log"\n _ "github.com/lib/pq"\n)\n\nfunc getUserByID(db *sql.DB, userID string) {\n // ¡PELIGROSO! Concatenación de cadenas\n query := fmt.Sprintf("SELECT id, name, email FROM users WHERE id = '%s'", userID)\n rows, err := db.Query(query)\n if err != nil {\n log.Fatal(err)\n }\n defer rows.Close()\n // ... procesamiento\n}\n\nSi un atacante pasa la cadena 1' OR '1'='1 en userID, la consulta se convierte en:
SELECT id, name, email FROM users WHERE id = '1' OR '1'='1'\n\n¡Voilà! Obtendrá todos los usuarios de la base de datos. Y si ingresa 1'; DROP TABLE users; --, perderá la tabla.
Solución: consultas parametrizadas (sentencias preparadas)
\n\nEl controlador de Go para PostgreSQL, MySQL y SQLite admite sentencias preparadas. Estas separan el código SQL de los datos, y el controlador escapa automáticamente los caracteres peligrosos.
\n\nfunc getUserByIDSafe(db *sql.DB, userID string) {\n // SEGURO: consulta parametrizada\n query := "SELECT id, name, email FROM users WHERE id = $1"\n rows, err := db.Query(query, userID)\n if err != nil {\n log.Fatal(err)\n }\n defer rows.Close()\n // ... procesamiento\n}\n\nObserve el $1 — es un marcador de posición para el primer argumento. En MySQL se usa el símbolo ?. Ahora, incluso si userID contiene 1' OR '1'='1, el controlador lo tratará como un valor de cadena, no como parte del código SQL.
Trabajo con el paquete sqlx
\n\nLa biblioteca github.com/jmoiron/sqlx hace que el código sea aún más limpio. Admite parámetros con nombre y escaneo automático a estructuras:
import "github.com/jmoiron/sqlx"\n\ntype User struct {\n ID int `db:"id"`\n Name string `db:"name"`\n Email string `db:"email"`\n}\n\nfunc getUserByIDSqlx(db *sqlx.DB, userID string) (*User, error) {\n user := &User{}\n // Parámetro con nombre :id\n err := db.Get(user, "SELECT * FROM users WHERE id = :id", sqlx.Named("id", userID))\n if err != nil {\n return nil, err\n }\n return user, nil\n}\n\nImportante: incluso si usa un ORM como GORM o Ent, asegúrese de que esté configurado para usar sentencias preparadas. Algunas versiones antiguas de ORM permitían consultas «en bruto».
\n\nXSS en Go: cuando su sitio web se dispara en el pie
\n\nCómo ocurre un ataque XSS
\n\nXSS (Cross-Site Scripting) ocurre cuando inserta contenido de usuario en una página sin escape. Un