22/12/2013

Como solucionar la limitación del max_input_vars

max_input_vars

Josep Sanz

Desde PHP 5.3.9, se ha añadido una directiva de configuracion max_input_vars que limita el número de variables que pueden llegar por $_GET, $_POST y $_COOKIE. Esto a llevado a que aplicaciones que emplean formularios con muchos inputs tipo checkbox / hidden / text, puedan tener problemas al perder datos en la recepción desde el servidor.

Esta directiva, básicamente, mitiga la posibilidad de ataques de denegación de servicio que utilizan colisiones de hash. Por lo tanto, puede pasar que en algunos hostings exista esta variable y este limitada a su valor por defecto (1000) y no tengamos opción de aumentar el valor. Si esto sucede, se deberá o bien:
  • Replantear la aplicación para no superar el número de variables máximas (algo realmente costoso en algunos casos)
  • Usar la técnica que a continuación voy a describir que solucionará este problema de forma transparente sin necesidad de tocar nada en el lado del servidor.
La técnica consiste en anticiparse al servidor, es decir, si conocemos de antemano la limitación que puede tener el servidor que va a recibir el formulario con las más de N variables, podemos hacer lo siguiente:

1) Desde un php, informar al cliente de el valor que tiene la variable max_input_vars:

<?php
function ini_get_max_input_vars() {
	return '".intval(ini_get("max_input_vars"))."';
}
?>

2) Desde el cliente, antes de enviar el formulario, añadir este código:

<script>
var max_input_vars=ini_get_max_input_vars();
if(max_input_vars>0) {
	var total_input_vars=$("input,select,textarea",jqForm).length;
	if(total_input_vars>max_input_vars) {
		var fix_input_vars=new Array();
		$("input[type=checkbox]:not(:checked):not(:visible)",jqForm).each(function() {
			if(total_input_vars>=max_input_vars) {
				$(this).remove();
				total_input_vars--;
			}
		});
		$("input[type=checkbox]:checked:not(:visible)",jqForm).each(function() {
			if(total_input_vars>=max_input_vars) {
				var temp=$(this).attr("name")+"="+rawurlencode($(this).val());
				fix_input_vars.push(temp);
				$(this).remove();
				total_input_vars--;
			}
		});
		$("input[type=hidden]",jqForm).each(function() {
			if(total_input_vars>=max_input_vars) {
				var temp=$(this).attr("name")+"="+rawurlencode($(this).val());
				fix_input_vars.push(temp);
				$(this).remove();
				total_input_vars--;
			}
		});
		fix_input_vars=base64_encode(utf8_encode(implode("&",fix_input_vars)));
		$(jqForm).append("<input type='hidden' name='fix_input_vars' value='"+fix_input_vars+"'/>");
	}
}
</script>

3) Desde el php que ha de recibir los datos, añadir este código antes de empezar a procesar la petición:

<?php
if(intval(ini_get("max_input_vars"))>0) {
	$temp=getParam("fix_input_vars");
	if($temp!="") {
		$temp=querystring2array(base64_decode($temp));
		if(isset($_GET["fix_input_vars"])) {
			unset($_GET["fix_input_vars"]);
			$_GET=array_merge($_GET,$temp);
		}
		if(isset($_POST["fix_input_vars"])) {
			unset($_POST["fix_input_vars"]);
			$_POST=array_merge($_POST,$temp);
		}
	}
}
?>

Notas:
  • El concepto es:
    • Informar del valor max_input_vars del servidor
    • Desde el cliente, proceder a reducir las variables si es necesario hasta que cumpla la condición que el número de variables sea inferior al máximo.
    • De esta manera, se podrá añadir una variable auxiliar que contendrá a las otras variables embedded
    • Desde la recepción, detectar ese caso y hacer el paso inverso para dejarlo todo tal como tiene que estar
  • En el fragmento de código 2:
  • En el fragmento de código 3:
    • Las funciones getParam y querystring2array son proporcionadas por el proyecto SaltOS (véase el fichero php/strutils.php)
  • Para más info, podeis mirar el código fuente de SaltOS que ya incorpora este trick.

Líneas de XML
60,895
Líneas de PHP
18,637
Líneas de JS
11,620
Líneas de XSLT
2,498
Líneas de CSV
1,919
Líneas de CSS
577