Hide Link to download

1

I'm creating a download code for holerite files, where everyone can only see your pocketbook. The code I did now works, however has a flaw, as I put all the files in the same folder to make it easier to upload them since they are over 400 when the person generates the download link code he can by renaming I want to know how to mask this link, in my code $ user-> cod_func is the employee code that he takes from the database to create the correct link but if the person changes that code by trial and error in the browser it may cease others and that is what I wanted to avoid.

        <?php
            $arquivos = 'holerith '."$user->cod_func".'  '.utf8_decode($_POST['select_mes']).' de '.$_POST['select_ano'].'.pdf';                
            $filename = 'arquivos/'.$arquivos;

            if (file_exists($filename)) {
                ?>  

                Vizualizar: <a href="<?php echo "$filename"; ?>"><?php echo $_POST['select_mes'].' '.$_POST['select_ano']; ?></a>
                <br>

                <?php

            } else {
                echo "Não existe holerith no mês selecionado";
            }
        ?>
    
asked by anonymous 13.03.2017 / 15:23

2 answers

1

One of the facies forms would be using HMAC, it has the purpose of verify information integrity , thus preventing it from being changed, as you say in "it can by renaming the file fetch others too much, "but it does not have the purpose of hiding . Another option is to check if the user requesting the download actually has access to it or uses both.

  

For both cases it would be necessary to read the file and pass it to the client via PHP, so the client does not have direct access to the file and this file should be out of public access for several reasons!

Using HMAC:

<?php

    const CHAVE_SECRETA =  '123456789';
    const LOCAL_ARQUIVO = '../arquivo/';


    if(!isset($_GET['arquivo']) || !isset($_GET['hash'])){
        echo 'Informações insuficientes';
        exit;
    }

    $nomeArquivo = $_GET['arquivo'];
    $hashNome = $_GET['hash'];

    if(!hash_equals(hash_hmac('SHA512', $nomeArquivo, CHAVE_SECRETA), $hashNome)){
        echo 'Algum parâmetro foi alterado!';
        exit;
    }

    if (!preg_match('/^(?:[a-z0-9_-]|\.(?!\.))+$/iD', $nomeArquivo)) {
        echo 'Arquivo é inválido';
        exit;    
        // Isto é feito para evitar ataques para ler aquivos internos (ex. "../etc/"...), na pior das hipóteses.
    }

    if (!file_exists(LOCAL_ARQUIVO.$nomeArquivo)) {
        echo 'Arquivo é inexistente';
        exit;
    }

    header('Content-Description: File Transfer');
    header('Content-Disposition: attachment; filename="'.$nomeArquivo.'"');
    header('Content-Type: application/octet-stream');
    header('Content-Transfer-Encoding: binary');
    header('Content-Length: ' . filesize(LOCAL_ARQUIVO.$nomeArquivo));
    header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
    header('Pragma: public');
    header('Expires: 0');

    readfile(LOCAL_ARQUIVO.$nomeArquivo);

This response was based on this publication with several changes.

Then to generate the download button use:

const CHAVE_SECRETA =  '123456789';
const LOCAL_ARQUIVO = '../arquivo/';

$nomeArquivo = 'holerith '."$user->cod_func".'  '.utf8_decode($_POST['select_mes']).' de '.$_POST['select_ano'].'.pdf';

$parametrosURL = [
    'arquivo' => $nomeArquivo,
    'hash' => hash_hmac('SHA512', $nomeArquivo, CHAVE_SECRETA)
];

$URL = 'download.php?'.http_build_query($parametrosURL);

if (file_exists(LOCAL_ARQUIVO.$nomeArquivo)) {
    ?>

    Vizualizar: <a href="<?= $URL ?>"><?= $_POST['select_mes'].' '.$_POST['select_ano']; ?></a>
    <br>

    <?php

} else {
    echo "Não existe holerith no mês selecionado";
}

The paths used are as follows:

C.
├───public_html
│       index.php
│       download.php
│
└───arquivo
        teste.pdf

In this way the files present in arquivo are outside the default access of the user. The download.php (who actually downloads) and index.php (who generates the link) are both accessible and in the same folder.

Explanations:

I will just summarize the HMAC, the HMAC is basically a HASH(Texo + HASH(Texto + Senha)) . This ensures that only those who have the password (in this case 123456789 , but logically must be a stronger one) can generate the same HMAC.

This way if the end user changes the parameter arquivo.pdf to arquivo2.pdf it will have a different HASH, so PHP will prevent the download, because the hashes are not equal, this comparison is done by hash_equals .

The only situation that can occur is if for some reason your secret password is leaked , if someone discovers the password used for hashing, such a person can access any file, to prevent access to files on the system itself, which would be more harmful .

If you have sessions you can only make the logged in user download, checking for example in MySQL if the user who is trying to download has access to the file or has access to the specific employee's data, as is the case.

You can also create a HMAC password per session, so if the user accesses the same link in another browser, where they are not in the same session, they will fail. You can also add more data to the HMAC or use the session, for example by limiting access to IP.

    
13.03.2017 / 19:52
1

I was able to do it in a similar way using a download.php helper file

<?php
            $arquivos = 'holerith '."$user->cod_func".'  '.utf8_decode($_POST['select_mes']).' de '.$_POST['select_ano'].'.pdf';
            $pasta = '/arquivos';               
            $filename = 'arquivos/'.$arquivos;

            if (file_exists($filename)) {
                ?>  

                Vizualizar: <a href="<?php echo "$filename"; ?>" embed><?php echo $_POST['select_mes'].' '.$_POST['select_ano']; ?></a>
                <br>

                Download Arquivo: <a href="download.php?id=1&m=<?php echo $_POST['select_mes']?>&a=<?php echo $_POST['select_ano']?>"><?php echo $_POST['select_mes'].' '.$_POST['select_ano']; ?></a>

                <?php

            } else {
                echo "Não existe holerith no mês selecionado";
            }
        ?>

Already in the download.php file I put it like this:

  <?php
        include 'conectar.php';

        $sql = "SELECT * FROM intra_users"; // seleciona as colonas da tabela usuarios
        $resultado = mysql_query($sql); // executa a contulta e armazena o resultado em array
        $num_linhas = mysql_num_rows($resultado);

        // Recuperando Sessão do usuário Joomla
        define('_JEXEC', 1);
        define('DS', DIRECTORY_SEPARATOR);
        $path = "\xampp\htdocs\intranet";  //caminho da instalação do Joomla
        define('JPATH_BASE', $path);
        require_once JPATH_BASE . DS . 'includes' . DS . 'defines.php';
        require_once JPATH_BASE . DS . 'includes' . DS . 'framework.php';

        $mainframe =& JFactory::getApplication('site');
        $db = &JFactory::getDBO();
        $mainframe->initialise();
        $user =& JFactory::getUser( );

        if (isset($_GET['id'])){
        $id = $_GET['id']; /* Pega o ID do arquivo para comparar com a array */
        $mes = $_GET['m'];
        $ano = $_GET['a'];

        /* Lista com os endereços */
        $d[1] = 'arquivos/holerith '."$user->cod_func".'  '.$mes.' de '.$ano.'.pdf';
        $d[2] = '';

        /* Loop para ler o atributo de 'id' e transformar na variável 'file'. */
        for($n = 1; $n < count($d); $n++) { 
            if ($id == $n){
            $file = $d[$n];
            /* Lista de Headers para preparar a página */
            header("Content-Type: application/save");
            $tam = filesize($file);
            header("Content-Length: $tam");
            header('Content-Disposition: attachment; filename="' . $file . '"'); 
            header("Content-Transfer-Encoding: binary");
            header('Expires: 0'); 
            header('Pragma: no-cache'); 

            /* Lê e evia o arquivo para download */
            $fp = fopen("$file", "r"); 
            fpassthru($fp); 
            fclose($fp); 
            $msg = '';
        } 
        else {$msg = 'Arquivo não existe.';} /* Caso o arquivo não exista */
        }}
        else {echo 'Código do arquivo incorreto.';} /* Caso o ID não seja colocado */
        if (isset($msg)){ echo $msg;} else { echo '<br>Arquivo não existe.';}
        ?>

the link will only have the GET of the month and year and not the user code "$ user-> cod_func" that is retrieved from the joomla login session, if someone not logged in tries to access that link does not work and if another login user will download the file corresponding to yours, I think this is enough or do you think you still have to cheat?

    
13.03.2017 / 23:22