Преглед на файлове

introduce new zerobincompatibility option, replacing the base64 one, if it is enabled, delete tokens use sha256; added per paste salt with server salt fallback; this resolves the points 2.2 & 2.9 in #103

El RIDO преди 10 години
родител
ревизия
0e217a42c5
променени са 8 файла, в които са добавени 87 реда и са изтрити 38 реда
  1. 6 5
      cfg/conf.ini.sample
  2. 1 1
      lib/configuration.php
  3. 14 2
      lib/model/paste.php
  4. 2 2
      lib/zerobin.php
  5. 3 0
      tst/bootstrap.php
  6. 1 1
      tst/configuration.php
  7. 13 9
      tst/jsonApi.php
  8. 47 18
      tst/zerobin.php

+ 6 - 5
cfg/conf.ini.sample

@@ -39,10 +39,6 @@ template = "bootstrap"
 ; (optional) notice to display
 ; notice = "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service."
 
-; base64.js library version, defaults to 2.1.9
-; use "1.7" if you are upgrading from a ZeroBin Alpha 0.19 installation
-base64version = "2.1.9"
-
 ; by default ZeroBin will guess the visitors language based on the browsers
 ; settings. Optionally you can enable the language selection menu, which uses
 ; a session cookie to store the choice until the browser is closed.
@@ -57,6 +53,11 @@ languageselection = false
 ; the pastes encryption key
 ; urlshortener = "https://shortener.example.com/api?link="
 
+; stay compatible with ZeroBin Alpha 0.19, less secure
+; if enabled will use base64.js version 1.7 instead of 2.1.9 and sha1 instead of
+; sha256 in HMAC for the deletion token
+zerobincompatibility = false
+
 [expire]
 ; expire value that is selected per default
 ; make sure the value exists in [expire_options]
@@ -121,4 +122,4 @@ dir = PATH "data"
 ;dsn = "sqlite:" PATH "data/db.sq3"
 ;usr = null
 ;pwd = null
-;opt[12] = true	; PDO::ATTR_PERSISTENT
+;opt[12] = true	; PDO::ATTR_PERSISTENT

+ 1 - 1
lib/configuration.php

@@ -41,10 +41,10 @@ class configuration
             'sizelimit' => 2097152,
             'template' => 'bootstrap',
             'notice' => '',
-            'base64version' => '2.1.9',
             'languageselection' => false,
             'languagedefault' => '',
             'urlshortener' => '',
+            'zerobincompatibility' => false,
         ),
         'expire' => array(
             'default' => '1week',

+ 14 - 2
lib/model/paste.php

@@ -27,7 +27,7 @@ class model_paste extends model_abstract
     public function get()
     {
         $this->_data = $this->_store->read($this->getId());
-        // See if paste has expired and delete it if neccessary.
+        // check if paste has expired and delete it if neccessary.
         if (property_exists($this->_data->meta, 'expire_date'))
         {
             if ($this->_data->meta->expire_date < time())
@@ -52,6 +52,12 @@ class model_paste extends model_abstract
                 $this->_data->meta->formatter = $this->_conf->getKey('defaultformatter');
             }
         }
+
+        // support old paste format with server wide salt
+        if (!property_exists($this->_data->meta, 'salt'))
+        {
+            $this->_data->meta->salt = serversalt::get();
+        }
         $this->_data->comments = array_values($this->getComments());
         $this->_data->comment_count = count($this->_data->comments);
         $this->_data->comment_offset = 0;
@@ -73,6 +79,7 @@ class model_paste extends model_abstract
             throw new Exception('You are unlucky. Try again.', 75);
 
         $this->_data->meta->postdate = time();
+        $this->_data->meta->salt = serversalt::generate();
 
         // store paste
         if (
@@ -151,7 +158,12 @@ class model_paste extends model_abstract
      */
     public function getDeleteToken()
     {
-        return hash_hmac('sha1', $this->getId(), serversalt::get());
+        if (!property_exists($this->_data->meta, 'salt')) $this->get();
+        return hash_hmac(
+            $this->_conf->getKey('zerobincompatibility') ? 'sha1' : 'sha256',
+            $this->getId(),
+            $this->_data->meta->salt
+        );
     }
 
     /**

+ 2 - 2
lib/zerobin.php

@@ -327,7 +327,6 @@ class zerobin
                 else
                 {
                     // Make sure the token is valid.
-                    serversalt::setPath($this->_conf->getKey('dir', 'traffic'));
                     if (filter::slow_equals($deletetoken, $paste->getDeleteToken()))
                     {
                         // Paste exists and deletion token is valid: Delete the paste.
@@ -364,6 +363,7 @@ class zerobin
             {
                 $data = $paste->get();
                 $this->_doesExpire = property_exists($data, 'meta') && property_exists($data->meta, 'expire_date');
+                if (property_exists($data->meta, 'salt')) unset($data->meta->salt);
                 $this->_data = json_encode($data);
             }
             else
@@ -439,7 +439,7 @@ class zerobin
         $page->assign('BURNAFTERREADINGSELECTED', $this->_conf->getKey('burnafterreadingselected'));
         $page->assign('PASSWORD', $this->_conf->getKey('password'));
         $page->assign('FILEUPLOAD', $this->_conf->getKey('fileupload'));
-        $page->assign('BASE64JSVERSION', $this->_conf->getKey('base64version'));
+        $page->assign('BASE64JSVERSION', $this->_conf->getKey('zerobincompatibility') ? '1.7' : '2.1.9');
         $page->assign('LANGUAGESELECTION', $languageselection);
         $page->assign('LANGUAGES', i18n::getLanguageLabels(i18n::getAvailableLanguages()));
         $page->assign('EXPIRE', $expire);

+ 3 - 0
tst/bootstrap.php

@@ -85,6 +85,7 @@ class helper
     public static function getPasteWithAttachment($meta = array())
     {
         $example = self::$paste;
+        $example['meta']['salt'] = serversalt::generate();
         $example['meta'] = array_merge($example['meta'], $meta);
         return $example;
     }
@@ -97,6 +98,8 @@ class helper
     public static function getPasteAsJson($meta = array())
     {
         $example = self::getPaste();
+        // the JSON shouldn't contain the salt
+        unset($example['meta']['salt']);
         if (count($meta))
             $example['meta'] = $meta;
         $example['comments'] = array();

+ 1 - 1
tst/configuration.php

@@ -13,10 +13,10 @@ class configurationTest extends PHPUnit_Framework_TestCase
             'sizelimit' => 2097152,
             'template' => 'bootstrap',
             'notice' => '',
-            'base64version' => '2.1.9',
             'languageselection' => false,
             'languagedefault' => '',
             'urlshortener' => '',
+            'zerobincompatibility' => false,
         ),
         'expire' => array(
             'default' => '1week',

+ 13 - 9
tst/jsonApi.php

@@ -46,13 +46,14 @@ class jsonApiTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         $response = json_decode($content, true);
         $this->assertEquals(0, $response['status'], 'outputs status');
+        $this->assertStringEndsWith('?' . $response['id'], $response['url'], 'returned URL points to new paste');
+        $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
+        $paste = $this->_model->read($response['id']);
         $this->assertEquals(
-            hash_hmac('sha1', $response['id'], serversalt::get()),
+            hash_hmac('sha256', $response['id'], $paste->meta->salt),
             $response['deletetoken'],
             'outputs valid delete token'
         );
-        $this->assertStringEndsWith('?' . $response['id'], $response['url'], 'returned URL points to new paste');
-        $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
     }
 
     /**
@@ -80,13 +81,14 @@ class jsonApiTest extends PHPUnit_Framework_TestCase
         $response = json_decode($content, true);
         $this->assertEquals(0, $response['status'], 'outputs status');
         $this->assertEquals(helper::getPasteId(), $response['id'], 'outputted paste ID matches input');
+        $this->assertStringEndsWith('?' . $response['id'], $response['url'], 'returned URL points to new paste');
+        $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
+        $paste = $this->_model->read($response['id']);
         $this->assertEquals(
-            hash_hmac('sha1', $response['id'], serversalt::get()),
+            hash_hmac('sha256', $response['id'], $paste->meta->salt),
             $response['deletetoken'],
             'outputs valid delete token'
         );
-        $this->assertStringEndsWith('?' . $response['id'], $response['url'], 'returned URL points to new paste');
-        $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
     }
 
     /**
@@ -97,9 +99,10 @@ class jsonApiTest extends PHPUnit_Framework_TestCase
         $this->reset();
         $this->_model->create(helper::getPasteId(), helper::getPaste());
         $this->assertTrue($this->_model->exists(helper::getPasteId()), 'paste exists before deleting data');
+        $paste = $this->_model->read(helper::getPasteId());
         $file = tempnam(sys_get_temp_dir(), 'FOO');
         file_put_contents($file, http_build_query(array(
-            'deletetoken' => hash_hmac('sha1', helper::getPasteId(), serversalt::get()),
+            'deletetoken' => hash_hmac('sha256', helper::getPasteId(), $paste->meta->salt),
         )));
         request::setInputStream($file);
         $_SERVER['QUERY_STRING'] = helper::getPasteId();
@@ -121,9 +124,10 @@ class jsonApiTest extends PHPUnit_Framework_TestCase
         $this->reset();
         $this->_model->create(helper::getPasteId(), helper::getPaste());
         $this->assertTrue($this->_model->exists(helper::getPasteId()), 'paste exists before deleting data');
+        $paste = $this->_model->read(helper::getPasteId());
         $_POST = array(
             'action' => 'delete',
-            'deletetoken' => hash_hmac('sha1', helper::getPasteId(), serversalt::get()),
+            'deletetoken' => hash_hmac('sha256', helper::getPasteId(), $paste->meta->salt),
         );
         $_SERVER['QUERY_STRING'] = helper::getPasteId();
         $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
@@ -254,4 +258,4 @@ class jsonApiTest extends PHPUnit_Framework_TestCase
         $this->assertEquals('{}', $content, 'does not output nasty data');
     }
 
-}
+}

+ 47 - 18
tst/zerobin.php

@@ -169,12 +169,13 @@ class zerobinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         $response = json_decode($content, true);
         $this->assertEquals(0, $response['status'], 'outputs status');
+        $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
+        $paste = $this->_model->read($response['id']);
         $this->assertEquals(
-            hash_hmac('sha1', $response['id'], serversalt::get()),
+            hash_hmac('sha256', $response['id'], $paste->meta->salt),
             $response['deletetoken'],
             'outputs valid delete token'
         );
-        $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
     }
 
     /**
@@ -288,13 +289,13 @@ class zerobinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         $response = json_decode($content, true);
         $this->assertEquals(0, $response['status'], 'outputs status');
+        $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
+        $paste = $this->_model->read($response['id']);
         $this->assertEquals(
-            hash_hmac('sha1', $response['id'], serversalt::get()),
+            hash_hmac('sha256', $response['id'], $paste->meta->salt),
             $response['deletetoken'],
             'outputs valid delete token'
         );
-        $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
-        $paste = $this->_model->read($response['id']);
         $this->assertGreaterThanOrEqual($time + 300, $paste->meta->expire_date, 'time is set correctly');
     }
 
@@ -320,13 +321,13 @@ class zerobinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         $response = json_decode($content, true);
         $this->assertEquals(0, $response['status'], 'outputs status');
+        $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
+        $paste = $this->_model->read($response['id']);
         $this->assertEquals(
-            hash_hmac('sha1', $response['id'], serversalt::get()),
+            hash_hmac('sha256', $response['id'], $paste->meta->salt),
             $response['deletetoken'],
             'outputs valid delete token'
         );
-        $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
-        $paste = $this->_model->read($response['id']);
         $this->assertGreaterThanOrEqual($time + 300, $paste->meta->expire_date, 'time is set correctly');
         $this->assertEquals(1, $paste->meta->opendiscussion, 'discussion is enabled');
     }
@@ -351,12 +352,13 @@ class zerobinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         $response = json_decode($content, true);
         $this->assertEquals(0, $response['status'], 'outputs status');
+        $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
+        $paste = $this->_model->read($response['id']);
         $this->assertEquals(
-            hash_hmac('sha1', $response['id'], serversalt::get()),
+            hash_hmac('sha256', $response['id'], $paste->meta->salt),
             $response['deletetoken'],
             'outputs valid delete token'
         );
-        $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
     }
 
     /**
@@ -426,17 +428,17 @@ class zerobinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         $response = json_decode($content, true);
         $this->assertEquals(0, $response['status'], 'outputs status');
-        $this->assertEquals(
-            hash_hmac('sha1', $response['id'], serversalt::get()),
-            $response['deletetoken'],
-            'outputs valid delete token'
-        );
         $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
         $original = json_decode(json_encode($_POST));
         $stored = $this->_model->read($response['id']);
         foreach (array('data', 'attachment', 'attachmentname') as $key) {
             $this->assertEquals($original->$key, $stored->$key);
         }
+        $this->assertEquals(
+            hash_hmac('sha256', $response['id'], $stored->meta->salt),
+            $response['deletetoken'],
+            'outputs valid delete token'
+        );
     }
 
     /**
@@ -459,12 +461,13 @@ class zerobinTest extends PHPUnit_Framework_TestCase
         $content = ob_get_contents();
         $response = json_decode($content, true);
         $this->assertEquals(0, $response['status'], 'outputs status');
+        $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
+        $paste = $this->_model->read($response['id']);
         $this->assertEquals(
-            hash_hmac('sha1', $response['id'], serversalt::get()),
+            hash_hmac('sha256', $response['id'], $paste->meta->salt),
             $response['deletetoken'],
             'outputs valid delete token'
         );
-        $this->assertTrue($this->_model->exists($response['id']), 'paste exists after posting data');
     }
 
     /**
@@ -705,6 +708,7 @@ class zerobinTest extends PHPUnit_Framework_TestCase
         ob_start();
         new zerobin;
         $content = ob_get_contents();
+        unset($burnPaste['meta']['salt']);
         $this->assertContains(
             '<div id="cipherdata" class="hidden">' .
             htmlspecialchars(helper::getPasteAsJson($burnPaste['meta']), ENT_NOQUOTES) .
@@ -796,6 +800,7 @@ class zerobinTest extends PHPUnit_Framework_TestCase
         new zerobin;
         $content = ob_get_contents();
         $oldPaste['meta']['formatter'] = 'plaintext';
+        unset($oldPaste['meta']['salt']);
         $this->assertContains(
             '<div id="cipherdata" class="hidden">' .
             htmlspecialchars(helper::getPasteAsJson($oldPaste['meta']), ENT_NOQUOTES) .
@@ -813,8 +818,9 @@ class zerobinTest extends PHPUnit_Framework_TestCase
         $this->reset();
         $this->_model->create(helper::getPasteId(), helper::getPaste());
         $this->assertTrue($this->_model->exists(helper::getPasteId()), 'paste exists before deleting data');
+        $paste = $this->_model->read(helper::getPasteId());
         $_GET['pasteid'] = helper::getPasteId();
-        $_GET['deletetoken'] = hash_hmac('sha1', helper::getPasteId(), serversalt::get());
+        $_GET['deletetoken'] = hash_hmac('sha256', helper::getPasteId(), $paste->meta->salt);
         ob_start();
         new zerobin;
         $content = ob_get_contents();
@@ -947,4 +953,27 @@ class zerobinTest extends PHPUnit_Framework_TestCase
         );
         $this->assertFalse($this->_model->exists(helper::getPasteId()), 'paste successfully deleted');
     }
+
+    /**
+     * @runInSeparateProcess
+     */
+    public function testDeleteMissingPerPasteSalt()
+    {
+        $this->reset();
+        $paste = helper::getPaste();
+        unset($paste['meta']['salt']);
+        $this->_model->create(helper::getPasteId(), $paste);
+        $this->assertTrue($this->_model->exists(helper::getPasteId()), 'paste exists before deleting data');
+        $_GET['pasteid'] = helper::getPasteId();
+        $_GET['deletetoken'] = hash_hmac('sha256', helper::getPasteId(), serversalt::get());
+        ob_start();
+        new zerobin;
+        $content = ob_get_contents();
+        $this->assertRegExp(
+            '#<div[^>]*id="status"[^>]*>.*Paste was properly deleted[^<]*</div>#s',
+            $content,
+            'outputs deleted status correctly'
+        );
+        $this->assertFalse($this->_model->exists(helper::getPasteId()), 'paste successfully deleted');
+    }
 }