1: <?php
2: /**
3: * This file is part of the PHPLucidFrame library.
4: * Core utility for input sanitizing, data escaping, CSRF protection and XSS prevention
5: *
6: * @package PHPLucidFrame\Core
7: * @since PHPLucidFrame v 1.0.0
8: * @copyright Copyright (c), PHPLucidFrame.
9: * @link http://phplucidframe.com
10: * @license http://www.opensource.org/licenses/mit-license.php MIT License
11: *
12: * This source file is subject to the MIT license that is bundled
13: * with this source code in the file LICENSE
14: */
15:
16: /**
17: * @internal
18: * @ignore
19: *
20: * Check the default security secret to be changed
21: */
22: function security_prerequisite()
23: {
24: $defaultSecret = md5('lucidframe');
25: $secret = trim(_cfg('securitySecret'));
26: if (function_exists('mcrypt_encrypt') && (empty($secret) || strcmp($secret, $defaultSecret) === 0)) {
27: $msg = 'To change your own security secret, ';
28: $msg .= 'open your terminal or command line, <code class="inline">cd</code> to your project directory, ';
29: $msg .= 'then run <code class="inline">php lucidframe secret:generate</span>';
30: _cfg('sitewideWarnings', function_exists('_t') ? _t($msg) : $msg);
31: }
32: }
33:
34: /**
35: * Return a component of the current path.
36: * When viewing a page http://www.example.com/foo/bar and its path would be "foo/bar",
37: * for example, _arg(0) returns "foo" and _arg(1) returns "bar"
38: *
39: * @param mixed $index
40: * The index of the component, where each component is separated by a '/' (forward-slash),
41: * and where the first component has an index of 0 (zero).
42: * @param string $path
43: * A path to break into components. Defaults to the path of the current page.
44: *
45: * @return mixed
46: * The component specified by `$index`, or `null` if the specified component was not found.
47: * If called without arguments, it returns an array containing all the components of the current path.
48: */
49: function _arg($index = null, $path = null)
50: {
51: if (isset($_GET[$index])) {
52: return _get($index);
53: }
54:
55: if (is_null($path)) {
56: $path = route_path();
57: }
58: $arguments = explode('/', $path);
59:
60: if (is_numeric($index)) {
61: if (!isset($index)) {
62: return $arguments;
63: }
64: if (isset($arguments[$index])) {
65: return strip_tags(trim($arguments[$index]));
66: }
67: } elseif (is_string($index)) {
68: $query = '-' . $index . '/';
69: $pos = strpos($path, $query);
70: if ($pos !== false) {
71: $start = $pos + strlen($query);
72: $path = substr($path, $start);
73: $end = strpos($path, '/-');
74: if ($end) {
75: $path = substr($path, 0, $end);
76: }
77: if (substr_count($path, '/')) {
78: return explode('/', $path);
79: } else {
80: return $path;
81: }
82: }
83: } elseif (is_null($index)) {
84: return explode('/', str_replace('/-', '/', $path));
85: }
86:
87: return '';
88: }
89:
90: /**
91: * Sanitize input values from GET
92: * @param mixed $name (Optional) The name in $_GET to be sanitized; if it is omitted, the whole array of $_GET will be sanitized
93: * @return mixed The cleaned value or array of values
94: */
95: function _get($name = null)
96: {
97: if ($name === null) {
98: $get = $_GET;
99: foreach ($get as $name => $value) {
100: if (is_array($value)) {
101: $get[$name] = _get($name);
102: } else {
103: $get[$name] = urldecode(_sanitize($value));;
104: }
105: }
106:
107: return $get;
108: } else {
109: if (isset($_GET[$name])) {
110: if (is_array($_GET[$name])) {
111: $get = $_GET[$name];
112: foreach ($get as $key => $value) {
113: if (is_array($value)) {
114: array_walk($get[$key], function(&$val) {
115: $val = is_array($val) ? $val : urldecode(_sanitize($val));
116: });
117: } else {
118: $get[$key] = urldecode(_sanitize($value));
119: }
120: }
121:
122: return $get;
123: } else {
124: return urldecode(_sanitize($_GET[$name]));
125: }
126: } else {
127: return null;
128: }
129: }
130: }
131:
132: /**
133: * Sanitize input values from POST
134: * @param mixed $name (Optional) The name in $_POST to be sanitized; if it is omitted, the whole array of $_POST will be sanitized
135: * @return mixed the cleaned value or array of values
136: */
137: function _post($name = null)
138: {
139: if (_isContentType('application/json')) {
140: return __input($name);
141: }
142:
143: if ($name === null) {
144: $post = $_POST;
145: foreach ($post as $name => $value) {
146: if (is_array($value)) {
147: $post[$name] = _post($name);
148: } else {
149: $post[$name] = _sanitize(stripslashes($value));
150: }
151: }
152:
153: return $post;
154: } else {
155: if (isset($_POST[$name])) {
156: if (is_array($_POST[$name])) {
157: $post = $_POST[$name];
158: foreach ($post as $key => $value) {
159: if (is_array($value)) {
160: array_walk($post[$key], function(&$val) {
161: $val = is_array($val) ? $val : _sanitize(stripslashes($val));
162: });
163: } else {
164: $post[$key] = _sanitize(stripslashes($value));
165: }
166: }
167:
168: return $post;
169: } else {
170: return _sanitize(stripslashes($_POST[$name]));
171: }
172: } else {
173: return null;
174: }
175: }
176: }
177:
178: /**
179: * Accessing PUT request data
180: * @param string $name The optional name of the value to be sanitized
181: * @return mixed the cleaned value
182: */
183: function _put($name = null)
184: {
185: return __input($name);
186: }
187:
188: /**
189: * Accessing PATCH request data
190: * @param string $name The optional name of the value to be sanitized
191: * @return mixed the cleaned value
192: */
193: function _patch($name = null)
194: {
195: return __input($name);
196: }
197:
198: /**
199: * Strips javascript tags in the value to prevent from XSS attack
200: * @param mixed $value The value or The array of values being stripped.
201: * @return mixed the cleaned value
202: */
203: function _xss($value)
204: {
205: if (is_object($value)) {
206: return $value;
207: }
208:
209: if (is_array($value)) {
210: foreach ($value as $key => $val) {
211: if (is_array($val)) {
212: $value[$key] = _xss($val);
213: } else {
214: $value[$key] = __xss($val);
215: }
216: }
217: } else {
218: $value = __xss($value);
219: }
220:
221: return $value;
222: }
223:
224: /**
225: * Sanitize strings
226: * @param mixed $input Value to filter
227: * @return mixed The filtered value
228: */
229: function _sanitize($input)
230: {
231: $input = htmlspecialchars_decode(trim($input), ENT_NOQUOTES);
232:
233: return htmlspecialchars($input, ENT_NOQUOTES);
234: }
235:
236: /**
237: * @internal
238: * @ignore
239: *
240: * Accessing PUT/PATCH data
241: * @param string $name The optional name of the value to be sanitized
242: * @return mixed the cleaned value
243: */
244: function __input($name = null)
245: {
246: $input = file_get_contents("php://input");
247: if (_isContentType('application/json')) {
248: $vars = json_decode($input, true);
249: } else {
250: parse_str($input, $vars);
251: }
252:
253: if ($name) {
254: return isset($vars[$name]) ? _sanitize(stripslashes($vars[$name])) : null;
255: }
256:
257: if (is_array($vars)) {
258: foreach ($vars as $key => $value) {
259: $vars[$key] = _sanitize(stripslashes($value));
260: }
261: }
262:
263: return $vars;
264: }
265:
266: /**
267: * @internal
268: * @ignore
269: *
270: * Strips javascript tags in the value to prevent from XSS attack
271: * @param mixed $value The value being stripped.
272: * @return mixed the cleaned value
273: */
274: function __xss($value)
275: {
276: $value = trim(stripslashes($value));
277: $ascii = '[\x00-\x20|&\#x0A;|&\#x0D;|&\#x09;|&\#14;|<|!|\-|>]*';
278:
279: # Remove some tags
280: $value = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|!--\#exec|style|form|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript)|title|xml|\?xml)[^>]*+>#i', '', $value);
281: # Remove any attribute starting with "on" or xmlns
282: $value = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $value);
283: # Remove javascript: protocol
284: $value = preg_replace('#([a-z]*)'.$ascii.'='.$ascii.'([`\'"]*)'.$ascii.'j'.$ascii.'a'.$ascii.'v'.$ascii.'a'.$ascii.'s'.$ascii.'c'.$ascii.'r'.$ascii.'i'.$ascii.'p'.$ascii.'t'.$ascii.':#iu', '$1=$2noscript:', $value);
285: # Remove vbscript: protocol
286: $value = preg_replace('#([a-z]*)'.$ascii.'='.$ascii.'([`\'"]*)'.$ascii.'v'.$ascii.'b'.$ascii.'s'.$ascii.'c'.$ascii.'r'.$ascii.'i'.$ascii.'p'.$ascii.'t'.$ascii.':#iu', '$1=$2noscript:', $value);
287: # Remove livescript: protocol (older versions of Netscape only)
288: $value = preg_replace('#([a-z]*)'.$ascii.'='.$ascii.'([`\'"]*)'.$ascii.'l'.$ascii.'i'.$ascii.'v'.$ascii.'e'.$ascii.'s'.$ascii.'c'.$ascii.'r'.$ascii.'i'.$ascii.'p'.$ascii.'t'.$ascii.':#iu', '$1=$2noscript:', $value);
289: # Remove -moz-binding: css (Firefox only)
290: $value = preg_replace('#([a-z]*)'.$ascii.'([\'"]*)'.$ascii.'(-moz-binding|javascript)'.$ascii.':#u', '$1$2noscript:', $value);
291: # Remove dec/hex entities in tags, such as &#106;, &#0000106;, &#x6A;
292: $value = preg_replace('#([a-z]*)'.$ascii.'='.$ascii.'([`\'"]*)((&\#x*[0-9A-F]+);*)+#iu', '$1', $value);
293:
294: # CSS expression; only works in IE: <span style="width: expression(alert('Ping!'));"></span>
295: $chunk = str_split('expression');
296: # to match
297: # - expression:
298: # - expr/*XSS*/ession:
299: # - ex/*XSS*//*/*/pression:
300: $expression = $ascii;
301: foreach ($chunk as $chr) {
302: $expression .= $chr . '(\/\*.*\*\/)*';
303: }
304: $expression .= $ascii;
305: $value = preg_replace('#(<[^>]+?)style'.$ascii.'='.$ascii.'[`\'"]*.*?'.$expression.'\([^>]*+>#i', '$1>', $value);
306:
307: # CSS behaviour
308: $chunk = str_split('behavior');
309: $behavior = $ascii;
310: foreach ($chunk as $chr) {
311: $behavior .= $chr . '(\/\*.*\*\/)*';
312: }
313: $behavior .= $ascii;
314: $value = preg_replace('#(<[^>]+?)style'.$ascii.'='.$ascii.'[`\'"]*.*?'.$behavior.'[^>]*+>#i', '$1>', $value);
315:
316: return $value;
317: }
318: