tag:blogger.com,1999:blog-26530340417611429332024-03-21T23:18:38.913+01:00coDementiaEduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.comBlogger55125tag:blogger.com,1999:blog-2653034041761142933.post-64377676531166305742014-08-15T00:36:00.000+02:002014-08-15T23:20:29.352+02:00Navegación con teclado para Easy UI Datagrid<div style="text-align: justify;">Hace poco me vi en la necesidad de usar el componente <b><a href="http://www.jeasyui.com/demo/main/index.php?plugin=DataGrid&theme=default&dir=ltr&pitem=" target="_blank">Datagrid</a> </b>de la extensión <b><a href="http://www.jeasyui.com/index.php" target="_blank">Easy UI</a></b> para <b><a href="http://jquery.com/" target="_blank">JQuery</a> </b>y, aunque sus características cubrían perfectamente mis requisitos me hacía falta poder navegar por las celdas mediante el ratón, no implementado en el componente. Los problemas que necesitaba resolver eran:</div><br />
<div style="text-align: justify;"></div><ul><li><b>Easy UI Datagrid</b> marca (<i>highlight</i>) la fila entera sobre la que seleccionamos una celda, no la celda en si.</li>
<li>También marca la fila entera sobre la que situamos el ratón (<i>hover</i>).</li>
<li>Dispone de un método para obtener el índice de la fila seleccionada, pero no lo hay para el índice de la columna.</li>
</ul><br />
<span style="text-align: justify;"></span><br />
<a name='more'></a><div style="text-align: justify;">Para evitar que aparezcan en diferente color la fila seleccionada y la fila donde situamos el ratón simplemente cambiamos el estilo de las clases <b style="text-align: justify;">CSS </b><span style="text-align: justify;">usadas para definirlas. Dentro de la carpeta del estilo que estemos usando localizamos dos ficheros: </span><span style="font-family: Courier New, Courier, monospace; text-align: justify;">easyui.css</span><span style="text-align: justify;"> y </span><span style="font-family: Courier New, Courier, monospace; text-align: justify;">datagrid.css</span><span style="text-align: justify;">, en los cuales localizamos las clases:</span></div><br />
<span style="font-family: Courier New, Courier, monospace;">.datagrid-row-selected</span><br />
<span style="font-family: Courier New, Courier, monospace;">.datagrid-row-over</span><br />
<br />
<div style="text-align: justify;">y las redefinimos. En mi caso necesitaba que no fueran visibles así que:</div><pre class="brush: css;">.datagrid-row-selected {
background: transparent;
color: #ffffff;
}
.datagrid-row-over {
background: transparent;
color: #ffffff;
cursor: default;
}
</pre><div style="text-align: justify;">Con ésto hemos solucionado los dos primeros puntos. Para solucionar el tercero necesitamos declarar una variable en nuestro <i>script </i>para contener el índice a la columna seleccionada en cada momento. Con ello podremos seguir la pista al cursor para marcar visualmente las celdas seleccionadas y también podremos obtener el nombre de la columna (<i>field</i>). Para empezar definimos una clase <b>CSS </b>para nuestras celdas seleccionadas, algo simple:</div><pre class="brush: css;">.cell-selected {
background-color: green;
color: #000000;
}
</pre><div style="text-align: justify;"><br />
</div><div style="text-align: justify;">Ahora necesitamos saber cómo localizar la celda que nos interesa dentro del árbol <b>DOM </b>generado por <b>Datagrid</b>. Un vistazo con el inspector del navegador nos permite observar la estructura de la página: los datos se organizan dentro de tablas donde cada fila (<b>TR</b>) se identifica con la cadena <span style="font-family: Courier New, Courier, monospace;">datagrid-row-2-3- </span> (no se si estos dígitos corresponden a la versión) seguida del índice de la fila a la que corresponde. Dentro de ella habrá por cada dato una etiqueta <span style="font-family: Courier New, Courier, monospace;">TD FIELD=nombre del campo</span> y dentro de ésta un <b>DIV </b>para contener el dato en si. Sabiendo ésto podemos acceder a dicho <b>DIV </b>para modificar sus atributos con el siguiente código:</div><pre class="brush: javascript;">var elem = $('#' + 'datagrid-row-r3-2-' + index).children('td[field="' + field + '"]').children('div');
$(elem).addClass('cell-selected');
</pre><div style="text-align: justify;"><br />
</div><div style="text-align: justify;">Con esta información ya podemos implementar una pequeña rutina de teclado para navegar por la tabla. Con el ejemplo siguiente además de usar las teclas arriba, abajo, derecha e izquierda podemos usar también tabulación, <i>enter </i>(para iniciar edición) o escape (para cancelar edición):</div><style>.framesh { height: 130em; }</style><br />
<pre class="brush: javascript; class-name:framesh;"> var gridId; // El selector de nuestro Datagrid
var column = 0; // Inicializamos a la primera columna
var editIndex = undefined; // Contiene indice de la fila en edición
function bindKeys()
{
// 'Bindeamos' el evento keydown al panel que contiene la tabla de datos
$(gridId).datagrid('getPanel').panel('panel').attr('tabindex',1).bind('keydown',function(e)
{
var selected = $(gridId).datagrid('getSelected');
var index = 0; // Indice de la fila seleccionada
var ed = null; // Editor activo (si lo hay)
if (selected)
{
index = $(gridId).datagrid('getRowIndex', selected);
ed = $(gridId).datagrid('getEditors', $(gridId).datagrid('getRowIndex', selected));
}
else
index = $(gridId).datagrid('getRows').length - 1;
if (index > -1)
{
// Cada código de desplazamiento comprueba que no se esté editando una celda (editIndex) y los límites
switch(e.keyCode)
{
case 38: // arriba
if (editIndex == undefined)
highlightCella((index - 1 > 0) ? index - 1 : 0);
break;
case 39: // derecha
if (editIndex == undefined)
{
column = (column < $(gridId).datagrid('getColumnFields').length)) ? column + 1 : column;
highlightCella(index);
}
break;
case 37: // izquierda
if (editIndex == undefined)
{
column = (column > 1) ? column - 1 : column;
highlightCella(index);
}
break;
case 40: // abajo
if (editIndex == undefined)
highlightCella((index + 1 < $(gridId).datagrid('getRows').length) ? index + 1 : index);
break;
case 13: // enter, editamos, gracias a 'column' obtenemos el nombre del campo
onDblClickCell(index, $(gridId).datagrid('getColumnFields')[column]);
break;
case 27: // ESC
if (ed.length > 0)
{
endEditing(true);
highlightCella(index);
}
break;
case 9: // TAB
if (ed.length > 0)
{
endEditing(true);
column = (column < $(gridId).datagrid('getColumnFields').length)) ? column + 1 : column;
highlightCella(index);
}
break;
}
}
if (e.keyCode == 9)
e.preventDefault(); // Si hemos pulsado TAB evitamos el proceso de la tecla para no sacar el foco del Datagrid
});
}
// Se añade/elimina la clase cell-selected a cada celda según estés seleccionada o no
function highlightCella(index, field)
{
if (cellaAnt != null) // Si hay una celda anterior seleccionada la deseleccionamos
$(cellaAnt).removeClass('cell-selected');
if (typeof field === 'undefined') // Si recibimos el field vacío lo leemos a partir de column
field = $(gridId).datagrid('getColumnFields')[column];
var elem = $('#' + 'datagrid-row-r3-2-' + index).children('td[field="' + field + '"]').children('div');
$(elem).addClass('cell-selected'); // Añadimos la clase a la celda seleccionamos
$(gridId).datagrid('selectRow', index); // Actualizamos el índice de fila de Datagrid
cellaAnt = elem; // Y guardamos la celda por si nos desplazamos
}
// Finalizar edición
// El parámetro escap sirve para indicar si entramos aquí por presionar ENTER o TAB
// Si se ha pulsado ENTER y el campo es validado se acepta normalmente
// sino se cancela la edición (else)
function endEditing(escap){
if (editIndex == undefined){return true}
if ($(gridId).datagrid('validateRow', editIndex)) // Si se valida la edición aceptamos y salimos ok
{
$(gridId).datagrid('endEdit', editIndex);
editIndex = undefined;
return true;
}
else
{ // Sino no aceptamos la edición (caso de las select list vacías)
if (typeof escap !== 'undefined')
{ // Si hemos llegado aquí es que se ha presionado ENTER o TAB
// Sin este test el editor no se cerraría
$(gridId).datagrid('cancelEdit', editIndex);
editIndex = undefined;
}
return false;
}
}
// Función para actualizar la columna
function buscaColumna(field)
{
$.each($(gridId).datagrid('getColumnFields'), function(idx, val) {
if (val == field)
{
column = idx;
return false;
}
});
}
// Función para iniciar la edición de una celda
// Llamamos a buscaColumna para mantener actualizado el índice de columnas
function onDblClickCell(index, field){
if (endEditing()){
buscaColumna(field);
$(gridId).datagrid('selectRow', index)
.datagrid('editCell', {index:index,field:field});
editIndex = index;
}
}
// Función para actualizar índices de fila al hacer click en una celda
function onClickCell(index, field)
{
if (editIndex == undefined)
{
$(gridId).datagrid('selectRow', index);
highlightCella(index, field);
}
}
</pre><div style="text-align: justify;">Las opciones de nuestro <b>Datagrid </b>deben contener las propiedades: </div><pre class="brush: javascript;"> onDblClickCell: onDblClickCell,
onClickCell: onClickCell
</pre>y para inicializar ejecutamos: <br />
<pre class="brush: javascript;"> gridId = selector del Datagrid
bindKeys();
$(gridId).datagrid('selectRow', 0);
highlightCella(0, $(gridId).datagrid('getColumnFields')[column]);
$(gridId).datagrid('getPanel').panel('panel').focus();</pre>Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-83660170780089865172014-02-21T18:08:00.000+01:002014-02-21T18:13:52.420+01:00Ticker para feeds RSS en JQuery<div style="text-align: justify;">
Hace poco me puse a trastear con <b>JQuery</b>, que aún no conocía, y opté por practicar desarrollando un <i>widget </i>que me permitiera leer <i>feeds rss</i> y mostrarlos en formato <i>ticker </i>horizontal. Me fui entusiasmando con el asunto y terminé creando una pequeña aplicación para mostrar varios <i>tickers</i> reordenables distintos organizados por categorías en carpetas verticales con opciones para añadir, renombrar, eliminar y guardar. En este articulo sólo hablaré del <i>widget </i>básico, si alguien quiere descargarse la aplicación completa puede encontrarla en <a href="https://github.com/EduardMillan/jTicker" target="_blank">GitHub</a>.</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdypl36B08yI9r3ehLlz8sVYCrukUE7i8LEjgo2q8iNvdH4rTs7IIuEUedhdXtRA-keVXtzvGnSqjFgucdI3vFpL59nYrFsDVR6Kfw3VguTd-FbtX64ejUYHWpouWYeFuXBGBrhXLcZ0rk/s1600/jTicker.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdypl36B08yI9r3ehLlz8sVYCrukUE7i8LEjgo2q8iNvdH4rTs7IIuEUedhdXtRA-keVXtzvGnSqjFgucdI3vFpL59nYrFsDVR6Kfw3VguTd-FbtX64ejUYHWpouWYeFuXBGBrhXLcZ0rk/s1600/jTicker.png" height="108" width="640" /></a></div>
<div style="text-align: justify;">
<br />
<a name='more'></a><br /></div>
Para empezar necesitamos:<br />
<br />
<ul>
<li><a href="http://jquery.com/" target="_blank">JQuery</a> 1.11.0 y 2.1.0 (para soporte <b>IE9 </b>o superior), instalamos ambos en <span style="font-family: Courier New, Courier, monospace;">js/jquery</span></li>
<li><a href="http://jqueryui.com/" target="_blank">JQuery UI</a> 1.10.4, instalamos en <span style="font-family: Courier New, Courier, monospace;">js/jquery.ui</span></li>
<li><a href="http://www.dyn-web.com/" target="_blank">Dinamyc Web Scroller</a>, instalamos en <span style="font-family: Courier New, Courier, monospace;">js/dw.scroller</span></li>
<li><a href="http://jqueryui.com/themeroller/" target="_blank">JQuery UI themes</a>, instalamos en <span style="font-family: Courier New, Courier, monospace;">css/ui-themes</span><span style="font-family: inherit;">, el tema usado en la demo es <b>black-tie</b></span></li>
</ul>
<br />
<div style="text-align: justify;">
<b>Dynamic Web Scroller</b> (<b>DWS</b>) es la librería que suelo usar cuando necesito desplazamientos de información en pantalla, creo que con <b>JQuery UI</b> también se dispone de dicha funcionalidad pero no lo comprobé. El <i>widget </i>es muy simple: se le envía la <i>url </i>del <i>feed </i>(<span style="font-family: Courier New, Courier, monospace;">options.url</span>) y una función <i>callback </i>si es preciso. La razón de dicho <i>callback </i>es que el <i>feed </i>se carga en modo asíncrono (usando la<a href="https://developers.google.com/feed/v1/devguide" target="_blank"> Google Feed API</a>). El <i>widget </i>invoca al <b>API </b>pasándole una función <i>callback </i>interna (<span style="font-family: Courier New, Courier, monospace;">_loadPosts()</span>) la cual, después de generar el <i>ticker </i>con su contenido en pantalla, termina llamando al <i>callback </i>del usuario. Asi podemos enlazar distintos <i>tickers </i>si lo deseamos como se ve en la demo.</div>
<div style="text-align: justify;">
Para crear el <i>ticker </i>se genera un árbol <b>DOM </b>adaptado a los requerimientos de la librería <b>DWS</b>:</div>
<pre class="brush:html;"><div class="container">
<div class="origin">
<a href="enlace a la web del feed">
<img src="favicon de la web">
<span>título del feed</span>
</a>
</div>
<div class="capsule">
<div class="nest">
<div class="ticker">
<span class="news-header">título y resumen de cada noticia</span>
[...]
</div>
</div>
</div>
</div>
</pre>
<div style="text-align: justify;">
Una vez generado se recogen los valores <b>ID </b>de los <i>divs </i><span style="font-family: Courier New, Courier, monospace;">capsule</span>, <span style="font-family: Courier New, Courier, monospace;">nest</span>, y <span style="font-family: Courier New, Courier, monospace;">news-header</span> en DWS para que inicie el <i>scroll</i>. El <i>div </i><span style="font-family: Courier New, Courier, monospace;">origin </span>se usa para contener el título y el <i>favicon </i>de la <i>web</i>. Si inspeccionamos el <i>div </i><span style="font-family: Courier New, Courier, monospace;">ticker </span>una vez en marcha comprobaremos que por cada titular se ha generado un <span style="font-family: Courier New, Courier, monospace;">news-header</span> y que estos se repiten dos o mas veces a partir de un <span style="font-family: Courier New, Courier, monospace;">span </span>determinado por un <b>ID </b>de tipo <span style="font-family: Courier New, Courier, monospace;">nt-news-header-idxxxxxxxx</span>. Es decir, la lista de titulares se genera por duplicado, triplicado o más. Ello se debe a la forma en que funciona <b>DWS</b>: para trabajar correctamente necesita que la lista de titulares a mostrar ocupe por lo menos 2 veces el ancho de la zona visible para el usuario, por lo tanto el <i>widget </i>se encarga de comprobar las medidas de cada titular que se inserta y decide posteriormente cuantas copias más debe añadir (en <span style="font-family: Courier New, Courier, monospace;">_procesaEntradas()</span> y<span style="font-family: Courier New, Courier, monospace;"> _processFeed()</span>). Los nombres de estas clases y otros valores y funciones comunes se encuentran en el archivo <span style="font-family: Courier New, Courier, monospace;">js/commons.js</span>.</div>
<style>.framesh { height: 185em; } </style><br />
<pre class="brush:javascript; class-name:framesh;">(function($)
{
var _self;
var _base;
var _cnt = 1;
var _titleticker;
var _me;
var _refresh_callback;
var _max_feed_loads = 100;
$.widget('news.ticker', {
options:
{
url: null,
callback: $.noop
},
_init: function()
{
_self = this;
if (_self.options.url !== null)
{
_base = _self.element;
_self._idx = genKey();
_self._selector = idNews.id().container + _self._idx;
_self._callback = function (TotalFeed) { _self._loadPosts(TotalFeed); }
_self._loadFeed(_self.options.url);
}
},
_setOption: function(name, value)
{
$.Widget.prototype._setOption.apply(this, arguments);
switch (name)
{ // Si cambia la url (parametro 1) callback = null, si existe callback se inicializa en siguiente llamada
// Esta operación evita llamadas recursivas al mismo callback
case 'url': this.options.callback = $.noop;
break;
}
},
// Se crean los divs container, origin, capusle, nest y ticker
_createTicker: function()
{
$(_base).append(
$('<div/>', {
class: classNews.cssclass().container + ' ' + uiClass.ui().container,
id: _self._selector,
}).append(
$('<div/>', {
class: classNews.cssclass().origin + ' ' + uiClass.ui().origin,
id: idNews.id().origin + _self._idx,
}),
$('<div/>', {
class: classNews.cssclass().capsule + ' ' + uiClass.ui().capsule,
id: idNews.id().capsule + _self._idx,
}).append($('<div/>', {
class: classNews.cssclass().nest,
id: idNews.id().nest + _self._idx,
}).append($('<div/>', {
class: classNews.cssclass().ticker,
id: idNews.id().ticker + _self._idx,
})
)
)
)
);
$(_base).tooltip({ hide: {duration: 1000 }});
},
// Llamada al API de Google para cargar el feed
_loadFeed: function(url)
{
var feed = new google.feeds.Feed(url);
feed.setNumEntries(_max_feed_loads);
feed.load(function(TotalFeed) { _self._callback(TotalFeed); });
},
// Se genera el ticker si existen feeds y se llama al callback del usuario (si existe)
_loadPosts: function(TotalFeed)
{
if (TotalFeed.feed.entries.length > 0)
{
_self._createTicker();
_self._processFeed(TotalFeed);
}
_self._trigger('callback');
},
// Se genera cada span news-header para titular y resumen. Si repeat == true se marca el span como inicio de la copia de la lista de titulares
_procesaEntradas: function(TotalFeed, iframe, favicon, idxcode, repeat)
{
var cancelRepeat = function() { repeat = false; return idNews.id().header + idxcode; };
var spanwidth = 0;
$.each(TotalFeed.feed.entries, function (idx, val) {
var href = $('<a/>', {
id: commons.label().urltitle + idxcode + '-' + _cnt,
href: val.link,
text: decodeEntities(val.title),
target: '_blank'
});
$(iframe).append($('<span/>', {
class: classNews.cssclass().header,
id: ((repeat) ? cancelRepeat() : commons.label().spantitle + idxcode + '-' + _cnt),
}).append(
$('<img/>', {
class: classNews.cssclass().favicon_thumb,
src: favicon
}),
$(href)
));
// $('#url' + _idx + idx).attr('title', decodeEntities(val.content)); // Sin decode aparece el formato HTML, con decode solo textos
$('#' + commons.label().urltitle + idxcode + '-' + _cnt).attr('title', val.content);
spanwidth += $('#' + commons.label().spantitle + idxcode + '-' + _cnt++).width();
});
return spanwidth;
},
// Se genera el título y favicon del feed y se lla a _procesaEntradas() para crear la lista de titulares
_processFeed: function(TotalFeed)
{
_self._selector = '#' + _self._selector;
iframe = $(_self._selector).find('#' + idNews.id().ticker + _self._idx);
var favicon = TotalFeed.feed.link.replace(/(:\/\/[^\/]+).*$/, '$1') + '/favicon.ico';
_titleticker = decodeEntities(TotalFeed.feed.title);
$(_self._selector).children('#' + idNews.id().origin + _self._idx).append(
$('<a/>', {
href: TotalFeed.feed.link,
target: '_blank'
}).append(
$('<img/>', {
class: classNews.cssclass().favicon,
src: favicon
}),
$('<span/>', {
text: decodeEntities(TotalFeed.feed.title)
}).css('position', 'absolute').css('float', 'left')
)
)
var wd = _self._procesaEntradas(TotalFeed, iframe, favicon, _self._idx, false);
for (i = 0; i < ~~(($('.' + classNews.cssclass().container).width() * 2) / wd) + 1; i++)
_self._procesaEntradas(TotalFeed, iframe, favicon, _self._idx, (i == 0));
_self._activaScroll();
},
_activaScroll: function()
{
if (DYN_WEB.Scroll_Div.isSupported())
{
DYN_WEB.Event.domReady( function()
{
var wndo2 = new DYN_WEB.Scroll_Div(idNews.id().capsule + _self._idx, idNews.id().nest + _self._idx);
wndo2.makeSmoothAuto( {axis: 'h', bRepeat: true, repeatId: idNews.id().header + _self._idx, speed: 60, bPauseResume: true} );
});
}
},
// Estas funciones se usan en jTicker
idTicker: function()
{
return _self._selector;
},
titleTicker: function()
{
return _titleticker;
},
// Funciones para refrescar el contenido del ticker
_feedRefresh: function(TotalFeed)
{
var iframe = $(_me).find('.' + classNews.cssclass().ticker);
var favicon = $(_me).children('.' + classNews.cssclass().origin).children('a').children('img').attr('src');
var idx = $(iframe).attr('id').substr(idNews.id().ticker.length);
var wd = _self._procesaEntradas(TotalFeed, iframe, favicon, idx, false);
for (i = 0; i < ~~(($('.' + classNews.cssclass().container).width() * 2) / wd) + 1; i++)
_self._procesaEntradas(TotalFeed, iframe, favicon, idx, (i == 0));
// _self._activaScroll(); // No hace falta reactivar scroll, sigue activo y se usan los mismos id's
_refresh_callback();
},
feedRefresh: function(callback)
{
_me = this.element;
_refresh_callback = callback;
$(_me).find('.' + classNews.cssclass().ticker).children('.news-header').remove();
_self._callback = function(TotalFeed) { _self._feedRefresh(TotalFeed); };
_self._loadFeed($(_me).attr('feedurl'));
}
});
}(jQuery));
</pre>
<br />
Para probarlo creamos un <b>index.html</b> de la forma:<br />
<br />
<style>.frameshtml { height: 50em; } </style><br />
<pre class="brush:html; class-name:frameshtml;"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>jTicker</title>
<link id="css1" rel="stylesheet" href="css/ui-themes/black-tie/jquery-ui.css">
<link id="css2" rel="stylesheet" href="css/ui-themes/black-tie/jquery.ui.theme.css">
<link id="css3" rel="stylesheet" type="text/css" href="css/jquery.news.ticker.css"/>
<link id="css3" rel="stylesheet" type="text/css" href="css/jquery.news.override.css"/>
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<!--[if lt IE 9]>
<script src="js/jquery/jquery-1.11.0.min.js" type="text/javascript"></script>
<![endif]-->
<!--[if gte IE 9]><!-->
<script src="js/jquery/jquery-2.1.0.min.js" type="text/javascript"></script>
<!--<![endif]-->
<script src="js/jquery.ui/jquery-ui-1.10.4.custom.min.js" type="text/javascript"></script>
<script src="js/dw.scroller/dw_con_scroller.js" type="text/javascript"></script>
<script src="js/jquery.news/jquery.news.commons.js" type="text/javascript"></script>
<script src="js/jquery.news/jquery.news.ui.tooltip.js" type="text/javascript"></script>
<script src="js/jquery.news/jquery.news.ticker.js" type="text/javascript"></script>
<style>
#test {
width: 1000px;
margin-left:auto;
margin-right: auto;
}
</style>
</head>
<body>
<div id="test"></div>
</body>
<script type="text/javascript">
function tickers()
{
var test = '#test';
$(test).ticker({url: 'http://codementia.blogspot.com/feeds/posts/default',
callback: function() { $(test).ticker({url: 'http://www.microsiervos.com/rss-ciencia.xml',
callback: function() { $(test).ticker({url: 'http://exampleusername.livejournal.com/data/atom'
})}
})}
});
$(test).sortable();
}
google.load("feeds", "1");
google.setOnLoadCallback( function() { tickers(); });
</script>
</html>
</pre>
<br />
Con lo que obtendremos un grupo de <i>tickers </i>apilados:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEwR-zVYm4cg99pZeYbGd2ENh0rxuTZjB6inqdItJ2c46XYzJOUeundXEM_YVLZSdLYldZNn9IFatoZHXbfxx3V-L15gzORcDqz8NSWC7QkP2dPXfxOLfCLyPjzWfVt9eM_OCTq6yaU_5B/s1600/jTicker-simple.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEwR-zVYm4cg99pZeYbGd2ENh0rxuTZjB6inqdItJ2c46XYzJOUeundXEM_YVLZSdLYldZNn9IFatoZHXbfxx3V-L15gzORcDqz8NSWC7QkP2dPXfxOLfCLyPjzWfVt9eM_OCTq6yaU_5B/s1600/jTicker-simple.png" height="96" width="640" /></a></div>
<br />
<div style="text-align: justify;">
Al usar la <b>API </b>de <b>Google </b>sólo podremos leer <i>feeds </i>en formato <b>RSS </b>y <b>ATOM</b>. El <i>widget </i>adicional que se necesita (<span style="font-family: Courier New, Courier, monospace;">news.ui.tooltip</span>) simplemente reescribe el método de <b>jQuery UI</b> para la propiedad <span style="font-family: Courier New, Courier, monospace;">title </span>de un <i>div</i>, que es donde se almacena el resumen de cada titular. Se puede <a href="http://drive.google.com/uc?export=view&id=0B0RWiOy4QH6XWlZCMklQcmE2VEk">descargar el fuente aquí</a>.</div>
Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com2tag:blogger.com,1999:blog-2653034041761142933.post-8213049035441546552014-01-25T16:17:00.001+01:002014-02-13T13:28:52.196+01:00Facebook: malo hasta la médulaEste <b>blog</b> ya no facilita enlaces con <b>Facebook.</b><br />
<br />
<div style="text-align: justify;">
No es por causa del horrible diseño de éste, digno heredero de <b>MySpace</b>, cuyo único mérito reside en haber desterrado las páginas con fondos negros y letras púrpura o verde fosforito y las fotos de vacaciones de 60000 x 10000 píxeles y 4 terabytes. Tampoco es por la brutal desorganización e incoherencia de las páginas, acentuada aún más cuando añadieron páginas personales que pueden configurarse como página principal, ni por la facilidad con la que cualquiera puede enviarte porquerías que no te interesan para nada. Si alguna de éstas fuera una razón de peso bastaría con olvidarlo y dejar que se pudriera, como ocurrió con <b>MySpace</b>.</div>
<br />
<div style="text-align: justify;">
Lo grave es que el mismo estilo provisional se extiende al corazón del sistema: ya van dos veces en las que observo que el botón <i>compartir </i>o <i>me gusta</i> en páginas de las que soy responsable no funciona como debe. En concreto el botón <i>compartir </i>simplemente muestra una ventana en blanco y ahí te quedas. Lo curioso es que no ocurre siempre, sólo en algunos enlaces que, en principio, no se diferencian de los demás. El depurador de <b>Facebook </b>sólo devuelve un problema:<b> Error linting URL</b>. Y punto. Una <a href="https://www.google.com/search?q=linting+error&rlz=1C1GGGE_esES433ES433&oq=linting+error&aqs=chrome..69i57j0l5.2082j0j7&sourceid=chrome&espv=210&es_sm=122&ie=UTF-8#q=facebook%20Error%20linting%20URL&safe=off" target="_blank">búsqueda en Google</a> arroja mas de 300.000 resultados sin ninguna solución y el soporte de <b>Facebook </b>contesta que <i>no podemos atender a todas las consultas pero gracias por avisarnos</i>. Muy cutre. Por ello voy a pasar completamente de <b>FB</b>. Con <b>Twitter </b>basta y sobra, siendo mucho más <i>social</i>, a mucha distancia, que la tontería absurda del <b>Zuckerberg</b>, que debe ser amigo del de <b>ShareThis</b>, otro que tal.<br />
<br />
Que poco me ha durado. Más sobre el tema en <a href="http://morrisongarden.com/porque-ya-uso-facebook/" target="_blank">¿Porqué ya no uso Facebook?</a></div>
Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-91719729434756561192014-01-16T17:55:00.000+01:002014-01-16T20:35:09.793+01:00Nube de etiquetas con índice dinámico para Blogger<div style="text-align: justify;">Hasta hace poco usaba un índice para las entradas basado en un <i>script </i>que las mostraba ordenadas por etiquetas de un tal <b>Abu Farhan</b>, pero lo eliminé al avisarme <b>Google </b>de que dicho <i>script </i>podía contener código malicioso. Me puse a buscar algo parecido y sólo encontré nubes de etiquetas por un lado o listas de entradas por el otro, ninguno que uniera ambas características, al menos en <i>javascript</i>, así que decidí hacérmelo yo mismo. El resultado puede verse en <a href="http://codementia.blogspot.com.es/p/indice_15.html" target="_blank">mi página de índice</a>. No es muy espectacular visualmente pero es simple y eficaz, visualmente y a nivel de código.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvhdacKsJWbYn7R1T5-kCVj7E1sRXKFckZ36zGIhNH5Tue95UyVKW1kwoT_8G6whMtdSQuhTrHEcI7yT2Ig-Emy8OpJqqPj2LuKbOmDHpERmqIY1vDpjnlhuXDI7VN0DYZ72YaF12DDBHL/s1600/TagCloudIndex.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvhdacKsJWbYn7R1T5-kCVj7E1sRXKFckZ36zGIhNH5Tue95UyVKW1kwoT_8G6whMtdSQuhTrHEcI7yT2Ig-Emy8OpJqqPj2LuKbOmDHpERmqIY1vDpjnlhuXDI7VN0DYZ72YaF12DDBHL/s1600/TagCloudIndex.jpg" height="225" width="400" /></a></div><a name='more'></a><br />
<div style="text-align: justify;">El <i>script </i>permite:</div><br />
<ul><li style="text-align: justify;">Ver las etiquetas de nuestro <i>blog </i>en formato nube con diferentes medidas de fuente según su frecuencia</li>
<li>Ordenar la nube alfabéticamente</li>
<li style="text-align: justify;">Mostrar los títulos de los artículos con la etiqueta seleccionada pudiendo mostrar sólo el título o el nombre del autor y/o la fecha de publicación</li>
</ul><br />
El código se divide en tres partes:<br />
<br />
<ul><li style="text-align: justify;">Se cargan todos los artículos y se guardan las etiquetas junto a las veces que éstas se repiten en una matriz bidimensional (peso) que posteriormente puede ordenarse o no (<span style="font-family: Courier New, Courier, monospace;">cargaEtiquetas()</span> y <span style="font-family: Courier New, Courier, monospace;">contarEtiquetas()</span>)</li>
<li style="text-align: justify;">Se genera la nube asignando a cada etiqueta un tamaño de fuente en porcentaje basado en su peso según el algoritmo ideado por <a href="http://blog.jeremymartin.name/2008/03/efficient-tag-cloud-algorithm.html" target="_blank">Jeremy Martin</a> y un enlace con evento <i>onclick </i>que llama a la función encargada de buscar los artículos correspondientes a la etiqueta elegida (<span style="font-family: Courier New, Courier, monospace;">generaCloud()</span>)</li>
<li style="text-align: justify;">Se muestran los títulos de las entradas bajo la nube en orden inverso de publicación (<span style="font-family: Courier New, Courier, monospace;">loadPosts()</span>)</li>
</ul><br />
Para modificar la apariencia de la nube disponemos de las etiquetas:<br />
<br />
<ul><li><b>#tagCloud</b> <i>div </i>para contener la nube en sí, con atributos para modificar la visualización:</li>
</ul><ul><li style="margin-left: 30px;"><b style="font-weight: bold;">data-maxfont</b>: porcentaje máximo para la fuente</li>
<li style="margin-left: 30px;"><b style="font-weight: bold;">data-minfont</b>: porcentaje mínimo para la fuente</li>
<li style="margin-left: 30px;"><b style="font-weight: bold;">linkColor</b>: color de la etiqueta normal</li>
<li style="margin-left: 30px;"><b style="font-weight: bold;">selectedLink</b>: color de la etiqueta seleccionada</li>
<li style="margin-left: 30px;"><b style="font-weight: bold;">authorName</b>: ="1" se muestra autor, cualquier otro valor no</li>
<li style="margin-left: 30px;"><b style="font-weight: bold;">date</b>: ="1" muestra la fecha de publicación de los artículos</li>
<li style="margin-left: 30px;"><b style="font-weight: bold;">sort</b>: ="1" ordena las etiquetas alfabéticamente</li>
</ul><ul><li><b>#listTitols</b> <i>div</i> para contener el índice de artículos</li>
<li><b>#dataPost</b> <i>span</i> para contener el nombre del autor y la fecha de publicación</li>
<li><b>#containerIdx</b> div contenedor para los anteriores</li>
</ul><ul></ul>Veamos el código:<br />
<style>.framesh { height: 110em; } </style><br />
<pre class="brush:javascript; class-name:framesh;">var lastLink = null;
var linkColor = '#000000', selectedLink = '#000000';
var showAuthor = true;
var showDate = true;
function gestionaColorLink(alink)
// Cambia el color de la etiqueta seleccionada y devuelve su color a la anterior
{
if (!(lastLink === null))
lastLink.style.color = linkColor;
lastLink = alink;
alink.style.color = selectedLink;
}
var MonthNames = ['Enero', 'Febrero', 'Marzo', 'Abril' , 'Mayo', 'Juny', 'Julio' , 'Agosto', 'Septiembre', 'Octubre' , 'Noviembre' , 'Diciembre'];
function loadPosts(label, alink)
// Cargamos el feed de los posts del tema (label) seleccionado
{
gestionaColorLink(alink);
iframe = document.getElementById('listTitols');
if (window.XMLHttpRequest)
{ // code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else
{ // code for IE6, IE5
xmlhttp=new ActiveXObject('Microsoft.XMLHTTP');
}
xmlhttp.open('GET', 'http://' + window.location.host + '/feeds/posts/summary/-/' + label, false);
xmlhttp.send();
xmlDoc=xmlhttp.responseXML;
iframe.innerHTML = '';
var x = xmlDoc.getElementsByTagName('entry');
for(var i = 0; i <= x.length - 1; i++) // Obtenemos la URL
{
urls = x[i].getElementsByTagName('link');
ThisPostURL = '';
for(LinkNum = 0; LinkNum < urls.length; LinkNum++)
{
if (urls[LinkNum].getAttribute('rel') == 'alternate')
{
ThisPostURL = urls[LinkNum].getAttribute('href');
break;
}
}
nomautor = '';
if (showAuthor) // Obtenemos el nombre del autor
{
autor = x[i].getElementsByTagName('author');
nomautor = autor[0].getElementsByTagName('name')[0].childNodes[0].nodeValue + ', ';
}
public = '';
if (showDate) // Obtenemos la fecha de publicación
{
data = x[i].getElementsByTagName('published')[0].childNodes[0].nodeValue;
public = data.substring(8,10) + ' ' + MonthNames[parseInt(data.substring(5,7), 10) - 1] + ' ' + data.substring(0,4);
}
iframe.innerHTML += '<a href="https://www.blogger.com/'%20+%20ThisPostURL%20+%20'">' + x[i].getElementsByTagName('title')[0].childNodes[0].nodeValue + '</a>' + ((showAuthor || showDate) ? ' <span id="dataPost">(' + nomautor + public + ')</span>' : '');
iframe.innerHTML += '
';
}
}
var listaEtiquetas = new Array(); // Array bidimensional: [etiqueta, peso]
function generaCloud()
// Generación de la nube de etiquetas
{
var tc = document.getElementById('tagCloud');
var minPercent = parseInt(tc.getAttribute('data-minfont'), 10);
var maxPercent = parseInt(tc.getAttribute('data-maxfont'), 10);
var ordena = (tc.getAttribute('sort') == '1');
var vmax = 1, vmin = 999;
linkColor = tc.getAttribute('linkColor');
selectedLink = tc.getAttribute('selectedLink');
showAuthor = (tc.getAttribute('authorName') == '1');
showDate = (tc.getAttribute('date') == '1');
if (ordena)
{
listaEtiquetas = listaEtiquetas.sort(function(a,b) {
s1 = a[0].toLowerCase();
s2 = b[0].toLowerCase();
return ((s1 < s2) ? -1 : (s1 > s2) ? 1 : 0); });
}
for (idx = 0; idx < listaEtiquetas.length; idx++)
{
data = listaEtiquetas[idx][1];
vmax = (data > vmax ? data : vmax);
vmin = (vmin > data ? data : vmin);
}
multiplier = (maxPercent - minPercent) / (vmax - vmin);
while (tc.hasChildNodes())
tc.removeChild(tc.lastChild);
for (idx = 0; idx < listaEtiquetas.length; idx++)
{
a = document.createElement('a');
a.setAttribute('href', '#');
a.setAttribute('onclick', 'loadPosts("' + listaEtiquetas[idx][0] + '", this)');
a.innerHTML = listaEtiquetas[idx][0];
a.style.fontSize = (minPercent + ((vmax - (vmax - (listaEtiquetas[idx][1] - vmin))) * multiplier)).toString() + '%';
a.style.color = linkColor;
tc.appendChild(a);
espai = document.createTextNode('\u00A0'); // No usar ' ', al recargar página desaparece
tc.appendChild(espai);
}
}
function contarEtiquetas(label)
// Añadimos la etiqueta (label) a la lista si no está o incrementamos su peso si ya existe
{
cnt = 0;
ok = false;
while ((cnt < listaEtiquetas.length) && (!ok))
{
ok = (listaEtiquetas[cnt][0] === label);
if (!ok) cnt++;
}
if (ok)
listaEtiquetas[cnt][1]++;
else
listaEtiquetas.push([label, 1]);
}
function cargaEtiquetas(TotalFeed) // <---- Entry point
{
if ('entry' in TotalFeed.feed)
{
var PostEntries = TotalFeed.feed.entry.length;
var PostNum = 0;
for(; PostNum < PostEntries; PostNum++)
{
ThisPost = TotalFeed.feed.entry[PostNum];
label = ThisPost.category;
for (i = 0; i < label.length; i++)
contarEtiquetas(label[i].term);
}
if (PostNum > 0) generaCloud();
}
}
</pre><br />
<div style="text-align: justify;">Para usar el <i>script </i>añadimos el siguiente <b>CSS </b>a nuestra página:</div><br />
<pre class="brush:css"><style>
#containerIdx
{
width: 100%;
}
#tagCloud
{
width: 90%;
font-family:"Times New Roman",Georgia,Serif;
text-align: center;
margin-bottom: 20px;
margin-left: auto;
margin-right: auto;
}
#listTitols
{
width: 90%;
text-align: center;
margin-left: auto;
margin-right: auto;
}
#dataPost
{
font-size: 12px;
}
</style>
<div id="containerIdx">
<div id="tagCloud" data-maxfont="250" data-minfont="100" linkColor="#000000" selectedLink="#666666" authorName="0" date="0" sort="1"></div>
<div id="listTitols">&lt/div>
</div>
</pre><br />
Y lo cargamos a continuación:<br />
<br />
<pre class="brush:javascript"><script type="text/javascript" src="http://localización_script_tagCloudIdx.js"></script>
<script src="http://host_de_tu_blog/feeds/posts/default?max-results=1000&alt=json-in-script&callback=cargaEtiquetas"></script>
</pre><br />
Puede descargarse en <a href="https://github.com/EduardMillan/Tag-Cloud-with-Dinamyc-Index-for-Blogger" target="_blank">GitHub</a>.<br />
<br />
Reconocimientos: <a href="http://blog.jeremymartin.name/2008/03/efficient-tag-cloud-algorithm.html" target="_blank">Efficient Tag Cloud Algorithm</a>, <a href="http://www.geekthis.net/blog/101/javascript-tag-cloud" target="_blank">JavaScript Tag Cloud</a>, <a href="http://www.threelas.com/2012/02/how-to-read-blogger-xml-feed-using.html" target="_blank">How To Read Blogger XML Feed Using Javascript</a>Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-12451464232582181612013-12-02T13:48:00.000+01:002013-12-02T13:48:09.125+01:00Si Engels levantara la cabeza...<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4RvHzcl4azrar7hfNvs5kRkI0X1UOgpTn6K7gfLNkY7hjoQYuCd7Qo7Fl1tINl3H7Li7Mf97imtXuXHhUxEkpkQLCASxyrZzhHQhk6FqVlmbTQRwq93ImPh0G8MSNkiEMaemNdkLk6iWG/s1600/Screenshot_2013-12-02-00-41-45.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4RvHzcl4azrar7hfNvs5kRkI0X1UOgpTn6K7gfLNkY7hjoQYuCd7Qo7Fl1tINl3H7Li7Mf97imtXuXHhUxEkpkQLCASxyrZzhHQhk6FqVlmbTQRwq93ImPh0G8MSNkiEMaemNdkLk6iWG/s1600/Screenshot_2013-12-02-00-41-45.png" height="400" width="240" /></a></div>
Cortesía del lector <i>rss </i>de mi móvil.Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-27190167303622407112013-11-17T15:50:00.004+01:002013-11-17T16:02:12.806+01:00OWASP ESAPI PHP: Autentificación y control de sesiones<div style="text-align: justify;">
En esta última entrega vamos a ver como gestionar la autentificación de usuarios y las sesiones. Empecemos por enumerar las características que vamos a implementar en nuestra clase <b>User</b>:</div>
<br />
<ul>
<li>Mejorar la gestión de sesiones</li>
<li>Implementar <a href="https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CC0QFjAA&url=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FCross-site_request_forgery&ei=qbCIUvq1IM-c0wXyuIC4DA&usg=AFQjCNHTv-BNCKp3W0AVfOZ4jWQLWbdhgA&sig2=ImfXEh0NVcGOxAsOasB0gg" target="_blank">tokens CSRF</a></li>
<li>Generar <i>hashes </i>de las claves</li>
<li>Leer propiedades de los objetos desde la base de datos</li>
<li>Limitar el tiempo de las sesiones (<i>expire time</i>)</li>
<li>Establecer qué guardamos en las variables de sesión</li>
</ul>
<br />
<br />
<a name='more'></a><br />
<span style="text-align: justify;">No es una lista exhaustiva pero nos va a permitir ver cómo usar </span><b style="text-align: justify;">ESAPI</b><span style="text-align: justify;">. </span><b style="text-align: justify;">ESAPI PHP</b><span style="text-align: justify;"> dispone de algunos objetos y utilidades para gestionar usuarios aunque para la mayoría de requerimientos no son suficientes. Entre las funciones más útiles y completas destaca la gestión de </span><i style="text-align: justify;">tokens <b>CSRF</b></i><b style="text-align: justify;"> </b><span style="text-align: justify;">mediante el control </span><b style="text-align: justify;">HTTPUtilities</b><span style="text-align: justify;">. Empecemos pues añadiendo a </span><b style="text-align: justify;">User.php</b><span style="text-align: justify;"> las rutas de acceso a los controles </span><b style="text-align: justify;">ESAPI </b><span style="text-align: justify;">que necesitamos:</span><br />
<div style="text-align: justify;">
<br /></div>
<pre class="brush: php; first-line:24;">require("lib/conf.php");
require_once(ESAPI_SRC_path . "/ESAPI.php");
require_once(ESAPI_SRC_path . "/reference/BlogHTTPUtilities.php");
</pre>
<br />
y le añadimos también nuevas propiedades:<br />
<br />
<pre class="brush: php; first-line:36;">private $esapi = null;
private $httputils = null;
private $logged_in = null;
private $salt = null;
private $expire_time = 600;
</pre>
<br />
<div style="text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">$esapi</span> es nuestro objeto <b>ESAPI</b>, ya lo vimos anteriormente. Guardamos el control <b>HTTPUtilities </b>en <span style="font-family: Courier New, Courier, monospace;">$httputils</span>. Como sólo hay un nivel de usuario usaremos una variable <i>booleana </i><span style="font-family: Courier New, Courier, monospace;">$logged_in</span> para guardar esta información. <span style="font-family: Courier New, Courier, monospace;">$salt</span> la usaremos para implementar el <i>hashing </i>de las claves y <span style="font-family: Courier New, Courier, monospace;">$expire_time</span> es el tiempo de inactividad antes de cerrar la sesión. El valor se <span style="font-family: Courier New, Courier, monospace;">$salt</span> debe guardarse en la base de datos, así que vamos a modificar ésta haciendo el campo más grande:</div>
<div style="text-align: justify;">
<br /></div>
<pre class="brush: php;">ALTER TABLE user ADD salt VARCHAR(30);
alter table user change password password varchar(100) not null;
</pre>
<br />
Veamos pues el constructor de nuestro <b>User </b>modificado:<br />
<br />
<pre class="brush: php; first-line:53;">function __construct($user_id='') {
$this->esapi = new ESAPI(ESAPI_XML_path);
ESAPI::setHTTPUtilities(new BlogHTTPUtilities());
$this->httputils = ESAPI::getHTTPUtilities();
$this->logged_in = false;
if($this->check_user_session()) {
if($this->retrieve_user()) {
$this->logged_in = true;
}
}
}
</pre>
<br />
<div style="text-align: justify;">
Como ya vimos, inicializamos nuestros objetos <b>ESAPI </b>y establecemos el valor de <span style="font-family: Courier New, Courier, monospace;">$logged_in</span> a <i>false </i>(no hay sesión abierta). A continuación comprobamos si hay una sesión activa y, de ser así, establecemos el valor de <span style="font-family: Courier New, Courier, monospace;">$logged_in</span> a <i>true</i>. La función <span style="font-family: Courier New, Courier, monospace;">retrieve_user()</span> la veremos después.</div>
<br />
Creamos ahora unos sencillos métodos <i>get/set</i> para las propiedades:<br />
<br />
<pre class="brush: php; first-line:291">function set_password($password) {
$this->password = $password;
}
function get_password() {
return $this->password;
}
function get_token() {
return $this->httputils->getCSRFToken();
}
function get_logged_in() {
return $this->logged_in;
}
function set_logged_in($logged_in) {
$this->logged_in = $logged_in;
}
</pre>
<br />
<div style="text-align: justify;">
El único punto que no resulta evidente es la función <span style="font-family: Courier New, Courier, monospace;">get_token</span>. El control <b>HTTPUtilities </b>se encarga de gestionar los <i>tokens <b>CSRF</b></i><b> </b>así que esta función sólo necesita llamar a <b>HTTPUtilities</b>.</div>
<br />
Sigamos con la función de <i>login</i>:<br />
<br />
<pre class="brush: php; first-line:141;">function login($username, $password) {
$db = DB::get_instance();
$sql = $db->prepare("SELECT id, username, password, salt FROM user WHERE username = ?");
$sql->bind_param('s', $username);
if(!$sql->execute()) {
$this->error_list[] = "Could not log in.";
$sql->close();
return false;
}
$sql->store_result();
if($sql->num_rows() != 1) {
$this->error_list[] = "Could not log in.";
$sql->free_result();
return false;
}
$sql->bind_result($user_id, $username, $stored_pass, $salt);
$sql->fetch();
$sql->free_result();
$sql->close();
if($this->hash_pass($password, $salt) != $stored_pass) {
echo("<br>password = $password<br>salt = $salt<br>stored_pass = $stored_pass<br>hashed_pass = " . $this->hash_pass($password,$salt) . "<br>");
$this->error_list[] = "Invalid password.";
return false;
}
$this->user_id = $user_id;
$this->username = $username;
$this->password = $stored_pass;
$this->salt = $salt;
if(!$this->create_user_session()) {
return false;
}
return true;
}
</pre>
<div>
<br /></div>
<div>
<div style="text-align: justify;">
Lo primero que notamos es que, si se ha proporcionado un nombre de usuario válido, leemos toda su información en la base de datos. Esto se debe al uso del campo <span style="font-family: Courier New, Courier, monospace;">salt</span>. Necesitamos <i>hashear </i>la clave que el usuario ha escrito para poderla comparar con la almacenada. También hemos cambiado ligeramente la comprobación de <span style="font-family: Courier New, Courier, monospace;">num_rows</span>. Sabemos que la consulta no debe devolver más de una fila, así que lo verificamos explícitamente. A continuación <i>hasheamos </i>la clave suministrada y la comparamos con la de la base de datos. Si coinciden, creamos el usuario e iniciamos la sesión.</div>
</div>
<div>
<br /></div>
<pre class="brush: php; first-line:194;">private function create_user_session() {
session_start();
if(!session_regenerate_id(true)) {
$this->error_list[] = "Could not create user session. Please try again";
return false;
}
$this->httputils->setCSRFToken();
$_SESSION['user_id'] = $this->user_id;
$_SESSION['expire_time'] = time() + $this->expire_time;
return true;
}
</pre>
<div>
<br /></div>
<div>
<div style="text-align: justify;">
En las versiones anteriores todo lo que hacíamos era inicializar <span style="font-family: Courier New, Courier, monospace;">user_id</span> y <span style="font-family: Courier New, Courier, monospace;">username</span>. Ahora, cuando un usuario se conecta, regeneramos cualquier <i>id </i>de sesión y generamos un <i>token <b>CSRF</b></i><b> </b>con el control <span style="font-family: Courier New, Courier, monospace;">httputils</span> (que será pasado a cualquier <i>request </i>del usuario para prevenir ataques). No guardamos el nombre del usuario porque ya lo tenemos en la base de datos. Terminamos estableciendo el tiempo de expiración de la sesión.</div>
</div>
<div>
<br /></div>
<div>
La función <span style="font-family: Courier New, Courier, monospace;">check_user_session</span> también ha ganado en complejidad:</div>
<div>
<br /></div>
<pre class="brush: php; first-line:222;">function check_user_session() {
session_start();
$token = $_GET['token'];
if(!$token) {
$token = $_POST['token'];
}
if(!$token) {
return false;
}
if(!$this->httputils->verifyCSRFToken($token)) {
$this->error_list[] = "Could not verify session.";
return false;
}
if(!$_SESSION['expire_time'] || time() > $_SESSION['expire_time']) {
$this->expire_session();
$this->error_list[] = "Session_expired";
return false;
}
if(!$_SESSION['user_id']) {
$this->error_list[] = "Session not found.";
return false;
} else {
$this->user_id = $_SESSION['user_id'];
}
$this->update_expire_time();
return true;
}
</pre>
<div>
<br />
<div style="text-align: justify;">
Básicamente hemos añadido más comprobaciones. Comprobamos el <i>token <b>CSRF</b></i><b> </b>mediante la función <span style="font-family: Courier New, Courier, monospace;">verifyCSRFToken</span> del control <b>HTTPUtilities</b>. Esta es la función que modificaremos en nuestra librería modificada <b>BlogHTTPUtilitie</b>s (la función original depende de una funcionalidad que aún no se implementó al 100%). Seguidamente comprobamos el tiempo límite de la sesión y la cerramos si se ha superado y el <span style="font-family: Courier New, Courier, monospace;">user_id</span> para terminar. Si todas las comprobaciones salen bien actualizamos el tiempo de la sesión y devolvemos <i>true </i>indicando que el usuario está conectado y la sesión es válida.</div>
</div>
<div>
<br /></div>
<div>
Las funciones para gestionar el límite de tiempo son:</div>
<div>
<br /></div>
<pre class="brush: php; first-line:267;">function expire_session() {
session_destroy();
}
function update_expire_time() {
$_SESSION['expire_time'] = time() + $this->expire_time;
}
</pre>
<div>
<br />
Veamos ahora la función <span style="font-family: Courier New, Courier, monospace;">retrieve_user</span> que llamábamos desde el constructor:</div>
<div>
<br /></div>
<pre class="brush: php; first-line:311;">private function retrieve_user() {
if(!$this->user_id) {
$this->error_list[] = "No user to retrieve!";
return false;
}
$db = DB::get_instance();
$sql = $db->prepare("SELECT username, password FROM user WHERE id = ?");
$sql->bind_param('i', $this->user_id);
if(!$sql->execute()) {
$this->error_list[] = "Could not retrieve user.";
$sql->close();
return false;
}
$sql->store_result();
if($sql->num_rows() != 1) {
$this->error_list[] = "Could not retrieve user";
$sql->free_result();
$sql->close();
return false;
}
$sql->bind_result($this->username, $this->password);
$sql->fetch();
$sql->free_result();
$sql->close();
return true;
}
</pre>
<div>
<br /></div>
<div>
<div style="text-align: justify;">
Sencilla, igual a las que ya vimos en las anteriores unidades. Ahora necesitamos una manera de crear nuevos usuarios con su clave <i>hasheada </i>así que implementamos una función <span style="font-family: Courier New, Courier, monospace;">write</span>:</div>
</div>
<div>
<br /></div>
<pre class="brush: php; first-line:343;">function write() {
$db = DB::get_instance();
if(!$this->salt) {
$this->gen_salt();
}
$hashed_pass = $this->hash_pass($this->password, $this->salt);
$sql = $db->prepare("INSERT INTO user (username, password, salt) VALUES (?, ?, ?)");
$sql->bind_param('sss', $this->username, $hashed_pass, $this->salt);
if(!$sql->execute()) {
$this->error_list[] = "Could not write user, please try again.";
$sql->close();
return false;
}
}
</pre>
<div>
<br /></div>
<div>
Y para terminar, las funciones <span style="font-family: Georgia, Times New Roman, serif;">salt </span>y <span style="font-family: Courier New, Courier, monospace;">hash </span>para codificar nuestras claves:</div>
<div>
<br /></div>
<pre class="brush: php; first-line:283;">private function gen_salt() {
$this->salt = rand(1,100000) . time() . $this->username . rand(1,100000);
}
private function hash_pass($password, $salt) {
return md5($password.$salt);
}
</pre>
<div>
<br /></div>
<div>
<div style="text-align: justify;">
Modificamos ahora ligeramente la función <span style="font-family: Courier New, Courier, monospace;">verifyCSRFToken</span> en <b>BlogHTTPUtilites </b>para gestionar los <i>tokens</i>:</div>
</div>
<div>
<br /></div>
<pre class="brush: php; first-line:153;">public function verifyCSRFToken($token) {
if(!$this->getCSRFToken() == $token) {
throw new IntrusionException('Authentication failed.', 'Possibly forged HTTP request without proper CSRF token detected.');
return false;
}
return true;
}
</pre>
<div>
<br /></div>
<div>
<div style="text-align: justify;">
Y eso es todo. Hay otros cambios para la gestión de <i>tokens </i>y sesiones pero son muy simples y no se van a tratar aquí. Cuando se <i>loggea </i>un usuario debemos añadir su <i>token <b>CSRF</b></i><b> </b>a cualquier enlace y también comprobamos la propiedad <span style="font-family: Courier New, Courier, monospace;">logged_in</span> en vez de acceder directamente a las variables.</div>
</div>
<div>
<br /></div>
<div>
<div style="text-align: justify;">
El último punto a tratar es referente a <b>post.php</b>. En su versión original se asume que si un usuario entra en esta página es porque está <i>loggeado</i>. Evidentemente eso significa que cualquiera que acceda puede <i>postear </i>lo que quiera, por lo tanto debemos comprobar la sesión de usuario antes de <i>postear</i>. El codigo de esta última entrega puede <a href="https://googledrive.com/host/0B0RWiOy4QH6XMnRYbDM0R3g2MzQ/EsapiTest.04.rar">descargarse aquí</a>.</div>
</div>
<div>
<br />
<span style="font-size: x-small;"><b>Anterior</b>: <a href="http://codementia.blogspot.com.es/2013/11/owasp-esapi-php-accesos-la-base-de-datos.html">OWASP ESAPI PHP: Accesos a la base de datos</a></span><br />
<span style="font-size: x-small;"><b>Enlaces</b>: <a href="http://www.eslomas.com/2011/09/proteccion-anti-csrf-con-tokens-en-php/" target="_blank">Protección anti CSRF con tokens en PHP</a></span></div>
Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-61710619406059609062013-11-16T13:36:00.000+01:002013-11-17T15:57:40.470+01:00OWASP ESAPI PHP: Accesos a la base de datos<div style="text-align: justify;">
En esta entrega vamos a tratar cómo defendernos de ataques <b>SQL <i>injection</i></b>. Hay varias maneras de intentar protegerse ante ataques de este tipo, una de ellas es con el <b>Encoder </b>de <b>ESAPI</b>, pero si vemos la documentación encontraremos la siguiente advertencia:</div>
<br />
<blockquote class="tr_bq" style="text-align: justify;">
<i>No se recomienda este método. La aproximación adecuada es usar la interface PreparedStatement. Si, por alguna razón, su uso no es posible, entonces este método es una solución menos mala.</i></blockquote>
<br />
<div style="text-align: justify;">
Así pues vamos a olvidarnos temporalmente de <b>ESAPI </b>para centrarnos en los <b><a href="http://php.net/manual/en/pdo.prepared-statements.php" target="_blank">PreparedStatement</a></b> (parte de la librería <i>mysqli </i>de <b>PHP</b>). Su uso nos permite separar totalmente los datos de las sentencias <b>SQL </b>con lo que las consultas se vuelven mucho más seguras. Su implementación no es nada difícil, sólo requiere pensar de manera ligeramente distinta.</div>
<a name='more'></a><br />
<br />
Empecemos por modificar nuestra clase <b>DB </b>para poder usar <i>mysqli</i>:<br />
<br />
<pre class="brush: php; first-line:26;">private $host = "localhost";
private $username = "insecureapp";
private $password = "supersecretpw";
private $db_name = "insecure";
private static $instance;
private function __construct() {
$this->connection = new mysqli($this->host, $this->username, $this->password, $this->db_name);
}
static function get_instance() {
if(!self::$instance) {
self::$instance = new DB;
}
return self::$instance->connection;
}
</pre>
<br />
<div style="text-align: justify;">
Puede verse que se ha cambiado el usuario de la base de datos a otro con menos privilegios (<i>este punto no se sigue en el código modificado</i>). Es importante que dicho usuario tenga el menor nivel de privilegios posible.</div>
<br />
Los pasos básicos para usar un <b>PreparedStatement </b>son:<br />
<br />
<ul>
<li>Preparar la consulta</li>
<li>Enlazar los parámetros</li>
<li>Ejecutar la consulta</li>
</ul>
<br />
Como ejemplo, veamos la función <span style="font-family: Courier New, Courier, monospace;">get_all_content</span> en <b>index.php</b>. La original es:<br />
<br />
<pre class="brush: php; first-line:56;">function get_all_content() {
$db = DB::get_instance();
$sql = "SELECT id FROM content order by date_created";
$result = $db->query($sql);
while($row=$db->fetch_assoc($result)) {
$content_arr[] = new Content($row['id']);
}
return $content_arr;
}
</pre>
<br />
y la nueva, usando <b>PreparedStatement</b>, es:<br />
<br />
<pre class="brush: php; first-line:56;">function get_all_content() {
$db = DB::get_instance();
$sql = $db->prepare("SELECT id, user_id, title, content, date_created FROM content ORDER BY date_created");
$sql->execute();
$sql->bind_result($id, $user_id, $title, $content, $date_created);
while($sql->fetch()) {
$post = new Content();
$post->set_content_id($id);
$post->set_user_id($user_id);
$post->set_title($title);
$post->set_content($content);
$post->set_date_created($date_created);
$content_arr[] = $post;
}
$sql->close();
return $content_arr;
}
</pre>
<br />
<div style="text-align: justify;">
Preparamos la consulta con la función <span style="font-family: Courier New, Courier, monospace;">prepare </span>y la ejecutamos (<span style="font-family: Courier New, Courier, monospace;">execute</span>). A continuación enlazamos el resultado mediante <span style="font-family: Courier New, Courier, monospace;">bind_result</span> con nuestras variables. El bucle controlado por <span style="font-family: Courier New, Courier, monospace;">fetch </span>asigna los valores de cada columna a las variables enlazadas. Una vez leído el resultado cerramos la consulta. La misma operación la realizamos en la función <span style="font-family: Courier New, Courier, monospace;">get_all_comments</span>:</div>
<div style="text-align: justify;">
<br /></div>
<pre class="brush: php; first-line:87;">function get_all_comments($content_id) {
$db = DB::get_instance();
$sql = $db->prepare("SELECT id, comment, content_id, date_created FROM comments WHERE content_id = ? order by date_created");
$sql->bind_param('i', $content_id);
$sql->execute();
$sql->bind_result($id, $comment_body, $content_id, $date_created);
while($sql->fetch()) {
$comment = new Comment();
$comment->set_comment_id($id);
$comment->set_comment($comment_body);
$comment->set_content_id($content_id);
$comment->set_date_created($date_created);
$comment_arr[] = $comment;
}
$sql->close();
return $comment_arr;
}
</pre>
<br />
<div style="text-align: justify;">
Veamos ahora nuestras tres clases. Empecemos por <b>Content.php</b>, en la que usamos consultas <b>SQL </b>en dos funciones: <span style="font-family: Courier New, Courier, monospace;">retrieve_content</span> y <span style="font-family: Courier New, Courier, monospace;">write</span>:</div>
<div style="text-align: justify;">
<br /></div>
<pre class="brush: php; first-line:218;">private function retrieve_content($content_id) {
$db = DB::get_instance();
$sql = $db->prepare("SELECT id, user_id, title, content, date_created FROM content WHERE id = ?");
$sql->bind_param('i', $content_id);
if(!$sql->execute()) {
$this->error_list[] = "Could not retrieve content.";
$sql->close;
return false;
}
if(!$sql->num_rows() == 0) {
$this->error_list[] = "Content not found.";
$sql->close;
return false;
}
$sql->bind_result($this->content_id, $this->user_id, $this->title, $this->content, $this->date_created);
$sql->fetch();
$sql->close();
return true;
}
</pre>
<br />
<div style="text-align: justify;">
Vemos cómo, si sólo necesitamos una fila de la consulta, la función <span style="font-family: Courier New, Courier, monospace;">bind_result</span> nos permite asignar el resultado a las propiedades de nuestro objeto. También podemos añadir algún tipo de gestión de errores si lo deseamos. Y ahora la función <span style="font-family: Courier New, Courier, monospace;">write</span>:</div>
<div style="text-align: justify;">
<br /></div>
<pre class="brush: php; first-line:246;">function write() {
$db = DB::get_instance();
$sql = $db->prepare("INSERT INTO content (user_id, title, content, date_created) values (?, ?, ?, ?)");
$sql->bind_param('isss', $this->user_id, $this->title, $this->content, date("Y-m-d"));
if(!$sql->execute()) {
$this->error_list[] = "Could not save post, please try again.";
$sql->close();
return false;
}
$sql->close();
return true;
}
</pre>
<br />
<div style="text-align: justify;">
Nada diferente a lo que hemos visto hasta ahora. Pasamos a la clase <b>Comment </b>en la que los cambios son casi idénticos. Las funciones a modificar son <span style="font-family: Courier New, Courier, monospace;">retrieve_comment</span> y <span style="font-family: Courier New, Courier, monospace;">write</span>:</div>
<div style="text-align: justify;">
<br /></div>
<pre class="brush: php; first-line:215;">function retrieve_comment($comment_id) {
$db = DB::get_instance();
$sql = $db->prepare("SELECT id, comment, content_id, date_created from comments where id = ?");
$sql->bind_param('i', $comment_id);
if(!$sql->execute()) {
$this->error_list[] = "Could not retrieve comment";
$sql->close();
return false;
}
$sql->bind_result($this->comment_id, $this->comment, $this->content_id, $this->date_created);
$sql->fetch();
$sql->close();
}
function write() {
$db = DB::get_instance();
$sql = $db->prepare("INSERT INTO comments (comment, content_id, date_created) VALUES (?, ?, ?)");
$sql->band_param("sis", $this->comment, $this->content_id, date("Y-m-d"));
if(!$sql->execute) {
$this->error_list[] = "Could not write comment.";
$sql->close();
return false;
}
$sql->close();
return true;
}
</pre>
<br />
<div style="text-align: justify;">
Y finalmente la clase <b>User</b>. Vamos a implementar también gestión de errores añadiéndole la propiedad <span style="font-family: Courier New, Courier, monospace;">error_list</span> tal como hicimos en las clases <b>Comment </b>y <b>Content</b>:</div>
<div style="text-align: justify;">
<br /></div>
<pre class="brush: php; first-line:21;">private $error_list = null;
</pre>
<br />
y las dos funciones de gestión:<br />
<br />
<pre class="brush: php; first-line:134;">function clear_error_list() {
$this->error_list = null;
}
function get_error_list() {
return $this->error_list;
}
</pre>
<br />
Ahora mejoramos la consulta en la función de <i>login </i>y disponemos de gestión de errores:<br />
<div style="text-align: justify;">
<br /></div>
<pre class="brush: php; first-line:73;">function login($username, $password) {
$db = DB::get_instance();
$sql = $db->prepare("SELECT id, username, password FROM user WHERE username = ? AND password = ?");
$sql->bind_param('ss', $username, $password);
if(!$sql->execute()) {
$this->error_list[] = "Could not login.";
$sql->close();
return false;
}
$sql->store_result();
if($sql->num_rows() == 0) {
$this->error_list[] = "Username or password not found";;
$sql->close();
return false;
}
$sql->bind_result($this->user_id, $this->username, $this->password);
$sql->fetch();
$this->create_user_session();
return true;
}
</pre>
<br />
<div style="text-align: justify;">
Sencillo. Sólo nos queda por modificar los controladores para gestionar los errores que se puedan producir. El código de esta entrega puede <a href="https://googledrive.com/host/0B0RWiOy4QH6XMnRYbDM0R3g2MzQ/EsapiTest.03.rar">descargarse aquí</a>.</div>
<br />
<span style="font-size: x-small;"><b>Anterior</b>: <a href="http://codementia.blogspot.com.es/2013/11/owasp-esapi-php-asegurando-las-entradas.html">OWASP ESAPI PHP: Asegurando las entradas</a></span><br />
<span style="font-family: inherit; font-size: x-small;"><b>Siguiente</b>: <a href="http://codementia.blogspot.com.es/2013/11/owasp-esapi-php-autentificacion-y.html">OWAPS ESAPI PHP: Autentificación y control de sesiones</a></span>Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-69391699126270189822013-11-14T14:18:00.001+01:002013-11-16T13:39:27.877+01:00OWASP ESAPI PHP: Asegurando las entradas<div style="text-align: justify;">Si probamos la aplicación desarrollada en la entrada anterior observamos que es vulnerable a un simple ataque SQL Injection como <span style="font-family: Courier New, Courier, monospace;">"1' AND 1='1"</span> para logearnos o XSS como <span style="font-family: Courier New, Courier, monospace;">"<script>javascript:alert(1);</script>"</span> tanto en los <i>posts </i>como en los comentarios. En esta entrega vamos pues a esterilizar (<i>sanitize</i>) las entradas del usuario.</div><div style="text-align: justify;"><br />
Usaremos las clases de referencia de <b>ESAPI </b>(<i>/src/reference</i>) siempre que podamos ya que son sencillas implementaciones de las interfaces definidas en <i>/src</i> y suficientes para nuestros propósitos con pequeños cambios. Empezamos modificando ligeramente la clase <b>DefaultSecurityConfiguration</b> ( <i>/src/reference/DefaultSecurityConfiguration.php</i>) para arreglar un pequeño <i>bug </i>que el equipo de <b>OWASP </b>no ha considerado necesario arreglar desde que se escribió el artículo original. Abrimos el archivo y ejecutamos un buscar + sustituir cambiando todos los <span style="font-family: Courier New, Courier, monospace;">$this->logSpecial</span> por <span style="font-family: Courier New, Courier, monospace;">$this->_logSpecial</span>. La función <span style="font-family: Courier New, Courier, monospace;">_logSpecial</span> imprime un mensaje de error en pantalla y nos interesa que lo haga en un <i>log </i>así que la cambiamos por:</div><pre class="brush: php; first-line:208;">private function _logSpecial($msg)
{
ESAPI::getAuditor('DefaultSecurityConfiguration')->warning(Auditor::SECURITY, false, $msg);
}
</pre><br />
<a name='more'></a><br />
<br />
<b>Adaptando la clase DefaultValidator</b><br />
<b><br />
</b> <br />
<div style="text-align: justify;">La clase <b style="text-align: justify;">DefaultValidator </b>(<i style="text-align: justify;">/src/reference/DefaultValidator.php</i>) se usa para aplicar criterios de comprobación a nuestro datos. La documentación puede encontrarse en <i style="text-align: justify;">/src/Validator.php</i> o en <a href="http://owasp-esapi-php.googlecode.com/svn/trunk_doc/latest/index.html" style="text-align: justify;" target="_blank">phpdocs</a>. Para adaptarla a nuestros requerimientos vamos a modificar la manera en que gestiona las excepciones. Cuando una comprobación falla se lanza una <b style="text-align: justify;">ValidationException </b>que contiene un mensaje seguro para mostrar al usuario. La clase <b style="text-align: justify;">DefaultValidator </b>atrapa la excepción y devuelve <i style="text-align: justify;">false</i>. Por ello, si queremos enviar un mensaje al usuario advirtiendo de algún problema en su entrada, no podemos especificar el motivo de dicho problema. Por ejemplo, si comprobamos un valor entero para verificar que se halle entre 1 y 20 y el resultado es <i style="text-align: justify;">false</i>, no tenemos forma de saber si el usuario a escrito 0, 50 o 'hola'. Para solucionarlo vamos a capturar la excepción y podremos crear nuestros propios mensajes de error.</div><div style="text-align: justify;"><span style="text-align: justify;"><br />
</span> <span style="text-align: justify;">Hacemos pues una copia del archivo <i>DefaultValidator.php</i>, lo renombramos a <i>BlogValidator.php</i> y añadimos una nueva propiedad privada:</span><br />
<span style="text-align: justify;"><br />
</span></div><pre class="brush: php; first-line:45;">private $_lastError = null;
</pre><div style="text-align: justify;"><span style="text-align: justify;"><br />
</span> <span style="text-align: justify;">A continuación añadimos dos funciones: una para leer el último mensaje de error generado y otra para limpiar la propiedad <span style="font-family: Courier New, Courier, monospace;">$_lastError</span>:</span><br />
<span style="text-align: justify;"><br />
</span></div><pre class="brush: php; first-line:628;">/**
* Clears the last error
*
* @return does not return a value
*/
public function clearLastError() {
$this->_lastError = null;
}
/**
* Gets the lastError property
*
* @return string lastError property
*/
public function getLastError() {
return $this->_lastError;
}
</pre><span style="text-align: justify;">Para terminar, modificamos todo los fragmentos de código donde se atrapa la excepción para poder leer nuestro mensaje. Sustituimos todos los:<br />
</span><br />
<pre class="brush: php;">catch ( Exception $e)
{
return false;
}
</pre><span style="text-align: justify;">por<br />
</span><br />
<pre class="brush: php;">catch ( Exception $e )
{
$this->lastError = $e->getUserMessage();
return false;
}
</pre><span style="text-align: justify;"><br />
<b>Validando los <i>posts </i>del usuario</b><br />
<br />
</span><br />
<div style="text-align: justify;"><span style="text-align: justify;">Ahora vamos a implementar la clase <b>BlogValidator </b>en la clase <b>Content </b>para validar los <i>posts </i>a nuestro <i>blog</i>. Lo primero es incluir los archivos <i>ESAPI.php</i> y <i>BlogValidator.php</i> en <i>Content.php</i>. Para ello he creado el archivo <i>/lib/conf.php</i> en la aplicación. Definimos en él dos constantes, una con la ruta a los módulos <b>ESAPI </b>(donde está <i>ESAPI.php</i>) y otra con la ruta completa del fichero <i>ESAPI.xml</i>:</span></div><pre class="brush: php; first-line:1;">define("ESAPI_SRC_path", "[ruta relativa a OWASP_ESAPI]/src");
define("ESAPI_XML_path", "[ruta absoluta a OWASP_ESAPI]/test/testresources/ESAPI.xml");
</pre><span style="text-align: justify;">y en <i>Content.php</i> añadimos:<br />
</span><br />
<pre class="brush: php; first-line:24;">require("lib/conf.php");
require_once(ESAPI_SRC_path . "/ESAPI.php");
require_once(ESAPI_SRC_path . "/reference/BlogValidator.php");
</pre><div style="text-align: justify;"><span style="text-align: justify;">A continuación agregamos nuevas propiedas a <b>Content </b>para gestionar los controles <b>ESAPI </b>y una propiedad <span style="font-family: Courier New, Courier, monospace;">$error_list</span> para los mensajes generados por <b>ValidationException</b>:</span></div><pre class="brush: php; first-line:35;">private $content_id = null;
private $user_id = null;
private $title = null;
private $content = null;
private $date_created = null;
private $esapi = null;
private $encoder = null;
private $validator = null;
private $error_list = null;
</pre><div style="text-align: justify;"><span style="text-align: justify;">Ahora ya podemos inicializar los objetos <b>ESAPI </b>en nuestro constructor. Usamos los métodos <i>get </i>de la librería para establecer los dos tipos de controles que necesitamos en esta entrega. También modificamos ligeramente el constructor para facilitar la validación: no se van a pasar todos los valores de inicialización sino que deberán usarse métodos <i>get/set</i>:</span></div><pre class="brush: php; first-line:54;">function __construct($content_id = '') {
$this->esapi = new ESAPI(ESAPI_XML_path);
ESAPI::setEncoder(new DefaultEncoder());
ESAPI::setValidator(new BlogValidator());
$this->encoder = ESAPI::getEncoder();
$this->validator = ESAPI::getValidator();
if($content_id) {
$this->retrieve_content($content_id);
}
}
</pre><div style="text-align: justify;"><span style="text-align: justify;">Lo que hacemos es instanciar una clase <b>ESAPI </b>y obtener sus objetos <b>Encoder </b>y <b>Validator</b>. Aún no hemos hablado del <b>Encoder</b>, de momento es suficiente saber que nos permite <a href="http://es.wikipedia.org/wiki/Programaci%C3%B3n_defensiva#Canonicalizar" target="_blank">canonicalizar</a> la entrada del usuario antes de validarla. Ahora debemos añadir dos métodos para gestionar la propiedad <span style="font-family: Courier New, Courier, monospace;">$error_list</span>, uno para limpiarla y otro para leerla, tal como hicimos en <b>BlogValidator</b>:</span></div><pre class="brush: php; first-line:222;">function clear_error_list() {
$this->error_list = null;
}
function get_error_list() {
return $this->error_list;
}
</pre><div style="text-align: justify;"><span style="text-align: justify;">Pasamos a ocuparnos de los métodos <i>setter</i>. Algunos de los métodos definidos no son usados en la aplicación, incluso pueden eliminarse sin que afecte a su funcionamiento, pero ello no significa que debamos hacerlo o ignorarlos a la hora de asegurarla. Puede que más adelante necesitemos de alguno para añadir nuevas funcionalidades, o, si la modifica otro programador, quiera saber qué puede hacer. Si pensamos ahora en lo que podemos necesitar será más fácil modificarlo en el futuro. Disponemos pues de las siguientes funciones:</span></div><ul><li><span style="font-family: Courier New, Courier, monospace; text-align: justify;">set_content_id</span></li>
<span style="text-align: justify;">
<li><span style="font-family: Courier New, Courier, monospace;">set_user_id</span></li>
<li><span style="font-family: Courier New, Courier, monospace;">set_title</span></li>
<li><span style="font-family: Courier New, Courier, monospace;">set_content</span></li>
<li><span style="font-family: Courier New, Courier, monospace;">set_date_created</span></li>
</span></ul><span style="text-align: justify;">Existe un importante punto que hemos estado obviando hasta aquí y que, desafortunadamente, es común a la mayoría de programadores: nunca se piensa realmente en los tipos de valores que nuestras propiedades pueden contener. Por ejemplo, establecemos un límite de 140 caracteres para el título de los <i>posts</i>. ¿Es razonable? ¿Cómo saberlo? ¿Debemos permitir código <b>javascript </b>en los comentarios? Probablemente no, pero ¿nos hemos parado a pensarlo? El único lugar dónde están definidas las propiedades de nuestro objeto es en la base de datos. Si pensamos en estas cosas mientras la diseñamos será más fácil el trabajo posterior.</span><br />
<span style="text-align: justify;"><br />
</span><br />
<div style="text-align: justify;"><span style="text-align: justify;">Empecemos por <span style="font-family: Courier New, Courier, monospace;">content_id</span>. Si vemos su definición en la base de datos vemos que es un entero con signo, clave primaria (por lo que debe ser única), no puede ser nulo y que <b>MySQL </b>la incrementa por nosotros. Consideremos ahora qué valores debe contener y modificaremos la base de datos en consecuencia.</span></div><span style="text-align: justify;"><br />
</span><br />
<div style="text-align: justify;"><span style="text-align: justify;">Un entero con signo en <b>MySQL </b>tiene un rango de -2,147,483,648 a 2,147,438,648. Excesivo para nuestras necesidades asi que podriamos redefinirlo como entero sin signo y <i>smallint</i>. Ya que es un identificador único para nuestros datos nos conviene que sea una clave primaria y también nos interesa que se autoincremente para no preocuparnos nosotros de generar ids únicos, así que modificamos la definición de <span style="font-family: Courier New, Courier, monospace;">content_id</span> en la base de datos:</span></div><pre class="brush: php;">alter table content change column id id smallint signed auto_increment;
</pre><span style="text-align: justify;">y establecemos su validación en nuestro método <i>setter</i>:<br />
</span><br />
<pre class="brush: php; first-line:75;">function set_content_id($content_id) {
$content_id = $this->canonicalize($content_id);
if($this->validator->isValidNumber("Content ID", $content_id, 1, 65535, false)) {
$this->content_id = $content_id;
} else {
$this->error_list[] = $this->validator->getLastError();
}
}
</pre><span style="text-align: justify;"> <br />
</span><br />
<div style="text-align: justify;"><span style="text-align: justify;">La única parte del nuevo método que no hemos visto aún es la función <i>canonicalize</i>. Esta función ejecuta una canonicalización de los datos de forma que quedan en su estado más básico antes de validarlos. Básicamente, si un usuario introduce código, esta función lo reduce a un formato que puede ser comprobado. El codificador lanza una <b>IntrusionException </b>si se detectan ciertos problemas, así que debemos atrapar esta excepción y gestionarla. La función <i>canonicalize </i>queda pues como:</span></div><span style="text-align: justify;"> </span><br />
<pre class="brush: php; first-line:88;">function canonicalize($input) {
try {
$input = $this->encoder->canonicalize($input);
} catch (IntrusionException $e) {
echo($e->getUserMessage());
exit();
}
return $input;
}
</pre><span style="text-align: justify;"><br />
</span><br />
<div style="text-align: justify;"><span style="text-align: justify;">Hacemos lo mismo con el resto de las propiedades: <span style="font-family: Courier New, Courier, monospace;">user_id</span> debe ser tratada como <span style="font-family: Courier New, Courier, monospace;">content_id</span> (excepto clave primaria y autoincremento) ya que se usa para referenciar los <i>ids </i>de cada tabla:</span></div><span style="text-align: justify;"> </span><br />
<pre class="brush: php;">alter table content change column user_id user_id smallint signed;
alter table user change column id id smallint signed auto_increment;
</pre><span style="text-align: justify;">y la nueva función <span style="font-family: Courier New, Courier, monospace;">set_user_id</span> queda como:<br />
</span><br />
<pre class="brush: php; first-line:111;">function set_user_id($user_id) {
$user_id = $this->canonicalize($user_id);
if($this->validator->isValidNumber("User ID", $user_id, 1, 10000, false)) {
$this->user_id = $user_id;
} else {
$this->error_list[] = $this->validator->getLastError();
}
}
</pre><span style="text-align: justify;"> <br />
</span><br />
<div style="text-align: justify;"><span style="text-align: justify;">La propiedad <span style="font-family: Courier New, Courier, monospace;">title</span><i> </i>la hemos definido como <i>string </i>de 140 caracteres que puede ser nulo. Puede que 140 sea razonable para un título pero no nos interesa que sea <b>NULL</b>:</span></div><pre class="brush: php;">alter table content change column title title varchar(140) not null;
</pre><div style="text-align: justify;"><span style="text-align: justify;">Cuando tratamos con números es bastante simple comprobar que la entrada es válida, pero con texto se complica ligeramente. Hemos de considerar tambíén la salida. Para nuestro <i>blog </i>establecemos la siguiente regla: el <i>post </i>no puede contener código <b>HTML </b>ni <b>javascript</b> en su título. Esto significa que debemos comprobar si existe una entrada, que ésta no exceda la longitud máxima y que no contenga ningún caracter no imprimible. Salvo estas limitaciones el usuario puede escribir lo que desee y los objectos de codificación y esterilización se encargarán de ello. Podrá intentar introducir código malicioso, pero vamos a manipularlo de forma segura.</span></div><span style="text-align: justify;">La función <i>setter </i>para <span style="font-family: Courier New, Courier, monospace;">title </span>queda pues como:<br />
</span><br />
<pre class="brush: php; first-line:134;">function set_title($title) {
$title = $this->canonicalize($title);
if($this->validator->isValidPrintable("Post Title", $title, 140, false)) {
$this->title = $title;
} else {
$this->error_list[] = $this->validator->getLastError();
}
}
</pre><span style="text-align: justify;"> <br />
</span><br />
<div style="text-align: justify;"><span style="text-align: justify;">Con la propiedad <span style="font-family: Courier New, Courier, monospace;">content </span>hacemos exactamente lo mismo, modificamos la base de datos para que no pueda ser nulo:</span></div><pre class="brush: php;">alter table content change column content content text not null;
</pre><span style="text-align: justify;"><br />
</span><br />
<div style="text-align: justify;"><span style="text-align: justify;">y modificamos la función <i>setter </i>para asegurarnos que sólo contiene caracteres imprimibles:</span></div><pre class="brush: php; first-line:157;">function set_content($content) {
$content = $this->canonicalize($content);
if($this->validator->isValidPrintable("Post Content", $content, 65535, false)) {
$this->content = $content;
} else {
$this->error_list[] = $this->validator->getLastError();
}
}
</pre><span style="text-align: justify;"><br />
</span><br />
<div style="text-align: justify;"><span style="text-align: justify;">La última función <i>setter </i>que cambiamos es <span style="font-family: Courier New, Courier, monospace;">date_created</span>. No es necesario modificar la base de datos en este caso:</span></div><span style="text-align: justify;"> <br />
</span><br />
<pre class="brush: php; first-line:180;">function set_date_created($date_created) {
$date_created = $this->canonicalize($date_created);
if($this->validator->isValidDate("Content date created", $date_created, "Y-m-d H:i:s", false)) {
$this->date_created = $date_created;
} else {
$this->error_list[] = $this->validator->getLastError();
}
}
</pre><span style="text-align: justify;"><br />
<br />
</span><br />
<div style="text-align: justify;"><span style="text-align: justify;">Usamos la función <span style="font-family: Courier New, Courier, monospace;">isValidDate </span>para comprobar el formato de la fecha introducida. El formato es el mismo que el de la función <span style="font-family: Courier New, Courier, monospace;">date </span>de <b>PHP</b>.</span></div><span style="text-align: justify;"> </span><br />
<div><span style="text-align: justify;"><span style="text-align: justify;"><br />
</span></span></div><div style="text-align: justify;"><span style="text-align: justify;"><b>Recodificando las salidas</b></span></div><div style="text-align: justify;"><span style="text-align: justify;"><br />
</span></div><div style="text-align: justify;"><span style="text-align: justify;">Hasta aquí hemos visto cómo usar <b>Encoder </b>para canonicalizar las entradas, ahora veremos cómo usarlo para codificar las salidas de los datos y poderlos mostrar con seguridad. La aplicación es muy vulnerable a ataques <b>XSS </b>asi que tratando los datos de salida antes de mostrarlos podremos reducir los riesgos.</span></div><div style="text-align: start;"><span style="text-align: justify;"><br />
</span></div><div style="text-align: justify;"><span style="text-align: justify;">En nuestra aplicación sólo hay un lugar en el que se envían los datos <i>posteados </i>por el usuario a la pantalla (al navegador): el fichero <b>index.php</b>. Hemos establecido que no vamos a permitir que el usuario añada código <b>HTML </b>ni <b>javascript </b>funcional a sus entradas, así que vamos a recodificarlas para poderlas mostrar sin problemas. Básicamente, en cualquier lugar dentro de <b>index.php</b> dónde se envíe al navegador algún dato del usuario, lo modificamos previamente mediante <b>encodeForHTML</b> de la clase <b>DefaultEncoder</b>. El único lugar donde se generan las salidas es en la función <span style="font-family: Courier New, Courier, monospace;">construct_content_display:</span></span><br />
<span style="text-align: justify;"><span style="font-family: Courier New, Courier, monospace;"><br />
</span></span></div><pre class="brush: php; first-line:121;">function construct_content_display($content_arr) {
$output = '';
$encoder = ESAPI::getEncoder();
for($i=0;$i<count($content_arr);$i++) {
$output .= "<p><b>" . $encoder->encodeForHTML($content_arr[$i]->get_title()) . "</b></p>";
$output .= "<br><p>" . $encoder->encodeForHTML($content_arr[$i]->get_content()) . "</p>";
$comment_arr = get_all_comments($content_arr[$i]->get_content_id());
for($j=0;$j<count($comment_arr);$j++) {
$output .= "<br><p>" . $encoder->encodeForHTML($comment_arr[$j]->get_comment()) . " - " . $encoder->encodeForHTML($comment_arr[$j]->get_date_created()) . "</p>";
}
$output .= "<br><a href=\"comment.php?content_id=" . $content_arr[$i]->get_content_id() . "\">Comment</a>";
}
return $output;
}
</pre><div style="text-align: justify;"><span style="text-align: justify;"><br />
</span> <span style="text-align: justify;">Para terminar, modificamos de la misma forma la clase <b>Comment </b>(<i>lib/Comment.php</i>) y los controladores <b>post.php</b> y <b>comment.php</b> con sus respectivos <b>html</b>. El código de esta entrega puede <a href="https://googledrive.com/host/0B0RWiOy4QH6XMnRYbDM0R3g2MzQ/EsapiTest.02.rar" target="_blank">descargarse aquí</a>.</span><br />
<span style="text-align: justify;"><br />
</span> <span style="text-align: justify;"><span style="font-size: x-small;"><b>Anterior:</b> <a href="http://codementia.blogspot.com.es/2013/11/owasp-esapi-php-introduccion.html">OWASP ESAPI PHP: Introducción</a></span></span><br />
<span style="font-size: x-small; text-align: justify;"><b>Siguiente</b>: <a href="http://codementia.blogspot.com.es/2013/11/owasp-esapi-php-accesos-la-base-de-datos.html">OWASP ESAPI PHP: Accesos a la base de datos</a></span></div>Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-22168429463244473692013-11-13T23:35:00.002+01:002013-11-17T17:58:54.029+01:00OWASP ESAPI PHP: Introducción<div style="text-align: justify;">
Hace tiempo estuve trasteando con la librería de seguridad <a href="https://www.owasp.org/index.php/Category:OWASP_Enterprise_Security_API#tab=Downloads" target="_blank">OWASP ESAPI</a> para <b>PHP </b>pero lo dejé por falta de tiempo y por la dificultad que tuve en encontrar información para usarla. Retomo ahora el tema adaptando un artículo elaborado en 2010 por <a href="http://jackwillk.blogspot.com.es/2010/06/using-owasp-php-esapi-part-1.html" target="_blank">Jack Kowalsky</a> en el que hace una introducción al uso de la librería. He actualizado el código original a <b>PHP 5.4.3</b>.</div>
<div style="text-align: justify;">
<b>OWASP ESAPI</b> (<b>Enterprise Security API</b>) es una librería de seguridad implementada en varios formatos para desarrollar aplicaciones de bajo riesgo con la que se dispone de varios módulos que podemos utilizar y/o modificar adaptándolos a nuestras necesidades. Aparte de para <b>PHP</b>, existen versiones para <b>Java </b>(quizás la más documentada), <b>.NET</b>, <b>ASP</b>, <b>Codlfusion</b>, <b>Python </b>y <b>Javascript</b>.</div>
<a name='more'></a><br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlv7Sg-lfBXW3ss-14wu8J75CWWItyfitOVJaWQT2WfsCxDtXvUSyXjCGppAX7eNykkPaHEvGl85pB8EEy41wD9WhHO-_06uelR0uutx7Ax2ChylCpsdd7Ej4BbksMVeGr22UtHBlEFbO9/s1600/owaspesapi.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlv7Sg-lfBXW3ss-14wu8J75CWWItyfitOVJaWQT2WfsCxDtXvUSyXjCGppAX7eNykkPaHEvGl85pB8EEy41wD9WhHO-_06uelR0uutx7Ax2ChylCpsdd7Ej4BbksMVeGr22UtHBlEFbO9/s1600/owaspesapi.jpg" height="320" width="173" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Módulos ESAPI</td></tr>
</tbody></table>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Dividiré este post en varias entregas, al igual que el original:</div>
<br />
<div style="text-align: justify;">
I. Crearemos un simple sistema de <i>blogging </i>lleno de vulnerabilidades.</div>
<div style="text-align: justify;">
II. Implementamos un primer nivel de seguridad usando <b>ESAPI</b>.</div>
<div style="text-align: justify;">
III. Aseguramos los accesos a la base de datos</div>
<div style="text-align: justify;">
IV. Aseguramos las sesiones de los usuarios</div>
<br />
<div style="text-align: justify;">
El código <a href="https://googledrive.com/host/0B0RWiOy4QH6XMnRYbDM0R3g2MzQ/EsapiTest.01.rar" target="_blank">puede descargarse aquí</a> y para que funcione como debe necesitamos crear una base de datos:</div>
<br />
<span style="font-family: Courier New, Courier, monospace;">USE insecure; </span><br />
<span style="font-family: Courier New, Courier, monospace;"> CREATE TABLE user (id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(16), password VARCHAR(16)); </span><br />
<span style="font-family: Courier New, Courier, monospace;"> CREATE TABLE content (id INT AUTO_INCREMENT PRIMARY KEY, user_id INT, title VARCHAR(140), content TEXT, date_created TIMESTAMP); </span><br />
<span style="font-family: Courier New, Courier, monospace;"> CREATE TABLE comments (id INT AUTO_INCREMENT PRIMARY KEY, comment VARCHAR(140), content_id INT, date_created TIMESTAMP);</span><br />
<br />
<div style="text-align: justify;">
Una vez generada la base de datos debemos crear un primer usuario/contraseña dado que el programa en su estado actual no lo permite.</div>
<br />
<div style="text-align: justify;">
El programa se estructura alrededor de 4 clases (en <i>/lib</i>):</div>
<br />
<b>DB</b>: para los accesos a la base de datos<br />
<b>User</b>: para la gestión de las sesiones de usuario<br />
<b>Content</b>: para gestionar el contenido del blog (añadir sólo)<br />
<b>Comment</b>: para gestionar los comentarios (añadir sólo)<br />
<br />
A su vez creamos también 4 controladores y sus correspondientes ficheros <b>HTML</b>:<br />
<br />
<b>index</b>: para mostrar el contenido del <i>blog </i>(<i>index.php</i> e <i>index.html</i>)<br />
<b>login</b>: para abrir sesiones de usuario (<i>login.php</i> y <i>login.html</i>)<br />
<b>post</b>: para <i>postear </i>artículos (<i>post.php</i> y <i>post.html</i>)<br />
<b>comment</b>: para <i>postear </i>comentarios (<i>comment.php</i> y <i>comment.html</i>)<br />
<br />
<div style="text-align: justify;">
Dentro del subdirectorio<i> /lib</i> existe un fichero más: <b>conf.php</b>. Lo he añadido a esta revisión para contener datos comunes a más de un módulo y no tener que andar repitiéndolos, sobretodo en lo referente a rutas de librerías. La primera instrucción que contiene es para evitar que aparezca el <a href="http://www.decapos.com/php-el-problema-del-strict-standards-only-variables-should-be-passed-reference" target="_blank">STRICT STANDARS ERROR</a> en el navegador. También he modificado <b>index.html</b> para permitir <i>logout </i>y facilitar las pruebas. Instalamos en el directorio público de nuestro servidor y probamos cómo funciona.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Si aún no hemos instalado <b>OWASP ESAPI PHP</b> simplemente <a href="https://code.google.com/p/owasp-esapi-php/" target="_blank">lo descargamos</a> y lo copiamos a nuestro servidor, bien en su propio directorio o en el de la aplicación. En un entorno de producción debemos situar el archivo <b>ESAPI.xml</b> (en <i>/test/testresources</i>) fuera del directorio raíz. En este tutorial sin embargo lo usaremos en su situación original.<br />
<br />
<span style="font-size: x-small;"><b>Siguiente:</b> <a href="http://codementia.blogspot.com.es/2013/11/owasp-esapi-php-asegurando-las-entradas.html">OWASP ESAPI PHP: Asegurando las entradas</a></span></div>
Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-89248352629764552272013-10-30T18:53:00.000+01:002013-10-30T19:02:26.752+01:00Practicas Cross-Site Scripting Offline<div style="text-align: justify;">
Hace poco descubrí un enlace en <a href="http://www.cyberhades.com/2013/10/13/16-retos-de-cross-site-scripting-xss/?utm_source=rss&utm_medium=rss&utm_campaign=16-retos-de-cross-site-scripting-xss" target="_blank">CyberHades</a> a una <a href="http://escape.alf.nu/" target="_blank">web donde practicar ataques </a><b><a href="http://escape.alf.nu/" target="_blank">XSS</a> </b>en la que se nos muestra el código a atacar y el resultado de nuestros intentos, consiguiendo pasar cada prueba si logramos generar un <b>javascript:alert(1)</b>. Me pareció interesante, y aún más trastear con ella, así que en mis ratos libres me hice una a medida tomando la original como referencia. Si a alguien le interesa puede descargarla <a href="https://drive.google.com/file/d/0B0RWiOy4QH6XM29HY21kaUZ3YmM/edit?usp=sharing" target="_blank">aquí</a>.</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgd27kAnf1pTm36R6qcPmYQkmWurs8COuUsFpRvzYTI-UqDdqDAKiUaq_tVdV3wFzKbtT0V_fhjJxtBKBzY01iw9e9iIX_dquxuMo01HnJp-GNrjx6jnlR_EbSPpXzsTIR9voBvY8QUOMYb/s1600/Imagen5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgd27kAnf1pTm36R6qcPmYQkmWurs8COuUsFpRvzYTI-UqDdqDAKiUaq_tVdV3wFzKbtT0V_fhjJxtBKBzY01iw9e9iIX_dquxuMo01HnJp-GNrjx6jnlR_EbSPpXzsTIR9voBvY8QUOMYb/s1600/Imagen5.png" height="380" width="640" /></a></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Se muestra cada función en una pestaña tabulada y debajo podemos introducir la cadena de texto de nuestro ataque de la cual se contarán los caracteres. Para ver la salida de la función oprimimos el botón <b>Show </b>y para ejecutarlo oprimimos <b>Exec</b>. Los <i>scripts </i>se ejecutan en un <i>iframe </i>situado al final de la página. Sencillo. El fichero que contiene el código (<b>code.js</b>) es fácil de entender y de modificar a nuestro gusto.<br />
<br />
La he probado únicamente con <b>Chrome </b>corriendo sobre <b>WampServer 2.2</b> para <b>Windows 7 x64</b>.</div>
Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-86867833246440156982013-07-08T23:55:00.000+02:002013-11-14T16:28:11.882+01:00Asegurar un File Upload comprobando la firma de los archivos<div style="text-align: justify;">
Tenemos un simple formulario para elegir un fichero y subirlo al servidor y una pequeña rutina en <b>PHP </b>para verificar que el fichero es del tipo correcto (una imagen <b>gif</b>, <b>jpeg </b>o <b>png</b>) comprobando el <a href="http://es.wikipedia.org/wiki/Multipurpose_Internet_Mail_Extensions" target="_blank">Content-type </a>enviado en la cabecera:</div>
<br />
<pre class="brush: php; first-line:72;"><?php
$valid_mime_types = array(
"image/gif",
"image/png",
"image/jpeg",
"image/pjpeg",
);
if(isset($_POST['upload']))
{
$destination = 'uploads/' . $_FILES['userfile']['name'];
if (in_array($_FILES['userfile']['type'], $valid_mime_types))
{
move_uploaded_file($_FILES['userfile']['tmp_name'], $destination);
echo "<div>Upload succesful: <a href='$destination'>here</a><a href=\"\">×</a></div>";
}
else
{
echo "<div>>Upload failed.<a href=\"\">×</a></div>";
}
}
?>
<form enctype="multipart/form-data" action="index.php" method="POST">
<fieldset>
<legend>Upload</legend>
<input name="userfile" class="small button" type="file" />
<input type="submit" class="small button" name="upload" id="upload" value="Upload" />
</fieldset>
</form>
</pre>
<div style="text-align: justify;">
<br />
<a name='more'></a>La linea <span style="font-family: Courier New, Courier, monospace;">if (in_array($_FILES['userfile']['type'], $valid_mime_types))</span> comprueba si el <b>Content-type</b> es alguno de los que aceptamos y, si la respuesta es positiva, procedemos a cargar el archivo en nuestro servidor y mostramos un mensaje de <b>OK </b>al usuario con un enlace al fichero cargado. El problema a resolver es <u>cómo evitar que nos cuelen archivos de otro tipo falsificando el <b>Content-type</b></u>. La solución que muestro aquí puede ser útil cuando admitimos poca diversidad de tipos como en el caso del ejemplo (sólo <b>gif</b>, <b>jpeg </b>y <b>png</b>). Como sabemos, la mayoría de archivos de datos estandarizados contienen una lista de bytes en un orden determinado en alguna posición fija dentro del archivo: su firma. Se trata de añadir una función <b>PHP </b>que, después de cargar el fichero, compruebe ésta. Si se corresponde con los tipos admitidos se muestra el mensaje de archivo subido al usuario, en caso contrario se muestra error y se elimina el fichero:</div>
<pre class="brush: php; first-line:30">function verificaArchivo($archivo)
{
// Matriz con los datos de cada tipo de archivo:
// offsett al inicio del fichero
// número de bytes
// firma en hexadecimal
$image_data = array (
"jpeg" => array (
"offsett" => 0,
"lon" => 4,
"firma" => "FFD8FFE0",
),
"gif" => array (
"offsett" => 0,
"lon" => 3,
"firma" => "474946",
),
"png" => array (
"offsett" => 0,
"lon" => 4,
"firma" => "89504E47",
),
);
$tipos = array_keys($image_data);
$ok = false;
do
{
if (!is_null($tipo = array_pop($tipos)))
// Lectura de los bytes necesarios del archivo cargado
if ($datos = file_get_contents($archivo, NULL, NULL, $image_data[$tipo]["offsett"], $image_data[$tipo]["lon"]))
{
$hex = '';
// Convertimos los bytes obtenidos a hexadecimal
for ($i = 0; $i < strlen($datos); $i++)
$hex .= strtoupper(dechex(ord($datos[$i])));
// Y comparamos
$ok = ($hex == $image_data[$tipo]["firma"]);
}
}
while (!empty($tipos) && !$ok);
return ($ok); // Devuelve TRUE si se encontró el tipo
}
</pre>
<div style="text-align: justify;">
La función dispone de los datos de cada firma según el tipo en la matriz <span style="font-family: Courier New, Courier, monospace;">$image_data</span> que se recorre para comparar la firma del fichero cargado con la de la matriz hasta encontrar una coincidencia, en caso de no encontrarla el fichero es rechazado. Modificamos pues la rutina original para llamar a esta función <u>después</u> de haber comprobado el <b>Content-type</b> y de haber subido el archivo;</div>
<pre class="brush: php; first-line:81">if (in_array($_FILES['userfile']['type'], $valid_mime_types))
{
move_uploaded_file($_FILES['userfile']['tmp_name'], $destination);
if (verificaArchivo($destination))
echo "<div>Upload succesful: <a href='$destination'>here</a></div>";
else
{
echo "<div>Upload failed.</div>";
unlink($destination); // Firma no encontrada, se elimina el archivo
}
}
</pre>
<div style="text-align: justify;">
Y ya disponemos de otro nivel de seguridad para nuestro formulario. Para las firmas de los tipos de archivos (<i><b>signatures</b></i>) puede consultarse la <a href="http://en.wikipedia.org/wiki/List_of_file_signatures" target="_blank">Wikipedia</a>. Las firmas usadas en el ejemplo para los ficheros de imagen pueden cambiar dependiendo del software de tratamiento de imágenes que se use. Aún así no olvidemos que <a href="http://www.hackplayers.com/2012/04/ocultando-el-backdoor-php-weevely-en.html" target="_blank">se puede insertar código malicioso en un JPG</a>.<br />
<br />
<span style="font-size: x-small;"><b>Fuente original del formulario</b>: <a href="http://sechow.com/bricks/index.html" target="_blank">OWASP Bricks</a>.</span><br />
<span style="font-size: x-small;"><b>Enlaces</b>: <a href="http://www.securityartwork.es/2013/11/14/malware-oculto-en-cabeceras-jpg-exif/" target="_blank">Malware oculto en cabeceras JPEG EXIF</a></span></div>
Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-88419253593281911972013-06-16T23:48:00.000+02:002013-06-16T23:56:52.537+02:00Una simple alerta de exceso de correo en cola con OSSEC<div style="text-align: justify;">Vamos a crear una alerta para <a href="http://www.ossec.net/" target="_blank">OSSEC</a> que se dispare cuando el número de correos en la cola supere un límite determinado. El proceso lo dividiremos en tres pasos:</div><br />
<ul><li style="text-align: justify;">Crear un <i>script </i>que compruebe el número de <i>emails </i>en cola a intervalos determinados y guarde el resultado en un fichero <i>log </i>si éste supera el límite impuesto.</li>
<li style="text-align: justify;">Crear un <i>decoder </i>para <b>OSSEC </b>que lea el <i>log </i>generado por el <i>script </i>y devuelva un determinado valor (<b>flag</b>) indicando que hay que lanzar la alerta.</li>
<li style="text-align: justify;">Crear una regla para <b>OSSEC </b>que establece el <i>id </i>de la alerta, su nivel, su grupo y el mensaje que recibiremos por correo.<a name='more'></a></li>
</ul><br />
<span style="font-size: large;"><i>Script </i>para comprobar el número de correos en cola</span><br />
<br />
<div style="text-align: justify;">Se ha desarrollado para funcionar con <a href="http://www.qmail.org/" target="_blank">QMail</a> pero debería ser fácilmente adaptable a <a href="http://www.postfix.org/" target="_blank">Postfix</a> u otras <b>MTA</b>. Su funcionamiento se basa en obtener el número de correos totales (locales + remotos) haciendo uso de la utilidad <a href="http://sourceforge.net/projects/qmhandle/" target="_blank">qmhandle</a>, aunque podemos usar <a href="http://www.qmail.org/qmail-manual-html/man8/qmail-qstat.html" target="_blank">qmail-qstat</a> u otro obteniendo el mismo resultado:</div><pre class="brush:bash">#!/bin/bash
PROGNAME=vigila-cola-qmail
LOG=/var/log/lon-cola-qmail
LIMIT=100
FLAG=alert
cola=$(/usr/bin/qmhandle.pl -l | grep "Total messages" | awk '{ print $3 }')
if [ "$cola" -ge "$LIMIT" ]; then
data=$(date +%s | awk '{ printf("%s", strftime("%h %d %H:%M:%S", $1)); }')
echo $data $HOSTNAME $PROGNAME":" $cola [limite: $LIMIT]" "$FLAG >> $LOG
fi
</pre><div style="text-align: justify;">El <i>script </i>simplemente invoca a <b>qmhandle </b>con el parámetro <b>-l</b> (obtener número de correos en cola), filtra la línea donde aparece el total y recoge éste. A continuación comprueba si el total obtenido es igual o mayor que el límite señalado y si es así graba una línea en formato <b>syslog </b>indicando la fecha y hora, el nombre del programa, el del <i>host</i>, el valor obtenido, el límite y el indicador (<b>flag</b>) que en este ejemplo es la cadena <b>alert</b>. A continuación creamos un <i>cron </i>para que se ejecute por ejemplo cada 5 minutos:</div><br />
<span style="font-family: Courier New, Courier, monospace;">crontab -e</span><br />
<br />
y añadimos<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">*/5 * * * * /path-al-script/vigila-cola-qmail</span><br />
<br />
<span style="font-size: large;"><i>Decoder </i><b>OSSEC </b>para leer las alertas</span><br />
<br />
<div style="text-align: justify;">El <i>decoder</i>, que añadiremos a <b>/ossec/etc/decoder.xml</b>, lee las nuevas líneas añadidas al <i>log </i>anterior y extrae la cadena del indicador asignándola al parámetro <b>extra_data</b>:<br />
<br />
</div><pre class="brush:bash;gutter:false"><decoder name="qmail-qeue-size">
<program_name>^vigila-cola-qmail</program_name>
<regex>^\S+ \plimite: \d+\p (\S+)</regex>
<order>extra_data</order>
</decoder>
</pre><br />
<span style="font-size: large;">Regla <b>OSSEC </b>para disparar la alerta</span><br />
<br />
<div style="text-align: justify;">Añadimos una directiva en la sección <b><ossec_config></b> de <b>/ossec/etc/ossec.conf</b> para monitorizar el <i>log </i>generado por el <i>script</i>:<br />
<br />
<pre class="brush:bash;gutter:false"><localfile>
<log_format>syslog</log_format>
<location>/var/log/lon-cola-qmail</location>
</localfile>
</pre><br />
Y para terminar añadimos la siguiente regla con nivel 13 (alta importancia), redefinimos su grupo a <b>spam </b>y establecemos el mensaje que aparecerá en el correo de aviso. El valor del indicador (<b>alert</b>) se especifica en las etiquetas <b><extra_data></b> y debe existir para que se dispare la regla:<br />
<br />
</div><pre class="brush:bash;gutter:false"><rule id="100105" level="13">
<decoded_as>qmail-qeue-size</decoded_as>
<extra_data>alert</extra_data>
<group>spam</group>
<description>QMail: Exceso de email en la cola de correo, posible spam</description>
</rule>
</pre><br />
<div style="text-align: justify;">Podemos añadir la regla a un fichero propio <b>/ossec/rules/mis-reglas.xml</b> dentro del grupo <b>syslog </b>(<b><group name="syslog,"> ... </group></b>) sin olvidar añadir éste a la lista de <i>includes </i>en <b>/ossec/etc/ossec.conf </b>(<b><include>mis-reglas.xml</include></b>) o en <b>local_rules.xml</b> por ejemplo, sin olvidar hacer la copia de seguridad antes. Reiniciamos <b>OSSEC </b>y desde este momento recibiremos un aviso en nuestro buzón si la cola de correo crece más de lo que debería. Entonces podremos tomar medidas manualmente o tener preparado algún otro <i>script</i> que realice algunas acciones previas para mitigar los posibles daños (caso de que nos pille durmiendo).</div>Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-79590506326986110942013-06-13T17:30:00.000+02:002013-06-14T00:44:19.623+02:00DKIM con QMail (con y sin DomainKey)<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBON-HEGYtCQiDfkYyWQ7myWJJUhRNqpx3wMTjbkbbx_nXzQMwDMt50TGTkYIwo0FipU7FJ0rT2s47A-MTSDTXkgZcIX7dvWmswAnl9OPt9Vh6LDmkr5xcERAGB-r55z4AxWdqXnLuREIN/s1600/Q.6.01.Logo.lg.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBON-HEGYtCQiDfkYyWQ7myWJJUhRNqpx3wMTjbkbbx_nXzQMwDMt50TGTkYIwo0FipU7FJ0rT2s47A-MTSDTXkgZcIX7dvWmswAnl9OPt9Vh6LDmkr5xcERAGB-r55z4AxWdqXnLuREIN/s1600/Q.6.01.Logo.lg.jpg" /></a></div>
<div style="text-align: justify;">
El método explicado aquí para implementar <a href="http://www.dkim.org/">DKIM</a> con <a href="http://www.qmail.org/">QMail</a> se basa en el uso de un <i>wrapper </i>que sustituye al <i>script </i><b>qmail-remote</b> original permitiendo firmar el correo saliente antes de enviarlo. Dicho <i>wrapper </i>está diseñado para generar una firma <a href="http://es.wikipedia.org/wiki/DomainKeys">DomainKey</a> y una firma <b>DKIM </b>a partir de la misma clave, útil para quienes no tengan aún implementada ninguna de las dos. En la segunda parte del artículo se explica cómo modificar el <i>wrapper </i>para generar sólo la firma <b>DKIM</b>, por si tu sistema ya tiene implementada la <b>DomainKey</b>. Se ha probado con <b>QMail 1.03</b> sobre <b>Centos 6</b>.</div>
<a name='more'></a><br />
<br />
<span style="font-size: large;">Instalar <i>wrapper </i><b>DKIM qmail-remote</b></span><br />
<br />
<div style="text-align: justify;">
Descargamos <b>qmail-remote.sh</b>, renombramos <b>qmail-remote</b> original a <b>qmail-remote.orig</b> (<u>importante</u>, el <i>wrapper </i>busca el <i>script </i>con este nombre), sustituimos el original y le asignamos los permisos correspondientes:</div>
<br />
<span style="font-family: Courier New, Courier, monospace;">wget http://www.memoryhole.net/qmail/qmail-remote.sh</span><br />
<span style="font-family: Courier New, Courier, monospace;">mv /var/qmail/bin/qmail-remote /var/qmail/bin/qmail-remote.orig</span><br />
<span style="font-family: Courier New, Courier, monospace;">cp qmail-remote.sh /var/qmail/bin/qmail-remote</span><br />
<span style="font-family: Courier New, Courier, monospace;">chmod 755 /var/qmail/bin/qmail-remote</span><br />
<span style="font-family: Courier New, Courier, monospace;">chown root:qmail /var/qmail/bin/qmail-remote</span><br />
<br />
<div style="text-align: justify;">
El nuevo <b>qmail-remote</b> necesita dos programas externos para funcionar: <b>dktest</b>, disponible en la librería <b><a href="http://sourceforge.net/projects/domainkeys/">libdomainkeys</a></b>, y <b>dkimsing.pl</b>, un <i>script </i>en <b>Perl </b>para firmar el correo. Éste último requiere las librerías <b>Mail::DKIM::Signer</b>, <b>Mail::DKIM::TextWrap</b>, <b>Getopt::Long</b> y <b>Pod::Usage</b>.</div>
<div style="text-align: justify;">
<br /></div>
<span style="font-family: Courier New, Courier, monospace;">wget http://www.memoryhole.net/qmail/dkimsign.pl</span><br />
<span style="font-family: Courier New, Courier, monospace;">cp dkimsign.pl /usr/local/bin</span><br />
<span style="font-family: Courier New, Courier, monospace;">chmod 755 /usr/local/bin/dkimsign.pl</span><br />
<span style="font-family: Courier New, Courier, monospace;">wget http://sourceforge.net/projects/domainkeys/files/libdomainkeys/0.69/libdomainkeys-0.69.tar.gz/download</span><br />
<span style="font-family: Courier New, Courier, monospace;">tar -xzf libdomainkeys-0.69.tar.gz</span><br />
<span style="font-family: Courier New, Courier, monospace;">cd libdomainkeys-0.69</span><br />
<br />
<div style="text-align: justify;">
Editamos ahora el <b>Makefile</b>, añadimos <b><i>-lresolv</i></b> al final de la línea <b>LIBS</b>, guardamos y <b><i>make</i></b>. Ya podemos instalarlos en los directorios donde los busca el <i>wrapper</i>:</div>
<br />
<span style="font-family: Courier New, Courier, monospace;">install -m 644 libdomainkeys.a /usr/local/lib</span><br />
<span style="font-family: Courier New, Courier, monospace;">install -m 644 domainkeys.h dktrace.h /usr/local/include</span><br />
<span style="font-family: Courier New, Courier, monospace;">install -m 755 dknewkey /usr/bin</span><br />
<span style="font-family: Courier New, Courier, monospace;">install -m 755 dktest /usr/local/bin</span><br />
<br />
<span style="font-size: large;">Generación de <b>DomainKey </b>para el dominio</span><br />
<br />
<div style="text-align: justify;">
Creamos un subdirectorio dentro de <b>/etc/domainkeys/</b> con el nombre del dominio para el que necesitemos una clave y generamos ésta con la utilidad <b>dknewkey</b>. El fichero <b>default </b>creado es la parte privada y <b>default.pub</b> la pública:</div>
<br />
<span style="font-family: Courier New, Courier, monospace;">mkdir -p /etc/domainkeys/ejemplo.com</span><br />
<span style="font-family: Courier New, Courier, monospace;">cd /etc/domainkeys/ejemplo.com</span><br />
<span style="font-family: Courier New, Courier, monospace;">dknewkey default 1024 > default.pub</span><br />
<span style="font-family: Courier New, Courier, monospace;">chown -R root:root /etc/domainkeys</span><br />
<span style="font-family: Courier New, Courier, monospace;">chmod 640 /etc/domainkeys/ejemplo.com/default</span><br />
<span style="font-family: Courier New, Courier, monospace;">chown root:qmail /etc/domainkeys/ejemplo.com/default</span><br />
<br />
Abrimos ahora el fichero <b>default.pub</b> y copiamos la parte entre comillas:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">default._domainkey IN TXT "<b>v=DKIM;</b> <b>k=rsa; p=MIGfMA0GCSqGSIb390BAQUAA4GNADCBiQKBgQDNmNtzcX6/XzYgalxJqdftSSUyED1+5QuegTSwBZrKm+jrdg55Xm2Dqyt1y2RcVqC0ENw2QjX3uibYKHY8S7Y7t5nxrUH58Yu7COeghV3nFZT5Rqtz6lbW/7YPohqbGntCiKv4PV5CJPcEC5Gr52HtNAD1jYsVY/7ORhtEHYlRMQIDAQAB</b>"</span><br />
<br />
<div style="text-align: justify;">
Esta información es la que debemos añadir a un registro <b>TXT </b>de la zona <b>DNS </b>de nuestro dominio cuyo nombre será: <b>default._domainkey.ejemplo.com</b>, también hay que añadir otro de nombre <b>_adsp._domainkey.ejemplo.com</b> igual a <b><i>dkim=unknown</i></b> y listo. Podemos verificar que funciona enviando correos de prueba a las direcciones facilitadas por <a href="http://www.port25.com/support/authentication-center/email-verification/">Port25</a>.</div>
<br />
<span style="font-size: large;">Modificar el <i>wrapper </i><b>DKIM qmail-remote</b> para usar sólo <b>DKIM</b></span><br />
<br />
<div style="text-align: justify;">
Y usar además claves ya creadas anteriormente con <b>OpenDKIM</b>. Éste es el caso si se ha migrado de una instalación anterior que usaba por ejemplo <b>Postfix </b>con <b>OpenDKIM</b>. En el ejemplo he creado un subdirectorio <b>/dkim</b> dentro de <b>/etc/domainkeys/ejemplo.com</b> para guardar la clave privada pero puede hacerse cambiando simplemente el nombre al fichero.</div>
<br />
Modificamos el <i>wrapper:</i> abrimos <b>qmail-remote</b> y cambiamos la linea 19:<br />
<br />
<pre class="brush:bash; gutter:true; first-line:19">[ "$DKSIGN" ] || DKSIGN="/etc/domainkeys/%/default"
</pre>
por<br />
<pre class="brush:bash; gutter:true; first-line:19">[ "$DKSIGN" ] || DKSIGN="/etc/domainkeys/%/dkim/k_dkim"
</pre>
y las líneas 40 a 48 (donde se genera la <b>DomainKey</b>) las comentamos:<br />
<br />
<pre class="brush:bash; gutter:true; first-line:40"># compute the DomainKey signature
# error=`(dktest -s "$DKSIGN" -c nofws -h <"$tmp" | \
# sed 's/; d=.*;/; d='"$DOMAIN"';/' > "$tmp2") 2>&1`
# if [ "$error" ] ; then
# # Communicate the problem to qmail (that's why the 'Z')
# echo "ZDomainKey error: $error"
# rm "$tmp" "$tmp2"
# exit -1
# fi
</pre>
<div style="text-align: justify;">
Copiamos la clave privada que ya creamos con <b>OpenDKIM</b>, normalmente en <b>/etc/opendkim/keys</b>, a la nueva carpeta <b>/dkim</b> que crearemos en <b>/etc/domainkeys/ejemplo.com</b>:</div>
<br />
<span style="font-family: Courier New, Courier, monospace;">mkdir /etc/domainkeys/ejemplo.com/dkim</span><br />
<span style="font-family: Courier New, Courier, monospace;">chmod 755 /etc/domainkeys/ejemplo.com/dkim</span><br />
<span style="font-family: Courier New, Courier, monospace;">cp /etc/opendkim/keys/ejemplo.com/default /etc/domainkeys/ejemplo.com/dkim/k_dkim</span><br />
<span style="font-family: Courier New, Courier, monospace;">chmod 649 /etc/domainkeys/ejemplo.com/dkim/k_dkim</span><br />
<span style="font-family: Courier New, Courier, monospace;">chown root:qmail /etc/domainkeys/ejemplo.com/dkim/k_dkim</span><br />
<br />
<div style="text-align: justify;">
Y para terminar<b> </b>debemos cambiar el parámetro <b>--selector</b> en la línea 51 del <i>wrapper </i>para indicarle el nombre del fichero clave/registro <b>DNS</b>:</div>
<br />
<pre class="brush:bash; gutter:true; first-line:51">error=`(dkimsign.pl --type=dkim --selector=k_dkim --domain="$DOMAIN" \
</pre>
<br />
<div style="text-align: justify;">
Este paso se realiza para evitar colisionar con el registro <b>DNS </b>de <b>DomainKeys</b>, si existe, que seguramente usará como selector a <b>default</b>. Hacemos los cambios pertinentes en la zona <b>DNS</b> y listo<b>.</b></div>
Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com2tag:blogger.com,1999:blog-2653034041761142933.post-67172322188362433682013-06-12T01:28:00.001+02:002013-06-12T01:40:48.020+02:00Estadísticas para QMail con QMailanalog<div style="text-align: justify;">
Hace poco necesité instalar un software de estadísticas para <a href="http://es.wikipedia.org/wiki/Qmail" target="_blank">QMail </a>y me decidí por <a href="http://cr.yp.to/qmailanalog.html" target="_blank">QMailanalog</a>, en modo texto y muy completo aunque un poco confuso a la hora de instalarlo y con poquísima documentación disponible. El objetivo de este artículo es poner un poco de orden en lo que he descubierto <i>googleando,</i> los ejemplos son de una máquina corriendo <b>Centos 6</b>.</div>
<a name='more'></a><br />
<br />
<span style="font-size: large;">Instalación</span><br />
<br />
<div style="text-align: justify;">
Necesitamos descargar el programa (versión 0.70) y dos parches: <b>errno </b>para el módulo de gestión de errores y <b>qmailanalog-sort</b>, si también usas <b>Centos</b>, para modificar los comandos <i>sort </i>en varios <i>scripts </i>que devuelven error al intentar listar los resultados.</div>
<br />
<span style="font-family: Courier New, Courier, monospace;">wget http://cr.yp.to/software/qmailanalog-0.70.tar.gz</span><br />
<span style="font-family: Courier New, Courier, monospace;">tar xzf qmailanalog-0.70.tar.gz</span><br />
<span style="font-family: Courier New, Courier, monospace;">cd qmailanalog-0.70</span><br />
<br />
<span style="font-family: Courier New, Courier, monospace;">wget http://www.qmailrocks.org/downloads/patches/0.70-errno.patch</span><br />
<span style="font-family: Courier New, Courier, monospace;">wget http://www.korsten.org/misc/qmailanalog-sort.patch</span><br />
<span style="font-family: Courier New, Courier, monospace;">patch < 0.70-errno.patch</span><br />
<span style="font-family: Courier New, Courier, monospace;">patch < qmailanalog-sort.patch</span><br />
<br />
<span style="font-family: Courier New, Courier, monospace;">make && make setup check</span><br />
<span style="font-family: Courier New, Courier, monospace;">cd ..</span><br />
<br />
<div style="text-align: justify;">
Si todo va bien el programa compilado se instalará en <b>/usr/local/qmailanalog</b>, para modificar esta ruta editamos antes de compilar el fichero <b>conf-home</b>.</div>
<br />
<span style="font-size: large;">Uso</span><br />
<br />
<div style="text-align: justify;">
Los <i>scripts </i>de <b>QMailanalog </b>se encuentran en <b>/usr/local/qmailanalog/bin</b> (si no hemos modificado la ruta de instalación) y se dividen en dos grupos: aquellos cuyo nombre empieza por z y aquellos cuyo nombre empieza por x. Los primeros son los que elaboran las estadísticas y los segundos extraen información:</div>
<br />
<br />
<ul>
<li><b>zddist </b>- Promedio de tiempos de envío</li>
<li><b>zdeferrals </b>- Motivos de aplazamiento (<i>deferral</i>)</li>
<li><b>zfailures </b>- Motivos de fallos</li>
<li><b>zoverall </b>- Estadísticas básicas</li>
<li><b>zrecipients<span class="Apple-tab-span" style="white-space: pre;"> </span> </b>- Lista de destinatarios</li>
<li><b>zrhosts </b>- <i>Hosts </i>de los destinatarios</li>
<li><b>zrxdelay </b>- Aplazamientos según el destinatario</li>
<li><b>zsenders </b>- Lista de remitentes</li>
<li><b>zsendmail </b>- Genera <i>log </i>al estilo <i>sendmail</i></li>
<li><b>zsuccesses </b>- Motivos de éxito</li>
<li><b>zsuids </b>- Envíos por <i>uid </i>de remitentes</li>
</ul>
<br />
<br />
<ul>
<li><b>xqp </b>- Extrae información sobre mensajes</li>
<li><b>xrecipient </b>- Extrae información sobre destinatarios</li>
<li><b>xsender </b>- Extrae información sobre remitentes</li>
</ul>
<br />
<br />
<div style="text-align: justify;">
Para generar los informes es necesario preprocesar el <i>log </i>original con el <i>script </i><b>matchup</b>:</div>
<br />
<span style="font-family: Courier New, Courier, monospace;">awk '{$1="";$2="";$3="";$4="";$5="";print}' </ruta_logs/log/maillog | /ruta_qmailanalog/bin/matchup > processed_log</span><br />
<br />
<div style="text-align: justify;">
El uso de <b>awk </b>se hace necesario para convertir el formato de fecha y hora a <a href="http://cr.yp.to/libtai/tai64.html" target="_blank">TAI64N </a>que es el que entiende <b>QMailanalog</b>. Una vez generado el fichero procesado podemos aplicar sobre el cualquiera de las utilidades mencionadas anteriormente, por ejemplo las estadísticas generales:</div>
<br />
<span style="font-family: Courier New, Courier, monospace;">/ruta_qmailanalog/bin/zoverall < processed_log</span><br />
<br />
con lo que obtenemos:<br />
<br />
<blockquote class="tr_bq">
Basic statistics<br />
qtime is the time spent by a message in the queue.<br />
ddelay is the latency for a successful delivery to one recipient---the<br />
end of successful delivery, minus the time when the message was queued.<br />
xdelay is the latency for a delivery attempt---the time when the attempt<br />
finished, minus the time when it started. The average concurrency is the<br />
total xdelay for all deliveries divided by the time span; this is a good<br />
measure of how busy the mailer is.<br />
Completed messages: 566<br />
Recipients for completed messages: 651<br />
Total delivery attempts for completed messages: 651<br />
Average delivery attempts per completed message: 1.15018<br />
Bytes in completed messages: 329154457<br />
Bytes weighted by success: 392284061<br />
Average message qtime (s): 6.0025<br />
Total delivery attempts: 670<br />
success: 651<br />
failure: 0<br />
deferral: 19<br />
Total ddelay (s): 4240.638244<br />
Average ddelay per success (s): 6.514037<br />
Total xdelay (s): 3983.372451<br />
Average xdelay per delivery attempt (s): 5.945332<br />
Time span (days): 0.692834<br />
Average concurrency: 0.0665439</blockquote>
<br />
<div style="text-align: justify;">
Las utilidades de extracción de información no las he utilizado aún así que quedan para otro artículo.</div>
<br />
<b>Enlaces: <a href="http://forum.parallels.com/pda/index.php/t-85027.html" target="_blank">QMail analysis tools</a> (con ejemplos), <a href="http://wiki.qmailtoaster.com/index.php/How_to_integrate_qms-analog_for_nicely_log_stats" target="_blank">How to integrate qms-analog for nicely log stats</a> (para generar distintos tipos de informes con utilidades extras), <a href="http://www.iecc.com/qmailanalog-date-patch" target="_blank">Patch para lectura de fecha/hora en formato antiguo</a> (<a href="http://flylib.com/books/en/2.641.1.103/1/" target="_blank">Collecting and Analyzing QMail Logs with Qmailanalog</a>)</b>Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-72819123425727384242013-06-03T00:58:00.000+02:002013-06-03T00:58:39.797+02:00Replicación Pasiva de DNS: base de datos para seguir la pista de actividad DNS maliciosa<div style="text-align: justify;">
<a href="http://blogs.cisco.com/security/tracking-malicious-activity-with-passive-dns-query-monitoring/" target="_blank">Interesante artículo</a> aparecido en <b>Cisco Blogs</b> en Octubre del 2012 hablando del seguimiento de actividad <b>DNS </b>maliciosa. El autor es miembro del <b>Equipo de Respuesta a Incidentes de Seguridad de Cisco</b> (<a href="http://www.csirt.org/" target="_blank">CSIRT</a>) y empieza su exposición con la siguiente cita:</div>
<blockquote class="tr_bq" style="text-align: justify;">
<i>La seguridad no es un juego limpio. Existen básicamente un ilimitado número de atacantes que pueden someter a prueba tus defensas impunemente hasta encontrar una brecha.</i><br />
<a name='more'></a></blockquote>
<div style="text-align: justify;">
Después de hacer un repaso rápido sobre las bases en que se fundamenta la red y comentar algunos tipos de ataques <b>DNS </b>se centra en una carencia que dificulta la resolución de esta clase de fallas: la falta de información sobre actividad <b>DNS</b> en nuestra red. Cuando un cliente accede a un servicio por su nombre debe resolver éste a una <i>ip</i>. Para ello dicho cliente envía una solicitud a un servidor de nombres el cual le devuelve la dirección. A efectos de seguridad el autor divide este proceso en dos partes: los nombres que los clientes están solicitando y los servidores que proporcionan servicios a dichos nombres, quién está solicitando un servicio (<i><b>DNS </b>query</i>) y quién lo está proporcionando (<i><b>DNS </b>answer</i>). La segunda parte se ha solucionado con el <b><a href="https://dnsdb.isc.org/#Home" target="_blank">Proyecto de Replicación Pasiva de DNS de ICS</a></b> (<b>ICSDNSDB</b>) y su base de datos <b>ICS DNS DATABASE</b>. Con esta base de datos podemos responder a preguntas como ¿Qué nombres <b>DNS </b>apuntan a esta <i>ip</i>? o ¿Cual <i>ip </i>proporciona servicios a este nombre?</div>
<br />
<div style="text-align: justify;">
El problema surge con la primera parte: <i><b>DNS </b>query</i>. La única manera hasta ahora de acometerla es accediendo a los servidores <b>DNS </b>para poder investigar sus <i>logs</i>. Las dificultades de esta solución son varias:</div>
<br />
<ul>
<li>Muchas organizaciones disponen de diversos tipos de servidores <b>DNS </b>(<i>Bind</i>, <i>Active Directory</i>...)</li>
<li style="text-align: justify;">Los clientes (y el <i>malware</i>) pueden hacer solicitudes a servicios externos como <b>Google Public DNS</b> o <b>OpenDNS</b>.</li>
<li style="text-align: justify;">Los clientes generan un gran volumen de solicitudes y resulta muy difícil una búsqueda en un alto volumen de <i>logs</i>.</li>
</ul>
<br />
<div style="text-align: justify;">
Dicho esto el autor explica cómo su departamento está desarrollando un proyecto para cazar las solicitudes <b>DNS </b><i>al vuelo</i> colocando sensores en determinados nodos de la red donde se almacenan localmente los datos capturados. Parece ser que la investigación les ha permitido seguir la pista a actividades <b>DNS </b>maliciosas con bastante facilidad. El resto del artículo da un breve repaso a los datos técnicos de la implementación y en los comentarios incluso se habla de un <i>software </i>comercial que hace algo parecido: <a href="https://www.damballa.com/solutions/damballa_failsafe.php" target="_blank">Damballa</a>. </div>
<br />
<div style="text-align: justify;">
Mientras los de <b>Cisco </b>publican su trabajo podemos mientras tanto usar la base de datos <b>ICS</b> (<b><a href="https://dnsdb.isc.org/#Home" target="_blank">ICSDNSDB</a></b>). Para acceder a esta base datos hay que <a href="https://dnsdb.isc.org/#Apply" target="_blank">solicitar registro</a> y son bastante estrictos en sus criterios, a mi me costó algunos <i>emails </i>convencerlos porque no aceptaban una cuenta de correo pública. Una vez conseguido el registro podemos acceder al <a href="https://sie.isc.org/DNSDB_Getting_Started/" target="_blank">formulario de consulta</a> desde donde podemos por ejemplo investigar un dominio desde el que hemos recibido <i>spam </i>y resolver las dos preguntas formuladas anteriormente: ¿Qué nombres apuntan a esta <i>ip</i>? y ¿Cual <i>ip </i>proporciona servicios a este nombre?. Y si nos interesa podemos incluso <a href="https://kb.isc.org/article/AA-00535" target="_blank">instalar un sensor en nuestro servidor</a>.</div>
<br />
Enlaces: <a href="https://sie.isc.org/DNSDB_Getting_Started/" target="_blank">ICS Security: Getting Started</a><br />
<br />Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-71592949822520731122013-05-31T11:45:00.000+02:002013-05-31T11:47:39.438+02:00Uso básico de PolicyD Cluebringer: Control de Acceso<div style="text-align: justify;">
En este último artículo sobre <b>PolicyD </b>hablaremos del <b>Control de Acceso</b> (<i>Acces Control</i>), una forma muy sencilla de establecer políticas sobre direcciones de destino. Pulsamos pues en la opción <b>Configure </b>bajo <b>Acces Control</b> en el menú de la izquierda y nos aparece la consabida lista desplegable desde la que seleccionamos la opción <b>Add</b>:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNeM70T6GSgc2oqGUi_LFwdvO7TOWz26cjqCaG4YAIhUZH9cbXHqkOoPMZAu6NHTQtZFnW1cl0OFw-fLKtBuh9ZLcT5tcYthsw0yBd_6vEzI9584O7itPc4fhgxn2ryV_orJU6-maaqzsu/s1600/policyd9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNeM70T6GSgc2oqGUi_LFwdvO7TOWz26cjqCaG4YAIhUZH9cbXHqkOoPMZAu6NHTQtZFnW1cl0OFw-fLKtBuh9ZLcT5tcYthsw0yBd_6vEzI9584O7itPc4fhgxn2ryV_orJU6-maaqzsu/s1600/policyd9.png" height="243" width="400" /></a></div>
<a name='more'></a><br />
<div style="text-align: justify;">
Elegimos un nombre para nuestro control, una política (en ese caso <b>Default Outbound</b>: todos los dominios e <i>ip's </i>locales como remitente y ningún dominio local como receptor) y un veredicto (acción a tomar). El veredicto puede ser:</div>
<br />
<ul>
<li style="text-align: justify;"><b>HOLD</b>: (<b>Data</b>: mensaje opcional) Se mantiene el mensaje en espera, afecta a todos los receptores. Se inserta el mensaje <i>test@ejemplo.com</i>: receptor mensaje opcional en el <i>log</i>.</li>
<li style="text-align: justify;"><b>REJECT</b>: (<b>Data</b>: mensaje opcional, si se deja en blanco se usa el mensaje por defecto) Se rechaza el <i>email</i>.</li>
<li><b>DISCARD</b>: (<b>Data</b>: mensaje opcional) Se elimina el <i>email</i>.</li>
<li><b>FILTER</b>: (<b>Data</b>: filtro de contenido) Se envía el mensaje al filtro especificado.</li>
<li><b>REDIRECT</b>: (<b>Data</b>: <i>usuario@dominio</i>) Se envía el <i>email </i>a otro usuario.</li>
<li style="text-align: justify;"><b>OK</b>: (<b>Data</b>: mensaje opcional) En las versiones 2.0.8 y 2.1.x, se devuelve el mensaje a la <b>MTA </b>sin más.</li>
</ul>
<br />
<div style="text-align: justify;">
Podemos escribir un comentario aclaratorio si lo deseamos, enviamos la consulta y listo (recordemos habilitar el control). En el ejemplo expuesto hemos creado un control sobre la política <b>Default Outbound</b> con veredicto <b>Reject </b>que rechazará todos los intentos de enviar desde una <i>ip</i>/dominio local a un dominio externo.</div>
<br />
<div style="text-align: justify;">
Y hasta aquí la serie de artículos sobre uso básico de <b>PolicyD Cluebringer</b>. Los que estén interesados en las opciones <b>Control de SPF</b> y <b><i>Greylisting </i></b>pueden consultar:</div>
<br />
<a href="http://wiki.policyd.org/checkspf" target="_blank">PolicyD SPFCheck</a><br />
<a href="http://wiki.policyd.org/greylisting" target="_blank">PolicyD Greylisting</a><br />
<br />
<b>Enlaces</b>: <a href="http://wiki.policyd.org/accesscontrol" target="_blank">PolicyD AccessControl</a><br />
<br />
<b>Artículo anterior</b>: <a href="http://codementia.blogspot.com.es/2013/05/uso-basico-de-policyd-cluebringer_30.html" target="_blank">Uso básico de PolicyD Cluebringer: Control de HELO/EHLO</a>Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-83146779233389779892013-05-31T01:23:00.001+02:002013-05-31T11:06:27.178+02:00Instalación de OSSEC en CentOS 6 con interface web AnaLogi<div style="text-align: justify;">
<a href="http://www.ossec.net/" target="_blank">OSSEC </a>es un sistema de detección de intrusiones (<a href="http://es.wikipedia.org/wiki/Sistema_de_detecci%C3%B3n_de_intrusos" target="_blank">IDS</a>) de código abierto con interesantes funciones y capaz de funcionar en una gran variedad de sistemas incluido <b>Windows</b>. A pesar de ello su <i>interface web</i> ha recibido muchas críticas por simple y poco informativa así que se han desarrollado otras, como <a href="http://www.splunk.com/download/?ac=ga0508_s_splunk&_kk=splunk&_kt=0d0ee68f-00b5-441f-966d-e7b6c9228ad1&gclid=CNO5p_PUvrcCFajKtAodyxQAhQ" target="_blank">Splunk </a>y <a href="https://github.com/ECSC/analogi" target="_blank">AnaLogi</a>. En este artículo veremos cómo instalar <b>OSSEC </b>con esta última.<br />
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3SR9M-g3pQI0eGtPoVjYj8WF2wCx-OK6y8_MLbYf5nLLA7WloG047PicyTvkmtGNYxNquefc6kGP2XCz3Lr9xXSBafrL6Z2ZbVUmbMH6GjJ7a02tvCNp208w66dgzbQU4_RsxVN4ZcMrM/s1600/ossec1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3SR9M-g3pQI0eGtPoVjYj8WF2wCx-OK6y8_MLbYf5nLLA7WloG047PicyTvkmtGNYxNquefc6kGP2XCz3Lr9xXSBafrL6Z2ZbVUmbMH6GjJ7a02tvCNp208w66dgzbQU4_RsxVN4ZcMrM/s1600/ossec1.png" height="243" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<a name='more'></a><br />
<br />
<div class="separator" style="clear: both; text-align: left;">
<span style="font-size: large;">Instalación de OSSEC: el servidor </span></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
La versión que se instalará es la <b>OSSEC 2.7</b> la cual requiere <b>MySQL 5.x</b>, la versión de <b>AnaLogi </b>es la <b>1.3</b>. Verificamos e instalamos las dependencias:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: Courier New, Courier, monospace;"># yum install mysql-devel mysql-server mysql gcc gcc-c++ autoconf automake php php-mysql httpd</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
Hay que asegurarse de que <a href="http://www.nsa.gov/research/selinux/index.shtml" target="_blank">SELINUX </a>esté inhabilitado, para ello abrimos <b>/etc/selinux/config</b> (si no existe es que no tenemos <b>SELINUX </b>activo) y comprobamos la línea:</div>
<div class="separator" style="clear: both; text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: Courier New, Courier, monospace;">SELINUX = disabled</span></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
y ya podemos instalar <b>OSSEC</b>:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># wget http://www.ossec.net/files/ossec-hids-2.7.tar.gz</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># tar -zxvf ossec-hids-2.7.tar.gz</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># cd ossec-hids-2.7/src</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># make setdb</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># cd ..</span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
Ahora debemos crear la base de datos que necesita <b>OSSEC </b>para trabajar:</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># mysql -u root -p</span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">mysql> create database ossec;</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">mysql> grant INSERT,SELECT,UPDATE,CREATE,DELETE,EXECUTE on ossec.* to ossec@localhost;</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">mysql> set password for ossec@localhost=PASSWORD('clave');</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">mysql> flush privileges;</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">mysql> quit</span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
e importamos el esquema:</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># cd ossec-hids-2.7/src/os_dbd</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># mysql -u root -p ossec < mysql.schema </span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
Llegados a este punto ya podemos lanzar el instalador:</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># ./install.sh</span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
Aparece el mensaje de bienvenida:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDrLNsccD9lJ5p7i9ZNDuuPpVz4bQ1hnFhHm8k56uTQwC73SuL1oQ777abjppmX7gslKXSyeXvgvscxneiUkJffLzFkqBAFvuVH9GlL85GKDLXiusek-ANnRgm4bNlT_cwbrGVsTtcBXlV/s1600/ossec2.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDrLNsccD9lJ5p7i9ZNDuuPpVz4bQ1hnFhHm8k56uTQwC73SuL1oQ777abjppmX7gslKXSyeXvgvscxneiUkJffLzFkqBAFvuVH9GlL85GKDLXiusek-ANnRgm4bNlT_cwbrGVsTtcBXlV/s1600/ossec2.jpg" height="140" width="400" /></a></div>
<div class="separator" style="clear: both;">
y oprimimos <b>ENTER </b>para empezar:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgy2oL4M2hOPKHgfaoheq71KOUcKaDE7FFqRxSixFEcTWlXA-DBNTLSwG3fFi0VkIQEraUbZrMPF-WmZrlDpGJz0rzUFU8qiv3LvFmAMNoBtOQRITqckX1TAojiXPQFrzw5mQljYVGm54od/s1600/ossec3.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgy2oL4M2hOPKHgfaoheq71KOUcKaDE7FFqRxSixFEcTWlXA-DBNTLSwG3fFi0VkIQEraUbZrMPF-WmZrlDpGJz0rzUFU8qiv3LvFmAMNoBtOQRITqckX1TAojiXPQFrzw5mQljYVGm54od/s1600/ossec3.jpg" height="640" width="456" /></a></div>
<div class="separator" style="clear: both; text-align: justify;">
Si vamos a usar <b>OSSEC</b> en una sola máquina, con o sin subdominios, podemos elegir tipo de instalación <b>local</b>. La opción <b>active response</b> permite que <b>OSSEC </b>ejecute acciones predeterminadas dependiendo de la alerta que se de, si no lo activamos funcionará sólo en modo monitor. Oprimimos <b>ENTER </b>de nuevo y el software se compilará e instalará con las opciones que hayamos seleccionado. Si se instala tras un <i>firewall </i>hay que tener en cuenta que el servidor se comunica con los agentes por el puerto <b>UDP 1454</b>:</div>
<div class="separator" style="clear: both; text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: Courier New, Courier, monospace;"># vi /etc/sysconfig/iptables </span><span style="font-family: 'Courier New', Courier, monospace;">-A INPUT -m state —state NEW -m udp -p udp —dport 1514 -j ACCEPT</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"># service iptables restart</span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
Modificamos ahora el archivo de configuración con los datos para acceder a la <b>DB</b>:</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># vi /var/ossec/etc/ossec.conf</span></div>
<pre class="brush:shell;gutter:false"><database_output>
<hostname>127.0.0.1</hostname>
<username>ossec</username>
<password>clave</password>
<database>ossec</database>
<type>mysql</type>
</database_output>
</pre>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
Para terminar, habilitamos la base de datos y arrancamos <b>OSSEC</b>:</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># /var/ossec/bin/ossec-control enable database</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># /var/ossec/bin/ossec-control start</span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
Si todo va bien obtendremos una salida similar a ésta:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZNF60d_SJR-fU3nRISA8GwkmpEgbo5hd21cEr9elaBT8i5KDye14m05QkhL1vr46oPO-EaldoPqibTp6KJhfm-gXBHNzxJq2FAIpkSqV9aaR2ByUnHH2b5LSFHXqZ5bQTxf67gsDfFTCL/s1600/ossec4.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZNF60d_SJR-fU3nRISA8GwkmpEgbo5hd21cEr9elaBT8i5KDye14m05QkhL1vr46oPO-EaldoPqibTp6KJhfm-gXBHNzxJq2FAIpkSqV9aaR2ByUnHH2b5LSFHXqZ5bQTxf67gsDfFTCL/s1600/ossec4.jpg" height="236" width="400" /></a></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
Para que <b>OSSEC </b>nos remita las alertas abrimos <b>/var/ossec/etc/ossec.conf</b> y en la sección <b><global></b>, justo al inicio, escribimos nuestro correo entre las etiquetas <b><email_to></b> (pueden ser más de una).</div>
<div class="separator" style="clear: both; text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-size: large;">Instalación del agente</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
Si efectuamos una instalación agente/servidor debemos indicarle ahora al servidor que se van a instalar clientes (agentes) posteriormente. Para ello ejecutamos:</div>
<div class="separator" style="clear: both; text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"># /var/ossec/bin/manage_agents</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
con lo que accedemos al menú de gestión de agentes que nos va a solicitar un nombre, su <i>ip </i>y un <i>id</i>:</div>
<div class="separator" style="clear: both; text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">****************************************</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">* OSSEC HIDS v2.6 Agent manager. *</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">* The following options are available: *</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">****************************************</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> (A)dd an agent (A).</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> (E)xtract key for an agent (E).</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> (L)ist already added agents (L).</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> (R)emove an agent (R).</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> (Q)uit.</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">Choose your action: A,E,L,R or Q: <b>A</b></span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">- Adding a new agent (use ‘\q’ to return to the main menu).</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> Please provide the following:</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> * A name for the new agent: cliente01</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> * The IP Address of the new agent: <IP></span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> * An ID for the new agent[001]: (ENTER)</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">Agent information:</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> ID:001 </span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> Name:cliente01</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> IP Address:<IP></span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">Confirm adding it?(y/n): y</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">Agent added.</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
El menú es muy claro y no requiere más explicación. Una vez añadido el o los agentes que necesitemos hay que reiniciar el servidor:</div>
<div class="separator" style="clear: both; text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: 'Courier New', Courier, monospace; text-align: start;"># /var/ossec/bin/ossec-control restart</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
Pasamos ahora a instalar el agente en otro <i>host</i>. Descargamos e instalamos <b>OSSEC </b>como hicimos en el paso anterior pero sin crear ninguna base de datos:</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># ./install.sh</span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
En este caso elegimos instalar agente y seleccionamos las mismas opciones que en el servidor. Se nos solicitará además la <i>ip</i> de éste. Terminada la compilación sin errores accedemos de nuevo al gestor de agentes desde el servidor para extraer la clave del agente (opción E):</div>
<div class="separator" style="clear: both; text-align: justify;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="text-align: justify;"><span style="font-family: Courier New, Courier, monospace;"># /var/ossec/bin/manage_agents</span></span></div>
<div class="separator" style="clear: both;">
<span style="text-align: justify;"><br /></span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">****************************************</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">* OSSEC HIDS v2.6 Agent manager. *</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">* The following options are available: *</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">****************************************</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> (A)dd an agent (A).</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> (E)xtract key for an agent (E).</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> (L)ist already added agents (L).</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> (R)emove an agent (R).</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> (Q)uit.</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">Choose your action: A,E,L,R or Q: <b>E</b></span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">Available agents:</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"> ID: 001, Name: cliente01, IP: <IP CLIENTE></span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">Provide the ID of the agent to extract the key (or ‘\q’ to quit): 001</span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">Agent key information for ‘001’ is:</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"><span style="text-align: justify;"></span></span></div>
<div class="separator" style="clear: both; text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">MDE0IHZwbi1mdy5yZWQtYWxmLmNvbSAxMC4wLjAuMjU0IGZhZjhhZWE4MDY1ZmVlMTUwMGIyN2ViNTk4M2FjODAzZTQwMzA5MzQ3ZjYzNzgzNWFhZDM2OTY5YmFkZDc5OTM= </span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
La clave obtenida la copiamos y, desde el <i>host </i>donde está el agente, ejecutamos el gestor que nos pedirá que se la peguemos:</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># /var/ossec/bin/manage_client </span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">****************************************</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">* OSSEC HIDS v2.6 Agent manager. *</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">* The following options are available: *</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">****************************************</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"> (I)mport key from the server (I).</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"> (Q)uit.</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">Choose your action: I or Q: I</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">* Provide the Key generated by the server.</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">* The best approach is to cut and paste it.</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">*** OBS: Do not include spaces or new lines.</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">Paste it here (or ‘\q’ to quit): MDE0IHZwbi1mdy5yZWQtYWxmLmNvbSAxMC4wLjAuMjU0IGZhZjhhZWE4MDY1ZmVlMTUwMGIyN2ViNTk4M2FjODAzZTQwMzA5MzQ3ZjYzNzgzNWFhZDM2OTY5YmFkZDc5OTM=</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">Agent information:</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"> ID:001</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"> Name:cliente01</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"> IP Address: IP CLIENTE</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">Confirm adding it?(y/n): y</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;">Added. </span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
Y ya podemos activarlo:</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># /var/ossec/bin/ossec-control start</span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-size: large;">Instalación de AnaLogi</span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
Necesitamos:</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># yum install httpd php php-mysql mod_ssl</span></div>
<div class="separator" style="clear: both;">
<b><br /></b></div>
<div class="separator" style="clear: both;">
<b>AnaLogi </b>se encuentra en <a href="https://github.com/" target="_blank">GitHub </a>así que podemos instalar este repositorio si no lo tenemos aun:</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># yum install git-core</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># cd /var/www/</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"></span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># git clone https://github.com/ECSC/analogi.git</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># mv analogi ossecui</span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
o lo descargamos directamente, lo descomprimimos y le cambiamos el nombre por uno mas simple:</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># cd /var/www/</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># wget https://github.com/downloads/ECSC/analogi/AnaLogi_v1.3.zip</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># unzip AnaLogi_v1.3.zip</span></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># mv ECSC-analogi-a1cd5e3 ossecui</span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
Asignamos usuario y grupo a la carpeta:</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># chown -R apache:apache ossecui</span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
Y para terminar configuramos el acceso a la base de datos en el archivo <b>db_ossec.php</b>:</div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># vi /var/www/ossecui/db_ossec.php</span></div>
<pre class="brush:shell;gutter:false">define ('DB_USER_O', 'ossec');
define ('DB_PASSWORD_O', 'clave');
define ('DB_HOST_O', '127.0.0.1');
define ('DB_NAME_O', 'ossec');
</pre>
<div class="separator" style="clear: both; text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
A partir de aquí debemos realizar las modificaciones oportunas en el sistema dependiendo de si accedemos a la interface como <b>vhost </b>o como carpeta protegida. Al acabar reiniciamos <b>httpd</b>:</div>
<div class="separator" style="clear: both; text-align: justify;">
<br /></div>
<div class="separator" style="clear: both;">
<span style="font-family: Courier New, Courier, monospace;"># /etc/init.d/httpd restart</span></div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both; text-align: justify;">
y ya podemos acceder desde <b>http://ip-o-dominio</b> o <b>http://dominio/ossecui</b> según lo hayamos configurado. </div>
<div class="separator" style="clear: both;">
<br /></div>
<div class="separator" style="clear: both;">
<b>Enlaces</b>: <a href="http://tonyonsecurity.com/2013/03/13/ossec-for-website-security-part-i/" target="_blank">OSSEC for Web Security Part I</a>, <a href="http://desdelocalhost.blogspot.com.es/2012/10/ossec-hids-deteccion-de-intrusos-en.html" target="_blank">OSSEC IDS: Detección de intrusos en CentOS</a>, <a href="http://maculi33.blogspot.com.es/2012/07/ossec-hids-open-source-security-host.html" target="_blank">OSSEC HIDS, Open Source Security Host Based Intrusion Detection System</a>, <a href="http://www.ossec.net/doc/" target="_blank">OSSEC's documentation</a></div>
Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com2tag:blogger.com,1999:blog-2653034041761142933.post-18905606439984618022013-05-30T18:14:00.000+02:002013-05-31T11:46:47.439+02:00Uso básico de PolicyD Cluebringer: Control de HELO/EHLO<div style="text-align: justify;">
En este artículo vamos a ver como configurar <b>Cluebringer </b>para controlar los accesos a nuestro servidor de correo estableciendo restricciones en la cláusula que identifica quién quiere enviar su <i>email</i>: <b>HELO/EHLO</b>. En el menú de la izquierda dentro de <b>HELO/EHLO Checks</b> pulsamos <b>Configure </b>y accedemos a la pantalla desde la que podemos crear nuestras políticas de control. Como en las anteriores, el menú desplegable central contiene las opciones para trabajar, y seleccionamos <b>Add</b>:</div>
<a name='more'></a><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinbWEDjmgLvq5r8FlsEiUZDcqfiWGQ-NPuXCArnOWOgDhSvhPLm6AeOgE7LktddvD8TSoIMsV7ucBJurPOKeLaEV-HUMnvEMetU09SM2UAZmsqsNZABYSyTsZivoxgA4gOIzJCWXPUCEpF/s1600/policyd8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinbWEDjmgLvq5r8FlsEiUZDcqfiWGQ-NPuXCArnOWOgDhSvhPLm6AeOgE7LktddvD8TSoIMsV7ucBJurPOKeLaEV-HUMnvEMetU09SM2UAZmsqsNZABYSyTsZivoxgA4gOIzJCWXPUCEpF/s1600/policyd8.png" height="387" width="400" /></a></div>
<div style="text-align: justify;">
Elegimos un nombre para nuestra configuración (aquí <i>ControlGlobal</i>) y la cuenta a la que se aplicará la política: <i>Defaul Inbound</i>. Si echamos un vistazo a los miembros de esta cuenta veremos como excluye a todos los <i>emisores </i>que pertenezcan al dominio local y acepta todos los <i>receptores </i>que estén en nuestro dominio, es decir, sólo aplicará los controles a los <i>emisores </i>ajenos a nuestro dominio. Los siguientes parámetros a definir son:</div>
<br />
<ul>
<li><b>Use Blacklist</b>: Habilita el uso de la <i>blacklist </i>de <b>HELO/EHLO</b></li>
<li style="text-align: justify;"><b>Blacklist Period</b>: Tiempo durante el cual un <i>host </i>será baneado <u>en segundos</u>. Cada vez que se intente un nuevo acceso la lista se resetea. En el ejemplo 2419200 seg. = 28 días.</li>
<li><b>Use HRP</b>: <i>Hello Randomization Prevention</i>. Se <i>logean </i>todos los accesos.</li>
<li style="text-align: justify;"><b>HRP Period</b>: Como el <b>Blacklist Period</b>. Si un mismo <i>host </i>supera el limite de <b>HELO's</b> (<b>HRP Limit</b>) se <i>banea </i>su <i>ip </i>durante el período señalado (en segundos), en el ejemplo 2419200 seg. = 28 días.</li>
<li><b>HRP Limit</b>: Límite de intentos <b>HELO </b>para un mismo <i>host</i>. Si se superan se rechaza el <i>mail</i>.</li>
<li style="text-align: justify;"><b>Reject Invalid</b>: Rechaza todo los <b>HELO </b>que no se identifiquen con un <b>FQDN </b>(no se permiten <i>ip's </i>ni nombres de dominio ilegales). Está opción debe habilitarse para que funcionen <b>Reject non-literal IP</b> y <b>Reject Unresolvable</b>.</li>
<li style="text-align: justify;"><b>Reject non-literal IP</b>: Rechaza todo <b>HELO </b>que no cumpla los requisitos <a href="http://www.ietf.org/rfc/rfc2821.txt" target="_blank">RFC 2821, pág. 22 sección 3.6</a>. Resumiendo: no se aceptará un <i>ip </i>a.b.c.d pero si [a.b.c.d].</li>
<li><b>Reject Unresolvable</b>: Rechaza todo <b>HELO </b>cuya <i>ip </i>no pueda resolverse.</li>
</ul>
<br />
<div style="text-align: justify;">
Enviamos la consulta y, recordando que por defecto se crean inhabilitadas, vamos a <b>Change</b> y cambiamos su estado <b>Disabled </b>a <b>no</b>. Desde ese momento <b>Cluebringer </b>empezará a vigilar todos los <i>emails </i>entrantes que no pertenezcan a nuestro dominio y, si van dirigidos a nosotros, les aplicará las restricciones. Para terminar, el control <b>HELO/EHLO</b> dispone de una <b>blacklist </b>y una <b>whitelist </b>que podemos modificar según nuestros intereses para prohibir o permitir <i>hosts </i>concretos.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<b>Enlaces:</b> <a href="http://wiki.policyd.org/checkhelo" target="_blank">PolicyD CheckHelo</a></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<b>Artículo anterior:</b> <a href="http://codementia.blogspot.com.es/2013/05/uso-basico-de-policyd-cluebringer-cuotas.html" target="_blank">Uso básico de PolicyD Cluebringer: Cuotas</a><br />
<b>Artículo siguiente</b>: <a href="http://codementia.blogspot.com.es/2013/05/uso-basico-de-policyd-cluebringer_31.html" target="_blank">Uso básico de PolicyD Cluebringer: Control de Acceso</a></div>
Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-65533123561820061972013-05-24T01:39:00.000+02:002013-05-24T01:40:14.643+02:00DMARC: otra capa de protección antiphishing<div style="text-align: justify;">
<a href="http://www.dmarc.org/index.html" target="_blank">DMARC</a> (<i>Domain-based Message Authentication, Reporting & Conformance</i>) es otro sistema de protección <i>antispam </i>y <i>antiphishing </i>patrocinado por algunos grandes del sector como <b>Google</b>, <b>Yahoo</b>, <b>Microsoft </b>y varios más. Su funcionamiento se basa en las <a href="http://codementia.blogspot.com.es/2013/05/reglas-spf-para-validar-el-correo-lo.html" target="_blank">reglas SPF</a> y en <a href="http://codementia.blogspot.com.es/2013/04/firma-tu-correo-con-opendkim-y-evita.html" target="_blank">firmas DKIM</a>, que deben implementarse para poder usar <b>DMARC</b>, cuya solución se basa en fortalecer ambos métodos.</div>
<a name='more'></a><br />
<br />
<span style="font-size: large;">Cómo funciona</span><br />
<br />
<div style="text-align: justify;">
Se ha comprobado que las reglas <b>SPF </b>pueden fallar en casos de reenvío de correo, y que <b>DKIM</b>, aunque tiene un mejor comportamiento en éste caso, no resulta fiable al 100% debido a la complejidad del algoritmo de firma. Si se une la potencia de ambos se fortalecen sus debilidades individuales, y eso es lo que se intenta con <b>DMARC </b>y la implementación del concepto de <b>alineación del identificador</b>. Para ello se utiliza la especificación <a href="http://www.rfc-editor.org/info/rfc5322" target="_blank">RFC5322</a> (<i>From Domain</i>). Se lee el dominio especificado en el campo <b>From </b>del <i>email </i>recibido y éste debe coincidir con el campo <b>Return-Path</b> (<b>MAIL FROM</b>) para una <b>alineación SPF </b>y con el campo <b>d</b> de <b>DKIM</b> para una alineación equivalente. Si una de las dos comprobaciones es correcta <b>DMARC </b>considera el <i>mail </i>como legítimo.</div>
<br />
<div style="text-align: justify;">
<b>SPF </b>y <b>DKIM </b>pueden trabajar en modo estricto (<i>strict</i>) y relajado (<i>relaxed</i>). La diferencia entre ambos es que en modo <i>relaxed </i>se permite enviar a subdominios (podemos enviar desde <i>usuario@ejemplo.dominio.com</i> siendo el <b>Return-Path </b><i>usuario@dominio.com</i>) mientras que en modo <i>strict </i>no se permitirá. A <b>DMARC </b>también se le puede indicar de qué modo debe tratar con <b>SPF </b>y <b>DKIM </b>(etiquetas <b>adkim </b>y <b>aspf</b>) y hay que tener cuidado al especificarlos: si no se especifican se asumen ambos <i>relaxed </i>por defecto, pero si definimos alguno, o ambos, como <i>strict </i>y en el servidor se han implementado en modo <i>relaxed </i><b>DMARC </b>devolverá alineación incorrecta, y viceversa, si en <b>DMARC </b>se especifica que son <i>relaxed </i>y se han implementado como <i>strict </i>se devolverá alineación correcta.</div>
<br />
<div style="text-align: justify;">
Cada 24 horas los servidores de correo que implementen <b>DMARC </b>nos enviarán un informe en formato <b>XML </b>con la cantidad de <i>emails </i>gestionados, sus <b>ip </b>y los resultados del test. <b>DMARC </b>puede implementarse para controlar sólo nuestro correo saliente (<i>sender</i>) o para gestionar también los <i>emails </i>recibidos que contengan la cabecera (<i>receiver</i>).</div>
<br />
<div style="text-align: justify;">
Véase: <a href="http://www.unlocktheinbox.com/resources/identifieralignments/" target="_blank">Email Authentication Identifier Alignments</a> para una detallada explicación técnica, proporcionan además una cuenta para comprobar la alineación de nuestro sistema.</div>
<br />
<span style="font-size: large;">Implementar DMARC como <i>sender</i></span><br />
<br />
<div style="text-align: justify;">
Para implementar <b>DMARC </b>como <i>sender </i>necesitamos definir las reglas <b>SPF </b>para nuestro dominio y la firma <b>DKIM </b>para el correo saliente. Una vez hecho ésto simplemente añadimos un registro <b>TXT </b>a nuestra zona <b>DNS </b>con el nombre <b>_dmarc.eldominio.com </b>y cuyo contenido básico debe ser:</div>
<br />
<span style="font-family: Courier New, Courier, monospace;">"v=DMARC1; p=none; rua=mailto:webmaster@eldominio.com"</span><br />
<br />
<div style="text-align: justify;">
Este registro indica la versión de <b>DMARC</b>, qué hacer con el <i>mail </i>comprobado (<b>p=none</b>, nada), y a qué cuenta remitir el informe diario. Se recomienda empezar en modo monitor (<b>p=none</b>) y aumentar la restricción a medida que se adquiera experiencia.</div>
<br />
Véase: <a href="http://support.google.com/a/bin/answer.py?hl=en&answer=2466563" target="_blank">Create a DMARC Record</a> para las directivas del registro y consejos para empezar.<br />
<br />
<span style="font-size: large;">Implementar DMARC como <i>receiver</i></span><br />
<br />
<div style="text-align: justify;">
Se dispone de dos implementaciones de código abierto: <a href="http://search.cpan.org/~shari/Mail-DMARC-opendmarc/" target="_blank">Mail-DMARC-opendmarc</a> y <a href="http://search.cpan.org/~msimerson/Mail-DMARC/" target="_blank">Mail-DMARC</a>.</div>
<br />
<div style="text-align: justify;">
Con el sistema ya implementado nos queda gestionar los informes recibidos en un incómodo formato <b>XML</b>: podemos usar alguna herramienta <i>online </i>preparada para ello como <a href="http://www.dmarcanalyzer.com/index.php" target="_blank">dmarcanalyzer</a> o <a href="https://dmarcian.com/" target="_blank">dmarcian</a> que incluso nos suministrán los resultados en formato grafico.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: left;">
<b>Enlaces</b>: <a href="http://engineering.linkedin.com/email/dmarc-new-tool-detect-genuine-emails" target="_blank">DMARC: a new tool to detect genuine emails</a>, <a href="http://support.sendgrid.com/entries/22007903-DMARC-and-YOU-The-future-of-email-authentication-" target="_blank">DMARC and YOU. The future of email authentication</a>, <a href="http://dmarc.org/draft-dmarc-base-00-01.html#iana_dmarc_tags" target="_blank">Domain-based Message Authentication, Reporting and Conformance (DMARC)</a>, <a href="http://codementia.blogspot.com.es/2013/04/firma-tu-correo-con-opendkim-y-evita.html" target="_blank">Firma tu correo con opendkim y evita ser tratado como spammer</a>, <a href="http://codementia.blogspot.com.es/2013/05/reglas-spf-para-validar-el-correo-lo.html" target="_blank">Reglas SPF para validar el correo: lo básico</a></div>
<br />Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com2tag:blogger.com,1999:blog-2653034041761142933.post-53743822247896339572013-05-15T17:42:00.003+02:002013-05-30T21:07:10.779+02:00Uso básico de PolicyD Cluebringer: Cuotas<div style="text-align: justify;">
En este punto hemos ya creado una cuenta (<b>BloqueoSalida</b>) con un miembro que, o bien nos permite aplicar alguna política a todas las cuentas del dominio <b>@ejemplo.com</b>, o contiene un grupo (<b>ListaInterna</b>) con varios dominios. Sea cual sea el caso vamos a aplicar una política de cuotas. Accedemos al panel de control de <b>PolicyD </b>y elegimos la opción de menú <b>Configure </b>en <b>Quotas</b>:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg81O6eBiNa0OU028p-B6IG1Iqw3hiZTD8Tk2_6KCwkH-BWJJIZJxdLC0SYc9wT8EP9e1mBfNUfMCvt1TInZv2INwIDo65-SbWVDFxihd0JAPftCLrlm721UU77NFnuRBxpkS65t6igJSXX/s1600/policyd5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg81O6eBiNa0OU028p-B6IG1Iqw3hiZTD8Tk2_6KCwkH-BWJJIZJxdLC0SYc9wT8EP9e1mBfNUfMCvt1TInZv2INwIDo65-SbWVDFxihd0JAPftCLrlm721UU77NFnuRBxpkS65t6igJSXX/s1600/policyd5.png" height="268" width="640" /></a></div>
<div style="text-align: justify;">
<br />
<a name='more'></a>Nos aparecen dos cuotas ya predefinidas que, como indique en el artículo anterior, he deshabilitado para empezar a trabajar desde cero. En esta pantalla abrimos el desplegable <b>Action </b>y seleccionamos <b>Add </b>(<i>añadir</i>) con lo que accedemos al formulario para añadir una cuota:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6tQnB8bS_38bnVLm1m3_14RGgkCNIGXJzG7pfdoC7Jz537JXaeE2YcthN1SX1EONrnRkwirgdD8q65scbq2LVEtDKHzKwyJ31w1LVYEyQMGnwV9wVyYJAdW6eUtgoTUWrQi9tZq0SBc2U/s1600/policyd6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6tQnB8bS_38bnVLm1m3_14RGgkCNIGXJzG7pfdoC7Jz537JXaeE2YcthN1SX1EONrnRkwirgdD8q65scbq2LVEtDKHzKwyJ31w1LVYEyQMGnwV9wVyYJAdW6eUtgoTUWrQi9tZq0SBc2U/s1600/policyd6.png" height="286" width="400" /></a></div>
<div style="text-align: justify;">
Le asignamos un nombre, aquí <b>LimitEjemplo </b>y pasamos a definir el <i><b>track </b></i>(qué hay que vigilar), que puede ser:</div>
<br />
<ul>
<li><span style="font-family: Courier New, Courier, monospace;">Sender IP</span> (<b>IP </b>del remitente, en cuyo caso se activa el <i>textbox </i>adyacente para escribirla)</li>
<li><span style="font-family: Courier New, Courier, monospace;">Sender:user@domain</span> (<b>usuario@dominio</b> del remitente)</li>
<li><span style="font-family: Courier New, Courier, monospace;">Sender:@domain</span> (<b>@dominio</b> del remitente)</li>
<li><span style="font-family: Courier New, Courier, monospace;">Sender:user@</span> (<b>usuario@</b> remitente de cualquier dominio)</li>
<li><span style="font-family: Courier New, Courier, monospace;">Recipient:user@domain</span> (<b>usuario@dominio</b> del receptor)</li>
<li><span style="font-family: Courier New, Courier, monospace;">Recipient:@domain</span> (<b>@dominio</b> del receptor)</li>
<li><span style="font-family: Courier New, Courier, monospace;">Recipient:user@</span> (<b>usuario@receptor</b> de cualquier dominio)</li>
<li><span style="font-family: Courier New, Courier, monospace;">SASLUsername:username</span> (nombre de usuario <b>SASL</b>)</li>
<li><span style="font-family: Courier New, Courier, monospace;">Policy</span><span style="font-family: Georgia, Times New Roman, serif;"> </span><span style="font-family: inherit;">(todo <i>email </i>coincidente con la política indicada se agrupa en un sólo contador)</span></li>
</ul>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Para detalles sobre <i>bitmasking</i>, <b>IPv4</b>, <b>IPv6 </b>y uso de <b>SASL </b>véase la <a href="http://wiki.policyd.org/quotas" target="_blank">ayuda de PolicyD - Quotas</a>. <span style="text-align: justify;">En nuestro ejemplo queremos asignar una cuota a todas las cuentas de </span><b style="text-align: justify;">ejemplo.com</b><span style="text-align: justify;">, así que seleccionamos </span><b style="text-align: justify;">Sender:@domain</b><span style="text-align: justify;">. A continuación elegimos el periodo durante el cual se aplica la cuota, reiniciándose ésta cada vez. El plan es que la cuota se aplique cada hora (n </span><i style="text-align: justify;">email </i><span style="text-align: justify;">por hora), lo que indicamos en el campo </span><b style="text-align: justify;">Period</b><span style="text-align: justify;"> </span><u style="text-align: justify;">en segundos</u><span style="text-align: justify;">: </span><b style="text-align: justify;">3600</b><span style="text-align: justify;">. En el campo </span><b style="text-align: justify;">Link to Policy</b><span style="text-align: justify;"> es donde debemos seleccionar la cuenta que creamos anteriormente y que contiene como miembro el dominio que queremos controlar: </span><b style="text-align: justify;">BloqueoSalida</b><span style="text-align: justify;">. El campo </span><b style="text-align: justify;">Verdict</b><span style="text-align: justify;"> es para indicar qué queremos hacer cuando se supera la cuota señalada. Los valores de este campo están relacionados con el siguiente, </span><b style="text-align: justify;">Data</b><span style="text-align: justify;">, que puede (o no) contener un mensaje para el usuario, una dirección para redireccionar el mensaje o un filtro de correo:</span></div>
<br />
<ul>
<li><b>HOLD </b>(mantener en espera, <b>Data </b>opcional que aparecerá en el <i>log </i>tras la dirección)</li>
<li><b>REJECT </b>(rechazar, si <b>Data </b>se deja vacio aparece un mensaje estándar)</li>
<li><b>DISCARD </b>(eliminar, <b>Data </b>opcional que aparecerá en el <i>log</i>)</li>
<li><b>FILTER </b>(filtrar, el campo <b>Data </b>debe contener un filtro de correo al que se envía el mensaje)</li>
<li><b>REDIRECT </b>(redireccionar, el campo <b>Data </b>debe contener una dirección de <i>email</i>)</li>
<li><b>OK </b>(aceptar, <b>Data </b>opcional, el mensaje se devuelve a la <b>MTA </b>sin más)</li>
</ul>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
En ese caso elegimos <b>REJECT</b>: cuando el usuario supere la cuota asignada le aparecerá una ventana de mensaje indicándoselo. El comentario final es opcional y ya podemos enviar la consulta.</div>
<br />
<br />
<div style="text-align: justify;">
Hasta aquí hemos configurado una política para filtrar las cuentas de correo de <b>@ejemplo.com</b>, independientemente de quién sea el remitente y el receptor y hemos indicado que vamos a aplicarle una cuota con períodos de una hora. Nos falta establecer dicha cuota. A efectos del ejemplo vamos a definirla muy roñosa: 2 <i>emails </i>por hora. Para ello, en la pantalla de <b>Quotas </b>a la que hemos regresado, seleccionamos la recien creada (<b>LimitEjemplo</b>) y desplegamos <b>Action </b>eligiendo <b>Limits</b>. Nos aparecerá una pantalla con tres columnas: <b>Type</b>, <b>Counter Limit</b> y <b>Disabled</b>. Recordemos una vez más que <u><b>PolicyD </b>crea todos los ítems inhabilitados por defecto</u> (<b>Disabled = Yes</b>). En la pantalla de <b>Limits, </b>pues, seleccionamos <b>Add </b>en el desplegable <b>Action </b>y obtenemos el formulario de límites para cuotas:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZn_negp_pc_nP6bPN8cp_kIdCf2L9_3OEqogrqI9KhWfR2fBuGrU6LBqWLXfrWG4w6G0k-wsn-wBtGpo9EVCYqTsOQBV9B5tfMBDgoWYZnLhMD9pGtfP7MJaVmP94ESnMzyBmiiD7L1IU/s1600/policyd7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZn_negp_pc_nP6bPN8cp_kIdCf2L9_3OEqogrqI9KhWfR2fBuGrU6LBqWLXfrWG4w6G0k-wsn-wBtGpo9EVCYqTsOQBV9B5tfMBDgoWYZnLhMD9pGtfP7MJaVmP94ESnMzyBmiiD7L1IU/s1600/policyd7.png" height="252" width="400" /></a></div>
El campo <b>Type </b>permite tres valores:<br />
<br />
<ul>
<li><b>Message Count</b> (contar el número de mensajes)</li>
<li><b>Message Cumulative Size</b> (tamaño del mensaje en bytes)</li>
</ul>
<br />
y, desde la version 2.1.x<br />
<br />
<ul>
<li style="text-align: justify;"><b>Last Quota</b> (la primera política encontrada es la que se usa, si se han definido 10 políticas con prioridades del 1 al 10 y se habilita la 5ª no se sigue buscando en las siguientes)</li>
</ul>
<br />
<span style="text-align: justify;">Para lo que nos interesa, limitar los </span><i style="text-align: justify;">emails </i><span style="text-align: justify;">a 2 cada hora, elegimos </span><b style="text-align: justify;">Message Count</b><span style="text-align: justify;"> y en el campo </span><b style="text-align: justify;">Counter Limit</b><span style="text-align: justify;"> es donde introducimos nuestro valor de límite. El comentario también es opcional. Guardamos los datos y en cuanto habilitemos cada item (Cuenta </span><span style="text-align: justify;">+ Cuota </span><span style="text-align: justify;">+ Límite) </span><b style="text-align: justify;">PolicyD </b><span style="text-align: justify;">empezará a vigilar el número de </span><i style="text-align: justify;">emails </i><span style="text-align: justify;">enviados desde </span><b style="text-align: justify;">@ejemplo.com</b><span style="text-align: justify;"> bloqueándolos si superan la cantidad de 2.</span><br />
<span style="text-align: justify;"><br /></span>
<span style="text-align: justify;"><b>Artículo anterior</b>: <a href="http://codementia.blogspot.com.es/2013/05/uso-basico-de-policyd-cluebringer.html" target="_blank">Uso básico de PolicyD Cluebringer: Creación de cuentas y miembros</a></span><br />
<b>Artículo siguiente</b>: <a href="http://codementia.blogspot.com.es/2013/05/uso-basico-de-policyd-cluebringer_30.html" target="_blank">Uso básico de PolicyD Cluebringer: Control de HELO/EHLO</a>Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-66866898675570251262013-05-15T01:34:00.001+02:002013-05-15T17:44:04.717+02:00Uso básico de PolicyD Cluebringer: Creación de cuentas y miembros<div style="text-align: justify;">
En el artículo anterior vimos como instalar y comprobar el servicio <b>Cluebringer.</b> En esta siguiente entrega veremos las reglas para definir políticas y como crear cuentas con miembros para posteriormente crear controles de accesos, comprobaciones de <b>EHLO/HELO</b> y <b>Cuotas</b>. La opciones de comprobación <b>SPF </b>y <b><i>Greylisting</i></b> no se cubrirán aquí. Para ello puede consultarse la <a href="http://wiki.policyd.org/start" target="_blank">página de ayuda de PolicyD</a> o <a href="http://codementia.blogspot.com.es/2013/05/reglas-spf-para-validar-el-correo-lo.html" target="_blank">mi artículo sobre uso de reglas SPF</a>. Empecemos pues por las reglas:</div>
<a name='more'></a><br />
<br />
Las reglas para especificar una política en <b>PolicyD Cluebringer</b> son:<br />
<br />
<ul>
<li>Una política puede especificarse para el origen (remitente) y para el destino (receptor).</li>
<li>Se permite el uso de direcciones <b>IP </b>sólo para el origen, no para el destino.</li>
<li style="text-align: justify;">El formato de las especificaciones es el que sigue más abajo y pueden unirse mediante una coma (<b>,</b>) para generar operaciones lógicas equivalentes a <b>AND</b>.</li>
<li>Todas las especificaciones, excepto <b>ANY</b>, permiten la negación con el uso del prefijo <b>!</b>.</li>
<li style="text-align: justify;">Prioridad: las prioridades se procesan en orden ascendente, la prioridad 0 tiene preferencia sobre la 10 y ésta sobre la 20.</li>
</ul>
<br />
<span style="font-size: large;">Especificaciones</span><br />
<br />
<table border="0"><tbody>
<tr><td style="white-space: nowrap;">Cualquier dirección o <u>lo que sea</u></td><td><span style="font-family: Courier New, Courier, monospace;">NULL ANY</span></td></tr>
<tr><td style="white-space: nowrap;">Cualquier dirección perteneciente a <b>@dominio</b></td><td><span style="font-family: Courier New, Courier, monospace;">@dominio</span></td></tr>
<tr><td>Cualquier dirección igual a <b>usuario@dominio</b></td><td><span style="font-family: Courier New, Courier, monospace;">usuario@dominio</span></td></tr>
<tr><td>Cualquier dirección <b><></b></td><td><span style="font-family: Courier New, Courier, monospace;">@</span></td></tr>
<tr><td colspan="2"><i>Desde la versión 2.1.x</i></td></tr>
<tr><td><b>IP </b>exacta</td><td><span style="font-family: Courier New, Courier, monospace;">a.b.c.d</span></td></tr>
<tr><td>Cualquier dirección en un rango <b>CDIR</b></td><td><span style="font-family: Courier New, Courier, monospace;">a.b.c.d/e</span></td></tr>
<tr><td><b>IP </b>exacta del servidor que gestiona la política (<i>peer server</i>)</td><td><span style="font-family: Courier New, Courier, monospace;">[a.b.c.d]</span></td></tr>
<tr><td>Cualquier dirección en un rango <b>CDIR </b>del <i>peer server</i></td><td><span style="font-family: Courier New, Courier, monospace;">[a.b.c.d/e]</span></td></tr>
<tr><td><b>IPv6</b> exacta</td><td><span style="font-family: Courier New, Courier, monospace;">a:b:c:d:e:f:g:h[/e]</span></td><td><div style="text-align: justify;">
<span style="font-size: x-small;"><i>Bitmask </i>opcional, por defecto 128 si se especifican todos los octetos o se especifica el del extremo derecho. Si éste no existe se calcula el <i>bitmask </i>automáticamente según el número de bits a la izquierda.</span></div>
</td></tr>
<tr><td>Cualquier dirección <b>IPv6 </b>en un rango <b>CDIR </b>del <i>peer server</i></td><td><span style="font-family: Courier New, Courier, monospace;">[a:b:c:d:e:f:g:h[/e]</span></td></tr>
<tr><td>Cualquier dirección definida en un grupo</td><td><span style="font-family: Courier New, Courier, monospace;">%grupo</span></td><td><div style="text-align: justify;">
<span style="font-size: x-small;">Dicho grupo debe contener especificaciones siguiendo estas mismas reglas y se recorren recursivamente. Con esta especificación y el uso de la negación (!) pueden implementarse operaciones <b>OR</b>.</span></div>
</td></tr>
<tr><td>Cualquier nombre de usuario <b>SASL</b></td><td><span style="font-family: Courier New, Courier, monospace;">$sasl_nombre_usuario</span></td><td><div style="text-align: justify;">
<span style="font-size: x-small;">Se permite <b>$*</b> para cualquier <b>SASL </b>y <b>$-</b> para ninguno.</span></div>
</td></tr>
<tr><td style="white-space: nowrap;"><b>rDNS</b> desde la que conecta el cliente</td><td><span style="font-family: Courier New, Courier, monospace;">servidor.ejemplo.com</span></td></tr>
</tbody></table>
<br />
<ul>
<li>El uso del comodín * equivale a <i>cualquiera</i> excepto entre puntos (<u>no es valido</u> <b>servidor.*.ejemplo.com</b>)</li>
<li>La especificación <b>ejemplo.com</b> sólo equivale a <b>ejemplo.com</b></li>
<li>La especificación <b>.ejemplo.com</b> equivale a <b>ejemplo.com</b> y también a <b>servidor.ejemplo.com</b></li>
<li style="text-align: justify;">Técnicamente, el comodín * se expande como<b> [a-z0-9\-_\.]</b> y si no se especifica el prefijo punto (<b>.</b>) se usa <b>^</b></li>
<li>Todas las especificaciones <b>DNS</b> inversa finalizan con <b>$</b></li>
</ul>
<br />
Para ejemplos véase <a href="http://wiki.policyd.org/policies" target="_blank">PolicyD - Policies</a>.<br />
<br />
<span style="font-size: large;">Creación de cuentas (<i>accounting</i>)</span><br />
<br />
<div style="text-align: justify;">
Con estas reglas en mente ya podemos empezar a definir nuestras políticas, empezando por las cuentas y sus miembros. Pulsamos la opción <b>Main </b>del menú <b>Policies</b>. Por defecto <b>PolicyD </b>ya viene configurado con 5 cuentas:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdi5zj67s7Wgud8WV6Mr1aHch1oCC-WxC63PtzAGz-xseG8D0-AGm5-Z9m-3jaanjJx0iXrLt7FWcxoPUqokJPE0MkXAFpZW4aLg0wWRoIM6CErDlB1ryT5-A_O8UKV2exFWOjWfkMFPx-/s1600/policyd1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdi5zj67s7Wgud8WV6Mr1aHch1oCC-WxC63PtzAGz-xseG8D0-AGm5-Z9m-3jaanjJx0iXrLt7FWcxoPUqokJPE0MkXAFpZW4aLg0wWRoIM6CErDlB1ryT5-A_O8UKV2exFWOjWfkMFPx-/s1600/policyd1.png" height="267" width="640" /></a></div>
<br />
<div style="text-align: justify;">
El proceso de creación de cuentas se desarrolla en dos fases: primero se crea una cuenta y después se le asignan miembros, que no son más que especificaciones sobre a qué origen y destino queremos controlar. En la lista desplegable central (<b>Action</b>) es donde podemos elegir <b>añadir</b>, <b>cambiar</b>, <b>eliminar </b>o <b>ver los miembros</b> de cada cuenta, después de haber seleccionado el correspondiente <i>radiobutton</i>. En mi caso decidí inhabilitar todas las políticas por defecto (y los grupos y todo lo que venía por defecto) y empezar desde cero. Para crear una cuenta abrimos el desplegable <b>Action </b>y elegimos <b>Añadir </b>(<i>add</i>) para acceder a la siguiente pantalla:</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9V7XoSp4loXirfrqwCAr4rgRu2EMdSLWcESypiesqcObBcGRUKePnfjXuho6e9GGz4Am-FqlTeMItNjrF0nMeWKlhVGzEM4dEQU-DbYO8Uzdn1dZ5A0Hi4mcq7bxz02064Ah1kc44Yyru/s1600/policyd2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9V7XoSp4loXirfrqwCAr4rgRu2EMdSLWcESypiesqcObBcGRUKePnfjXuho6e9GGz4Am-FqlTeMItNjrF0nMeWKlhVGzEM4dEQU-DbYO8Uzdn1dZ5A0Hi4mcq7bxz02064Ah1kc44Yyru/s1600/policyd2.png" height="212" width="400" /></a></div>
<div style="text-align: justify;">
Vamos a abrir una cuenta para asignar posteriormente cuotas de salida a ciertos usuarios del correo. Le asignamos un nombre, por ejemplo <b>BloqueoSalida</b>, especificamos una prioridad, podemos usar <b>10 </b>que es la que viene en los ejemplos, y una <u>descripción obligatoria</u>. Aceptamos y ya tenemos una cuenta preparada para agregarle miembros. <b>Importante</b>: nótese en la columna <b>Disabled </b>que la cuenta recién creada aparece como <b>Yes </b>(<b>inhabilitada</b>). <u>Cualquier ítem creado en <b>PolicyD </b>está por defecto inhabilitado</u> hasta que no lo cambiamos (<b>Action - Change</b>) para evitar problemas mientras definimos los parámetros.</div>
<br />
<span style="font-size: large;">Miembros</span><br />
<br />
<div style="text-align: justify;">
Seleccionamos la cuenta recien creada y elegimos <b>Action - Members</b> en el menú desplegable. Accederemos a una pantalla vacía con el desplegable <b>Action </b>en el centro y tres columnas: <b>Source, Destination y Disabled</b>. Aquí es donde especificamos a cuales direcciones de origen (remitentes) y destino (receptores) afectará nuestra cuenta. La idea de ejemplo que vamos a usar es que para cualquier correo cuyo dominio del remitente sea <b>ejemplo.com</b>, independientemente del destinatario que sea, queremos aplicarle la regla. Así pues elegimos <b>Action - Add</b> y accedemos a la pantalla de miembros:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAfUvLCRPfjkvfygwXfXcdhhXxAW7zL4Q2YIkOO-o6UTOUGWXXtpQNnBjD8I-_2hVWu_veLZkAOfSQ-Qf3iMUJjvIAgFVMO0J7re7fhgxtUJRtwrMez_ziE5kaLidNPxuT4Gd7S-4sjmE7/s1600/policyd3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAfUvLCRPfjkvfygwXfXcdhhXxAW7zL4Q2YIkOO-o6UTOUGWXXtpQNnBjD8I-_2hVWu_veLZkAOfSQ-Qf3iMUJjvIAgFVMO0J7re7fhgxtUJRtwrMez_ziE5kaLidNPxuT4Gd7S-4sjmE7/s1600/policyd3.png" height="295" width="400" /></a></div>
<div style="text-align: justify;">
En <b>Source </b>especificamos a qué direcciones de origen queremos controlar: según las reglas anteriores para indicar que queremos afectar a un dominio escribiremos <b>@ejemplo.com</b>; en <b>Destination </b>especificamos a cuales direcciones de destino, se nos permiten las mismas especificaciones que en <b>Source </b>excepto el uso de <b>IPs</b>. En este caso es indiferente a quien vaya dirigido el <i>email </i>así que escribimos <b>any</b>. El comentario es optativo. Pulsamos <b>Enviar consulta</b> y listo. Ya tenemos una cuenta creada con un miembro que afecta a todas las cuentas del dominio <b>ejemplo.com</b> sea quien sea el destinatario. Este sería el caso más sencillo. </div>
<br />
<div style="text-align: justify;">
Supongamos ahora que no solo queremos que nuestra política afecte a <b>ejemplo.com</b>, sino también a <b>clientes.com</b> y a <b>ofertas.es</b>. Una manera de hacerlo sería creando un miembro para cada dominio tal como hicimos con <b>ejemplo.com</b>, pero hay otra manera más cómoda: usando grupos. Un grupo nos permite reunir todas las direcciones que deseemos en un solo conjunto de forma que indicándole al miembro que grupo debe consultar ya obtenemos la funcionalidad buscada, siendo más cómodo realizar cambios posteriormente. Para crear un grupo elegimos la opcíon de menú <b>Group</b> en <b>Policies </b>y elegimos <b>Add </b>en el desplegable <b>Action</b>:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5L0mpq6pLNKHcWbUcZLb4Bt3uNz5kSQz_fegiQztRZF07x23BOksvSt0jfXWDDFgB-IKnkkkeHLIxYCoKyLMQzpJ7NfdciNZyqG0tVs9Lm5NboHObG94rFoTqkSRNWPBJbtiJlhSFNcgF/s1600/policyd4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5L0mpq6pLNKHcWbUcZLb4Bt3uNz5kSQz_fegiQztRZF07x23BOksvSt0jfXWDDFgB-IKnkkkeHLIxYCoKyLMQzpJ7NfdciNZyqG0tVs9Lm5NboHObG94rFoTqkSRNWPBJbtiJlhSFNcgF/s1600/policyd4.png" height="192" width="400" /></a></div>
<div style="text-align: justify;">
Le damos un nombre, por ejemplo <b>ListaInterna</b> y enviamos la consulta. El comentario es optativo. Una vez creado el grupo lo seleccionamos y, de nuevo en la lista desplegable, seleccionamos <b>Members</b>. Accedemos a una pantalla en blanco con dos columnas: <b>Member </b>y <b>Disabled</b>. Seleccionamos otra vez <b>Action - Add</b> y aquí es donde podemos especificar las direcciones a las que afectará nuestra política. Se aplican las reglas de las especificaciones, por lo tanto añadimos las direcciones <b>@ejemplo.com</b>, <b>@clientes.com</b> y <b>@ofertas.es</b>, el comentario también es optativo. Con este grupo definido volvemos a la opción <b>Main </b>de <b>Policies</b>, seleccionamos nuestra cuenta<b> </b>recien creada <b>BloqueoSalida</b> y elegimos <b>Action - Members</b>. En <i>members </i>seleccionamos la regla definida y de nuevo <b>Action - Change</b> y, en el campo <b>Source </b>donde especificamos <b>@ejemplo.com</b>, lo sustituimos por <b>%ListaInterna</b>. Guardamos y listo. Nuestra cuenta nos permitirá aplicar políticas a los correos cuyo origen sea algún dominio de los anteriormente descritos pudiendo modificar el grupo cómodamente cuando nos interese.</div>
<br />
<div style="text-align: justify;">
En la siguiente entrega veremos como aplicar límites a los buzones de correo mediante cuotas usando la cuenta recién creada.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<b>Artículo anterior</b>: <a href="http://codementia.blogspot.com.es/2013/05/establece-una-cuota-para-tus-buzones-de.html" target="_blank">Establece una cuota de correo (y algo más) con Policy Cluebringer</a><br />
<b>Artículo siguiente</b>: <a href="http://codementia.blogspot.com.es/2013/05/uso-basico-de-policyd-cluebringer-cuotas.html" target="_blank">Uso básico de PolicyD Cluebringer: Cuotas</a></div>
Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com1tag:blogger.com,1999:blog-2653034041761142933.post-20914453865892308522013-05-10T18:56:00.001+02:002013-05-11T01:01:04.848+02:00Reglas SPF para validar el correo: lo básico<div style="text-align: justify;">
<b>SPF </b>(<a href="http://en.wikipedia.org/wiki/Sender_Policy_Framework" target="_blank">Sender Policy Framework</a>) es el sistema generalmente implementado en los servidores de correo para evitar el <i>spam </i>mediante <i><a href="http://en.wikipedia.org/wiki/Email_spoofing" target="_blank">spoofing</a> </i>(falsificación del origen) y consiste en verificar la <b>ip </b>del remitente. Veamos un ejemplo muy sencillo de <i>email spoofing</i>. Para simplificar, supongamos que tenemos acceso a un servidor de correo (<b>MTA</b>) <b>Postfix </b>desastrosamente configurado: sin mecanismo de autentificación, sin limitación de retransmisión (<i>relay</i>) y, obviamente, sin reglas <b>SPF </b>especificadas. Accedemos pues tranquilamente al puerto 25 del blanco y saludamos:<br />
<a name='more'></a></div>
<br />
<span style="font-family: Courier New, Courier, monospace;">telnet servidor.com 25</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">220 correo.servidor.com ESMTP Postfix</span><br />
<span style="font-family: Courier New, Courier, monospace;">HELO comoestas.com</span><br />
<span style="font-family: Courier New, Courier, monospace;">250 correo.servidor.com</span><br />
<span style="font-family: Courier New, Courier, monospace;">MAIL FROM: mailfalso@otroservidor.com</span><br />
<span style="font-family: Courier New, Courier, monospace;">250 2.1.0. Ok</span><br />
<span style="font-family: Courier New, Courier, monospace;">RCPT TO: mailreal@otroservidormas.com</span><br />
<span style="font-family: Courier New, Courier, monospace;">250 2.1.0 Ok</span><br />
<span style="font-family: Courier New, Courier, monospace;">DATA</span><br />
<span style="font-family: Courier New, Courier, monospace;">354 Start mail input; end with <CRLF>.<CRLF></span><br />
<span style="font-family: Courier New, Courier, monospace;">Compra ésto barato barato.</span><br />
<span style="font-family: Courier New, Courier, monospace;">.</span><br />
<span style="font-family: Courier New, Courier, monospace;">250 Ok</span><br />
<br />
<div style="text-align: justify;">
El servidor enviará nuestro correo con remitente falso a un destinatario real sin preocuparse de nada: no comprueba si dicho remitente existe y mucho menos si es un usuario legítimo del servidor. Para evitarlo es para lo que se creo <b>SPF</b>. ¿Cómo se valida una dirección de <i>email </i>con este sistema? Tomando el dominio del remitente y usándolo para buscar su <b>ip </b>en ciertos registros que se especifican en la <a href="http://en.wikipedia.org/wiki/DNS_zone" target="_blank">zona DNS</a> de nuestro (su) servidor. Todo servidor en la red tiene asociado un fichero de texto que describe su zona dentro de la red, el <a href="http://en.wikipedia.org/wiki/Zone_file" target="_blank">fichero de zona</a> precisamente. Su formato es engañosamente sencillo: <b>Nombre - TTL - Tipo - Prioridad - Valor</b>, por ejemplo:</div>
<br />
<span style="font-family: Courier New, Courier, monospace;">servidor.com 600 A 0 245.136.54.211</span><br />
<br />
<div style="text-align: justify;">
y una de las acciones que se puede realizar consultando dicho fichero es, dado un nombre de dominio, conocer su <b>ip </b>y viceversa (<a href="http://en.wikipedia.org/wiki/Reverse_DNS_lookup" target="_blank"><i>dns inversa</i></a>). En concreto <b>SPF </b>se vale de los registros <b>A </b>(o <b>AAAA</b>), <b>MX </b>y <b>PTR</b>. El registro <b>A </b>debe contener una <b>ip </b>de 32 bits (<b>ipv4</b>) o de 128 (<b>ipv6</b>) si el registro es <b>AAAA</b>. El registro <b>MX </b>se usa para apuntar a servidores de correo que podemos usar además del propio y el registro <b>PTR </b>contiene el nombre de nuestro dominio en un formato especial. Sabiendo esto, ¿cómo especificamos una directiva <b>SPF</b>? Añadiendo también en la zona <b>DNS </b>un registro de tipo <b>TXT </b>cuyo nombre será nuestro dominio y que contendrá una cadena de texto según la especificación <b>SPF</b>:</div>
<br />
<span style="font-family: Courier New, Courier, monospace;">servidor.com 600 TXT 0 v=spf1 a mx -all</span><br />
<br />
<div style="text-align: justify;">
Esta cadena de ejemplo simplemente especifica la versión de <b>SPF </b>(obligatorio) y dispone que se compruebe si el nombre de dominio del remitente tiene una <b>ip </b>asociada (consulta al registro <b>A</b>) o si dicho dominio dispone de registros <b>MX </b>(servidores de correo) cuyos <b>A </b>puedan autentificar dicha <b>ip</b>, en cuyo caso el <i>email </i>será enviado, sino se rechaza (<b>-all</b>). Evidentemente con algo tan simple no sería excesivamente complicado crear un servidor con los datos de zona <b>DNS </b>falsificados para eludir el control, por lo que <b>SPF </b>permite afinar mucho más el control.</div>
<br />
<div style="text-align: justify;">
<b>SPF </b>dispone de <b>mecanismos </b>a los que se puede aplicar <b>prefijos </b>para cambiar su comportamiento, y de <b>modificadores</b>. Los <b>mecanismos </b>en un registro <b>SPF </b><u>pueden ser cero o más</u>, y son</div>
<br />
<ul>
<li><b>all</b></li>
<li><b>a</b></li>
<li><b>mx</b></li>
<li><b>ptr</b></li>
<li><b>ip4</b></li>
<li><b>ip6</b></li>
<li><b>include</b></li>
<li><b>exists</b></li>
</ul>
<br />
a los que se les puede aplicar cualquiera de los siguientes <b>prefijos</b>:<br />
<br />
<ul>
<li><b><i><span class="Apple-tab-span" style="white-space: pre;">- </span>fail</i></b>, se rechaza la <b>ip </b>que coincide con el <b>mecanismo </b>y se desecha el mensaje</li>
<li><b><i>~<span class="Apple-tab-span" style="white-space: pre;"> </span>softfail</i></b>, no se desecha el <i>email </i>pero se marca una cabecera especial para darle algún tratamiento posterior</li>
<li><b><i>+<span class="Apple-tab-span" style="white-space: pre;"> </span>pass</i></b>, <span class="Apple-tab-span" style="white-space: pre;">se </span>aprueba la <b>ip </b>y se añade una cabecera de tipo <b>Received-SPF: pass</b></li>
<li><b><i>?<span class="Apple-tab-span" style="white-space: pre;"> </span>neutral</i></b>, no se da el mensaje como bueno pero tampoco como malo. Añade una cabecera de tipo <b>Received-SPF: neutral</b>, se utiliza sobretodo para periodos de prueba</li>
</ul>
<br />
Los <b>modificadores</b>, que no veremos aquí, son<br />
<br />
<ul>
<li><b>redirect</b></li>
<li><b>explanation</b></li>
</ul>
<br />
<span style="font-size: large;">Cómo funcionan</span><br />
<br />
<div style="text-align: justify;">
El servidor de correo toma el dominio del remitente y lo somete a las pruebas <b>SPF </b>que se le indiquen para saber si debe enviarlo, rechazarlo o bloquearlo. <b>SPF </b>consultará los registros <b>A </b>necesarios para ello e incluso podrá realizar alguna consulta de <i>dns inversa</i> si se requiere. Las reglas por defecto son:</div>
<br />
<ul>
<li>El <b>prefijo </b>por defecto es <b>pass </b>(+).</li>
<li style="text-align: justify;">Los <b>mecanismos </b>se evalúan de izquierda a derecha, hasta que se encuentra una coincidencia entre <b>mecanismo </b>y dirección <b>ip </b>del emisor, entonces se usa el valor de su <b>prefijo</b>.</li>
<li>Si no hay coincidencias de <b>mecanismo </b>o <b>modificador</b>, el resultado por defecto es <b>neutral</b>.</li>
<li>Si un dominio no tiene registro <b>SPF</b>, el valor que toma la comprobación de <b>SPF </b>es <b>none</b>.</li>
<li style="text-align: justify;">Si un dominio tiene un error temporal durante el procesamiento <b>DNS</b>, se devuelve el valor <b>TempError</b>. - Si ocurre algún tipo de error sintáctico o de evaluación (por ejemplo, el domino especifica un <b>mecanismo </b>desconocido) el resultado es <b>PermError</b>.</li>
</ul>
<br />
<span style="font-size: large;">Mecanismos</span><br />
<br />
<div style="text-align: justify;">
A partir de este punto el resultado <b>cierto </b>se usa para indicar que se ha encontrado un <b>ip </b>para el remitente, y <b>falso </b>en caso contrario. En todos los ejemplos se asume que el remitente es <i>usuario@servidor.com</i></div>
<br />
<span style="font-size: large;">all</span><br />
<br />
<div style="text-align: justify;">
Devuelve siempre cierto, es decir, admite todos los remitentes sean cuales sean. Suele usarse con el <b>prefijo - </b>(o <b>~</b>) al final de la cadena para indicar que deben rechazarse todas las direcciones que no hayan sido validadas por reglas anteriores.</div>
<br />
<span style="font-size: large;">a</span><br />
<br />
Sin especificar nada más se utiliza el dominio del remitente para consultar los registros <b>A</b>.<br />
<br />
<span style="font-size: large;">a:dominio, a:dominio/rango, a/rango</span><br />
<br />
<div style="text-align: justify;">
Se comprueban todos los registros <b>A </b>en el dominio, si se especifica un rango cada <b>ip </b>devuelta por la consulta se expande a la correspondiente subred <a href="http://es.wikipedia.org/wiki/Classless_Inter-Domain_Routing" target="_blank">CIDR</a> para seguir buscando en dicha subred.</div>
<br />
Ejemplos:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">v=spf1 a -all</span><br />
<span style="font-family: Courier New, Courier, monospace;">v=spf1 a:servidor.com -all</span><br />
<br />
<div style="text-align: justify;">
En el primer caso se toma el dominio del remitente y se consulta la <b>ip</b>, el resto se rechazan. El segundo caso equivale al primero solo que se ha especificado el dominio.</div>
<br />
<span style="font-family: Courier New, Courier, monospace;">v=spf1 a/24 a:servidor2.com/24 -all</span><br />
<br />
<div style="text-align: justify;">
Si la <b>ip</b> del remitente se resuelve a <b>237.245.0.10</b> se buscara su <b>ip </b>en la red de clase C <b>237.245.0.0/24</b>, después se consulta en <b>servidor2.com</b> cuyos registros <b>A </b>se expanden a su vez a subredes <b>CDIR </b>para seguir buscando.</div>
<br />
<span style="font-size: large;">mx</span><br />
<br />
Se comprueban todos los registros <b>A </b>de los <b>MX </b>del dominio del remitente <i>en orden de prioridad <b>MX</b></i>.<br />
<br />
<span style="font-size: large;">mx/rango, mx:dominio, mx:dominio/rango</span><br />
<br />
<div style="text-align: justify;">
Al igual que con el <b>mecanismo a</b>, si se especifica un rango la <b>ip </b>se expande en una subred <b>CDIR </b>donde se buscan los <b>MX </b>para obtener los registros <b>A</b>.</div>
<br />
Ejemplos:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">v=spf1 mx mx:otrodominio.com -all</span><br />
<br />
Se consultan los <b>MX </b>propios y los de otro servidor para reintentar el envío.<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">v=spf1 mx/24 mx:otrodominio.com/24 -all</span><br />
<br />
<div style="text-align: justify;">
Se comprueban todos los registros <b>MX </b>de todo el rango de red del servidor propio y de <b>otrodominio</b>.</div>
<br />
<span style="font-size: large;">ptr, ptr:dominio</span><br />
<br />
<div style="text-align: justify;">
Se consulta el <i>hostname </i>(o <i>hostnames</i>) para la <b>ip </b>del cliente y se validan posteriormente buscando los registros <b>A</b>. Un <i>hostname </i>invalido se descarta.</div>
<br />
<span style="font-size: large;">ip4:ip, ip4:ip/rango</span><br />
<br />
Se permite enviar correo desde la <b>ipv4 </b>o rango de <b>ip's</b> indicado. Si no se especifica rango se asume <b>/32</b>.<br />
<br />
<span style="font-size: large;">ip6:ip, ip6:ip/rango</span><br />
<br />
Se permite enviar correo desde la <b>ipv6 </b>o rango de <b>ip's</b> indicado. Si no se especifica rango se asume <b>/128</b>.<br />
<br />
<div style="text-align: justify;">
Con estos <b>mecanismos </b>básicos ya podemos ponérselo muy difícil al que intente <i>spoofear </i>nuestro servidor de correo. Para más información sobre los <b>modificadores </b>y otras características avanzadas o de prueba pueden consultarse los enlaces.</div>
<br />
<b>Enlaces</b>: <a href="http://franjasan.tumblr.com/post/20640508565/registro-spf" target="_blank">Registro SPF</a> (Franja San), <a href="http://en.wikipedia.org/wiki/PTR_record#PTR" target="_blank">List of DNS Record Types</a>, <a href="http://en.wikipedia.org/wiki/MX_record" target="_blank">MX Record</a>Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com1tag:blogger.com,1999:blog-2653034041761142933.post-45521040222602956682013-05-09T15:41:00.002+02:002013-05-09T15:42:22.337+02:00Servidores de Blacklists: ¿solución o problema?<div style="text-align: justify;">
Todo administrador de un servidor sabe que una de las peores cosas que le puede pasar es que se le cuele un <i>spammer </i>y empiece a enviar correo no deseado utilizando su <b>ip</b>. Aparte del trabajo que supone localizar al maldito y limpiar sus fechorías sin perturbar el funcionamiento normal del resto de usuarios también se verá enfrentado a otro problema, sino igual mayor: los servidores de listas negras. La teoría es muy simple: un servicio de listas negras se dedica a recoger denuncias y comportamientos considerados ilegales o nocivos para el buen funcionamiento de la red y la tranquilidad de los navegantes y almacena la <b>ip </b>desde la que se originan tales comportamientos para que cualquiera que consulte su base de datos sepa que no debe tener tratos con ella. Lo mismo que las listas de morosos usadas por la banca. La <b>ip </b><i>baneada </i>puede que esté siendo usada por alguna persona o grupo con intenciones delictivas y aquí este tipo de servicio cumple bien su función, y si la <b>ip </b>es de un servidor cuyas actividades son totalmente legales pero que se ha visto temporalmente comprometido se solicita al servidor de listas negras la baja y no debería haber mayor problema.<br />
<a name='more'></a></div>
<br />
<div style="text-align: justify;">
Pero no es así. Y no lo es por la forma en la que funcionan estos servicios. Si alguna vez tienes la desgracia de ver la <b>ip </b>de tu servidor incluida en una de estas listas prepárate para un largo camino. El servicio que te <i>banee </i>te avisará mediante <i>email </i>para que compruebes tus datos y hagas las modificaciones/reclamaciones oportunas, modificaciones que no suelen coincidir entre una y otra lista. La mayoría de ellos utilizan un robot para gestionar las solicitudes así que prepárate para un rollo al estilo de pulse 1 si tal y 2 para cual. Algunos suelen contestar rápido, otros te ponen en lista de espera (incluso te indican cuántos hay delante de ti), algunos simplemente <i>banean </i>y otros utilizan sistemas de clasificación de reputación, algunos te <i>desbanean </i>enseguida, otros marcan plazos. Resumiendo: cada uno a su bola. </div>
<br />
<div style="text-align: justify;">
Aun así lo peor no es eso. Si sólo te <i>banea </i>un servidor de listas negras el problema se reduce a un simple toma y daca con <i>emails </i>de ida y vuelta hasta dar con la solución. Lo peor es que estos servidores se vigilan entre ellos, no se si aposta o por que se puede. Aparte de banear <b>ip's</b> denunciadas comprueban las listas equivalentes de otros servidores del gremio y añaden las <b>ip </b>que encuentran a su propia lista. La consecuencia es que, por prisa que te des, tu <b>ip </b>termina siendo <i>baneada </i>rápidamente por varios de estos servicios, con los que debes ponerte en contacto uno por uno para solucionarlo y que, mientras no se solucione, tendrán tu <b>ip </b>marcada como paria y disponible para otras listas del mismo estilo. Evidentemente cada servidor te impondrá sus propias reglas para quitarte de la lista y, eso no falla en ninguno, te recomendará que añadas las correspondientes directivas a tu <b>MTA </b>para usar sus servicios. Si tardaste 3 días en limpiar los rastros del <i>spammer </i>puedes tardar 15 en hacer que te quiten de las listas, y sin ninguna disculpa por los problemas ocasionados.</div>
<br />
<div style="text-align: justify;">
¿Debe ser esto así? No lo creo. Lo que hacen estas empresas puede que sea legal pero no parece muy limpio, no huele muy bien. ¿A santo de qué se les permite compartir las <b>ip's</b> entre ellas? ¿Porqué no justifican claramente el motivo por el que <i>banean</i>? ¿Que autoridad tienen para imponer reglas? Entiendo que una empresa debe generar beneficios pero esto es casi una extorsión. ¿Seria muy complicado centralizar este servicio al estilo de los servidores de nombres con una única autoridad competente como responsable? No es de extrañar que haya <a href="http://nakedsecurity.sophos.com/es/2013/03/28/massive-ddos-attack-against-anti-spam-provider-impacts-millions-of-internet-users/" target="_blank">ataques como el del mes de Marzo contra un servidor SpamHaus</a>, aun siendo inocentes. Lo gestionan muy mal.</div>
Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-18366081562252282322013-05-08T14:55:00.000+02:002013-05-25T00:07:20.908+02:00Establece una cuota para tus buzones de correo (y algo más) con PolicyD Cluebringer<div style="text-align: justify;">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_XEGMBKYdTQ2sOUbBS81MxorOxpVBTtbiBfVrn7IByM7R4be7KY5Qo9IRMP1AlucUF_rryMWrMMkPAAgsY_2G__t4HPgYF3T561xYODylHjbpsM-yAa9VxCEusHTSxN_2uyTFcoV-_stq/s1600/policyd.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_XEGMBKYdTQ2sOUbBS81MxorOxpVBTtbiBfVrn7IByM7R4be7KY5Qo9IRMP1AlucUF_rryMWrMMkPAAgsY_2G__t4HPgYF3T561xYODylHjbpsM-yAa9VxCEusHTSxN_2uyTFcoV-_stq/s1600/policyd.png" /></a></div>
<a href="http://www.policyd.org/" target="_blank">PolicyD</a> es un software desarrollado en <b>C </b>que permite implementar políticas de seguridad en nuestro servidor <b>MTA </b>con facilidad dado que nos proprociona un interface web muy simple<b>: </b>gestión de <b>EHLO/HELO</b>, gestión de <b>SPF</b>, <i>Greylisting </i>y cuotas. La versión que instalé es la 2.0.10 (<b>Cluebringer</b>) en un sistema <b>CentOS 5</b> con servidor <b>Postfix </b>y <b>MySQL</b>. Los requisitos previos son:<br />
<br /></div>
<ul>
<li>MySQL, PostgreSQL, or SQLite</li>
<li>Net::Server >= 0.96</li>
<li>Net::CIDR</li>
<li>Config::IniFiles</li>
<li>Cache::FastMmap</li>
<li>Mail::SPF</li>
<li>PHP 5+ con soporte PDO para tu DB (si vas a usar la interface web, muy recomendable)<a name='more'></a></li>
</ul>
<br />
<span style="font-size: large;">Descargamos el software</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br />wget http://sourceforge.net/projects/policyd/files/2.0.x%20%28Stable%29/v2.0.10/cluebringer-2.0.10.tar.bz2/download?use_mirror=biznetnetworks</span><br />
<br />
y descomprimimos<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">for file in cluebringer-2.0.10.tar.bz2; do tar jxf "${file}"; done</span><br />
<span style="font-size: large;"><br /></span>
<span style="font-size: large;">Configuramos MySQL</span><br />
<br />
Entramos en la carpeta <span style="font-family: Courier New, Courier, monospace;">/cluebringer-2.0.10/database</span> y ejecutamos <i>mysql</i><br />
<i><br /></i>
<span style="font-family: Courier New, Courier, monospace;">mysql -u root -p</span><br />
<span style="font-family: Courier New, Courier, monospace;">Enter password</span><br />
<span style="font-family: Courier New, Courier, monospace;">...</span><br />
<span style="font-family: Courier New, Courier, monospace;">> CREATE DATABASE policyd;</span><br />
<span style="font-family: Courier New, Courier, monospace;">> GRANT ALL ON policyd TO 'policyduser'@'localhost' IDENTIFIED BY 'password';</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Courier New, Courier, monospace;">> quit</span><br />
<br />
Creamos el fichero de tablas que necesita <b>PolicyD </b>en formato <b>MySQL</b>:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">for i in core.tsql access_control.tsql quotas.tsql amavis.tsql checkhelo.tsql checkspf.tsql greylisting.tsql</span><br />
<span style="font-family: Courier New, Courier, monospace;">do</span><br />
<span style="font-family: Courier New, Courier, monospace;"> ./convert-tsql mysql $i</span><br />
<span style="font-family: Courier New, Courier, monospace;">done > policyd.mysql</span><br />
<br />
lo cargamos en nuestra base de datos:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">mysql -u root -p policyd < policyd.mysql</span><br />
<br />
y volvemos a la carpeta superior.<br />
<br />
<span style="font-size: large;">Instalamos el software</span><br />
<br />
<span style="font-family: Courier New, Courier, monospace;">cp -r cbp /usr/local/lib/policyd-2.0/</span><br />
<span style="font-family: Courier New, Courier, monospace;">cp cbpadmin /usr/local/bin/</span><br />
<span style="font-family: Courier New, Courier, monospace;">cp cbpolicyd /usr/local/sbin/</span><br />
<br />
Creamos un usuario<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">useradd -d /etc/cluebringer -u 125 cluebringer</span><br />
<br />
Y configuramos<br />
<br />
<div style="text-align: justify;">
Creamos una carpeta para el fichero de configuración <span style="font-family: Courier New, Courier, monospace;">mkdir /etc/cluebringer</span> en la que copiamos <b>cluebringer.conf</b>, las directivas de este fichero son muy claras. En mi caso inhabilité el protocolo <b>Bizanga </b>y cargo todos los módulos aunque no los use, en cuanto al resto:</div>
<pre class="brush:bash;gutter:false"># User to run this daemon as
user=cluebringer
group=cluebringer
# Filename to store pid of parent process (imprescindible para poder activar el servicio)
pid_file=/var/run/cbpolicyd/cbpolicyd.pid
...
# File to log to instead of stdout
log_file=/var/log/cbpolicyd.log
...
log_detail=tracking,policies
# IP to listen on, * for all
host=*
# Port to run on
port=10031
...
[database]
#DSN=DBI:SQLite:dbname=policyd.sqlite
DSN=DBI:mysql:database=policyd;host=localhost
Username=policyduser
Password=password
#
</pre>
<div style="text-align: justify;">
y para terminar necesitamos un par de ficheros de inicialización que <a href="https://googledrive.com/host/0B0RWiOy4QH6XejlDVTBac29oNTg/postfix-cluebringer.init.rar" target="_blank">pueden descargarse aquí</a>. Están modificados para correr en <b>CentOS 5</b> sin complicarme la vida, la versión original para correr sobre <b>Ubuntu </b><a href="http://signalboxes.net/howto/policyd/" target="_blank">puede encontrarse aquí</a>. Los renombramos y los movemos, ambos deben tener de propietario <b>root</b>:</div>
<div style="text-align: justify;">
</div>
<ul>
<li><i>postfix-cluebringer.init.txt</i> lo renombramos a <i>cluebringer </i>y lo movemos a<i> /etc/init.d</i>, permisos 755</li>
<li><i>postfix-cluebringer.default</i> lo renombramos a <i>postfix-cluebringer</i> y lo movemos a <i>/etc/default</i>, permisos 644</li>
</ul>
<br />
<div style="text-align: justify;">
<span style="font-size: large;">Arrancar el daemon</span></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">/etc/init.d/cluebringer start</span></div>
<br />
y echamos un vistazo a <i>/var/log/cbpolicyd.log</i> para ver si todo va bien. Si no aparecen errores ejecutamos <i>/etc/init.d/cluebringer status</i> y nos mostrará los procesos activos y el <i>bind </i>al puerto 10031:<br />
<pre class="brush:bash;gutter:false"> 3619 ? Ss 0:02 /usr/bin/perl /usr/local/sbin/cbpolicyd --config /etc/cluebringer.conf
5713 ? S 0:03 /usr/bin/perl /usr/local/sbin/cbpolicyd --config /etc/cluebringer.conf
16205 ? S 0:03 /usr/bin/perl /usr/local/sbin/cbpolicyd --config /etc/cluebringer.conf
21529 ? S 0:03 /usr/bin/perl /usr/local/sbin/cbpolicyd --config /etc/cluebringer.conf
32730 ? S 0:03 /usr/bin/perl /usr/local/sbin/cbpolicyd --config /etc/cluebringer.conf
tcp 0 0 :::10031 :::* LISTEN 3619/perl
</pre>
Para que el programa arranque automáticamente en los reinicios:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">chkconfig --add cluebringer</span><br />
<span style="font-family: Courier New, Courier, monospace;">chkconfig --level 2345 cluebringer on</span><br />
<br />
<br />
<span style="font-size: large;">Instalar WebUI</span><br />
<br />
Copiamos la carptea /webui a nuestro directorio web:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">cp -r webui /var/www/.../webui</span><br />
<br />
y abrimos el fichero <i>/includes/config.php</i> para configurar el acceso a la base de datos:<br />
<br />
<pre class="brush:bash;gutter:false"># mysql:host=localhost;dbname=policyd
#
# pgsql:host=xx;dbname=yyy
#
# sqlite:////full/unix/path/to/file.db?mode=0666
#
#$DB_DSN="sqlite:////tmp/cluebringer.sqlite";
$DB_DSN="mysql:host=localhost;dbname=policyd";
$DB_USER="policyduser";
$DB_PASS="password";
</pre>
Para terminar protegemos nuestra carpeta con clave: creamos un <b>.htaccess</b> en <i>/webui</i><br />
<pre class="brush:bash;gutter:false"># /var/www/.../webui/.htaccess
AuthUserFile /usr/share/cluebringer/webui/.htpasswd
AuthGroupFile /dev/null
AuthName "user and password"
AuthType Basic
<LIMIT GET>
require valid-user
</LIMIT>
</pre>
<div>
</div>
<div>
creamos el usuario con su clave</div>
<div>
<pre class="brush:bash;gutter:false">htpasswd -c /usr/share/cluebringer/webui/.htpasswd elusuario</pre>
</div>
<div>
</div>
<div>
y modificamos <b>httpd.conf</b></div>
<div>
</div>
<pre class="brush:bash;gutter:false"><div>
<Directory "/var/www/.../webui">
AllowOverride AuthConfig
Order allow,deny
Allow from all
</Directory>
</div>
</pre>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<span style="font-size: large;">Integración con Postfix</span></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Para indicarle a <b>Postfix </b>que use <b>PolicyD </b>modificamos las directivas <i>smtpd_recipient_restrictions</i> y <i>smtpd_end_of_data_restrictions</i> añadiéndole <i>check_policy_service inet:127.0.0.1:10031</i> <b>al principio de la lista</b>, importante. Ya podemos reiniciar <b>Postfix</b>:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<span style="font-family: Courier New, Courier, monospace;">postfix reload</span></div>
<div>
</div>
<div>
</div>
<br />
<div style="text-align: justify;">
En el próximo artículo veremos como configurar los chequeos <b>EHLO/HELO</b> y el establecimiento de cuotas para los buzones, la gestión <b>SPF </b>y el <i>Greylisting</i> los realizo por otro lado.</div>
<br />
<div style="text-align: justify;">
<b>Enlaces</b>: <a href="http://www.kutukupret.com/2009/09/13/postfix-centos-policyd-v2-mysql/" target="_blank">Postfix + Centos + Policyd V2 + MySQL</a> (2.0.7), <a href="http://signalboxes.net/howto/policyd/" target="_blank">Using Policyd to limit mail</a> (Ubuntu, Apache vHosts, Nginx), <a href="http://shriikant.blogspot.com.es/2011/08/start-stop-daemon-script-for-centos-5.html" target="_blank">start-stop-daemon script for CentOS 5.!</a><br />
<br />
<b>Siguiente artículo</b>: <a href="http://codementia.blogspot.com.es/2013/05/uso-basico-de-policyd-cluebringer.html" target="_blank">Uso básico de PolicyD Cluebringer: Creación de cuentas y miembros</a></div>
Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com0tag:blogger.com,1999:blog-2653034041761142933.post-34185929695934965552013-04-30T16:12:00.000+02:002013-06-13T19:03:36.211+02:00Firma tu correo con opendkim y evita ser tratado como spammer<div style="text-align: justify;">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPx5JTfAYyG0Uk4wX2dUZzBus6ln5tnnSTbztq5fvSaFWUwDH3HK3Run_8lS9qIlFJkj-JPmUjCSShNH5QS0k4_NLCG8PXTU3GFVNEE9kVprpEkulhP6jZ57Wz4reKgpTHSIHbZCyH0eP0/s1600/opendkim.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPx5JTfAYyG0Uk4wX2dUZzBus6ln5tnnSTbztq5fvSaFWUwDH3HK3Run_8lS9qIlFJkj-JPmUjCSShNH5QS0k4_NLCG8PXTU3GFVNEE9kVprpEkulhP6jZ57Wz4reKgpTHSIHbZCyH0eP0/s1600/opendkim.png" /></a></div>
Una manera de certificar nuestro correo para evitar ser rechazados como <i>spam</i>, y de comprobar también los que nos llegan, es instalar el software <a href="http://www.opendkim.org/" target="_blank">opendkim</a>, la implementación abierta y en estos momentos más actualizada de <a href="http://www.dkim.org/" target="_blank">DKIM</a> (<i>dkim-milter</i>). <b>DKIM </b>implementa un mecanismo de autentificación de correo de forma que éste puede ser validado por el destinatario mediante criptografía de clave pública. El proceso es muy sencillo: se generan dos claves para el servidor, una pública y una privada, la clave pública se coloca en un registro <b>DNS</b>, se configura <b>opendkim </b>para que sepa dónde encontrarlas y <b>Postfix </b>(o <b>SendMail</b>) para que utilize <b>DKIM </b>y listo. Todos nuestros correos se enviarán firmados y se comprobará la autenticidad de los que se reciban (siempre que contengan una clave <b>DKIM</b>).<br />
<a name='more'></a></div>
<div style="text-align: justify;">
Lo he probado sobre <b>CentOS 5</b> pero no debería haber problema con otras versiones. En primer lugar descargamos <i>opendkim </i>desde el <a href="http://fedoraproject.org/wiki/EPEL/es" target="_blank">repositorio REHL EPEL de Fedora</a> que aunque no está soportado oficialmente por <b>CentOS</b>, sirve a nuestros propósitos. Si no tienes dicho repositorio ejecuta:</div>
<pre class="brush:bash;gutter:false">wget http://dl.fedoraproject.org/pub/epel/5/x86_64/epel-release-5-4.noarch.rpm
wget http://rpms.famillecollet.com/enterprise/remi-release-5.rpm
sudo rpm -Uvh remi-release-5*.rpm epel-release-5*.rpm
</pre>
<pre class="brush:bash;gutter:false"></pre>
En estas notas se asume que:<br />
<br />
<ul>
<li style="text-align: justify;">Usamos una versión actual de <b>Linux </b>compatible <b>Red-Hat</b> (<b>RHEL 5/6</b>, <b>CentOS 5/6</b>, <b>Fedora</b>...)</li>
<li>Está corriendo una <b>MTA </b>capaz de correr un filtro de correo (<i>milter</i>) como <b>Postfix 2.3.3</b> o superior (ejecuta <b>postconf -d mail_version</b>) o <b>SendMail</b></li>
<li>La configuración de <b>Postix </b>(o <b>SendMail</b>) está actualmente activa</li>
<li>Si prefieres usar <b>Postfix </b>debes detener <b>SendMail </b>(ejecuta <b>service sendmail status</b> para verificarlo), de hecho la distribución <b>CentOS 5</b> recomienda incluso desinstalar <b>SendMail</b></li>
<li>Si prefieres usar <b>SendMail </b>debes detener <b>Postfix </b>(<b>service postfix status</b> para verificarlo)</li>
<li>Todo los ejemplos se ejecutan como <b>root</b>, si lo pruebas mediante <b>sudo </b>puede que algo no funcione como debe</li>
</ul>
<div style="text-align: justify;">
Empezamos pues instalando <i>opendkim</i>: <b>yum install opendkim</b> y creando un usuario: <b>useradd -s /sbin/nologin -b /var/run/opendkim opendkim</b></div>
<br />
<span style="font-size: large;">Generación de claves</span><br />
<br />
<div style="text-align: justify;">
Por defecto, cuando <i>opendkim </i>se instala se crean dos ficheros de clave (publica y privada) en la carpeta <b>/etc/opendkim/keys</b>: <b>default.private</b> y <b>default.txt</b>, usando el nombre de tu dominio. Nosotros vamos a generarlos de todas formas para aprender el proceso. El primer paso es elegir un <b>selector</b>, esto es, una palabra clave que será asociada con ambas claves, incluida en todas las firmas y publicada como registro <b>DNS</b>. En el ejemplo usaré el selector <i><b>clave</b></i>. A continuación nuestro nombre de dominio: aquí usaré <i><b>ejemplo.com</b></i></div>
<pre class="brush:bash;gutter:false">mkdir /etc/opendkim/keys/ejemplo.com
/usr/sbin/opendkim-genkey -D /etc/opendkim/keys/ejemplo.com/ -d ejemplo.com -s clave
chown -R opendkim:opendkim /etc/opendkim/keys/ejemplo.com
mv /etc/opendkim/keys/ejemplo.com/clave.private /etc/opendkim/keys/ejemplo.com/clave
</pre>
<div style="text-align: justify;">
La primera línea crea la carpeta <b>ejemplo.com</b> donde guardaremos nuestras claves, la segunda genera las claves: <b>-D</b> para que las guarde en la carpeta creada, <b>-d</b> el dominio para el que las creamos y <b>-s</b> el <b>selector </b>que dará nombre a los ficheros. La tercera linea asigna la carpeta al usuario <i>opendkim </i>y la cuarta simplemente renombra el fichero <b>*.private</b> para tener un nombre más manejable. Para ver las opciones de <b>opendkim-genkey</b> puedes <a href="http://www.opendkim.org/opendkim-genkey.8.html" target="_blank">consultar su web</a> o cargar <b>man</b>.</div>
<br />
<span style="font-size: large;">Editar los ficheros de configuración</span><br />
<br />
Los ficheros de configuración de <i>opendkim </i>son:<br />
<ul>
<li><b>/etc/opendkim.conf</b> – configuración principal</li>
<li><b>/etc/opendkim/KeyTable</b> – lista de claves disponibles para firmas</li>
<li><b>/etc/opendkim/SigningTable</b> - lista de dominios y/o cuentas a las que se les permite firmar</li>
<li><b>/etc/opendkim/TrustedHosts</b> – lista de servidores fiables (<i>trust</i>) cuando se firma o se verifica</li>
</ul>
<span style="text-align: justify;">Cuando se instala </span><b style="text-align: justify;">DKIM </b><span style="text-align: justify;">el fichero principal de configuración está configurado para modo verificación sólo (</span><b style="text-align: justify;">v</b><span style="text-align: justify;">) para evitar que se marquen como </span><i style="text-align: justify;">spam </i><span style="text-align: justify;">los correos mientras se configura adecuadamente. Esta es una de las opciones que vamos a modificar. El fichero </span><b style="text-align: justify;">opendkim.conf</b><span style="text-align: justify;"> debe quedar así:</span><br />
<style>.framesh { height: 59em; }</style><br />
<pre class="brush: bash;class-name:framesh">## CONFIGURATION OPTIONS
# Specifies the path to the process ID file.
PidFile /var/run/opendkim/opendkim.pid
# Selects operating modes. Valid modes are s (signer) and v (verifier). Default is v.
Mode sv
# Log activity to the system log.
Syslog yes
# Log additional entries indicating successful signing or verification of messages.
SyslogSuccess yes
# If logging is enabled, include detailed logging about why or why not a message was
# signed or verified. This causes a large increase in the amount of log data generated
# for each message, so it should be limited to debugging use only.
#LogWhy yes
# Attempt to become the specified user before starting operations.
UserID opendkim:opendkim
# Create a socket through which your MTA can communicate.
Socket inet:8891@localhost
# Required to use local socket with MTAs that access the socket as a non-
# privileged user (e.g. Postfix)
Umask 002
# This specifies a file in which to store DKIM transaction statistics.
#Statistics /var/spool/opendkim/stats.dat
## SIGNING OPTIONS
# Selects the canonicalization method(s) to be used when signing messages.
Canonicalization relaxed/simple
# Domain(s) whose mail should be signed by this filter. Mail from other domains will
# be verified rather than being signed. Uncomment and use your domain name.
# This parameter is not required if a SigningTable is in use.
Domain ejemplo.com
# Defines the name of the selector to be used when signing messages.
Selector clave
# Gives the location of a private key to be used for signing ALL messages.
#KeyFile /etc/opendkim/keys/default.private
# Gives the location of a file mapping key names to signing keys. In simple terms,
# this tells OpenDKIM where to find your keys. If present, overrides any KeyFile
# setting in the configuration file.
KeyTable refile:/etc/opendkim/KeyTable
# Defines a table used to select one or more signatures to apply to a message based
# on the address found in the From: header field. In simple terms, this tells
# OpenDKIM how to use your keys.
SigningTable refile:/etc/opendkim/SigningTable
# Identifies a set of "external" hosts that may send mail through the server as one
# of the signing domains without credentials as such.
ExternalIgnoreList refile:/etc/opendkim/TrustedHosts
# Identifies a set internal hosts whose mail should be signed rather than verified.
InternalHosts refile:/etc/opendkim/TrustedHosts</pre>
<br />
<div style="text-align: justify;">
Las claves generadas son de 1024 bits por defecto. Si quieres cambiar su longitud y experimentar recuerda cambiar la línea:</div>
<pre class="brush:bash"># Specifies the minimum number of key bits for acceptable keys and signatures.
MinimumKeyBits 1024
</pre>
<br />
<div style="text-align: justify;">
Descomenta las opciones <b>KeyTable</b>, <b>SigningTable</b>, <b>ExternalIgnoreList </b>e <b>InternalHosts</b>. Si sólo generamos una clave para un dominio especificamos éste en la directiva <b>Domain</b>. Configuramos ahora <b>KeyTable </b>para indicarle a <b>DKIM </b><u>dónde</u> encontrar las claves. El formato es:</div>
<pre class="brush:bash">clave._domainkey.ejemplo.com ejemplo.com:clave:/etc/opendkim/keys/ejemplo.com/clave
</pre>
en una sola línea. Si estableces múltiples claves debes añadir una línea para cada una:<br />
<pre class="brush:bash">clave._domainkey.ejemplo.com ejemplo.com:clave:/etc/opendkim/keys/ejemplo.com/clave
clave2._domainkey.ejemplo2.com ejemplo2.com:clave2:/etc/opendkim/keys/ejemplo2.com/clave2
</pre>
<div style="text-align: justify;">
Pasamos a configurar ahora <b>SigningTable </b>para indicarle a <b>DKIM </b><u>cómo</u> usar las claves, qué <b>selectores </b>usar para firmar según el dominio:</div>
<pre class="brush:bash">*@ejemplo.com clave._domainkey.ejemplo.com
</pre>
<div style="text-align: justify;">
Con dicha línea indicamos que cualquier correo del dominio <b>ejemplo.com</b> debe ser firmado con el <b>selector </b><i><b>clave</b></i>. Igualmente si establecemos varios dominios:</div>
<pre class="brush:bash">*@ejemplo.com clave._domainkey.ejemplo.com
micorreo@ejemplo3.com clave3._domainkey.ejemplo3.com
</pre>
<div style="text-align: justify;">
En este ejemplo se firman todos los correos de <b>ejemplo.com</b> pero sólo los correos <b>micorreo </b>del dominio <b>ejemplo3.com</b>, la opción del comodín (*) solo funciona si el fichero <b>SigningTable </b>aparece con el prefijo <b>refile:</b> en <b>opendkim.conf</b>, véase la <a href="http://opendkim.org/opendkim.conf.5.html" target="_blank">documentación</a>.</div>
<div style="text-align: justify;">
Ahora modificamos <b>TrustedHosts </b>para indicar a <b>DKIM </b><u>quién</u> tiene permiso para usar estas claves. Como está referenciado por la directiva <b>ExternalIgnoreList </b>los <i>hosts </i>aquí listados se ignorarán cuando se verifiquen los correos entrantes y, como esta referenciado también por <b>InternalHosts</b>, todos los correos salientes serán firmados.</div>
<pre class="brush:bash">127.0.0.1
hostname.ejemplo.com
ejemplo.com
hostname1.ejemplo1.com
ejemplo2.com
</pre>
<div style="text-align: justify;">
Es importante que figure la <i>ip </i><b>127.0.0.1</b> o <b>DKIM </b>no firmará correos salientes del <i>host </i>local. Si dispones de varios servidores en la misma red que reenvían su correo a través de éste y quieres que sus correos también sean firmados deben estar incluidos en esta lista, cada una en su propia línea. Cada entrada puede ser un nombre de dominio, un <i>host</i>, una <i>ip </i>(<b>IPV4 </b>o <b>IPV6</b>) o estilo <b>CIDR </b>(192.168.33.210/24). Tambien hay que tener en cuenta que si se desea firmar correo procedente de otros <i>hosts </i>hay que tener configurado el <i>relay </i>para dichos servidores.</div>
<div style="text-align: justify;">
<br /></div>
<span style="font-size: large;">Editar la configuración <b>MTA</b></span><br />
<br />
Para usuarios de <b>Postfix </b>añadir estas líneas a <b>main.cf</b>:<br />
<pre class="brush:bash">smtpd_milters = inet:127.0.0.1:8891
non_smtpd_milters = $smtpd_milters
milter_default_action = accept
</pre>
<div>
y, si la versión de Postfix es anterior a la 2.6:</div>
<div>
<pre class="brush:bash">milter_protocol = 2</pre>
</div>
<div>
<div style="text-align: justify;">
Para los usuarios de SendMail, editar el fichero de configuración .mc que se uso para compilar la versión instalada en tu servidor y añadir:</div>
</div>
<div>
<pre class="brush_bash;gutter:false">INPUT_MAIL_FILTER(`opendkim', `S=inet:8891@localhost')
</pre>
</div>
<div>
Recompilar e instalar de nuevo.</div>
<div>
<br />
<span style="font-size: large;">Iniciar <b>OpenDKIM </b>y reiniciar la <b>MTA</b></span><br />
<br /></div>
<div>
<div style="text-align: justify;">
Reseteamos la tabla hash de la shell: <b>hash -r</b> y arrancamos <i>opendkim</i>: <b>service opendkim start</b>. Debe aparece el mensaje</div>
<pre class="brush:bash;gutter:false">Starting OpenDKIM Milter: [ OK ]</pre>
si aparece algo como<br />
<pre class="brush:bash;gutter:false">Starting OpenDKIM Milter: opendkim: /etc/opendkim.conf: configuration error at line 6: unrecognized parameter</pre>
<div style="text-align: justify;">
hay que volver a repasar los pasos dados. Una vez iniciado <i>opendkim </i>refrescamos <i>postfix</i>: <b>postfix reload</b> o, si usamos <i>SendMail</i>: <b>service sendmail restart</b>. Si todo va bien podemos indicar al sistema que cargue <i>opendkim </i>en los reinicios: <b>chkconfig opendkim on</b>.</div>
<br />
<span style="font-size: large;">Añadir registros <b>DNS</b></span><br />
<br />
<div style="text-align: justify;">
En este punto los correo enviados son firmados y los entrantes son verificados. Ahora debemos añadir alguna información a nuestra zona <b>DNS </b>para que otros servidores puedan verificar nuestra firma. Necesitamos la clave pública que obtenemos con <b>cat /etc/opendkim/keys/example.com/clave.txt</b> cuya salida será algo como</div>
<pre class="brush:bash;gutter:false">clave._domainkey IN TXT "v=DKIM1; g=*; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHY7Zl+n3SUldTYRUEU1BErHkKN0Ya52gazp1R7FA7vN5RddPxW/sO9JVRLiWg6iAE4hxBp42YKfxOwEnxPADbBuiELKZ2ddxo2aDFAb9U/lp47k45u5i2T1AlEBeurUbdKh7Nypq4lLMXC2FHhezK33BuYR+3L7jxVj7FATylhwIDAQAB" ; ----- DKIM default for example.com</pre>
</div>
<div style="text-align: justify;">
Copiamos todo el contenido entre las comillas, sin incluirlas, y creamos dos registros en la zona <b>DNS</b> de tipo <b>TXT:</b></div>
<div style="text-align: justify;">
<br />
<ul>
<li><b>clave._domainkey.ejemplo.com</b> con la clave obtenida</li>
<li><b>_adsp._domainkey.ejemplo.com </b>con el contenido <b>dkim=unknown</b></li>
</ul>
</div>
<div style="text-align: justify;">
Este registro publica nuestra política de firmas (<a href="http://en.wikipedia.org/wiki/Author_Domain_Signing_Practices" target="_blank">Author Domain Signing Practices</a>). <b><i>Unknown </i></b>es la directiva menos restrictiva y la más usada.</div>
<br />
<span style="font-size: large;">Comprobar DKIM</span><br />
<br />
Podemos comprobar si <b>DKIM </b>está activo con un vistazo al log de la cola de correo:<br />
<pre class="brush:bash;gutter:false">tail -f /var/log/maillog (o tail -f /var/usr/local/psa/log/maillog en CentOS)
</pre>
donde deberían aparecer unas líneas como éstas<br />
<pre class="brush:bash;gutter:false">opendkim[4397]: OpenDKIM Filter: mi_stop=1
opendkim[4397]: OpenDKIM Filter v2.4.2 terminating with status 0, errno = 0
opendkim[27444]: OpenDKIM Filter v2.4.2 starting (args: -x /etc/opendkim.conf)
</pre>
y cuando enviamos un correo<br />
<pre class="brush:bash;gutter:false">opendkim[22254]: 53D0314803B: DKIM-Signature header added
</pre>
También podemos enviar correos de prueba a<br />
<br />
<a href="http://www.brandonchecketts.com/emailtest.php" target="_blank">Brandon Checketts Email Validator</a><br />
<a href="mailto:autorespond+dkim@dk.elandsys.com" target="_blank">autorespond+dkim@dk.elandsys.com</a><br />
<a href="mailto:sa-test@sendmail.net" target="_blank">sa-test@sendmail.net</a><br />
<a href="mailto:check-auth@verifier.port25.com" target="_blank">check-auth@verifier.port25.com</a><br />
<br />
<div style="text-align: justify;">
Si lo probamos con <b>GMail</b>: enviamos un correo a cualquiera de las anteriores direcciones y se nos devolverá con el campo <b>signed by</b> indicando el nombre del dominio (opcion <b>show details</b>).</div>
Eduardhttp://www.blogger.com/profile/10085262151085209347noreply@blogger.com2