Hide Link for download

Asked

Viewed 1,134 times

1

I’m creating a code to download holerite files, where everyone can only see their holerite. The code that I made now works, however has a flaw, as I put all the files in the same folder to facilitate the upload of them since they are more than 400, when the person generates the code of the link to download he can when changing the name of the file search other other holerites, I would like to know a way to mask this link, in my code the $user->cod_func is the employee code that he takes from the database to create the correct link but if the person changes this code by trial and error in the browser he can cease others and that’s 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";
            }
        ?>
  • Instead of printing directly, I recommend that before printing, make sure that the code that was passed to print is the same as the logged-in user, and allow printing only if it is

2 answers

1

One of the facial forms would be using HMAC, it has the purpose of verify the integrity of the information, thus preventing it from being changed, as you say in " it may by changing the name of the file fetch others too", however he has no purpose to hide. Another option is to check if the user who requests the download actually has access to it or use both.

For both cases it would be necessary to read the file and pass 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 amendments.

Then to generate the download button utilize:

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 deducing the following form:

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

Thus the files present in arquivo are out of access pattern of the user. The download.php (who actually downloads) and the index.php (who generates the link) are accessible and in the same folder.

Explanations:

I’ll 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) can generate the same HMAC.

This way if the end user changes the parameter arquivo.pdf for arquivo2.pdf it will have a different HASH, so PHP will prevent the download, because the hash are not equal, this comparison is made 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 hash, such person will be able to access any file, precisely why was used the preg_match, to prevent access to own system files, which would be more damaging.


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

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

  • Below I put how I got is even similar to your answer but without the key, I found it very interesting, even though my working I’ll try your solution seems even safer.

1

I was able to do it in a similar way using a downloadable auxiliary file.php

<?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";
            }
        ?>

Ai in the file download.php I put so:

  <?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.';}
        ?>

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

Browser other questions tagged

You are not signed in. Login or sign up in order to post.