Cómo prevenir inyección SQL en ColdFusion usando cfqueryparam correctamente

Programador revisando código ColdFusion seguro contra inyección SQL
Foto: Jakub Żerdzicki / Unsplash

Para prevenir la inyección SQL en ColdFusion, la técnica correcta es envolver cada valor variable de tu consulta con la etiqueta <cfqueryparam>, especificando siempre el cfsqltype adecuado (como cf_sql_integer o cf_sql_varchar). Esto obliga al motor CFML a tratar el valor como un parámetro enlazado y nunca como texto SQL ejecutable, bloqueando cualquier intento de manipular la consulta. En este tutorial vas a aprender, paso a paso, cómo aplicar cfqueryparam correctamente para prevenir inyección SQL en ColdFusion, incluyendo casos con listas, LIKE y columnas dinámicas.

¿Por qué una consulta sin cfqueryparam es vulnerable?

Cuando concatenas directamente un valor recibido del usuario dentro del SQL, ese valor se ejecuta tal cual llega. Un atacante solo necesita cerrar la cadena de texto e inyectar su propio código SQL.

<!--- VULNERABLE: nunca hagas esto --->
<cfquery name="qUsuario" datasource="miDSN">
  SELECT id, nombre
  FROM usuarios
  WHERE email = '#form.email#'
</cfquery>

Si form.email llega como ' OR '1'='1, la condición se vuelve siempre verdadera y el atacante puede leer registros que no le corresponden, o incluso encadenar sentencias destructivas según el motor de base de datos.

¿Cómo se previene la inyección SQL en ColdFusion con cfqueryparam?

El proceso es siempre el mismo, sin importar qué tan simple o compleja sea tu consulta:

  1. Identifica todos los valores dinámicos de la consulta (los que vienen de form, url, session o cualquier fuente externa).
  2. Envuelve cada uno de esos valores con <cfqueryparam> en lugar de concatenarlos como texto.
  3. Define el atributo cfsqltype correcto según el tipo de dato de la columna.
  4. Agrega maxlength en campos de texto para limitar el tamaño esperado.
  5. Prueba la consulta enviando caracteres peligrosos (comillas simples, --, ;) para confirmar que el motor los trata como texto plano y no rompe la sentencia.
<!--- SEGURO: valor parametrizado --->
<cfquery name="qUsuario" datasource="miDSN">
  SELECT id, nombre
  FROM usuarios
  WHERE email = 
    <cfqueryparam value="#form.email#" cfsqltype="cf_sql_varchar" maxlength="150">
</cfquery>

Con este cambio, el valor de email nunca se interpreta como parte del SQL: el motor lo envía como parámetro enlazado a la base de datos, sin importar qué caracteres contenga.

¿Qué tipos de cfsqltype existen y cuándo usarlos?

Elegir el cfsqltype correcto no es solo buena práctica de seguridad, también evita errores de conversión de tipos:

  • cf_sql_varchar: texto corto (nombres, correos, códigos).
  • cf_sql_longvarchar: texto largo (descripciones, comentarios).
  • cf_sql_integer: IDs numéricos, cantidades, contadores.
  • cf_sql_decimal o cf_sql_double: montos y valores con decimales.
  • cf_sql_date / cf_sql_timestamp: fechas y fechas con hora.
  • cf_sql_bit: valores booleanos (activo/inactivo).

Si trabajas frecuentemente con identificadores numéricos como llaves primarias, te conviene revisar también nuestra guía de mejores prácticas con IDs en bases de datos, que complementa este tutorial con el diseño correcto del esquema, más allá de la parametrización de la consulta.

¿Cómo usar cfqueryparam dentro de una cláusula IN con una lista?

Cuando necesitas filtrar por varios valores a la vez, usa el atributo list="yes":

<cfquery name="qProductos" datasource="miDSN">
  SELECT id, nombre
  FROM productos
  WHERE categoria_id IN (
    <cfqueryparam value="#url.categorias#" cfsqltype="cf_sql_integer" list="true">
  )
</cfquery>

Aquí url.categorias es una lista separada por comas (por ejemplo 3,7,12). ColdFusion arma automáticamente los parámetros individuales, uno por cada elemento de la lista.

¿Cómo aplicar cfqueryparam en búsquedas con LIKE?

El truco es construir el comodín % antes de pasarlo al parámetro, no dentro del SQL:

<cfset busqueda = "%" & form.termino & "%">
<cfquery name="qBusqueda" datasource="miDSN">
  SELECT id, nombre
  FROM productos
  WHERE nombre LIKE 
    <cfqueryparam value="#busqueda#" cfsqltype="cf_sql_varchar">
</cfquery>

De esta forma el operador LIKE sigue funcionando con comodines, pero el valor completo sigue estando parametrizado y protegido.

¿Qué hacer con nombres de tabla o columna dinámicos?

cfqueryparam solo protege valores, nunca puede parametrizar nombres de tablas o columnas. Si tu aplicación arma el nombre de una tabla o columna dinámicamente, la única defensa es validar contra una lista blanca fija en el código:

<cfset columnasPermitidas = "nombre,fecha_creacion,estado">
<cfif not listFindNoCase(columnasPermitidas, url.orden)>
  <cfset url.orden = "nombre">
</cfif>

Nunca aceptes el nombre de columna directamente del usuario sin compararlo contra una lista de valores conocidos y seguros.

¿Sigue siendo relevante cfqueryparam en 2026 con IA integrada en ColdFusion?

Con Adobe ColdFusion 2025 Update 8 llegó un framework de IA nativo con soporte para LLMs (OpenAI, Claude, Gemini, Mistral, Azure, Ollama local), RAG con vector stores y protocolo MCP, además de mejoras de seguridad como passkeys y hashing con Argon2. Ninguna de estas novedades reemplaza la necesidad de cfqueryparam: si un flujo de IA genera consultas SQL de forma dinámica a partir de texto libre del usuario, el riesgo de inyección es igual o mayor, y la parametrización sigue siendo tu primera línea de defensa. Lo mismo aplica si migras partes de tu stack a BoxLang con ColdBox 8: la sintaxis puede variar, pero el principio de nunca concatenar valores externos en SQL se mantiene igual.

Preguntas frecuentes

¿cfqueryparam afecta el rendimiento de mis consultas?

No lo perjudica; al contrario, muchos motores de base de datos reutilizan el plan de ejecución de consultas parametrizadas, lo que puede mejorar el rendimiento en consultas repetitivas.

¿Necesito usar cfqueryparam incluso en aplicaciones internas sin acceso público?

Sí. Las amenazas internas, errores de otros sistemas conectados o futuras exposiciones accidentales a internet son razones suficientes para parametrizar siempre.

¿cfqueryparam protege contra todos los tipos de ataque?

Protege específicamente contra inyección SQL en los valores de la consulta. No protege contra XSS, CSRF ni contra nombres de tabla/columna inyectados dinámicamente; esos requieren otras defensas.

¿Qué pasa si olvido el atributo cfsqltype?

ColdFusion intentará inferir el tipo, pero puede fallar o generar errores de conversión. Siempre declara el cfsqltype de forma explícita.

Si quieres dominar estas técnicas de seguridad junto con los fundamentos completos de desarrollo en CFML, el curso Aprende a Desarrollar en Adobe Coldfusion en Udemy es el siguiente paso lógico para profundizar con ejercicios guiados.