Codeigniter REST. How to properly protect against csrf - php

I am using the REST server by Phil Sturgeon and I was wondering how I can do CSRF Protection properly? Currently I am using this method:
if(stripos($_SERVER["REQUEST_URI"],'API')!=""){
$config['csrf_protection'] = FALSE;
}else{
$config['csrf_protection'] = TRUE;}
However, I have read that this is not the proper way to protect against csrf attacks. I have tried to extend MY_Security but I am unsure of what I need to do after I extend the class. This is the extended class
defined('BASEPATH') OR exit('No direct script access allowed');
class MY_Security extends CI_Security {
public function csrf_verify()
{
// If it's not a POST request we will set the CSRF cookie
if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST')
{
return $this->csrf_set_cookie();
}
/**
* mine implementation for application/json
*/
$reqHeaders = getallheaders();
$content_type = $reqHeaders["Content-Type"];
#it's a json request?
if(preg_match("/(application\/json)/i",$content_type))
{
#the check the cookie from request
$reqCookies = explode("; ",$reqHeaders["Cookie"]);
foreach($reqCookies as $c)
{
if(preg_match("/(".$this->_csrf_cookie_name."\=)/", $c))
{
$c = explode("=",$c);
if($_COOKIE[$this->_csrf_cookie_name] == $c[1])
{
return $this;
}
}
}
}
//< end
// Check if URI has been whitelisted from CSRF checks
if ($exclude_uris = config_item('csrf_exclude_uris'))
{
$uri = load_class('URI', 'core');
foreach ($exclude_uris as $excluded)
{
if (preg_match('#^'.$excluded.'$#i'.(UTF8_ENABLED ? 'u' : ''), $uri->uri_string()))
{
return $this;
}
}
}
// Do the tokens exist in both the _POST and _COOKIE arrays?
if ( ! isset($_POST[$this->_csrf_token_name], $_COOKIE[$this->_csrf_cookie_name])
OR $_POST[$this->_csrf_token_name] !== $_COOKIE[$this->_csrf_cookie_name]) // Do the tokens match?
{
$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]);
// Regenerate on every submission?
if (config_item('csrf_regenerate'))
{
// Nothing should last forever
unset($_COOKIE[$this->_csrf_cookie_name]);
$this->_csrf_hash = NULL;
}
$this->_csrf_set_hash();
$this->csrf_set_cookie();
log_message('info', 'CSRF token verified');
return $this;
}
}
Do I need to set headers for each json POST or get request? This is an example of what I send to my REST backend
public function user_create_activity($activity){
$this->curl->create(UserCreateActivity);
$this->curl->http_login(REST_KEY_ID,REST_KEY_PASSWORD);
$this->curl->post(array(
'activity' => $activity,
));
return json_decode($this->curl->execute(),true);
}
I am quite unsure if I am on the right path. Hopefully someone can guide me.
Thank you.

Related

Symfony redirect in helper service does not work

Introduction
For my personal project i am using
Symfony v4.2 with
XAMPP and
Widows 10 Pro
In order to not to display route parameters in URL i save them in the table.
Then in the controller i check if there is variable (that keeps UUID that corresponds to route parameters) in the session.
If i get no variable in session it should redirect to section start page, where UUID and initial data in the table are setup.
Redirect logic is extracted to helper service. In order to redirect to work there are copied functions redirectToRoute and redirect
I test this functionalit by deleting php session variables in temp folder and PHPSESSID cookie in the browser.
Problem
The prolem is - it does not redirect to secton start page.
I can see that correct if branch is selected, but then it "just stops" and does not execute redirect.
Code
public function checkWhereaboutsExist()
{
$em = $this->entityManager;
$repo_whereabouts = $em->getRepository(Whereabouts::class);
$whereabouts = $this->session->get('whereabouts');
if (($whereabouts === null) || ($whereabouts === ''))
{
$data = 'whereabouts === '.$whereabouts;
dump($data);
/*
HERE IT STOPS
*/
return $this->redirectToRoute('section_start');
}
else
{
$my_whereabouts = $repo_whereabouts->getWhereabouts($whereabouts);
if (!$my_whereabouts)
{
return $this->redirectToRoute('section_start');
}
}
}
Question
Does enyone have some ideas about what is the culprit in this case?
You could try to inject the router into your service class:
use Symfony\Component\Routing\RouterInterface;
class MyService
{
private $router;
public function __construct(RouterInterface $router)
{
$this->router = $router;
}
public function checkWhereaboutsExist()
{
// your code ...
return new RedirectResponse($this->router->generate('section_start'));
}
}
Hummmm, i suppose that your code is in a service and not in your controller ?
You can't redirect from a service but only from controller as controller send the final response.
You have to return a boolean from your service and redirect from your controller :
public function hasToGoToStart()
{
$em = $this->entityManager;
$repo_whereabouts = $em->getRepository(Whereabouts::class);
$whereabouts = $this->session->get('whereabouts');
if (($whereabouts === null) || ($whereabouts === ''))
{
return true;
}
else
{
$my_whereabouts = $repo_whereabouts->getWhereabouts($whereabouts);
if (!$my_whereabouts)
{
return true;
}
}
return false;
}
and in your controller :
if ($myService->hasToGoToStart()) {
// redirect
}

How to bypass web session key when I request device API

I have a web application and a ios application(wasn't made by me, I can't do any code changes there), but both of them are using same backend in order to get data(this is the part where I have access) backend part if written in php on codeigniter framework.
When I did login part on the website I used a login controller and a hook to pass the session on all my controllers.
this is the login side:
public function login_validation() {
$this->load->library('form_validation');
$this->form_validation->set_rules('username','Username',
'required|trim|callback_validate_credentials');
$this->form_validation->set_rules('password','Password','required|trim');
if ($this->form_validation->run()) {
$data= array(
'username'=> $this->input->post('username'),
'is_logged_in' => true
);
$this->session->set_userdata($data);
redirect('Monitor');
} else {
$this->load->view('login');
}
}
and this is my hook:
<?php
class Authenticate {
protected $CI;
public function __construct() {
$this->CI = &get_instance();
}
public function check_user_login() {
$isLoginRequested = $this->_endsWith(base_url(uri_string()), "Main/login");
$isLoginValidating = $this->_endsWith(base_url(uri_string()), "Main/login_validation");
// echo $this->CI->session->userdata('is_logged_in') ;
if(false == $this->CI->session->userdata('is_logged_in') &&
$isLoginRequested == false && $isLoginValidating == false) {
redirect('Main/login');
}
}
function _endsWith($haystack, $needle) {
$length = strlen($needle);
if ($length == 0) {
return true;
}
return (substr($haystack, -$length) === $needle);
}
}
?>
Now the login on the ios device is requesting header X-SESSION-KEY on the api. On this api :
public function types() {
$userSession = $this->_get_user_session();
$reqLanguage = $this->input->get_request_header('Accept-Language');
if ($userSession != null) {
if ($userSession->isLoggedIn() == true) {
$this->_issue_success($this->ptypes->getAll($reqLanguage));
} else {
$this->_issue_respond_with_reason("The provided session is invalid.", HttpResponse::Forbidden);
}
} else {
$this->_issue_respond_with_reason("No session key supplied.", HttpResponse::Forbidden);
}
But what is happening now, on the same URL(like I told both applications are using same backend) my ios app is requesting now the web session not the API side, and I will get redirected on the login.
And on that URL(on the ios app) I get:
{"reason":"No session key supplied."}
and my controllers aren't sending any data.
My question is that, how I can make a if variable on the login controller side, when the user (logged from ios) is requesting the API sessions side, bypass my web session session and avoid that no session key supplied.
Before my login, the application was working fine.

How to validate a parameter for a class within a method and prevent use if invalid?

I am building a small class to handle api requests and I am running into an issue with error handling (I am also a novice at OOP so please with me) I need to restrict or throw an error with any methods in my class that require the user parameter to be set and I also need to samething if token has not been retreived and I can't seem to wrap my head around it.
This is what I have so far...
$user array is set in a config file outside of class like so (empty by default):
$user = array(
'user_email' = '',
'user_pass' = ''
);
Class for handling API (simplified for question)
class eventAPI {
private $user
private $token
public function __construct($user) {
$this->user = $user;
// if possible assign token when instantiated
$this->retrieve_token($user);
}
private function retreive_token($user) {
// Check if user parameter has been set
if($this->validate_user_parameter()) {
// use credentials to make HTTP request for token
$token = 'somerandomtoken';
// assign token property retreived value
$this->token = $token;
} else {
echo 'User parameter has not been set.' // Not real message just for testing
return FALSE;
}
}
public function show_all_events() {
// Check if token has been retreived
if($this->validate_token_retreived()) {
// Use token to retreive events list via HTTP request
} else {
echo 'API not active. No valid token detected'; // for testing purposes
return FALSE
}
}
// reduntant code... Can't wrap my head around another way for checking for token.
public function show_single_event() {
// Check if token has been retreived
if($this->validate_token_retreived()) {
// Use token to retreive events list via HTTP request
} else {
echo 'API not active. No valid token detected'; // for testing purposes
return FALSE
}
}
// This is mostly where I am confused how to solve.
private function validate_user_parameter() {
foreach($this->user as $key => $value) {
// Remove whitespace from value
$value = trim($value);
if(empty($value)) {
echo 'User credentials have not been set'; // for testing purposes
return FALSE;
}
}
}
private function validate_token_retreived() {
$result = FALSE;
// Bool value not sure if this is the best way to do this
if(isset($this->$token)) {
$result = TRUE;
}
return $result;
}
}
First issue: I need to validate the user parameter which is in an array so I can use with a private method to retrieve the token. I chose to use a foreach loop to check each value but it seems a little archaic.
Second Issue: I have a redundant check in place for each public method to check if token is valid. Is there a better way to do this with OOP? I have many methods that require the token.
In short, how can I make sure that once the class is instantiated a public method that will be used by end user will not fire if any validation fails. The user info only needs to be valid once when instantiated and then the token needs to be valid for most remaining methods.
You don't need to pass $user parameter to retreive_token function. You got it in class scope. Just use $this->user in the function to access it. Also you didn't use it in that function, so why u passed it?
You didn't send true in any function.
There's nothing wrong with for-each but you want to check array_map too. Atleast you're applying a function to every item in array. It can be usefull. ps: seems for-each still faster then array_map
Also you would want to check empty function on which cases it returns false
You can use multiple returns in a function. You dont need to set a variable to do that.
Example
private function validate_token_retreived()
{
if(isset($this->token))
return true;
return false;
}
You couldn't use else in most cases.
Example
public function show_all_events()
{
if($this->validate_token_retreived()) {
// Use token to retreive events list via HTTP request
// and return here
}
echo 'API not active. No valid token detected'; // for testing purposes
return FALSE; // do you really return here? seems you are just generating an output
}

Phalconphp flash SESSION messages after redirect doesnt show

I have a prepared AccessControll plugin for cheking access to resources and actions, so when i set flash message in plugin and then redirect to the login page the message doesn't show.
I have in access control plugin lines:
if(!$role || !$moduleAcl || !$moduleAcl->isAllowed($role,$controller,$action)){
$this->flash->warning('Nemáte oprávnění na provedení této akce.');
if(!$moduleAcl->isAllowed($role, 'index', 'index')){
$auth = \Core\Auth::logout();
}
else {
return $this->response->redirect($module.'/');
}
}
In, base controller i have a line:
if(!$identity)
{
return $this->response->redirect('manager/auth/');
}
Can someone tell what i'm doing wrong ?
In you controller just put
$this->view->disable();
before
$this->redirect();
It will help. That was unexpected for me as well some time ago ;)
I made a solution for that:
<?php
namespace Core\Http;
/**
* Description of Response
*
* #author softdream
*/
class Response extends \Phalcon\Http\Response {
//put your code here
public function redirect($locationPath = null, $baseUrl = null, $statusCode = null) {
if($statusCode){
$this->setStatusHeader($code);
}
if(substr($locationPath, 0,1) === '/'){
$locationPath = substr($locationPath, 1);
}
header("Location: ".$baseUrl.'/'.$locationPath);
exit;
}
protected function setStatusHeader($code){
header("HTTP/1.0 ".$code);
}
}
This will resolve all problems with showing flash message after redirect, the problem is that phalcon doesn't stop script when redirect, so it can render some data before redirect.
I think that disabling view is not a good and cleaned solution how to do it :)

Connection between CI and simple php for to read $_SESSION

I'm trying to integrate a forum (created in Codeigniter) into a website (simple php >>> no framework used).
In order to automatically login to the forum, when I login in my website, I need to use a function of the forum which expects 2 parameters $username and $password.
I already have this informations (username and password) from my website, in $_SESSION.
How can I read the $_SESSION from the forum(as I say before Codeigniter based), because, I have no acces to it.
Is there a posibility to define 2 constants, somewhere in the core / config of the forum, to hold these details from $_SESSION, in order to have acces from anywhere inside the forum ?
I know that the sessions from CI are different from $_SESSION, so please help me with something more practical, in order to solve my problem.
Thanks.
Read this url;-
http://codeigniter.com/forums/viewthread/158923/#766011
http://codeigniter.com/forums/viewthread/188648/#892137
In case for those who want to do native session with 2.0.2
Just copy the native_session.php file to your application/libraries/ and rename it as Session.php
Then change the class name and constructor name to CI_Session
Also add the following then it should work fine.
function sess_destroy()
{
$this->destroy();
}
or
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/*
Native / Database hybrid
Code Igniter
Citrusmedia - Matthew Lymer
*/
class CI_Session
{
var $sess_table_name = '';
var $sess_expiration = 7200;
var $sess_match_ip = FALSE;
var $sess_match_useragent = TRUE;
var $sess_time_to_update = 300;
var $encryption_key = '';
var $flashdata_key = 'flash';
var $time_reference = 'time';
var $gc_probability = 5;
var $userdata = array();
var $CI;
var $now;
/**
* Session Constructor
*
* The constructor runs the session routines automatically
* whenever the class is instantiated.
*/
function CI_Session($params = array())
{
log_message('debug', "Session Class Initialized");
// Set the super object to a local variable for use throughout the class
$this->CI =& get_instance();
// Set all the session preferences, which can either be set
// manually via the $params array above or via the config file
foreach (array('sess_table_name', 'sess_expiration', 'sess_match_ip', 'sess_match_useragent', 'sess_time_to_update', 'time_reference', 'encryption_key') as $key)
{
$this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key);
}
// Sessions, start your engines!
ini_set("session.gc_maxlifetime", $this->sess_expiration);
session_start();
// Load the string helper so we can use the strip_slashes() function
$this->CI->load->helper('string');
// Are we using a database? If so, load it
if( !$this->sess_table_name ) {
die('Session class database table name not configured');
}
$this->CI->load->database();
// Set the "now" time. Can either be GMT or server time, based on the
// config prefs. We use this to set the "last activity" time
$this->now = $this->_get_time();
// Set the session length. If the session expiration is
// set to zero we'll set the expiration two years from now.
if ($this->sess_expiration == 0)
{
$this->sess_expiration = (60*60*24*365*2);
}
// Run the Session routine. If a session doesn't exist we'll
// create a new one. If it does, we'll update it.
if ( ! $this->sess_read())
{
$this->sess_create();
}
else
{
$this->sess_update();
}
// Delete 'old' flashdata (from last request)
$this->_flashdata_sweep();
// Mark all new flashdata as old (data will be deleted before next request)
$this->_flashdata_mark();
// Delete expired sessions if necessary
$this->_sess_gc();
log_message('debug', "Session routines successfully run");
}
// --------------------------------------------------------------------
/**
* Fetch the current session data if it exists
*
* #access public
* #return bool
*/
function sess_read()
{
// Unserialize the session array
// $session = $this->_unserialize($session);
$session = array();
foreach( array('session_id', 'ip_address', 'user_agent', 'last_activity') as $key )
{
if( !isset($_SESSION[$key]) ) {
$this->sess_destroy();
return FALSE;
}
$session[$key] = $_SESSION[$key];
}
// Is the session current?
if (($session['last_activity'] + $this->sess_expiration) < $this->now)
{
$this->sess_destroy();
return FALSE;
}
// Does the IP Match?
if ($this->sess_match_ip == TRUE AND $session['ip_address'] != $this->CI->input->ip_address())
{
$this->sess_destroy();
return FALSE;
}
// Does the User Agent Match?
if ($this->sess_match_useragent == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 50)))
{
$this->sess_destroy();
return FALSE;
}
$this->CI->db->where('session_id', $session['session_id']);
if ($this->sess_match_ip == TRUE)
{
$this->CI->db->where('ip_address', $session['ip_address']);
}
if ($this->sess_match_useragent == TRUE)
{
$this->CI->db->where('user_agent', $session['user_agent']);
}
$query = $this->CI->db->get($this->sess_table_name);

Categories