1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
<?php
/**
* Piwik - Open source web analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
* @version $Id$
*
* @category Piwik
* @package Piwik
*/
/**
* Nonce class.
*
* A cryptographic nonce -- "number used only once" -- is often recommended as part of a robust defense against cross-site request forgery (CSRF/XSRF).
* Desrable characteristics: limited lifetime, uniqueness, unpredictability (pseudo-randomness).
*
* We use a session-dependent nonce with a configurable expiration that combines and hashes:
* - a private salt because it's non-public
* - time() because it's unique
* - a mix of PRNGs (pseudo-random number generators) to increase entropy and make it less predictable
*
* @package Piwik
*/
class Piwik_Nonce
{
/**
* Generate nonce
*
* @param string $id Unique id to avoid namespace conflicts, e.g., ModuleName.ActionName
* @param int $ttl Optional time-to-live in seconds; default is 5 minutes
* @return string Nonce
*/
static public function getNonce($id, $ttl = 300)
{
// save session-dependent nonce
$ns = new Piwik_Session_Namespace($id);
$nonce = $ns->nonce;
// re-use an unexpired nonce (a small deviation from the "used only once" principle, so long as we do not reset the expiration)
// to handle browser pre-fetch or double fetch caused by some browser add-ons/extensions
if(empty($nonce))
{
// generate a new nonce
$nonce = md5(Piwik_Common::getSalt() . time() . Piwik_Common::generateUniqId());
$ns->nonce = $nonce;
$ns->setExpirationSeconds($ttl, 'nonce');
}
return $nonce;
}
/**
* Verify nonce and check referrer (if present, i.e., it may be suppressed by the browser or a proxy/network).
*
* @param string $id Unique id
* @param string $cnonce Nonce sent to client
* @return bool true if valid; false otherwise
*/
static public function verifyNonce($id, $cnonce)
{
$ns = new Piwik_Session_Namespace($id);
$nonce = $ns->nonce;
// validate token
if(empty($cnonce) || $cnonce !== $nonce)
{
return false;
}
// validate referer
$referer = Piwik_Url::getReferer();
if(!empty($referer) && !Piwik_Url::isLocalUrl($referer))
{
return false;
}
// validate origin
$origin = self::getOrigin();
if(!empty($origin) &&
($origin == 'null'
|| !in_array($origin, self::getAcceptableOrigins())))
{
return false;
}
return true;
}
/**
* Discard nonce ("now" as opposed to waiting for garbage collection)
*
* @param string $id Unique id
*/
static public function discardNonce($id)
{
$ns = new Piwik_Session_Namespace($id);
$ns->unsetAll();
}
/**
* Get ORIGIN header, false if not found
*
* @return string|false
*/
static public function getOrigin()
{
if(!empty($_SERVER['HTTP_ORIGIN']))
{
return $_SERVER['HTTP_ORIGIN'];
}
return false;
}
/**
* Returns acceptable origins (not simply scheme://host) that
* should handle a variety of proxy and web server (mis)configurations,.
*
* @return array
*/
static public function getAcceptableOrigins()
{
$host = Piwik_Url::getCurrentHost(null);
$port = '';
// parse host:port
if(preg_match('/^([^:]+):([0-9]+)$/D', $host, $matches))
{
$host = $matches[1];
$port = $matches[2];
}
if(empty($host))
{
return array();
}
// standard ports
$origins[] = 'http://'.$host;
$origins[] = 'https://'.$host;
// non-standard ports
if(!empty($port) && $port != 80 && $port != 443)
{
$origins[] = 'http://'.$host.':'.$port;
$origins[] = 'https://'.$host.':'.$port;
}
return $origins;
}
}
|