jhc_phpids: PHPIDs para Textpattern

PHPIDS for Textpattern

Recientemente, en concreto el 28/08/2010, se descubrió una vulnerabilidad en nuestro querido Textpattern, a partir de ahora TXP por economía del lenguaje, que supuestamente permitía incluir ficheros externos y ejecutarlos (una definición de andar por casa para el término ‘Remote File Inclusion Vulnerability’ o RFI). Como siempre que sale una vulnerabilidad en una aplicación Web no tardan en aparecer visitantes curiosos que intentan comprobar si el aviso es cierto e intentar explotarlo (como imaginaréis no he estado exento de dichas pruebas).

PHPIDs actuando en Textpattern
Ejemplos de pruebas que algunos han realizado en mi web.

Teniendo en mente estos problemas de seguridad, hace tiempo pensé en por qué no aportar una capa más de seguridad a TXP. De muchos es sabido la labor que los IDS realizan en el mundo de la seguridad informática, entre los que me cuento, así que por qué no incluir uno en TXP. Sobre cuál no tuve duda: PHPIDS. Con la firme idea de crear un plugin que incorporase las cualidades de PHPIDS me puse a trabajar, no os diré hace cuanto, hasta que saqué la primera versión funcional del mismo, al que llamé jhc_phpids: PHPIDS para Textpattern.

En estas primeras versiones lo que me ha interesado es tener un plugin sencillo de instalar (en cinco pasos tal y como podéis ver en la Wiki) y que nada más hacerlo se tuviera una versión funcional de PHPIDS; y el listado de los futuros posibles ataques (todo ello desde una nueva pestaña del Gestor). Así mismo, aprovechando que el plugin no será estático, sino que tendrá una evolución tanto por fallos como por los cambios de PHPIDS, lo he colgado en Googe Code donde espero poder manejar mejor dicha evolución, vuestras posibles incidencias y, por qué no, probar el control de fuentes con Subversion.

Para una configuración más detallada de PHPIDS os recomiendo leer su documentación.

PHPIDs actuando en Textpattern
Pantalla con el error 500 que jhc_phpids mostrará ante alguna petición algo rara

Textpattern y la seguridad

Desde hace años vengo usando Textpatterm como CMS de los proyectos Web que desarrollo (ahora estoy probando Drupal que, por lo que voy viendo, me está gustando mucho) porque como dicen ellos mismos:

Textpattern is a flexible, elegant and easy-to-use content management system. It is both free and open source.

Textpattern es un sistema de gestión de contenido flexible, elegante, fácil de usar… y yo añadiría seguro.

No pongo en duda que los equipos de desarrollo de la mayoría de los CMS actuales (WordPress, Drupal, Joomla, etc) ponen especial interés en el tema de la seguridad y que la gestionan adecuadamente pero, salvo descuidos, el problema suele venir en la calidad de los plugins que la comunidad desarrolla y ofrece. Lo cierto es que desconozco los procedimientos que tienen cada uno de ellos para controlar la calidad de estos plugins, lo poco que he visto es que Textpattern no debe hacer ninguno, porque el código que he visto en alguno fallar no fallaba pero de calidad tampoco era, y Drupal algo debe hacer ya que en su repositorio de plugins los marca con un campo Status para indicar la versión del mismo para la que están recomendados.

Si me metiera en analizar los pros y los contras de cada uno de estos gestores podría estar escribiendo hasta el día del juicio pero como quisiera poder hacer algo antes de que llegue ese día, mejor no me meto en esa batalla. Todo esto viene a que el otro día me encontré con PHPIDS un IDS desarrollado en PHP y me encantó. Como ya sabéis me gustan los temas de Seguridad informática y me piqué con aprender a usar PHPIDS y, por qué no, a integrarlo en Textpattern. Ya me he puesto manos a la obra y, si he de seros sinceros, ya lo he conseguido pero como soy más vago que la chaqueta de un guardia dejo para mañana el escribir dos artículos: uno sobre cómo se instala y configura PHPIDS (que a pesar de existir unos cuantos en inglés nunca viene mal otro en castellano) y otro sobre la inclusión de PHPIDS en Textpattern.

Para que no os aburráis os dejo una primera lista de avisos de seguridad para tres de los muchos CMS que existen en el mercado y veréis, lo que me llena de orgullo, que Textpattern por el momento es el que menos tiene.

Avisos de seguridad para Joomla, Drupal y Textpattern

jhc_ckeditor: CKEditor para Textpattern

Todos los que usamos el TXP sabemos que su editor no es de lo mejor que tiene, la verdad es que fue de lo primero que decidí cambiar en cuanto lo instalé por primera vez (allá en su versión 4.0.2 creo recordar).

Desde aquellos tiempos, hasta ahora, no se han desarrollado, o por lo menos no los he encontrado [1], demasiados editores para TXP por lo que siempre he estado utilizando hak_tinymce[2]. Esto ha cambiado recientemente cuando cansado de la austeridad visual hak_tinymce pensé, ¿por qué no cambiamos de editor?

Con esa idea en la cabeza comienzo a buscar editores WYSIWYG y me encuentro con no pocos editores de los que voy descartando y me quedo con:

markitup nicEdit SPAW
CKeditor tinymce

¿Con cuál de estos 5 me quedo? Como quería dar una alternativa a hak_tinymce descarto tinymce. En su momento probé SPAW por lo que ahora lo dejo fuera. De las tres opciones que me quedan descarto nicEdit y markitup por su inicial simplicidad, sé que no es del todo cierta ya que ambos son editores en toda regla con más o menos opciones, pero estaba buscando un editor con muchas opciones y de los tres el que más tenía era CKeditor.

Hecha ya la elección, ¿impresiona el criterio utilizado, eh?, me pongo a investigar un poco sobre su configuración y veo que es más sencilla de lo que pensaba (lo que volvió a despertar la pregunta de por qué nadie lo había integrado ya con TXP). Con tan solo definir las barras de herramientas [3], los posibles estilos que se pueden establecer desde el propio editor [4], y establecer la configuración básica del editor [5], para que cogiera todo lo antes definido, consigo ver el editor tal y como se ve en su demo [6].

ckeditor ya instalado

Tras enredar demasiado en las posibilidades del plugin me quedé con una versión reducida, pero funcional, del mismo que os dejo al final del documento. Pero para que os hagáis una idea os pongo aquí la ayuda del plugin jhc_ckeditor

Este plugin permite el uso de CKEditor como editor WYSIWYG de nuestro TXP. 
Lo único que hay que hacer es descargar, descomprimir e instalar CKEditor
en una carpeta del sitio Web. Tras ello, no hay más que configurar el plugin 
modificando el valor de las variables $path, $toolbody, $excerpt, $toolexcerpt
y $width para que TXP comience a usar CKEditor.

    * path: Ruta al fichero de configuracion del editor, relativo a la raiz
			del sitio Web y que por defecto es '/ckeditor/ckeditor.js'.
    * toolbody: Nombre de la barra de herramientas configurada para CKeditor,
			en el fichero de configuración indicado por path, a usar para el 
			cuerpo del articulo (body).
    * excerpt: Indica si queremos que el resumen del articulo tambien sea 
			gestionado por CKEditor.
    * toolExcerpt: Nombre de la barra de herramientas configurada para CKeditor
			a usar para el resumen del articulo (excerpt).
    * width: Anchura, en tanto por ciento, de el/los elemento/s sobre el/los 
			que actua CKeditor (body y/o resumen).

Desarrollado con licencia Open Source

Sencillo, ¿no? Pués aquí os dejo el código del plugin jhc_ckeditor para implementar CKEditor dentro de nuestro Textpattern. Espero que os sea de ayuda, ciao.

Listado de referencias.

  1. http://textpattern.org/?q=editor
  2. http://textpattern.org/plugins/505/hak_tinymce
  3. Definición de barras de herramientas en CKeditor
  4. Definición de estilos en CKeditor
  5. Configuración de CKeditor
  6. Demo de CKeditor

jhc_most_visited_articles: un plugin para Textpattern

En el desarrollo de mi última colaboración tuvimos que implementar el listado de los artículos más visitados de una sección, cosa que no había hecho antes con Textpattern (bien por vagancia, porque no me tocó o vete tú a saber). El tema es que buscando en el repositorio de plugins de Textpattern encontré el plugin jas_popular_articles de Jose Antonio Solís que a priori me pareció válido.

Pero como suele ocurrir, y con esto no desmerezco el trabajo de nadie, lo que a unos les vale a otros no. Y este fué mi caso. El plugin jas_popular_articles no realiza correctamente los cálculos si durante un periodo de tiempo Textpattern trabaja con messy URLs (URL sucias) y en otro lo hace con non-messy URLS o URLs amigables.

Después de implementar una primera versión para salir del paso me apunté, en mi lista particular de TO-DOs, el modificar, más todavía, el plugin de Jose Antonio Solís para que funcionase con independencia del tipo de URL con el que Textpattern trabajase.

Manos a la obra

Para refereirnos a un mismo artículo, con identificador 23 y título “Primer microformato en castellano”, Textpattern nos permite trabajar con 5 URLs diferentes.

  1. messy o sucia, son URLs del tipo http://www.example.com/?id=23
  2. mes/dia/año/titulo: http://www.example.com/12/01/2008/primer-microformato-en-castellano
  3. secion/titulo: http://www.example.com/section/primer-microformato-en-castellano
  4. titulo: http://www.example.com/primer-microformato-en-castellano
  5. blabalbal

Esta versatilidad en el uso de una u otra URL dificulta la extracción de los artículos más visitados a partir de los registros almacenados en la tabla de accesos, ya que debemos tener en cuenta que el hecho de que un usuario visite la página http://www.example.com/?id=23 es lo mismo que si visitase cualquiera de las demás opciones que Textpattern nos proporciona.

Por otro lado, la información de la que disponemos en la tabla de accesos dependerá de cómo Textpattern esté configurado para registrar las visitas. Si lo tenemos preparado para que registre todos los hits nos encontraremos con que tenemos registro de todas las peticiones que se realicen a URLs dentro de nuestra web, desde la que el usuario usó para acceder a la Web hasta la última antes de abandonarla. En cambio, si Textpattern está registrando sólo referentes únicamente almacenará información sobre la primera vez que un nuevo usuario accede a un documento de nuestra Web.

Así pués, a la hora de extraer los artículos más visitados debemos tener en cuenta dos cosas.

  • Sólo hemos de contabilizar entradas que sean artículos, no valen secciones o categorías.
  • Teniendo en cuenta los tipos de URLs para un mismo artículo, hemos de evitar duplicidades.

Con estas dos consideraciones en mente, decidí crear un plugin que se apoyase más en la base de datos que en el PHP, que mediante consultas preparase la mayor cantidad de datos posible y los dejase lo más preparados posible para que con una posterior extracción vía PHP tuvieramos los artículos del modo más sencillo. Pués bien, esto es lo que me salió.

function jhc_most_visited_article($atts) {
	global $prefs, $s;
	$siteurl = $prefs['siteurl'];
	extract($atts);
	$break    = (empty($break))    ? '<br>' : $break;
	$wraptag  = (empty($wraptag))  ? 'li' : $wraptag;
	$limit    = (empty($limit))    ? 10 : $limit;
	$range    = (empty($range))    ? '' : $range; // y = yearly; m = monthly
	$section  = (empty($section))  ? '' : $section;

	$output = ''; // output string

	// create the query string
	if ($range == 'm') { // Monthly
		$range = 'AND SUBSTR(time,1,7) = "' .  date("Y") . '-' . date("m") . '"';
	}
	if ($range == 'y') { // Yearly
		$range = 'AND SUBSTR(time,1,4) = "' . date("Y") . '"';
	}
	// temp table
	$sql = 'CREATE TEMPORARY TABLE IF NOT EXISTS tmp_articles (id int(12), title varchar(255), max int(12))';
	safe_query($sql, false);
	// messy urls
	$sql = 'INSERT INTO tmp_articles SELECT SUBSTR(page, LOCATE("id", page)+3)+0 AS ID, "", COUNT(*) AS max FROM ' . safe_pfx_j('txp_log') .
			' WHERE page LIKE "%id=%" ' . $range . ' GROUP BY ID ORDER BY ID DESC LIMIT ' . $limit;
	safe_query($sql, false);
	// no messy urls
	$what = 'COUNT(*) AS max, SUBSTRING_INDEX(page, "/", -1) AS page2 ';
	$where = ' LENGTH(SUBSTRING_INDEX(page, "/", -1)) > 0 AND EXISTS (SELECT * FROM textp_textpattern AS t WHERE t.url_title = page2) ' . $range .' GROUP BY page2 ORDER BY max DESC LIMIT ' . $limit;
	$rest = '';
	// Get URLs ordered by number of visits
	$rs = safe_rows($what,'txp_log', $where . $rest, false);
	// Extract article IDs
	while(list($c,$data)=each($rs)){
		$where2 = 'url_title = "' . $data["page2"] . '"';
		if(!empty($section)) $where2 .= ' AND Section = "'.$section.'"';
		if($rs2 = safe_row('ID, Title, url_title', 'textpattern', $where2, false)){
			$sql = 'INSERT INTO tmp_articles (id, title, max) VALUES('.$rs2['ID'].',"'.$rs2['Title'].'",'.$data['max'].')';
			safe_query($sql, false);
		}
	}
	// final sql     
	$sql = 'SELECT DISTINCT textpattern.ID, textpattern.Title FROM tmp_articles AS t, ' .safe_pfx_j('textpattern').
			' WHERE t.id = textpattern.ID ';
	if(!empty($section)) $sql .= ' AND textpattern.Section = "'.$section.'" ';
	$sql .= 'ORDER BY t.max DESC LIMIT ' . $limit;
	$rsF = getRows($sql, false);
	if($rsF){
		while(list($c,$v)=each($rsF)){
			$content = "<txp:permlink id='".$v['ID']."'>".$v['Title']."</txp:permlink>";
			$output .= (empty($wraptag)) ? $content . $break : tag($content, $wraptag) . "\n";
		}
	}
	return $output;
}

Sé que hay mejores modos de hacerlo, como cambiar el modo en que generamos los enlaces, pero este es el que se me ocurrió y me funciona 🙂 Si tenéis algún comentario al respecto no dudéis en compartirlo con todos, ciao.