sending challenge on paste creation, adding logic to store and check it on view requests

This commit is contained in:
El RIDO
2019-06-28 21:33:52 +02:00
parent d0c8975b89
commit 79db7ddafc
13 changed files with 247 additions and 14 deletions

View File

@@ -276,9 +276,7 @@ class Controller
// accessing this method ensures that the paste would be
// deleted if it has already expired
$paste->get();
if (
Filter::slowEquals($deletetoken, $paste->getDeleteToken())
) {
if ($paste->isDeleteTokenCorrect($deletetoken)) {
// Paste exists and deletion token is valid: Delete the paste.
$paste->delete();
$this->_status = 'Paste was properly deleted.';
@@ -315,9 +313,20 @@ class Controller
try {
$paste = $this->_model->getPaste($dataid);
if ($paste->exists()) {
// handle challenge response
if (!$paste->isTokenCorrect($this->_request->getParam('token'))) {
// we send a generic error to avoid leaking information
// about the existance of a burn after reading pastes
// this avoids an attacker being able to poll, if it has
// been read by the intended recipient or not
$this->_return_message(1, self::GENERIC_ERROR);
return;
}
$data = $paste->get();
if (array_key_exists('salt', $data['meta'])) {
unset($data['meta']['salt']);
foreach (array('salt', 'challenge') as $key) {
if (array_key_exists($key, $data['meta'])) {
unset($data['meta'][$key]);
}
}
$this->_return_message(0, $dataid, (array) $data);
} else {

View File

@@ -67,6 +67,13 @@ class FormatV2
if (!($ct = base64_decode($message['ct'], true))) {
return false;
}
// - (optional) challenge
if (
!$isComment && array_key_exists('challenge', $message['meta']) &&
!base64_decode($message['meta']['challenge'], true)
) {
return false;
}
// Make sure some fields have a reasonable size:
// - initialization vector

View File

@@ -14,6 +14,7 @@ namespace PrivateBin\Model;
use Exception;
use PrivateBin\Controller;
use PrivateBin\Filter;
use PrivateBin\Persistence\ServerSalt;
/**
@@ -23,6 +24,14 @@ use PrivateBin\Persistence\ServerSalt;
*/
class Paste extends AbstractModel
{
/**
* Token for challenge/response.
*
* @access protected
* @var string
*/
protected $_token = '';
/**
* Get paste data.
*
@@ -32,6 +41,11 @@ class Paste extends AbstractModel
*/
public function get()
{
// return cached result if one is found
if (array_key_exists('adata', $this->_data) || array_key_exists('data', $this->_data)) {
return $this->_data;
}
$data = $this->_store->read($this->getId());
if ($data === false) {
throw new Exception(Controller::GENERIC_ERROR, 64);
@@ -48,10 +62,16 @@ class Paste extends AbstractModel
unset($data['meta']['expire_date']);
}
// check if non-expired burn after reading paste needs to be deleted
// check if non-expired burn after reading paste needs to be deleted,
// but don't delete it if an incorrect token was sent
if (
(array_key_exists('adata', $data) && $data['adata'][3] === 1) ||
(array_key_exists('burnafterreading', $data['meta']) && $data['meta']['burnafterreading'])
(
(array_key_exists('adata', $data) && $data['adata'][3] === 1) ||
(array_key_exists('burnafterreading', $data['meta']) && $data['meta']['burnafterreading'])
) && (
!array_key_exists('challenge', $data['meta']) ||
$this->_token === $data['meta']['challenge']
)
) {
$this->delete();
}
@@ -94,6 +114,12 @@ class Paste extends AbstractModel
$this->_data['meta']['created'] = time();
$this->_data['meta']['salt'] = serversalt::generate();
// if a challenge was sent, we store the HMAC of paste ID & challenge
if (array_key_exists('challenge', $this->_data['meta'])) {
$this->_data['meta']['challenge'] = hash_hmac(
'sha256', $this->getId(), base64_decode($this->_data['meta']['challenge'])
);
}
// store paste
if (
@@ -201,6 +227,40 @@ class Paste extends AbstractModel
(array_key_exists('opendiscussion', $this->_data['meta']) && $this->_data['meta']['opendiscussion']);
}
/**
* Check if paste challenge matches provided token.
*
* @access public
* @param string $token
* @throws Exception
* @return bool
*/
public function isTokenCorrect($token)
{
$this->_token = $token;
if (!array_key_exists('challenge', $this->_data['meta'])) {
$this->get();
}
if (array_key_exists('challenge', $this->_data['meta'])) {
return Filter::slowEquals($token, $this->_data['meta']['challenge']);
}
// paste created without challenge, accept every token sent
return true;
}
/**
* Check if paste salt based HMAC matches provided delete token.
*
* @access public
* @param string $deletetoken
* @throws Exception
* @return bool
*/
public function isDeleteTokenCorrect($deletetoken)
{
return Filter::slowEquals($deletetoken, $this->getDeleteToken());
}
/**
* Sanitizes data to conform with current configuration.
*