Javascript insertAfter()
O Javascript possui a função insertBefore(), mas não possui uma função similar para inserir após o objeto como é de se esperar que existisse uma chamada insertAfter(), mas podemos inserir um elemento após um determinado objeto utilizando a insertBefore():
function insertAfter ( newObj, target)
{
target.parentNode.insertBefore ( newObj, target.nextSibling);
}
Para entender esta função, vamos primeiramente ver a sintaxe do comando insertBefore():
- Função: insertBefore()
- Descrição: Insere um elemento ao final do documento ou após um elemento especificado.
- Sintaxe: elemento.insertBefore ( param1, param2)
- Parâmetros:
- param1: REQUERIDO - Novo elemento a ser adicionado.
- param2: OPCIONAL - Elemento que se especificado o novo elemento (param1) será inserido após este. Se omitido, inserere no final.
Como a função insertAfter() que foi criada informa o “target”.nextSibling como segundo parâmetro, quando não ouver um nextSibling no elemento, será retornado nulo, e fará com que a função insertBefore() insira o elemento no final.
Podemos extender o DOM e adicionar esta função a todos os elementos, utilizando o seguinte código:
Element.prototype.insertAfter = function ( newObj)
{
this.parentNode.insertBefore ( newObj, this.nextSibling);
}
Desta forma, podemos utilizar o comando insertAfter() como utilizamos o comando insertBefore(), tal como:
document.getElementById ( 'alvo').insertAfter ( objeto);
Para testar, .
Simples e prático.
JavaScript |
| Nenhum comentário »
Recuperação de arquivos
Quem nunca apagou um arquivo por acidente, teve seu disco danificado por um vírus ou danificou sem querer a tabela de partição? Em sistema de arquivos FAT ou NTFS a recuperação é relativamente simples e fácil. Já no Linux com EXT2/EXT3, ReiserFS e outros, já não é tão simples assim.
Depois de ter removido um arquivo de uma pen drive por acidente, pesquisei rapidinho no Google uma solução em Linux e acabei descobrindo o TestDisk, uma ferramenta simples e muito poderosa que além de recuperar arquivos removidos de quaisquer dispositivo de armazenamento (disco, pen drive, SD Card, etc…), pode recuperar tabela de partições, localizar partições perdidas no disco e outras diversas ferramentas que ajudam e muito na hora do desespero.
O melhor de tudo é que está disponível para Linux, Windows (NT4, 2000, XP, 2003 e Vista), DOS (nativo ou em janela no Windows 9x), MaxOS X e OS2. É um software livre sob licença GPL, com fontes disponíveis, podendo ser executado também em sistemas BSD ou SunOS.
Um amigo testou ele no Windows Vista, e teve alguns problemas com a disposição do texto na tela, mas fora isso, funcionou normalmente.
Ele possui também um outro aplicativo voltado para a recuperação de imagens perdidas de cartões de memória de máquinas digitais, com suporte a EXIF, reconhecimento de arquivos JPEG, entre outras funções muito úteis, chamado PhotoRec.
É um ótimo aplicativo para termos sempre a mão naquela pen drive de utilitários que virou chaveiro, até porque ele não precisa ser instalado no Windows, basta executar diretamente do diretório.
Segurança, Utilitários |
| 1 comentário »
Sony Ericsson MD300
Esses
dias um cliente me deixou um modem USB Claro 3G modelo Sony Ericsson MD300 para utilizar em uma viagem. O modem é um pouco grande se comparado ao Huawei E220, além de ter o incômodo de ocupar a porta USB próxima a conectada, por ser muito largo. Ao contrário do Huawei, tive um pouco de dificuldade para configurar ele no meu Slackware, até descobrir que o modem tem o rádio desativado por padrão (o que não é nem um pouco lógico para um modem 3G, mas foi a implementação do fabricante). Para que o modem funcione no Linux, deve-se fazer com que o udev identifique corretamente o dispositivo, e na inicialização antes da discagem, ativar o rádio.
Para que o Linux reconheça o modem como dispositivo Modem USB, crie o arquivo /etc/udev/rules.d/50-md300modem.rules com o seguinte conteúdo:
ACTION!="add", GOTO="3G_End"
BUS=="usb", SYSFS{idProduct}=="d0cf", SYSFS{idVendor}=="0fce", NAME="%k",SYMLINK="modem3G-%n", PROGRAM="/bin/sh -c 'echo 3 > /sys/%p/device/bConfigurationValue'"
LABEL="3G_End"
Não esqueça de reinicializar o serviço do udev. Em sistemas BSD Like, utilize o comando “/etc/rc.d/rc.udev reload”, e para RedHat, Ubuntu, e outros, utilize “service udev restart”. Depois disso, configure o modem para utilizar as seguintes strings de inicialização:
- ATZ (Reseta configuração do modem)
- AT+CFUN=1 (Habilita rádio do modem)
- ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0 (Outras configurações diversas)
- AT+CGDCONT=1,”IP”,”bandalarga.claro.com.br” (Seleciona operadora Claro 3G)
Agora é só configurar para discar o número *99***1#, autenticando com o usuário “claro” e a senha “claro”. Você pode utilizar o gnome-ppp, vmdial, entre outros utilizando as informações acima. Qualquer dúvida, poste o seu comentário.
Autenticação segura utilizando AJAX
Um grande problema que sempre existiu foi a autenticação via formulário, onde o usuário envia o seu nome de usuário e senha de forma “texto puro” na rede, possibilitando que quaisquer pessoa que tenha acesso ao tráfego web ou à aplicação no servidor tenha acesso a estas informações. O ideal é que um sistema não tenha acesso a senha do usuário, e normalmente armazena um hash criptográfico da mesma, mas mesmo assim, ele necessita receber a senha para validar a autenticação.
Para evitar esse problema, pode-se utilizar um serviço HTTP com criptografia (HTTPS), mas mesmo assim ainda não resolve o problema de a aplicação possuir a senha do usuário. Pensando nisso, desenvolvi uma solução utilizando AJAX, onde a autenticação é segura através de utilização de um “desafio”.
Para implementar este método de autenticação onde nenhuma informação sigilosa é trafegada em forma de texto puro na rede (nem mesmo hash da senha), devemos seguir os seguintes passos:
- O cliente requisita um desafio ao servidor utilizando AJAX, para manter o conteúdo atual do formulário e página;
- O servidor retorna um desafio, que é uma string randômica e única. Este desafio fica armazenado no servidor tendo uma validade de alguns segundos;
- O cliente calcula um hash com a concatenação do desafio com o hash da senha informada pelo usuário. O resultado então é enviado para o servidor juntamente com o nome do usuário;
- O servidor verifica na base qual é o último desafio enviado para o cliente (atendendo o tempo de expiração), concatena o mesmo com o hash da senha que possui no banco de dados, e compara o hash resultante desta concatenação com o valor enviado pelo cliente (que é o mesmo hash, do desafio concatenado com o hash da senha informada pelo usuário);
- Coincidindo os valores, a autenticação é bem sucedida.
Este processo faz com que a senha nunca seja exposta, nem mesmo o hash dela, pois é enviado apenas um hash do desafio (texto randômico e único gerado pelo servidor) com o hash real da senha. Como a cada autenticação o desafio criado pelo servidor é diferente, o resultado do hash será sempre diferente, fazendo com que seja impossível se obter alguma informação da senha.
O maior problema deste método de autenticação é que a senha na base de dados deve utilizar um único sistema de hash padronizado, ou serem mantidas em “texto puro”, o que é extremamente desaconselhado. Já vi algumas bases de autenticação que utilizavam SHA1 e MD5 misturados. Neste tipo de base este sistema de autenticação não funcionaria.
O processo é um pouco difícil de se entender, então vamos implementar passo a passo.
Primeiro precisamos escolher um método de hash a ser utilizado tanto no cliente quanto no servidor para criar a autenticação e o hash da senha em si. Existem diversas bibliotecas para JavaScript, sendo as que eu recomendo:
- MD4, MD5 e SHA (SHA1): Paj’s Home: Criptography;
- SHA2 (SHA-256, SHA-384, SHA-512): jsSHA2.
Utilizei por muito tempo o MD5, mas atualmente substitui pelo SHA2 de 256 bits por ser mais seguro e ser suportado por todas linguagens que utilizo. Por isto, o exemplo feito aqui utilizará ele como base. Você pode alterar para outros algorítimos facilmente se desejar.
O hash gerado pelo SHA2 de 256 bits possui 32 bytes em sua forma binária, mas utilizaremos ele em hexadecimal, que ocupa 64 bytes, para evitar problemas com caracteres especiais. Lembre-se que você deve armazenar o hash da senha no servidor utilizando o mesmo método utilizado no navegador do cliente.
Primeiramente, vamos criar uma função para retornar um objeto XMLHTTP:
function createXMLHTTP ()
{
// Navegadores Mozilla/Safari:
if ( window.XMLHttpRequest)
{
xmlHttpReq = new XMLHttpRequest ();
xmlHttpReq.overrideMimeType ( 'text/xml');
} else {
// Navegadores IE:
if ( window.ActiveXObject)
{
// O IE utiliza Msxml2.XMLHTTP:
var ActiveXName = 'Msxml2.XMLHTTP';
// O IE 5.5 ou superior utiliza Microsoft.XMLHTTP:
if ( navigator.appVersion.indexOf ( 'MSIE 5.5') >= 0)
{
ActiveXName = 'Microsoft.XMLHTTP';
}
// Tenta instanciar:
try
{
xmlHttpReq = new ActiveXObject ( ActiveXName);
}
// Avisa que o ActiveX está desabilitado:
catch ( e)
{
alert ( 'Suporte a script no ActiveX deve ser habilitado.');
return false;
}
} else {
// Quaisquer outro navegador não é suportado:
alert ( 'O seu navegador não é suportado por este aplicativo.');
return false;
}
}
return xmlHttpReq;
}
Então implementamos o código JavaScript que cria o objeto XMLHTTP (AJAX) e requisita o desafio ao servidor:
function getChallenge ( username)
{
var xmlHttpReq = false;
// Instancia um novo objeto XMLHTTP:
xmlHttpReq = createXMLHTTP ();
if ( ! xmlHttpReq)
{
// Retorna falso:
return false;
}
// Insere os valores no objeto e envia:
xmlHttpReq.open ( 'POST', 'auth.php', false);
xmlHttpReq.setRequestHeader ( 'Content-Type', 'application/x-www-form-urlencoded; charset=utf-8');
xmlHttpReq.send ( 'usuario=' + escape ( username));
// Verifica se ocorreu erro (código HTTP diferente de 200):
if ( xmlHttpReq.status != 200)
{
// Mostra uma mensagem de erro:
alert ( "Ocorreu um erro ao autenticar no sistema.");
// Retorna falso:
return false;
}
// Verifica se o valor retornado é válido (possui 64 bytes):
if ( xmlHttpReq.responseText.length != 64)
{
// Mostra uma mensagem de erro:
alert ( "Ocorreu um erro ao autenticar no sistema.");
// Retorna falso:
return false;
}
// Retorna o valor randômico recebido do servidor:
return xmlHttpReq.responseText;
}
Precisamos também de uma função para enviar o resultado da autenticação para o servidor:
function doLogin ( username, hash)
{
var xmlHttpReq = false;
// Instancia um novo objeto XMLHTTP:
xmlHttpReq = createXMLHTTP ();
// Insere os valores no objeto e envia:
xmlHttpReq.open ( 'POST', 'auth.php', false);
xmlHttpReq.setRequestHeader ( 'Content-Type', 'application/x-www-form-urlencoded; charset=utf-8');
xmlHttpReq.send ( 'usuario=' + escape ( username) + '&hash=' + escape ( hash));
// Verifica se ocorreu erro (código HTTP diferente de 200):
if ( xmlHttpReq.status != 200)
{
// Mostra uma mensagem de erro:
alert ( "Ocorreu um erro ao autenticar no sistema.");
// Retorna falso:
return false;
}
// Retorna o resultado:
return xmlHttpReq.responseText;
}
Finalmente, precisamos de uma função para gerenciar a autenticação, ou seja, requisitar o desafio, criar o hash resultante do desafio e hash da senha e enviar ao servidor. Para isto, criamos a função de autenticação em JavaScript:
function handleLogin ( form)
{
// Recupera informações da autenticação do formulário informado:
username = document.getElementById('login_username').value;
password = document.getElementById('login_password').value;
// Retorna caso não seja informado o usuário:
if ( ! username)
{
return false;
}
// Desabilita o botão de envio de formulário para evitar envio duplicado:
document.getElementById('ok_button').disabled = true;
document.getElementById('ok_button').value = 'Autenticando...';
// Requisita o desafio:
challenge = getChallenge ( username);
// Envia requisição de autenticação:
login = doLogin ( username, hex_sha256 ( hex_sha256 ( password) + challenge), challenge);
// Verifica resultado:
if ( login == '1')
{
// Autenticação bem sucedida:
alert ( 'Autenticado como "' + username + '"!');
} else {
// Autenticação falhou. Exibe mensagem de erro:
alert ( 'Falha na autenticação!');
// Limpa o campo de senha:
document.getElementById('login_password').value = "";
// Seta o foco do navegador para o campo de senha:
document.getElementById('login_password').focus ();
}
// Habilita o botão de autenticação do sistema:
document.getElementById('ok_button').disabled = false;
document.getElementById('ok_button').value = 'Autenticar';
// Retorna falso:
return false;
}
No lado do servidor criamos o código PHP para realizar a geração de desafios e autenticação no sistema. O desafio gerado será uma string de 64 bytes, um hash SHA256 de um número randômico. Este será o arquivo “auth.php”:
<?php
// Primeiro, precisamos conectar ao banco de dados:
if ( ! $_sql_id = @mysql_connect ( "localhost", "myuser", "mypass"))
{
die ( "Erro: Não foi possível conectar ao banco de dados.");
}
// Selecionamos a base de dados padrão da conexão:
if ( ! @mysql_select_db ( "mydb", $_sql_id))
{
die ( "Erro: Não foi possível selecionar a base de dados.");
}
// Verificamos se a requisição possui senha (se não possui, é uma requisição de desafio):
if ( empty ( $_POST["senha"]))
{
// Criamos o hash randômico de 64 bytes:
$desafio = bin2hex ( mhash ( MHASH_SHA256, uniqid ( rand ())));
// Verificamos se o usuário informado existe, e recuperamos a informação do banco de dados:
if ( ! $result = @mysql_query ( "SELECT * FROM `usuarios` WHERE `Usuario` = '" . mysql_real_escape_string ( $_POST["usuario"], $_sql_id) . "'", $_sql_id))
{
// Atenção: Não devemos nunca informar se um usuário existe ou não, por isto, retornaremos um hash como se a operação fosse bem sucedida:
die ( $desafio);
}
// Verificamos se foi localizada a informação:
if ( mysql_num_rows ( $result) != 1)
{
// Leia o comentário do "if" anterior.
die ( $desafio);
}
// Recuperamos a informação para um array e liberamos a memória:
$usuario = mysql_fetch_array ( $result);
mysql_free_result ( $result);
// Guardamos o desafio gerado na base de dados:
@mysql_query ( "INSERT INTO `sessoes` (`Usuario`, `Desafio`, `Validade`, `IP`) VALUES (" . (int) $usuario["ID"] . ", '" . mysql_real_escape_string ( $desafio, $_sql_id) . "', " . ( time () + 30) . ", INET_ATON('" . mysql_real_escape_string ( $_SERVER["REMOTE_ADDR"], $_sql_id) . "'))", $_sql_id);
// Retorna-se o desafio (mesmo que a gravação na base de dados tenha ocorrido erro):
die ( $desafio);
} else {
// É uma autenticação completa, ou seja, enviado usuário e hash da senha concatenada com o desafio.
// Primeiramente, verificamos o último desafio enviado para o IP do cliente que ainda esteja válido:
if ( ! $result = @mysql_query ( "SELECT * FROM `sessoes` WHERE `IP` = INET_ATON('" . mysql_real_escape_string ( $_SERVER["REMOTE_ADDR"], $_sql_id) . "') AND `Validade` >= " . time () . " ORDER BY ID DESC LIMIT 0, 1", $_sql_id))
{
// Retornamos sempre autenticação inválida (nenhuma mensagem de erro):
die ( "0");
}
// Verifica se foi retornado algum valor:
if ( mysql_num_rows ( $result) != 1)
{
// Retornamos sempre autenticação inválida (nenhuma mensagem de erro):
die ( "0");
}
// Armazenamos a informação do desafio válido para concatenarmos com o hash da senha:
$desafio = mysql_fetch_array ( $result);
mysql_free_result ( $result);
// Requisitamos a informação do usuário informado:
if ( ! $result = @mysql_query ( "SELECT * FROM `usuarios` WHERE `Usuario` = '" . mysql_real_escape_string ( $_POST["usuario"], $_sql_id) . "'", $_sql_id))
{
die ( "0");
}
// Verificamos se foi retornado um valor válido:
if ( mysql_num_rows ( $result) != 1)
{
// Retorna autenticação inválida (não informa que o usuário não existe):
die ( "0");
}
$usuario = mysql_fetch_array ( $result);
mysql_free_result ( $result);
// Agora que temos a informação do último desafio válido enviado para este cliente, e a informação do usuário, fazemos a verificação do hash enviado:
if ( bin2hex ( mhash ( MHASH_SHA256, $usuario["Senha"] . $desafio["Desafio"])) != $_POST["hash"])
{
// Se a comparação falhou, a senha informada é inválida:
die ( "0");
}
// Temos a confirmação da senha enviada através do hash concatenado com o desafio. Então eliminamos o desafio da base de dados e criamos a sessão do usuário.
// Criamos o hash randômico de 64 bytes para a cookie:
$cookie = bin2hex ( mhash ( MHASH_SHA256, uniqid ( rand ())));
// Alteramos o registro contendo o desafio para cookie:
if ( ! @mysql_query ( "UPDATE `sessoes` SET `Desafio` = '', `Cookie` = '" . mysql_real_escape_string ( $cookie, $_sql_id) . "', `Validade` = " . ( time () + 3600) . " WHERE `IP` = INET_ATON('" . mysql_real_escape_string ( $_SERVER["REMOTE_ADDR"], $_sql_id) . "') AND `Desafio` = '" . mysql_real_escape_string ( $desafio["Desafio"], $_sql_id) . "' AND `Usuario` = " . (int) $usuario["ID"], $_sql_id))
{
die ( "0");
}
// Retornamos a cookie para o cliente juntamente com a confirmação de autenticação:
setcookie ( "myapp", $cookie, 0, dirname ( $_SERVER["PHP_SELF"]), $_SERVER["HTTP_HOST"]);
die ( "1");
}
?>
Para a base de dados, utilizaremos a seguinte estrutura de tabelas:
CREATE TABLE `sessoes` ( `ID` bigint unsigned NOT NULL auto_increment, `Usuario` bigint unsigned NOT NULL, `Desafio` char(64) NOT NULL, `Cookie` char(64) NOT NULL, `Validade` int unsigned NOT NULL, `IP` int unsigned NOT NULL, PRIMARY KEY (`ID`), KEY `Cookie` (`Cookie`), KEY `ID` (`ID`), KEY `Usuario` (`Usuario`), KEY `Validade` (`Validade`), KEY `IP` (`IP`) ) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE `usuarios` ( `ID` bigint unsigned NOT NULL auto_increment, `Usuario` varchar(255) NOT NULL, `Senha` char(64) NOT NULL, PRIMARY KEY (`ID`), KEY `ID` (`ID`), KEY `Usuario` (`Usuario`) ) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
Esta tabela será utilizada tanto para arquivar o hash randômico, quanto para arquivar o hash de sessão, que é uma string randômica única gerada para o cliente como chave da sessão, e enviada através de cookie. Sempre que uma cookie do sistema é recebida, verificamos nesta tabela o IP do cliente, a cookie e se a mesma ainda está dentro do tempo de expiração da sessão do sistema, sendo que desta informação temos o ID do usuário, que é armazenado na tabela de usuários. Este procedimento é muito similar ao utilizado internamente pelo PHP para manipulação de variáveis de sessão, mas implementado manualmente. As variáveis de sessão não podem ser consideradas seguras, visto que são armazenadas em “texto puro” dentro de arquivos temporários que são compartilhados no servidor com outras aplicações, ou seja, se você gravar dados sensíveis nas variáveis de sessão, outros aplicativos podem vir a ter acesso a esta informação. Veja o artigo Segurança de Sessões em PHP para maiores detalhes. A entrada nunca poderá ter os valores de Desafio e Cookie simultaneamente, assim que autenticado, o registro perde o valor de ‘Desafio’ e cria-se um valor para Cookie da sessão.
Para o teste, adicionei dois usuários. O usuário “usuario” com a senha “teste” e o usuário “admin” com a senha “admin”:
INSERT INTO `usuarios` (`Usuario`, `Senha`) VALUES ('usuario', '46070d4bf934fb0d4b06d9e2c46e346944e322444900a435d7d9a95e6d7435f5');
INSERT INTO `usuarios` (`Usuario`, `Senha`) VALUES ('admin', '8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918');
Para testar o sistema de autenticação, criei este exemplo.
Caso você deseje, pode fazer o download dos códigos deste artigo aqui.
Este código está sendo disponibilizado sob licença GPL versão 3. Caso você utilize este processo de autenticação em algum software, por favor me informe qual o sistema implementado, linguagem utilizada e se possuo permissão para postar uma referencia ao seu software neste artigo.
AJAX, JavaScript, Segurança |
| Nenhum comentário »
Fonetização
A função de fonetização para nomes já é utilizada a muito tempo em sistemas de grande porte para facilitar a localização de cadastros pelo nome. É extremamente útil quando um atendente está precisando localizar algum cadastro, para que não necessite ficar perguntando “Christine com H?”, “Daniela ou Daniele?”, etc… Ele é de simples utilização, bastando-se criar um campo adicional na base de dados contendo a forma fonética do nome, passando pela função de fonetização, assim como o termo pesquisado.
Fiz uma pesquisa bem extensa na internet e não consegui localizar nenhum código disponível em PHP que fosse realmente útil. Pesquisando um pouco mais a fundo, consegui localizar um código em Java, disponibilizado pelo Serviço de Informática do Instituto Do Coração de Porto Alegre. Este código foi gentilmente fornecido pela PROCEMPA e distribuído sob licença GPL versão 2. Ele pode ser encontrado aqui. Fiz uma versão simplificada em Java, que pode ser encontrada aqui.
Baseado neste código, reescrevi o mesmo em PHP. Ficou um pouco extenso para se publicar aqui, por isso, baixe o mesmo. A versão PHP foi disponibilizada sob a licença GPL versão 3.
Java, PHP |
| Nenhum comentário »
WP-Print em português (Brasil)
Para aqueles que utilizam o WordPress e o plugin WP-Print, disponibilizo o arquivo traduzido para o Português Brasil da versão 2.30 que fiz. Caso tenham alguma sugestão, podem enviar por email ou colocar um comentário neste artigo.
- Arquivo binário wp-print-pt_BR.mo;
- Arquivo fonte wp-print-pt_BR.pot.
O arquivo wp-print-pt_BR.pot contém o texto puro, necessitando ser processado pelo aplicativo “msgfmt” do GNU GetText para gerar o arquivo binário com extensão .mo, que é o arquivo utilizado pelo sistema. Caso você não se interesse pelo “fonte” da tradução, basta utilizar apenas o arquivo wp-print-pt_BR.mo. É utilizado a página de caracteres UTF-8.
Para instalar, basta colocá-lo no mesmo diretório onde está instalado o plugin WP-Print.
Plugins, WordPress |
| Nenhum comentário »
WP-EMail em português (Brasil)
Para aqueles que utilizam o WordPress e o plugin WP-EMail, disponibilizo o arquivo traduzido para o Português Brasil da versão 2.30 que fiz. Caso tenham alguma sugestão, podem enviar por email ou colocar um comentário neste artigo.
- Arquivo binário wp-email-pt_BR.mo;
- Arquivo fonte wp-email-pt_BR.pot.
O arquivo wp-email-pt_BR.pot contém o texto puro, necessitando ser processado pelo aplicativo “msgfmt” do GNU GetText para gerar o arquivo binário com extensão .mo, que é o arquivo utilizado pelo sistema. Caso você não se interesse pelo “fonte” da tradução, basta utilizar apenas o arquivo wp-email-pt_BR.mo. É utilizado a página de caracteres UTF-8.
Para instalar, basta colocá-lo no mesmo diretório onde está instalado o plugin WP-EMail.
Plugins, WordPress |
| Nenhum comentário »
Interface de sessões do PHP em MySQL
Conforme visto no artigo Funcionamento de sessões no PHP, o sistema de arquivamento em disco é inseguro. Este artigo implementa uma classe que armazena os dados em banco de dados MySQL. Isto além de possibilitar uma maior segurança dos dados de sessão, permite que uma aplicação seja portada para um sistema de balanceamento de cargas muito mais facilmente, pois independentemente de qual servidor a requisição for processada, terá acesso as variáveis de sessão de forma transparente.
Esta classe nasceu da idéia postada em um comentário nas páginas do manual do PHP, mais especificamente na página do comando session_set_save_handler(). O exemplo original foi criado por maria (arroba) junkies (ponto) jp, melhorado por klose (arroba) openriverbed (ponto) de e algumas mudanças feitas por mim.
Este comando permite que sejam especificadas funções para o processamento de sessões do PHP, informadas como parâmetros, sendo elas:
- open: Executada quando é iniciada a sessão através da chamada da função session_start() ou automaticamente;
- close: Executada ao término da execução do script, normalmente após a gravação das informações da sessão através da função write() (veja abaixo), ou da destruição da sessão através da função destroy() (veja abaixo);
- read: Executada sempre que a sessão for inicializada para recuperar uma string com as informações da sessão;
- write: Executada sempre ao término da execução do script, antes da chamada da função close() (veja acima), para arquivar as mudanças efetuadas nas variáveis de sessão. Ocorre sempre que a execução do script termina ou através da chamada da função session_write_close();
- destroy: Executada quando uma sessão é destruída através do comando session_destroy(), eliminando os dados da sessão e encerrando o sistema de sessões através da chamada da função close() (veja acima);
- gc: É executada sempre que o PHP deseja limpar as variáveis com tempo de vida expirado. É chamado de “garbage cleanup”, e é executada automaticamente pelo servidor.
Seguindo as especificações desta função, podemos implementar a classe conforme segue:
<?php
/**
* PHP session handling with MySQL-DB
*
* Created on 12.03.2008
* @license http://www.opensource.org/licenses/cpl.php Common Public License 1.0
*/
class MySQL_Session
{
/**
* A database connection resource and configuration.
* @var resource
*/
private static $_sess_id;
private static $_sess_host;
private static $_sess_user;
private static $_sess_pass;
private static $_sess_db;
private static $_sess_table;
/**
* Open the session
* @return bool
*/
public static function open ()
{
if ( self::$_sess_id = @mysql_connect ( self::$_sess_host, self::$_sess_user, self::$_sess_pass))
{
return @mysql_select_db ( self::$_sess_db, self::$_sess_id);
}
return false;
}
/**
* Close the session
* @return bool
*/
public static function close ()
{
return @mysql_close ( self::$_sess_id);
}
/**
* Read the session
* @param string session id
* @return string string of the session
*/
public static function read ( $id)
{
if ($result = @mysql_query ( "SELECT `data` FROM `" . mysql_real_escape_string ( self::$_sess_table, self::$_sess_id) . "` WHERE `handler` = '" . mysql_real_escape_string ( $id, self::$_sess_id) . "'", self::$_sess_id))
{
if ( mysql_num_rows ( $result))
{
$record = mysql_fetch_assoc ( $result);
return $record["data"];
}
}
return "";
}
/**
* Write the session
* @param string session id
* @param string data of the session
*/
public static function write ( $id, $data)
{
return @mysql_query ( "REPLACE INTO `" . mysql_real_escape_string ( self::$_sess_table, self::$_sess_id) . "` VALUES ('" . mysql_real_escape_string ( $id, self::$_sess_id) . "', " . time () . ", '" . mysql_real_escape_string ( $data, self::$_sess_id) . "')", self::$_sess_id);
}
/**
* Destroy the session
* @param string session id
* @return bool
*/
public static function destroy ( $id)
{
return @mysql_query ( "DELETE FROM `" . mysql_real_escape_string ( self::$_sess_table, self::$_sess_id) . "` WHERE `handler` = '" . mysql_real_escape_string ( $id, self::$_sess_id) . "'", self::$_sess_id);
}
/**
* Garbage Collector
* @param int life time (sec.)
* @return bool
* @see session.gc_divisor 100
* @see session.gc_maxlifetime 1440
* @see session.gc_probability 1
* @usage execution rate 1/100
* (session.gc_probability/session.gc_divisor)
*/
public static function gc ( $max)
{
return @mysql_query ( "DELETE FROM `" . mysql_real_escape_string ( self::$_sess_table, self::$_sess_id) . "` WHERE `expires` < " . (int) ( time () - $max), self::$_sess_id);
}
/**
* Class constructor function
* @param string MySQL server hostname
* @param string username
* @param string password
* @param string database name
* @param string table name
*/
function __construct ( $hostname = "localhost", $username = "root", $password = "", $database = "", $table = "sessions")
{
// Change the database configurations:
self::$_sess_host = $hostname;
self::$_sess_user = $username;
self::$_sess_pass = $password;
self::$_sess_db = $database;
self::$_sess_table = $table;
// Change the session handler type:
ini_set ( "session.save_handler", "user");
// Change the session handler functions:
session_set_save_handler ( array ( &$this, "open"), array ( &$this, "close"), array ( &$this, "read"), array ( &$this, "write"), array ( &$this, "destroy"), array ( &$this, "gc"));
// Be nice, return true.
return true;
}
}
?>
Não devemos esquecer também a estrutura da tabela MySQL necessária para o funcionamento:
CREATE TABLE IF NOT EXISTS `sessions` ( `handler` char(26) NOT NULL, `expires` int unsigned NOT NULL default '0', `data` blob, PRIMARY KEY (`handler`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
A ativação da classe é extremamente simples, basta se incluir o arquivo contendo a classe e instanciar um novo objeto MySQL_Session, passando como parâmetros:
- hostname: Endereço do servidor MySQL;
- username: Usuário para autenticação no banco de dados;
- password: Senha para autenticação;
- database: Nome da base de dados a ser selecionada;
- table: Nome da tabela a ser utilizada. Normalmente (se utilizado a estrutura SQL acima descrita sem alterações) é a tabela “sessions”.
Podemos testar a classe com o seguinte exemplo:
<!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" xml:lang="en_US" lang="en_US">
<head>
<title>MySQL Session Handling Class - Example
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<?php
// Include class file:
require_once ( "mysql-session.class.php");
// Instance new class:
$session = new MySQL_Session ( "hostname", "username", "password", "database", "table");
if ( session_id () == "")
{
session_start ();
}
if ( isset ( $_SESSION["counter"]))
{
$_SESSION["counter"]++;
} else {
$_SESSION["counter"] = 1;
}
echo "Session ID: " . session_id () . "<br />\n";
echo "Counter: " . $_SESSION["counter"] . "<br />\n";
?>
</body>
</html>
Este exemplo pode ser facilmente portado para outros bancos de dados, assim como implementado funções de criptografia, etc.
Caso você deseje, pode fazer o download do conteúdo deste artigo aqui.
MySQL, PHP, Segurança |
| Nenhum comentário »
Funcionamento de sessões no PHP
O PHP possui um sistema interno de gerenciamento de sessões muito prático, porém inseguro, tanto que existe uma referência no manual do PHP chamado Sessões e segurança com dicas para aumentar a segurança das variáveis de sessão. Para entender o porque desta insegurança, vamos compreender como o sistema funciona.
Sempre que uma sessão é inicializada, seja através da função session_start(), ou automaticamente, o PHP envia uma cookie para o cliente com um hash único, e cria um arquivo chamado “sess_” concatenado com o hash único enviado para o cliente.
Este arquivo é criado dentro do diretório especificado na variável de configuração do PHP chamada session.save_path. Se não for configurada, o valor padrão é “/tmp”. Este diretório deve estar acessível caso esteja sendo utilizado a funcionabilidade de open_basedir.
O arquivo criado contém todos os valores armazenados nas variáveis de sessão, que é a array super global do PHP $_SESSION, sendo mantida toda estrutura através da função serialize().
Sempre que a função session_start() é executada, o PHP procura no diretório de sessões se existe um arquivo “sess_” mais o conteúdo da variável. Caso o mesmo exista, ele lê o conteúdo deste arquivo e utiliza a função unserialize() para restaurar o conteúdo da super global $_SESSION.
Ocorre que normalmente esta configuração é mantida como padrão, ou seja, todos os sites hospedados em um servidor possuem um diretório temporário em comum, incluindo suas variáveis de sessões, possibilitando que um atacante possa escrever um script que acesse todos arquivos começados com “sess_” no diretório temporário, leia o seu conteúdo e exponha os dados, através do comando unserialize().
Além da possibilidade de leitura dos dados, pode-se também alterar o seu conteúdo, incluindo, excluindo ou alterando valores, pois o servidor web possui permissão de leitura e escrita.
Existem diversas alternativas para implementar mais segurança aos dados armazenados através de sessões. Uma delas é a utilização da função session_set_save_handler() para definir a seqüência de funções de armazenamento utilizadas no gerenciamento do controle de sessão. Com isto, podemos criar uma interface para armazenar os dados em MySQL, ao invés de gravá-los em disco, ou simplesmente adicionar uma função de criptografia para o armazenamento e carga dos dados no disco.
PHP, Segurança |
| Nenhum comentário »
Removendo frames automaticamente
Existem diversos sites famosos com serviços que adicionam um frame superior (contendo propaganda, links, etc…) e a sua página abaixo, no espaço restante.
Também pode acontecer de um site malicioso que acesse as informações de sua página através de javascript, fazendo com que um script escondido em um frame de 0 pixels acesse o conteúdo de um formulário, por exemplo, enviando o usuário e senha do seu site para o site atacante, ou simplesmente coletando informações sobre o uso do seu site (semelhante a um spybot).
Para evitar estes problemas, podemos utilizar um script bem simples em JavaScript, que pode ser adicionado ao cabeçalho da página. Aconselho a sua utilização antes de quaisquer comando de carga de folhas de estilo ou outros scripts, pois ele é executado assim que recebido pelo navegador, evitando a carga desnecessária do restante do conteúdo.
Abaixo, segue o script comentado e compatível com quase todos os navegadores existentes hoje em dia:
// Verificamos se o frame superior é diferente do atual:
if ( self != top)
{
// Verificamos se o navegador suporta a função replace(), e trocamos
// o endereço da página superior para o endereço deste frame:
if ( document.images)
{
top.location.replace ( window.location.href);
} else {
top.location.href = window.location.href;
}
}
Você pode ver este script em ação nesta mesma página, acessando ela pelo redirecionador http://diskfull.blogsite.org/ do serviço DynDNS.
JavaScript, Segurança |
| 1 comentário »


(4 votos, média: 4,25 de 5)