Teknik is a suite of services with attractive and functional interfaces. https://www.teknik.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Nonce.php 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik;
  10. use Piwik\Session\SessionNamespace;
  11. /**
  12. * Nonce class.
  13. *
  14. * A cryptographic nonce -- "number used only once" -- is often recommended as
  15. * part of a robust defense against cross-site request forgery (CSRF/XSRF). This
  16. * class provides static methods that create and manage nonce values.
  17. *
  18. * Nonces in Piwik are stored as a session variable and have a configurable expiration.
  19. *
  20. * Learn more about nonces [here](http://en.wikipedia.org/wiki/Cryptographic_nonce).
  21. *
  22. * @api
  23. */
  24. class Nonce
  25. {
  26. /**
  27. * Returns an existing nonce by ID. If none exists, a new nonce will be generated.
  28. *
  29. * @param string $id Unique id to avoid namespace conflicts, e.g., `'ModuleName.ActionName'`.
  30. * @param int $ttl Optional time-to-live in seconds; default is 5 minutes. (ie, in 5 minutes,
  31. * the nonce will no longer be valid).
  32. * @return string
  33. */
  34. public static function getNonce($id, $ttl = 600)
  35. {
  36. // save session-dependent nonce
  37. $ns = new SessionNamespace($id);
  38. $nonce = $ns->nonce;
  39. // re-use an unexpired nonce (a small deviation from the "used only once" principle, so long as we do not reset the expiration)
  40. // to handle browser pre-fetch or double fetch caused by some browser add-ons/extensions
  41. if (empty($nonce)) {
  42. // generate a new nonce
  43. $nonce = md5(SettingsPiwik::getSalt() . time() . Common::generateUniqId());
  44. $ns->nonce = $nonce;
  45. }
  46. // extend lifetime if nonce is requested again to prevent from early timeout if nonce is requested again
  47. // a few seconds before timeout
  48. $ns->setExpirationSeconds($ttl, 'nonce');
  49. return $nonce;
  50. }
  51. /**
  52. * Returns if a nonce is valid and comes from a valid request.
  53. *
  54. * A nonce is valid if it matches the current nonce and if the current nonce
  55. * has not expired.
  56. *
  57. * The request is valid if the referrer is a local URL (see {@link Url::isLocalUrl()})
  58. * and if the HTTP origin is valid (see {@link getAcceptableOrigins()}).
  59. *
  60. * @param string $id The nonce's unique ID. See {@link getNonce()}.
  61. * @param string $cnonce Nonce sent from client.
  62. * @return bool `true` if valid; `false` otherwise.
  63. */
  64. public static function verifyNonce($id, $cnonce)
  65. {
  66. $ns = new SessionNamespace($id);
  67. $nonce = $ns->nonce;
  68. // validate token
  69. if (empty($cnonce) || $cnonce !== $nonce) {
  70. return false;
  71. }
  72. // validate referrer
  73. $referrer = Url::getReferrer();
  74. if (!empty($referrer) && !Url::isLocalUrl($referrer)) {
  75. return false;
  76. }
  77. // validate origin
  78. $origin = self::getOrigin();
  79. if (!empty($origin) &&
  80. ($origin == 'null'
  81. || !in_array($origin, self::getAcceptableOrigins()))
  82. ) {
  83. return false;
  84. }
  85. return true;
  86. }
  87. /**
  88. * Force expiration of the current nonce.
  89. *
  90. * @param string $id The unique nonce ID.
  91. */
  92. public static function discardNonce($id)
  93. {
  94. $ns = new SessionNamespace($id);
  95. $ns->unsetAll();
  96. }
  97. /**
  98. * Returns the **Origin** HTTP header or `false` if not found.
  99. *
  100. * @return string|bool
  101. */
  102. public static function getOrigin()
  103. {
  104. if (!empty($_SERVER['HTTP_ORIGIN'])) {
  105. return $_SERVER['HTTP_ORIGIN'];
  106. }
  107. return false;
  108. }
  109. /**
  110. * Returns a list acceptable values for the HTTP **Origin** header.
  111. *
  112. * @return array
  113. */
  114. public static function getAcceptableOrigins()
  115. {
  116. $host = Url::getCurrentHost(null);
  117. $port = '';
  118. // parse host:port
  119. if (preg_match('/^([^:]+):([0-9]+)$/D', $host, $matches)) {
  120. $host = $matches[1];
  121. $port = $matches[2];
  122. }
  123. if (empty($host)) {
  124. return array();
  125. }
  126. // standard ports
  127. $origins = array(
  128. 'http://' . $host,
  129. 'https://' . $host,
  130. );
  131. // non-standard ports
  132. if (!empty($port) && $port != 80 && $port != 443) {
  133. $origins[] = 'http://' . $host . ':' . $port;
  134. $origins[] = 'https://' . $host . ':' . $port;
  135. }
  136. return $origins;
  137. }
  138. /**
  139. * Verifies and discards a nonce.
  140. *
  141. * @param string $nonceName The nonce's unique ID. See {@link getNonce()}.
  142. * @param string|null $nonce The nonce from the client. If `null`, the value from the
  143. * **nonce** query parameter is used.
  144. * @throws Exception if the nonce is invalid. See {@link verifyNonce()}.
  145. */
  146. public static function checkNonce($nonceName, $nonce = null)
  147. {
  148. if ($nonce === null) {
  149. $nonce = Common::getRequestVar('nonce', null, 'string');
  150. }
  151. if (!self::verifyNonce($nonceName, $nonce)) {
  152. throw new \Exception(Piwik::translate('General_ExceptionNonceMismatch'));
  153. }
  154. self::discardNonce($nonceName);
  155. }
  156. }