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.