summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--application/config/config.php2
-rw-r--r--application/controllers/file.php86
-rw-r--r--application/controllers/user.php8
-rw-r--r--application/core/MY_Controller.php64
-rwxr-xr-xapplication/errors/error_general.php10
-rw-r--r--application/helpers/filebin_helper.php20
-rw-r--r--application/models/mfile.php1
-rw-r--r--application/models/muser.php35
-rw-r--r--application/views/file/client.php4
-rw-r--r--application/views/file/deleted.php9
-rw-r--r--application/views/file/too_big.php3
-rw-r--r--application/views/file/upload_error.php6
-rw-r--r--application/views/file/upload_form.php2
-rw-r--r--application/views/file_plaintext/client.php4
-rw-r--r--application/views/file_plaintext/too_big.php2
-rw-r--r--application/views/file_plaintext/upload_error.php2
-rwxr-xr-xsystem/core/Common.php11
-rwxr-xr-xsystem/core/URI.php5
-rwxr-xr-xsystem/libraries/Security.php737
19 files changed, 190 insertions, 821 deletions
diff --git a/application/config/config.php b/application/config/config.php
index dda82de97..4aadac68d 100644
--- a/application/config/config.php
+++ b/application/config/config.php
@@ -293,7 +293,7 @@ $config['global_xss_filtering'] = FALSE;
| 'csrf_cookie_name' = The cookie name
| 'csrf_expire' = The number in seconds the token should expire.
*/
-$config['csrf_protection'] = FALSE;
+$config['csrf_protection'] = FALSE; // our controller enables this later
$config['csrf_token_name'] = 'csrf_test_name';
$config['csrf_cookie_name'] = 'csrf_cookie_name';
$config['csrf_expire'] = 7200;
diff --git a/application/controllers/file.php b/application/controllers/file.php
index ef2e87084..6e660b306 100644
--- a/application/controllers/file.php
+++ b/application/controllers/file.php
@@ -9,6 +9,12 @@
class File extends MY_Controller {
+ protected $json_enabled_functions = array(
+ "upload_history",
+ "do_upload",
+ "do_delete",
+ );
+
function __construct()
{
parent::__construct();
@@ -280,6 +286,10 @@ class File extends MY_Controller {
}
}
+ if (request_type() == "json") {
+ return send_json_reply($this->data["urls"]);
+ }
+
if (is_cli_client()) {
$redirect = false;
}
@@ -456,7 +466,7 @@ class File extends MY_Controller {
ORDER BY date $order
", array($user))->result_array();
- if ($this->input->get("json") !== false) {
+ if (request_type() == "json") {
return send_json_reply($query);
}
@@ -499,11 +509,11 @@ class File extends MY_Controller {
$ids = $this->input->post("ids");
$errors = array();
- $msgs = array();
+ $deleted = array();
$deleted_count = 0;
$total_count = 0;
- if (!$ids) {
+ if (!$ids || !is_array($ids)) {
show_error("No IDs specified");
}
@@ -511,20 +521,34 @@ class File extends MY_Controller {
$total_count++;
if (!$this->mfile->id_exists($id)) {
- $errors[] = "'$id' didn't exist anymore.";
+ $errors[] = array(
+ "id" => $id,
+ "reason" => "doesn't exist",
+ );
continue;
}
if ($this->mfile->delete_id($id)) {
- $msgs[] = "'$id' has been removed.";
+ $deleted[] = $id;
$deleted_count++;
} else {
- $errors[] = "'$id' couldn't be deleted.";
+ $errors[] = array(
+ "id" => $id,
+ "reason" => "unknown error",
+ );
}
}
+ if (request_type() == "json") {
+ return send_json_reply(array(
+ "errors" => $errors,
+ "deleted" => $deleted,
+ "total_count" => $total_count,
+ "deleted_count" => $deleted_count,
+ ));
+ }
+
$this->data["errors"] = $errors;
- $this->data["msgs"] = $msgs;
$this->data["deleted_count"] = $deleted_count;
$this->data["total_count"] = $total_count;
@@ -538,17 +562,14 @@ class File extends MY_Controller {
$this->muser->require_access("apikey");
if (!is_cli_client()) {
- echo "Not a listed cli client, please use the history to delete uploads.\n";
- return;
+ show_error("Not a listed cli client, please use the history to delete uploads.\n", 403);
}
$id = $this->uri->segment(3);
$this->data["id"] = $id;
if ($id && !$this->mfile->id_exists($id)) {
- $this->output->set_status_header(404);
- echo "Unknown ID '$id'.\n";
- return;
+ show_error("Unknown ID '$id'.", 404);
}
if ($this->mfile->delete_id($id)) {
@@ -562,6 +583,7 @@ class File extends MY_Controller {
function do_paste()
{
// desktop clients get a cookie to claim the ID later
+ // don't force them to log in just yet
if (is_cli_client()) {
$this->muser->require_access();
}
@@ -571,20 +593,11 @@ class File extends MY_Controller {
$filename = "stdin";
if (!$content) {
- $this->output->set_status_header(400);
- $this->data["msg"] = "Nothing was pasted, content is empty.";
- $this->load->view('header', $this->data);
- $this->load->view($this->var->view_dir.'/upload_error', $this->data);
- $this->load->view('footer');
- return;
+ show_error("Nothing was pasted, content is empty.", 400);
}
if ($filesize > $this->config->item('upload_max_size')) {
- $this->output->set_status_header(413);
- $this->load->view('header', $this->data);
- $this->load->view($this->var->view_dir.'/too_big');
- $this->load->view('footer');
- return;
+ show_error("Error while uploading: File too big", 413);
}
$limits = $this->muser->get_upload_id_limits();
@@ -605,6 +618,7 @@ class File extends MY_Controller {
function do_upload()
{
// desktop clients get a cookie to claim the ID later
+ // don't force them to log in just yet
if (is_cli_client()) {
$this->muser->require_access("apikey");
}
@@ -624,8 +638,6 @@ class File extends MY_Controller {
foreach ($files as $key => $file) {
// getNormalizedFILES() removes any file with error == 4
if ($file['error'] !== UPLOAD_ERR_OK) {
- $this->output->set_status_header(400);
-
// ERR_OK only for completeness, condition above ignores it
$errors = array(
UPLOAD_ERR_OK => "There is no error, the file uploaded with success",
@@ -638,27 +650,20 @@ class File extends MY_Controller {
UPLOAD_ERR_EXTENSION => "A PHP extension stopped the file upload",
);
- $this->data["msg"] = "Unknown error.";
+ $msg = "Unknown error.";
if (isset($errors[$file['error']])) {
- $this->data["msg"] = $errors[$file['error']];
+ $msg = $errors[$file['error']];
} else {
- $this->data["msg"] = "Unknown error code: ".$file['error'].". Please report a bug.";
+ $msg = "Unknown error code: ".$file['error'].". Please report a bug.";
}
- $this->load->view('header', $this->data);
- $this->load->view($this->var->view_dir.'/upload_error', $this->data);
- $this->load->view('footer');
- return;
+ show_error("Error while uploading: ".$msg, 400);
}
$filesize = filesize($file['tmp_name']);
if ($filesize > $this->config->item('upload_max_size')) {
- $this->output->set_status_header(413);
- $this->load->view('header', $this->data);
- $this->load->view($this->var->view_dir.'/too_big');
- $this->load->view('footer');
- return;
+ show_error("Error while uploading: File too big", 413);
}
}
@@ -699,9 +704,16 @@ class File extends MY_Controller {
$this->muser->require_access();
$last_upload = $this->session->userdata("last_upload");
+
+ if ($last_upload === false) {
+ show_error("Failed to get last upload data");
+ }
+
$ids = $last_upload["ids"];
$errors = array();
+ assert(is_array($ids));
+
foreach ($ids as $key => $id) {
$filedata = $this->mfile->get_filedata($id);
diff --git a/application/controllers/user.php b/application/controllers/user.php
index 498a626d7..56f571d6a 100644
--- a/application/controllers/user.php
+++ b/application/controllers/user.php
@@ -8,6 +8,10 @@
*/
class User extends MY_Controller {
+ protected $json_enabled_functions = array(
+ "apikeys",
+ );
+
function __construct()
{
@@ -127,6 +131,10 @@ class User extends MY_Controller {
WHERE `user` = ? order by created desc
", array($userid))->result_array();
+ if (request_type() == "json") {
+ return send_json_reply($query);
+ }
+
$this->data["query"] = $query;
$this->load->view('header', $this->data);
diff --git a/application/core/MY_Controller.php b/application/core/MY_Controller.php
index 278768ad2..312b0f763 100644
--- a/application/core/MY_Controller.php
+++ b/application/core/MY_Controller.php
@@ -11,7 +11,7 @@ class MY_Controller extends CI_Controller {
public $data = array();
public $var;
- private $json_enabled_functions = array(
+ protected $json_enabled_functions = array(
);
function __construct()
@@ -19,6 +19,7 @@ class MY_Controller extends CI_Controller {
parent::__construct();
$this->var = new StdClass();
+ $csrf_protection = true;
$this->load->library('migration');
if ( ! $this->migration->current()) {
@@ -31,6 +32,67 @@ class MY_Controller extends CI_Controller {
mb_internal_encoding('UTF-8');
$this->load->helper(array('form', 'filebin'));
+ // TODO: proper accept header handling or is this enough?
+ if (isset($_SERVER["HTTP_ACCEPT"])) {
+ if ($_SERVER["HTTP_ACCEPT"] == "application/json") {
+ request_type("json");
+ }
+ }
+
+ // Allow for easier testing in browser
+ if ($this->input->get("json") !== false) {
+ request_type("json");
+ }
+
+ if (request_type() == "json" && ! in_array($this->uri->rsegment(2), $this->json_enabled_functions)) {
+ show_error("Function not JSON enabled");
+ }
+
+ if ($this->input->post("apikey") !== false) {
+ /* This relies on the authentication code always verifying the supplied
+ * apikey. If the key is not verified/logged in an attacker could simply
+ * add an empty "apikey" field to the CSRF form to circumvent the
+ * protection. If we always log in if a key is supplied we can ensure
+ * that an attacker (and the victim since they get a cookie) can only
+ * access the attacker's account.
+ */
+ $csrf_protection = false;
+ }
+
+ $uri_start = $this->uri->rsegment(1)."/".$this->uri->rsegment(2);
+ $csrf_whitelisted_handlers = array(
+ "always" => array(
+ /* Whitelist the upload pages because they don't cause harm and a user
+ * might keep the upload page open for more than csrf_expire seconds
+ * and we don't want to annoy them when they upload a big file and the
+ * CSRF check fails.
+ */
+ "file/do_upload",
+ "file/do_paste",
+ ),
+ "cli_client" => array(
+ "file/do_delete",
+ "file/delete",
+ "file/upload_history",
+ ),
+ );
+ if (in_array($uri_start, $csrf_whitelisted_handlers["always"])) {
+ $csrf_protection = false;
+ }
+
+ // TODO: replace cli client with request_type("plain")?
+ if (is_cli_client() && in_array($uri_start, $csrf_whitelisted_handlers["cli_client"])) {
+ $csrf_protection = false;
+ }
+
+ if ($csrf_protection && !$this->input->is_cli_request()) {
+ // 2 functions for accessing config options, really?
+ $this->config->set_item('csrf_protection', true);
+ config_item("csrf_protection", true);
+ $this->security->__construct();
+ $this->security->csrf_verify();
+ }
+
$this->data['title'] = "FileBin";
}
}
diff --git a/application/errors/error_general.php b/application/errors/error_general.php
index da3999cbd..fc3d3f607 100755
--- a/application/errors/error_general.php
+++ b/application/errors/error_general.php
@@ -9,6 +9,16 @@ if (class_exists("CI_Controller") && !isset($GLOBALS["is_error_page"])) {
$CI->load->helper("filebin");
$CI->load->helper("url");
+ if (request_type() == "json") {
+ $array = array(
+ "status" => "error",
+ "message" => strip_tags($message),
+ );
+ header('Content-type: application/json');
+ echo json_encode($array);
+ exit();
+ }
+
if (is_cli_client()) {
$message = strip_tags($message);
echo "$heading: $message\n";
diff --git a/application/helpers/filebin_helper.php b/application/helpers/filebin_helper.php
index 4af106a14..9b124506f 100644
--- a/application/helpers/filebin_helper.php
+++ b/application/helpers/filebin_helper.php
@@ -326,14 +326,30 @@ function auth_driver_function_implemented($function)
function user_logged_in()
{
$CI =& get_instance();
+ $CI->load->model("muser");
return $CI->muser->logged_in();
}
-function send_json_reply($array)
+function send_json_reply($array, $status = "success")
{
+ $reply = array();
+ $reply["status"] = $status;
+ $reply["data"] = $array;
+
$CI =& get_instance();
$CI->output->set_content_type('application/json');
- $CI->output->set_output(json_encode($array));
+ $CI->output->set_output(json_encode($reply));
+}
+
+function request_type($set_type = null)
+{
+ static $type = null;
+
+ if ($set_type !== null) {
+ $type = $set_type;
+ }
+
+ return $type;
}
# vim: set noet:
diff --git a/application/models/mfile.php b/application/models/mfile.php
index fe762d954..e862f1930 100644
--- a/application/models/mfile.php
+++ b/application/models/mfile.php
@@ -312,7 +312,6 @@ class Mfile extends CI_Model {
function delete_id($id)
{
- $this->muser->require_access("apikey");
$filedata = $this->get_filedata($id);
$userid = $this->muser->get_userid();
diff --git a/application/models/muser.php b/application/models/muser.php
index 843b7cad6..b3c16bf78 100644
--- a/application/models/muser.php
+++ b/application/models/muser.php
@@ -67,14 +67,6 @@ class Muser extends CI_Model {
{
$username = $this->input->post("username");
$password = $this->input->post("password");
- $apikey = $this->input->post("apikey");
-
- if ($apikey !== false) {
- if ($this->apilogin(trim($apikey))) {
- return true;
- }
- show_error("API key login failed", 401);
- }
// prefer post parameters if either (username or password) is set
if ($username === false && $password === false) {
@@ -84,19 +76,24 @@ class Muser extends CI_Model {
}
}
- if ($apikey === false && $username !== false && $password !== false) {
+ if ($username !== false && $password !== false) {
if ($this->login($username, $password)) {
return true;
} else {
show_error("Login failed", 401);
}
}
+
+ return null;
}
function apilogin($apikey)
{
$this->require_session();
+ // get rid of spaces and newlines
+ $apikey = trim($apikey);
+
$query = $this->db->query("
SELECT a.user userid
FROM apikeys a
@@ -111,7 +108,7 @@ class Muser extends CI_Model {
return true;
}
- return false;
+ show_error("API key login failed", 401);
}
function logout()
@@ -168,22 +165,26 @@ class Muser extends CI_Model {
return true;
}
- show_error("Access denied", 403);
+ show_error("Access denied: Access level too low", 403);
}
function require_access($wanted_level = "full")
{
+ if ($this->input->post("apikey") !== false) {
+ $this->apilogin($this->input->post("apikey"));
+ }
+
+ if (is_cli_client()) {
+ $this->login_cli_client();
+ }
+
if ($this->logged_in()) {
return $this->check_access_level($wanted_level);
}
+ // if a CLI client reaches this point it failed to log in
if (is_cli_client()) {
- if ($this->login_cli_client()) {
- return $this->check_access_level($wanted_level);
- }
-
- echo "FileBin requires you to have an account, please go to the homepage for more information.\n";
- exit();
+ show_error("Not authenticated. FileBin requires you to have an account, please go to the homepage for more information.\n", 401);
}
// desktop clients get redirected to the login form
diff --git a/application/views/file/client.php b/application/views/file/client.php
index 5e141f141..29e254a80 100644
--- a/application/views/file/client.php
+++ b/application/views/file/client.php
@@ -42,7 +42,7 @@ machine <?php echo $domain; ?> login my_username password my_secret_password
<h1>Shell</h1>
<pre>
-curl -n -F "file=@/home/user/foo" <?php echo site_url(); ?> (binary safe)
-cat file | curl -n -F "file=@-;filename=stdin" <?php echo site_url(); ?> (binary safe)
+curl -n -F "file=@/home/user/foo" <?php echo site_url("file/do_upload"); ?> (binary safe)
+cat file | curl -n -F "file=@-;filename=stdin" <?php echo site_url("file/do_upload"); ?> (binary safe)
</pre>
diff --git a/application/views/file/deleted.php b/application/views/file/deleted.php
index ef02398d9..8a5818f2d 100644
--- a/application/views/file/deleted.php
+++ b/application/views/file/deleted.php
@@ -1,12 +1,9 @@
<div class="center">
<?php if (!empty($errors)) {
echo "<p>";
- echo implode("<br />\n", $errors);
- echo "</p>";
- } ?>
- <?php if (!empty($msgs)) {
- echo "<p>";
- echo implode("<br />\n", $msgs);
+ foreach ($errors as $error) {
+ echo "${error["id"]}: ${error["reason"]}<br>\n";
+ }
echo "</p>";
} ?>
diff --git a/application/views/file/too_big.php b/application/views/file/too_big.php
deleted file mode 100644
index 5b970a4c8..000000000
--- a/application/views/file/too_big.php
+++ /dev/null
@@ -1,3 +0,0 @@
-<div class="center">
- <p>Sorry, the file you uploaded is too big.</p>
-</div>
diff --git a/application/views/file/upload_error.php b/application/views/file/upload_error.php
deleted file mode 100644
index 83968eec2..000000000
--- a/application/views/file/upload_error.php
+++ /dev/null
@@ -1,6 +0,0 @@
-<div class="center">
- <p>
- An error occurred while uploading.<br />
- <?php echo $msg; ?>
- </p>
-</div>
diff --git a/application/views/file/upload_form.php b/application/views/file/upload_form.php
index 841ac3746..21a2cc4e6 100644
--- a/application/views/file/upload_form.php
+++ b/application/views/file/upload_form.php
@@ -46,7 +46,7 @@
<?php } else { ?>
<?php echo form_open('user/login'); ?>
- <input type="text" name="username" placeholder="Username" />
+ <input type="text" name="username" placeholder="Username" autofocus />
<input type="password" name="password" placeholder="Password" />
<input type="submit" class="btn btn-primary" value="Login" name="process" style="margin-bottom: 9px" />
<?php if(auth_driver_function_implemented("can_reset_password")) { ?>
diff --git a/application/views/file_plaintext/client.php b/application/views/file_plaintext/client.php
index b37fd81bd..0ab556df2 100644
--- a/application/views/file_plaintext/client.php
+++ b/application/views/file_plaintext/client.php
@@ -1,6 +1,6 @@
Shell (binary safe):
- curl -n -F "file=@/home/user/foo" <?php echo site_url()."\n"; ?>
- cat file | curl -n -F "file=@-;filename=stdin" <?php echo site_url()."\n"; ?>
+ curl -n -F "file=@/home/user/foo" <?php echo site_url("file/do_upload")."\n"; ?>
+ cat file | curl -n -F "file=@-;filename=stdin" <?php echo site_url("file/do_upload")."\n"; ?>
Client:
Development (git): http://git.server-speed.net/users/flo/fb
diff --git a/application/views/file_plaintext/too_big.php b/application/views/file_plaintext/too_big.php
deleted file mode 100644
index d27a0295c..000000000
--- a/application/views/file_plaintext/too_big.php
+++ /dev/null
@@ -1,2 +0,0 @@
-Sorry, the file you uploaded is too big.
-
diff --git a/application/views/file_plaintext/upload_error.php b/application/views/file_plaintext/upload_error.php
deleted file mode 100644
index c86c56911..000000000
--- a/application/views/file_plaintext/upload_error.php
+++ /dev/null
@@ -1,2 +0,0 @@
-An error occurred while uploading. <?php echo $msg; ?>
-
diff --git a/system/core/Common.php b/system/core/Common.php
index 07534c51f..f8d80b957 100755
--- a/system/core/Common.php
+++ b/system/core/Common.php
@@ -1,5 +1,10 @@
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
+ * MODIFIED
+ * config_item(): option to override returned values
+ */
+
+/**
* CodeIgniter
*
* An open source application development framework for PHP 5.1.6 or newer
@@ -268,7 +273,7 @@ if ( ! function_exists('get_config'))
*/
if ( ! function_exists('config_item'))
{
- function config_item($item)
+ function config_item($item, $value = null)
{
static $_config_item = array();
@@ -283,6 +288,10 @@ if ( ! function_exists('config_item'))
$_config_item[$item] = $config[$item];
}
+ if ($value !== null) {
+ $_config_item[$item] = $value;
+ }
+
return $_config_item[$item];
}
}
diff --git a/system/core/URI.php b/system/core/URI.php
index d78c8ee49..d3c25070e 100755
--- a/system/core/URI.php
+++ b/system/core/URI.php
@@ -1,5 +1,10 @@
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
+ * MODIFIED:
+ * _detect_uri(): ltrim instead of trim at the end to preserve tailing slashes
+ */
+
+/**
* CodeIgniter
*
* An open source application development framework for PHP 5.1.6 or newer
diff --git a/system/libraries/Security.php b/system/libraries/Security.php
deleted file mode 100755
index ba64c7326..000000000
--- a/system/libraries/Security.php
+++ /dev/null
@@ -1,737 +0,0 @@
-<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
-/**
- * CodeIgniter
- *
- * An open source application development framework for PHP 5.1.6 or newer
- *
- * @package CodeIgniter
- * @author ExpressionEngine Dev Team
- * @copyright Copyright (c) 2008 - 2011, EllisLab, Inc.
- * @license http://codeigniter.com/user_guide/license.html
- * @link http://codeigniter.com
- * @since Version 1.0
- * @filesource
- */
-
-// ------------------------------------------------------------------------
-
-/**
- * Security Class
- *
- * @package CodeIgniter
- * @subpackage Libraries
- * @category Security
- * @author ExpressionEngine Dev Team
- * @link http://codeigniter.com/user_guide/libraries/sessions.html
- */
-class CI_Security {
-
- public $xss_hash = '';
- public $csrf_hash = '';
- public $csrf_expire = 7200; // Two hours (in seconds)
- public $csrf_token_name = 'ci_csrf_token';
- public $csrf_cookie_name = 'ci_csrf_token';
-
- /* never allowed, string replacement */
- public $never_allowed_str = array(
- 'document.cookie' => '[removed]',
- 'document.write' => '[removed]',
- '.parentNode' => '[removed]',
- '.innerHTML' => '[removed]',
- 'window.location' => '[removed]',
- '-moz-binding' => '[removed]',
- '<!--' => '&lt;!--',
- '-->' => '--&gt;',
- '<![CDATA[' => '&lt;![CDATA['
- );
- /* never allowed, regex replacement */
- public $never_allowed_regex = array(
- "javascript\s*:" => '[removed]',
- "expression\s*(\(|&\#40;)" => '[removed]', // CSS and IE
- "vbscript\s*:" => '[removed]', // IE, surprise!
- "Redirect\s+302" => '[removed]'
- );
-
- public function __construct()
- {
- $this->csrf_token_name = (config_item('csrf_token_name')) ? config_item('csrf_token_name') : 'csrf_token_name';
- $this->csrf_cookie_name = (config_item('csrf_cookie_name')) ? config_item('csrf_cookie_name') : 'csrf_cookie_name';
- $this->csrf_expire = (config_item('csrf_expire')) ? config_item('csrf_expire') : 7200;
-
- // Append application specific cookie prefix to token name
- $this->csrf_cookie_name = (config_item('cookie_prefix')) ? config_item('cookie_prefix').$this->csrf_token_name : $this->csrf_token_name;
-
- // Set the CSRF hash
- $this->_csrf_set_hash();
-
- log_message('debug', "Security Class Initialized");
- }
-
- // --------------------------------------------------------------------
-
- /**
- * Verify Cross Site Request Forgery Protection
- *
- * @access public
- * @return null
- */
- public function csrf_verify()
- {
- // If no POST data exists we will set the CSRF cookie
- if (count($_POST) == 0)
- {
- return $this->csrf_set_cookie();
- }
-
- // Do the tokens exist in both the _POST and _COOKIE arrays?
- if ( ! isset($_POST[$this->csrf_token_name]) OR ! isset($_COOKIE[$this->csrf_cookie_name]))
- {
- $this->csrf_show_error();
- }
-
- // Do the tokens match?
- if ($_POST[$this->csrf_token_name] != $_COOKIE[$this->csrf_cookie_name])
- {
- $this->csrf_show_error();
- }
-
- // We kill this since we're done and we don't want to polute the _POST array
- unset($_POST[$this->csrf_token_name]);
-
- // Nothing should last forever
- unset($_COOKIE[$this->csrf_cookie_name]);
- $this->_csrf_set_hash();
- $this->csrf_set_cookie();
-
- log_message('debug', "CSRF token verified ");
- }
-
- // --------------------------------------------------------------------
-
- /**
- * Set Cross Site Request Forgery Protection Cookie
- *
- * @access public
- * @return null
- */
- public function csrf_set_cookie()
- {
- $expire = time() + $this->csrf_expire;
-
- setcookie($this->csrf_cookie_name, $this->csrf_hash, $expire, config_item('cookie_path'), config_item('cookie_domain'), 0);
-
- log_message('debug', "CRSF cookie Set");
- }
-
- // --------------------------------------------------------------------
-
- /**
- * Set Cross Site Request Forgery Protection Cookie
- *
- * @access private
- * @return null
- */
- private function _csrf_set_hash()
- {
- if ($this->csrf_hash == '')
- {
- // If the cookie exists we will use it's value. We don't necessarily want to regenerate it with
- // each page load since a page could contain embedded sub-pages causing this feature to fail
- if (isset($_COOKIE[$this->csrf_cookie_name]) AND $_COOKIE[$this->csrf_cookie_name] != '')
- {
- $this->csrf_hash = $_COOKIE[$this->csrf_cookie_name];
- }
- else
- {
- $this->csrf_hash = md5(uniqid(rand(), TRUE));
- }
- }
-
- return $this->csrf_hash;
- }
-
- // --------------------------------------------------------------------
-
- /**
- * Show CSRF Error
- *
- * @access public
- * @return null
- */
- public function csrf_show_error()
- {
- show_error('The action you have requested is not allowed.');
- }
-
- // --------------------------------------------------------------------
-
- /**
- * XSS Clean
- *
- * Sanitizes data so that Cross Site Scripting Hacks can be
- * prevented. This function does a fair amount of work but
- * it is extremely thorough, designed to prevent even the
- * most obscure XSS attempts. Nothing is ever 100% foolproof,
- * of course, but I haven't been able to get anything passed
- * the filter.
- *
- * Note: This function should only be used to deal with data
- * upon submission. It's not something that should
- * be used for general runtime processing.
- *
- * This function was based in part on some code and ideas I
- * got from Bitflux: http://channel.bitflux.ch/wiki/XSS_Prevention
- *
- * To help develop this script I used this great list of
- * vulnerabilities along with a few other hacks I've
- * harvested from examining vulnerabilities in other programs:
- * http://ha.ckers.org/xss.html
- *
- * @access public
- * @param mixed string or array
- * @return string
- */
- public function xss_clean($str, $is_image = FALSE)
- {
- /*
- * Is the string an array?
- *
- */
- if (is_array($str))
- {
- while (list($key) = each($str))
- {
- $str[$key] = $this->xss_clean($str[$key]);
- }
-
- return $str;
- }
-
- /*
- * Remove Invisible Characters
- */
- $str = remove_invisible_characters($str);
-
- /*
- * Protect GET variables in URLs
- */
-
- // 901119URL5918AMP18930PROTECT8198
-
- $str = preg_replace('|\&([a-z\_0-9\-]+)\=([a-z\_0-9\-]+)|i', $this->xss_hash()."\\1=\\2", $str);
-
- /*
- * Validate standard character entities
- *
- * Add a semicolon if missing. We do this to enable
- * the conversion of entities to ASCII later.
- *
- */
- $str = preg_replace('#(&\#?[0-9a-z]{2,})([\x00-\x20])*;?#i', "\\1;\\2", $str);
-
- /*
- * Validate UTF16 two byte encoding (x00)
- *
- * Just as above, adds a semicolon if missing.
- *
- */
- $str = preg_replace('#(&\#x?)([0-9A-F]+);?#i',"\\1\\2;",$str);
-
- /*
- * Un-Protect GET variables in URLs
- */
- $str = str_replace($this->xss_hash(), '&', $str);
-
- /*
- * URL Decode
- *
- * Just in case stuff like this is submitted:
- *
- * <a href="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">Google</a>
- *
- * Note: Use rawurldecode() so it does not remove plus signs
- *
- */
- $str = rawurldecode($str);
-
- /*
- * Convert character entities to ASCII
- *
- * This permits our tests below to work reliably.
- * We only convert entities that are within tags since
- * these are the ones that will pose security problems.
- *
- */
-
- $str = preg_replace_callback("/[a-z]+=([\'\"]).*?\\1/si", array($this, '_convert_attribute'), $str);
-
- $str = preg_replace_callback("/<\w+.*?(?=>|<|$)/si", array($this, '_decode_entity'), $str);
-
- /*
- * Remove Invisible Characters Again!
- */
- $str = remove_invisible_characters($str);
-
- /*
- * Convert all tabs to spaces
- *
- * This prevents strings like this: ja vascript
- * NOTE: we deal with spaces between characters later.
- * NOTE: preg_replace was found to be amazingly slow here on large blocks of data,
- * so we use str_replace.
- *
- */
-
- if (strpos($str, "\t") !== FALSE)
- {
- $str = str_replace("\t", ' ', $str);
- }
-
- /*
- * Capture converted string for later comparison
- */
- $converted_string = $str;
-
- /*
- * Not Allowed Under Any Conditions
- */
-
- foreach ($this->never_allowed_str as $key => $val)
- {
- $str = str_replace($key, $val, $str);
- }
-
- foreach ($this->never_allowed_regex as $key => $val)
- {
- $str = preg_replace("#".$key."#i", $val, $str);
- }
-
- /*
- * Makes PHP tags safe
- *
- * Note: XML tags are inadvertently replaced too:
- *
- * <?xml
- *
- * But it doesn't seem to pose a problem.
- *
- */
- if ($is_image === TRUE)
- {
- // Images have a tendency to have the PHP short opening and closing tags every so often
- // so we skip those and only do the long opening tags.
- $str = preg_replace('/<\?(php)/i', "&lt;?\\1", $str);
- }
- else
- {
- $str = str_replace(array('<?', '?'.'>'), array('&lt;?', '?&gt;'), $str);
- }
-
- /*
- * Compact any exploded words
- *
- * This corrects words like: j a v a s c r i p t
- * These words are compacted back to their correct state.
- *
- */
- $words = array('javascript', 'expression', 'vbscript', 'script', 'applet', 'alert', 'document', 'write', 'cookie', 'window');
- foreach ($words as $word)
- {
- $temp = '';
-
- for ($i = 0, $wordlen = strlen($word); $i < $wordlen; $i++)
- {
- $temp .= substr($word, $i, 1)."\s*";
- }
-
- // We only want to do this when it is followed by a non-word character
- // That way valid stuff like "dealer to" does not become "dealerto"
- $str = preg_replace_callback('#('.substr($temp, 0, -3).')(\W)#is', array($this, '_compact_exploded_words'), $str);
- }
-
- /*
- * Remove disallowed Javascript in links or img tags
- * We used to do some version comparisons and use of stripos for PHP5, but it is dog slow compared
- * to these simplified non-capturing preg_match(), especially if the pattern exists in the string
- */
- do
- {
- $original = $str;
-
- if (preg_match("/<a/i", $str))
- {
- $str = preg_replace_callback("#<a\s+([^>]*?)(>|$)#si", array($this, '_js_link_removal'), $str);
- }
-
- if (preg_match("/<img/i", $str))
- {
- $str = preg_replace_callback("#<img\s+([^>]*?)(\s?/?>|$)#si", array($this, '_js_img_removal'), $str);
- }
-
- if (preg_match("/script/i", $str) OR preg_match("/xss/i", $str))
- {
- $str = preg_replace("#<(/*)(script|xss)(.*?)\>#si", '[removed]', $str);
- }
- }
- while($original != $str);
-
- unset($original);
-
- /*
- * Remove JavaScript Event Handlers
- *
- * Note: This code is a little blunt. It removes
- * the event handler and anything up to the closing >,
- * but it's unlikely to be a problem.
- *
- */
- $event_handlers = array('[^a-z_\-]on\w*','xmlns');
-
- if ($is_image === TRUE)
- {
- /*
- * Adobe Photoshop puts XML metadata into JFIF images, including namespacing,
- * so we have to allow this for images. -Paul
- */
- unset($event_handlers[array_search('xmlns', $event_handlers)]);
- }
-
- $str = preg_replace("#<([^><]+?)(".implode('|', $event_handlers).")(\s*=\s*[^><]*)([><]*)#i", "<\\1\\4", $str);
-
- /*
- * Sanitize naughty HTML elements
- *
- * If a tag containing any of the words in the list
- * below is found, the tag gets converted to entities.
- *
- * So this: <blink>
- * Becomes: &lt;blink&gt;
- *
- */
- $naughty = 'alert|applet|audio|basefont|base|behavior|bgsound|blink|body|embed|expression|form|frameset|frame|head|html|ilayer|iframe|input|isindex|layer|link|meta|object|plaintext|style|script|textarea|title|video|xml|xss';
- $str = preg_replace_callback('#<(/*\s*)('.$naughty.')([^><]*)([><]*)#is', array($this, '_sanitize_naughty_html'), $str);
-
- /*
- * Sanitize naughty scripting elements
- *
- * Similar to above, only instead of looking for
- * tags it looks for PHP and JavaScript commands
- * that are disallowed. Rather than removing the
- * code, it simply converts the parenthesis to entities
- * rendering the code un-executable.
- *
- * For example: eval('some code')
- * Becomes: eval&#40;'some code'&#41;
- *
- */
- $str = preg_replace('#(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', "\\1\\2&#40;\\3&#41;", $str);
-
- /*
- * Final clean up
- *
- * This adds a bit of extra precaution in case
- * something got through the above filters
- *
- */
- foreach ($this->never_allowed_str as $key => $val)
- {
- $str = str_replace($key, $val, $str);
- }
-
- foreach ($this->never_allowed_regex as $key => $val)
- {
- $str = preg_replace("#".$key."#i", $val, $str);
- }
-
- /*
- * Images are Handled in a Special Way
- * - Essentially, we want to know that after all of the character conversion is done whether
- * any unwanted, likely XSS, code was found. If not, we return TRUE, as the image is clean.
- * However, if the string post-conversion does not matched the string post-removal of XSS,
- * then it fails, as there was unwanted XSS code found and removed/changed during processing.
- */
-
- if ($is_image === TRUE)
- {
- if ($str == $converted_string)
- {
- return TRUE;
- }
- else
- {
- return FALSE;
- }
- }
-
- log_message('debug', "XSS Filtering completed");
- return $str;
- }
-
- // --------------------------------------------------------------------
-
- /**
- * Random Hash for protecting URLs
- *
- * @access public
- * @return string
- */
- public function xss_hash()
- {
- if ($this->xss_hash == '')
- {
- if (phpversion() >= 4.2)
- mt_srand();
- else
- mt_srand(hexdec(substr(md5(microtime()), -8)) & 0x7fffffff);
-
- $this->xss_hash = md5(time() + mt_rand(0, 1999999999));
- }
-
- return $this->xss_hash;
- }
-
- // --------------------------------------------------------------------
-
- /**
- * Compact Exploded Words
- *
- * Callback function for xss_clean() to remove whitespace from
- * things like j a v a s c r i p t
- *
- * @access private
- * @param type
- * @return type
- */
- private function _compact_exploded_words($matches)
- {
- return preg_replace('/\s+/s', '', $matches[1]).$matches[2];
- }
-
- // --------------------------------------------------------------------
-
- /**
- * Sanitize Naughty HTML
- *
- * Callback function for xss_clean() to remove naughty HTML elements
- *
- * @access private
- * @param array
- * @return string
- */
- private function _sanitize_naughty_html($matches)
- {
- // encode opening brace
- $str = '&lt;'.$matches[1].$matches[2].$matches[3];
-
- // encode captured opening or closing brace to prevent recursive vectors
- $str .= str_replace(array('>', '<'), array('&gt;', '&lt;'), $matches[4]);
-
- return $str;
- }
-
- // --------------------------------------------------------------------
-
- /**
- * JS Link Removal
- *
- * Callback function for xss_clean() to sanitize links
- * This limits the PCRE backtracks, making it more performance friendly
- * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in
- * PHP 5.2+ on link-heavy strings
- *
- * @access private
- * @param array
- * @return string
- */
- private function _js_link_removal($match)
- {
- $attributes = $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]));
- return str_replace($match[1], preg_replace("#href=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si", "", $attributes), $match[0]);
- }
-
- /**
- * JS Image Removal
- *
- * Callback function for xss_clean() to sanitize image tags
- * This limits the PCRE backtracks, making it more performance friendly
- * and prevents PREG_BACKTRACK_LIMIT_ERROR from being triggered in
- * PHP 5.2+ on image tag heavy strings
- *
- * @access private
- * @param array
- * @return string
- */
- private function _js_img_removal($match)
- {
- $attributes = $this->_filter_attributes(str_replace(array('<', '>'), '', $match[1]));
- return str_replace($match[1], preg_replace("#src=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si", "", $attributes), $match[0]);
- }
-
- // --------------------------------------------------------------------
-
- /**
- * Attribute Conversion
- *
- * Used as a callback for XSS Clean
- *
- * @access private
- * @param array
- * @return string
- */
- private function _convert_attribute($match)
- {
- return str_replace(array('>', '<', '\\'), array('&gt;', '&lt;', '\\\\'), $match[0]);
- }
-
- // --------------------------------------------------------------------
-
- /**
- * Filter Attributes
- *
- * Filters tag attributes for consistency and safety
- *
- * @access private
- * @param string
- * @return string
- */
- private function _filter_attributes($str)
- {
- $out = '';
-
- if (preg_match_all('#\s*[a-z\-]+\s*=\s*(\042|\047)([^\\1]*?)\\1#is', $str, $matches))
- {
- foreach ($matches[0] as $match)
- {
- $out .= preg_replace("#/\*.*?\*/#s", '', $match);
- }
- }
-
- return $out;
- }
-
- // --------------------------------------------------------------------
-
- /**
- * HTML Entity Decode Callback
- *
- * Used as a callback for XSS Clean
- *
- * @access private
- * @param array
- * @return string
- */
- private function _decode_entity($match)
- {
- return $this->entity_decode($match[0], strtoupper(config_item('charset')));
- }
-
- // --------------------------------------------------------------------
-
- /**
- * HTML Entities Decode
- *
- * This function is a replacement for html_entity_decode()
- *
- * In some versions of PHP the native function does not work
- * when UTF-8 is the specified character set, so this gives us
- * a work-around. More info here:
- * http://bugs.php.net/bug.php?id=25670
- *
- * NOTE: html_entity_decode() has a bug in some PHP versions when UTF-8 is the
- * character set, and the PHP developers said they were not back porting the
- * fix to versions other than PHP 5.x.
- *
- * @access public
- * @param string
- * @param string
- * @return string
- */
- public function entity_decode($str, $charset='UTF-8')
- {
- if (stristr($str, '&') === FALSE) return $str;
-
- // The reason we are not using html_entity_decode() by itself is because
- // while it is not technically correct to leave out the semicolon
- // at the end of an entity most browsers will still interpret the entity
- // correctly. html_entity_decode() does not convert entities without
- // semicolons, so we are left with our own little solution here. Bummer.
-
- if (function_exists('html_entity_decode') && (strtolower($charset) != 'utf-8' OR is_php('5.0.0')))
- {
- $str = html_entity_decode($str, ENT_COMPAT, $charset);
- $str = preg_replace('~&#x(0*[0-9a-f]{2,5})~ei', 'chr(hexdec("\\1"))', $str);
- return preg_replace('~&#([0-9]{2,4})~e', 'chr(\\1)', $str);
- }
-
- // Numeric Entities
- $str = preg_replace('~&#x(0*[0-9a-f]{2,5});{0,1}~ei', 'chr(hexdec("\\1"))', $str);
- $str = preg_replace('~&#([0-9]{2,4});{0,1}~e', 'chr(\\1)', $str);
-
- // Literal Entities - Slightly slow so we do another check
- if (stristr($str, '&') === FALSE)
- {
- $str = strtr($str, array_flip(get_html_translation_table(HTML_ENTITIES)));
- }
-
- return $str;
- }
-
- // --------------------------------------------------------------------
-
- /**
- * Filename Security
- *
- * @access public
- * @param string
- * @return string
- */
- public function sanitize_filename($str, $relative_path = FALSE)
- {
- $bad = array(
- "../",
- "<!--",
- "-->",
- "<",
- ">",
- "'",
- '"',
- '&',
- '$',
- '#',
- '{',
- '}',
- '[',
- ']',
- '=',
- ';',
- '?',
- "%20",
- "%22",
- "%3c", // <
- "%253c", // <
- "%3e", // >
- "%0e", // >
- "%28", // (
- "%29", // )
- "%2528", // (
- "%26", // &
- "%24", // $
- "%3f", // ?
- "%3b", // ;
- "%3d" // =
- );
-
- if ( ! $relative_path)
- {
- $bad[] = './';
- $bad[] = '/';
- }
-
- return stripslashes(str_replace($bad, '', $str));
- }
-
-}
-// END Security Class
-
-/* End of file Security.php */
-/* Location: ./system/libraries/Security.php */ \ No newline at end of file