1: <?php
2: /**
3: * This file contains the the request validator class.
4: *
5: * @package Core
6: * @subpackage Security
7: * @version SVN Revision $Rev:$
8: *
9: * @author Mischa Holz, Andreas Kummer
10: * @copyright four for business AG <www.4fb.de>
11: * @license http://www.contenido.org/license/LIZENZ.txt
12: * @link http://www.4fb.de
13: * @link http://www.contenido.org
14: */
15:
16: defined('CON_FRAMEWORK') || die('Illegal call: Missing framework initialization - request aborted.');
17:
18: /**
19: * Class to check get and post variables
20: *
21: * @package Core
22: * @subpackage Security
23: */
24: class cRequestValidator {
25:
26: /**
27: * Instance of this class
28: *
29: * @var cRequestValidator
30: */
31: private static $_instance = NULL;
32:
33: /**
34: * Path and filename of logfile
35: *
36: * @var string
37: */
38: protected $_logPath;
39:
40: /**
41: * Flag whether to write log or not.
42: *
43: * @var bool
44: */
45: protected $_log = true;
46:
47: /**
48: * Path to config file.
49: *
50: * @var string
51: */
52: protected $_configPath;
53:
54: /**
55: * Array with all possible parameters and parameter formats.
56: * Structure has to be:
57: *
58: * <code>
59: * $check['GET']['param1'] = VALIDATE_FORMAT;
60: * $check['POST']['param2'] = VALIDATE_FORMAT;
61: * </code>
62: *
63: * Possible formats are defined as constants in top of these class file.
64: *
65: * @var array
66: */
67: protected $_check = array();
68:
69: /**
70: * Array with forbidden parameters.
71: * If any of these is set the request will be invalid
72: *
73: * @var array
74: */
75: protected $_blacklist = array();
76:
77: /**
78: * Contains first invalid parameter name.
79: *
80: * @var string
81: */
82: protected $_failure = '';
83:
84: /**
85: * Current mode
86: *
87: * @var string
88: */
89: protected $_mode = '';
90:
91: /**
92: * Regexp for integers.
93: *
94: * @var string
95: */
96: const CHECK_INTEGER = '/^[0-9]*$/';
97:
98: /**
99: * Regexp for primitive strings.
100: *
101: * @var string
102: */
103: const CHECK_PRIMITIVESTRING = '/^[a-zA-Z0-9 -_]*$/';
104:
105: /**
106: * Regexp for strings.
107: *
108: * @var string
109: */
110: const CHECK_STRING = '/^[\w0-9 -_]*$/';
111:
112: /**
113: * Regexp for 32 character hash.
114: *
115: * @var string
116: */
117: const CHECK_HASH32 = '/^[a-zA-Z0-9]{32}$/';
118:
119: /**
120: * Regexp for valid belang values.
121: *
122: * @var string
123: */
124: const CHECK_BELANG = '/^[a-z]{2}_[A-Z]{2}$/';
125:
126: /**
127: * Regexp for valid area values.
128: *
129: * @var string
130: */
131: const CHECK_AREASTRING = '/^[a-zA-Z_]*$/';
132:
133: /**
134: * Regexp for validating file upload paths.
135: *
136: * @var string
137: */
138: const CHECK_PATHSTRING = '!([*]*\/)|(dbfs:\/[*]*)|(dbfs:)|(^)$!';
139:
140: /**
141: * The constructor sets up the singleton object and reads the config from
142: * 'data/config/' . CON_ENVIRONMENT . '/config.http_check.php'
143: * It also reads existing local config from
144: * 'data/config/' . CON_ENVIRONMENT . '/config.http_check.local.php'
145: *
146: * @throws cFileNotFoundException if the configuration can not be loaded
147: */
148: private function __construct() {
149: // globals from config.http_check.php file which is included below
150: global $bLog, $sMode, $aCheck, $aBlacklist;
151:
152: // some paths...
153: $installationPath = str_replace('\\', '/', realpath(dirname(__FILE__) . '/../..'));
154: $configPath = $installationPath . '/data/config/' . CON_ENVIRONMENT;
155:
156: $this->_logPath = $installationPath . '/data/logs/security.txt';
157:
158: // check config and logging path
159: if (cFileHandler::exists($configPath . '/config.http_check.php')) {
160: $this->_configPath = $configPath;
161: } else {
162: throw new cFileNotFoundException('Could not load cRequestValidator configuration! (invalid path) ' . $configPath . '/config.http_check.php');
163: }
164:
165: // include configuration
166: require($this->_configPath . '/config.http_check.php');
167:
168: // if custom config exists, include it also here
169: if (cFileHandler::exists($this->_configPath . '/config.http_check.local.php')) {
170: require($this->_configPath . '/config.http_check.local.php');
171: }
172:
173: $this->_log = $bLog;
174: $this->_mode = $sMode;
175:
176: if ($this->_log === true) {
177: if (empty($this->_logPath) || !is_writeable(dirname($this->_logPath))) {
178: $this->_log = false;
179: }
180: }
181:
182: $this->_check = $aCheck;
183: foreach ($aBlacklist as $elem) {
184: $this->_blacklist[] = strtolower($elem);
185: }
186: }
187:
188: /**
189: * Returns the instance of this class.
190: *
191: * @return cRequestValidator
192: */
193: public static function getInstance() {
194: if (self::$_instance === NULL) {
195: self::$_instance = new self();
196: }
197:
198: return self::$_instance;
199: }
200:
201: /**
202: * Checks every given parameter.
203: * Parameters which aren't defined in config.http_check.php are considered
204: * to be fine
205: *
206: * @return bool
207: * True if every parameter is fine
208: */
209: public function checkParams() {
210: if ((!$this->checkGetParams()) || (!$this->checkPostParams())) {
211: $this->logHackTrial();
212:
213: if ($this->_mode == 'stop') {
214: ob_end_clean();
215: $msg = 'Parameter check failed! (%s = %s %s)';
216: // prevent XSS!
217: $msg = sprintf($msg, htmlentities($this->_failure), htmlentities($_GET[$this->_failure]), htmlentities($_POST[$this->_failure]));
218: die($msg);
219: }
220: }
221:
222: return true;
223: }
224:
225: /**
226: * Checks GET parameters only.
227: *
228: * @see cRequestValidator::checkParams()
229: * @return bool
230: * True if every parameter is fine
231: */
232: public function checkGetParams() {
233: return $this->checkArray($_GET, 'GET');
234: }
235:
236: /**
237: * Checks POST parameters only.
238: *
239: * @see cRequestValidator::checkParams()
240: * @return bool
241: * True if every parameter is fine
242: */
243: public function checkPostParams() {
244: return $this->checkArray($_POST, 'POST');
245: }
246:
247: /**
248: * Checks a single parameter.
249: *
250: * @see cRequestValidator::checkParams()
251: *
252: * @param string $type
253: * GET or POST
254: * @param string $key
255: * the key of the parameter
256: * @param mixed $value
257: * the value of the parameter
258: * @return bool
259: * True if the parameter is fine
260: */
261: public function checkParameter($type, $key, $value) {
262: $result = false;
263:
264: if (in_array(strtolower($key), $this->_blacklist)) {
265: return false;
266: }
267:
268: if (in_array(strtoupper($type), array(
269: 'GET',
270: 'POST'
271: ))) {
272: if (!isset($this->_check[$type][$key]) && (is_null($value) || empty($value))) {
273: // if unknown but empty the value is unaesthetic but ok
274: $result = true;
275: } elseif (isset($this->_check[$type][$key])) {
276: // parameter is known, check it...
277: $result = preg_match($this->_check[$type][$key], $value);
278: } else {
279: // unknown parameter. Will return true
280: $result = true;
281: }
282: }
283:
284: return $result;
285: }
286:
287: /**
288: * Returns the first bad parameter
289: *
290: * @return string
291: * the key of the bad parameter
292: */
293: public function getBadParameter() {
294: return $this->_failure;
295: }
296:
297: /**
298: * Writes a log entry containing information about the request which led to
299: * the halt of the execution
300: */
301: protected function logHackTrial() {
302: if ($this->_log === true && !empty($this->_logPath)) {
303: $content = date('Y-m-d H:i:s') . ' ';
304: $content .= $_SERVER['REMOTE_ADDR'] . str_repeat(' ', 17 - strlen($_SERVER['REMOTE_ADDR'])) . "\n";
305: $content .= ' Query String: ' . $_SERVER['QUERY_STRING'] . "\n";
306: $content .= ' Bad parameter: ' . $this->getBadParameter() . "\n";
307: $content .= ' POST array: ' . print_r($_POST, true) . "\n";
308: cFileHandler::write($this->_logPath, $content, true);
309: } elseif ($this->_mode == 'continue') {
310: echo "\n<br>VIOLATION: URL contains invalid or undefined paramaters! URL: '" . conHtmlentities($_SERVER['QUERY_STRING']) . "' <br>\n";
311: }
312: }
313:
314: /**
315: * Checks an array for validity.
316: *
317: * @param array $arr
318: * the array which has to be checked
319: * @param string $type
320: * GET or POST
321: * @return bool
322: * true if everything is fine.
323: */
324: protected function checkArray($arr, $type) {
325: $result = true;
326:
327: foreach ($arr as $key => $value) {
328: if (!$this->checkParameter(strtoupper($type), $key, $value)) {
329: $this->_failure = $key;
330: $result = false;
331: break;
332: }
333: }
334:
335: return $result;
336: }
337:
338: }
339: