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.
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
– Artur Trapp