yii mass model attribute assignment and xss security concerns - security

$model->attributes = $_GET[ 'Submission' ];
This looks really scary to me, but it's how yii assigns attributes to a model. Is this a security risk for XSS? Should it not be sanitized somehow first? I know models get validated, but is it enough from malicious input, especially if saving to the database and if you forget to sanitize output...

Massive assignment is not default 'on' . It will only be made for fields which have passed some explicit validation rule.
$model->attributes = $_GET[ 'Submission' ];
is equivalent to this code,
$model->attribute1 = $_GET['Submission']['attribute1'];
$model->attribute2 = $_GET['Submission']['attribute2'];
$model->attribute3 = $_GET['Submission']['attribute3'];
Any XSS, SQL Injection vulnerabilities present in the former will be present in later too;
To prevent against XSS, SQL Injection you can use the bundled CHtmlPurifier class which is a wrapper for HTML Purifier filter library. There are multiple ways to use CHtmlPurifier, one way to use is as a filter in the model rules, which will check for XSS strings.
If your model rules function was like this for example
public function rules(){
return array(
array('username, password, salt, email', 'required'),
array('username, password, salt, email', 'length', 'max'=>128),
array('first_name,last_name,username,email','safe','on'=>'search'),
);
}
if you use massive assignment with this rules set with $model->attributes = $_GET[ 'Submission' ];
username and email will be assigned however first_name, last_name will not be assigned to the model as they are only safe on search scenario.
You can add a rule to make them search on say create,update like this
public function rules(){
return array(
array('username, password, salt, email', 'required'),
array('username, password, salt, email', 'length', 'max'=>128),
array('first_name,last_name','safe','on'=>'create,update'),
array('first_name,last_name,username,email','safe','on'=>'search'),
);
}
This will make the safe for massive assignment, however leaving you still vulnerable to XX. To protect against XSS you can use this type of filter as a rule
public function rules(){
return array(
array('username, password, salt, email', 'required'),
array('username, password, salt, email', 'length', 'max'=>128),
array('first_name,last_name,username,email','filter'=>array($obj=new CHtmlPurifier(),'purify')),
array('first_name,last_name,username,email','safe','on'=>'search'),
);
}
Now first_name,last_name,username,email are all purified for XSS,SQL strings before being validated and massive assignment also works for all four attributes.
Tl;DR
Yii provides with you default safety feature, massive assignment only works when there is explicit model validation rule
XSS will happen independent of whether or not massive assignment is used

This is probably a decent article on secure Yii apps http://www.yiiframework.com/wiki/275/how-to-write-secure-yii-applications/
Additionally, you can filter any input through the models with their filters and rules or beforeSave() methods

Related

how to build aggregate in ddd, passing VO or raw data?

What is the correct way of designing aggregate in DDD, for exmaple you need to create some user, and to create it we need to have id, email, living address and born address.
So we can do it like this (I'll use PHP as it's my main language):
class User {
private function __construct(
private UserId $id,
private Email $email,
private Address $livingAddress,
private Address $bornAddress
) {}
public static create(
string $uuid,
string $email,
string $city,
string $country,
string $address,
string $bornCity,
string $bornCountry,
string $bornAddress,
) {
return new self (
UsesId::fromString($uuid),
new Email($email),
new Address($city, $country, $address),
new Address($bornCity, $bornCountry, $bornAddress)
);
}
}
in this case we following a rule that aggregate root responsible of checking all invariants , because he creating all VO's and they has own validation. But it's adding complexity to actual User class, we have to many parameters passed etc.
Another possible solution is to build it like this:
class User {
private function __construct(
private UserId $id,
private Email $email,
private Address $livingAddress,
private Address $bornAddress
) {}
public static create(
UserId $id,
Email $email,
Address $livingAddress,
Address $bornAddress
) {
return new self (
id,
$email,
$livingAddress,
$bornAddress
);
}
}
now User object is smaller, maybe even more "elegant", but we breaking the rule that aggregate should check all invariants.
And the third option is probably to use Factory, I'll not provide code here but explain how I see it. Basically factory takes a row data, create VO, and passing it to aggregate, so aggregate creation will look like in the second example. Again aggregate is not responsible for all invariants, but Factory is on one of allowed patterns by DDD so I think thats fine.
I know probably there is no a right way, but I want to hear some best practices and suggestions from someone who has distinct knowledge in DDD about how to do it right.
we breaking the rule that aggregate should check all invariants.
I think you are misunderstanding this rule.
The aggregate is responsible for domain dynamics: how information changes over time. In particular, it is responsible for ensuring that the information stored is internally consistent.
That's not the same problem as input validation, which as a rule we want to solve as close to the boundary as we can manage.
Imagine, if you will, a form submission that looks like
...&userId=alphabet-soup&...
Sure, "alphabet-soup" is a string, but it's not consistent with the schemas described in RFC 4122, so something has gone Very Wrong, and we should be bailing out with a client error rather than forwarding the suspect data to the domain model.
See also Parse, Don't Validate (Alexis King, 2019)

Rails has_secure_password: is it actually hashing the password in the DB?

Using typical Rails 4.1 app with has_secure_password and the User model has a password_digest column in the DB. When I create a new user, I can still access the plaintext password in the console:
# in rails console
> u = User.new(email: "test#test.com", password: "password")
> u.save
> u.password => "password"
> u.password_digest => "xjdk..."
However, when I close the console session and start a new one, I can no longer retrieve the plaintext password:
# close above console session and open a new one
> u = User.find_by(email: "test#test.com")
> u.password => nil
I'm assuming that the plaintext password is only retrievable in the first situation because it's being stored in memory and when I call u.password => "password" it is retrieving the value from memory, NOT the database.
I had always thought has_secure_password stored the (salt + password) as a hash and I thought that meant it was theoretically impossible (if I can use that terminology) to reverse the password_digest and get the original password.
I'm just making sure my assumption that the password is stored as a real hash (ie, can't retrieve original password) is valid. I've read the Rails has_secure_password API but it didn't clarify my question.
You are correct — the DB is only saving the hashed password, not the password itself. You can confirm this by accessing the database directly using the read_attribute method (http://www.rubydoc.info/docs/rails/3.0.0/ActiveRecord/AttributeMethods/Read):
> u = User.new …
> u.read_attribute(:password_digest)
=> # Some hash
> u.read_attribute(:password)
=> nil
Incidentally, also make sure your User model does not have a password column. Otherwise it would save the password directly, defeating the purpose of hashing the password.

Spring security integration with open id in grails

I am working on Integrating spring security with openId for my grails Application using springsecurity core and springsecurity openid plugins. I have integrated it, and it works well but I need to access the email for the logged in person. How can I get that, all that I am able to access is a token which is used for identifying the person.
Thanks to Ian Roberts.
He gives me this reply,Which exactly solves my problem.
His reply was:
As it happens I implemented exactly this in one of my applications
yesterday :-) Unfortunately it's not an open-source app so I can't just
point you at my code but I can explain what I did.
The spring-security-openid plugin supports the "attribute exchange"
mechanism of OpenID, although the support is not documented much (if at
all). How well it works depends on the provider at the far end but this
at least worked for me using Google and Yahoo.
In order to request the email address from the provider you need to add
the following to Config.groovy:
grails.plugins.springsecurity.openid.registration.requiredAttributes.email
= "http://axschema.org/contact/email"
Now to wire that into your user registration process you need an email
field in your S2 user domain class, and you need to edit the generated
OpenIdController.groovy in a few places.
add an email property to the OpenIdRegisterCommand
in the createAccount action there's a line
"if(!createNewAccount(...))" which passes the username, password and
openid as parameters. Change this along with the method definition to
pass the whole command object instead of just these two fields.
in createNewAccount pass the email value forward from the command
object to the User domain object constructor.
And finally add an input field for email to your
grails-app/views/openId/createAccount.gsp.
You can do the same with other attributes such as full name.
grails.plugins.springsecurity.openid.registration.requiredAttributes.fullname
= "http://axschema.org/namePerson"
The important thing to wire it together is that the thing after the last
dot following requiredAttributes (fullname in this example) must match
the name of the property on the OpenIdRegisterCommand.
Regards
Charu Jain
I've never used the springsecurity openid plugin, but when using springsecurity core you can expose additional information about the current user by implmenting a custom UserDetails. In my app, I added this implementation, so that I can show the name property of logged-in users. You'll need to change this slightly, so that the email address is exposed instead
/**
* Custom implementation of UserDetails that exposes the user's name
* http://grails-plugins.github.com/grails-spring-security-core/docs/manual/guide/11%20Custom%20UserDetailsService.html
*/
class CustomUserDetails extends GrailsUser {
// additional property
final String name
CustomUserDetails(String username,
String password,
boolean enabled,
boolean accountNonExpired,
boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<GrantedAuthority> authorities,
long id,
String displayName) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities, id)
this.name = displayName
}
}
You then need to create a custom implementation of UserDetailsService which returns instances of the class above
class UserDetailsService implements GrailsUserDetailsService {
/**
* Some Spring Security classes (e.g. RoleHierarchyVoter) expect at least one role, so
* we give a user with no granted roles this one which gets past that restriction but
* doesn't grant anything.
*/
static final List NO_ROLES = [new GrantedAuthorityImpl(SpringSecurityUtils.NO_ROLE)]
UserDetails loadUserByUsername(String username, boolean loadRoles) {
return loadUserByUsername(username)
}
UserDetails loadUserByUsername(String username) {
User.withTransaction { status ->
User user = User.findByUsername(username)
if (!user) {
throw new UsernameNotFoundException('User not found', username)
}
def authorities = user.authorities.collect {new GrantedAuthorityImpl(it.authority)}
return new CustomUserDetails(
user.username,
user.password,
user.enabled,
!user.accountExpired,
!user.passwordExpired,
!user.accountLocked,
authorities ?: NO_ROLES,
user.id,
user.name)
}
}
}
You need to register an instance of this class as a Spring bean named userDetailsService. I did this by adding the following to Resources.groovy
userDetailsService(UserDetailsService)

How to secure the segment passed to the controller in CI

I am trying to pass a segment to a controller. The url is like base_url/controller/function/seg1. I want to ensure that if the user try to enter the segment in the address bar, the controller would make sure there are not other words to be proceeded except the segment I want to pass.
For example, If the user tries to type base_url/main/function/(change this to other words) in address bar, the controller will filter the segment. I am not sure how to do it and would appreciate if someone can help me out.
Okay, so the best way to "secure" against such things would be to simply create a session at the time the user logs into your site with two values stored in that session;
1) Their database primary key id, and
2) a session item called 'logged_in'
At the time that your user would log into your site, you would store those two values like this;
$this->session->set_userdata('logged_in', true);
$this->session->set_userdata('user_id', $id);
Where $id is pulled from their user record during authentication.
Now that you have those in there, the next part would be that, in your controller, you would put an if statement in that checks if the user is logged in, as such;
function show($id) {
if($this->session->userdata('logged_in')) {
$posts = $this->Model_posts->get_user_posts($id);
}
}
Now, in your model, you would create a function for pulling the record that you want the user to be able to view based on their user_id. We'll say user posts for example.
function get_user_posts($user_id, $post_id) {
$sql = "SELECT * FROM posts WHERE user_id = ? AND id = ?";
$binds = array($user_id, $post_id);
$qry = $this->db->query($sql, $binds);
$result = array();
while($row = $qry->result_array()) {
array_push($result, $row);
}
return $result;
}
Now, when a logged in user or visitor tries to access records that don't belong to them, they will not retrieve any records because the select statement limits what's returned only to that user.
The structure you have there is
base_url/controller/action
So, your controller is already "filtering" it out because if you don't have a method/function in the controller (methods = actions) then your controller will trigger a 404 Page Not Found error. Of coarse, you could then handle your errors however you see fit, but from what you presented, the item you wish to filter is known as a controller action.
So for instance;
http://www.base_url.com/users/add
denotes that you wish to call the add (function) in the users controller.
If you want to pass the add action an argument, then you would do this as;
http://www.base_url.com/users/show/1
Where show would be a controller action and 1 would be the id of the user you wish to show.
I know it seems like I'm giving a basic intro to MVC methodologies, but like I said, the structure you showed plays out like I described.
Hope this helps.

Noob way to login the user in Prestashop

This is a walkthrough on how to make a user login on prestashop without passing through the login screen. This is helpful if you do not want the user to login again like when you want to transfer his session from one website to prestashop.
Step 1 Eliminate the need for password salting. Under config/settings.inc.php, set _COOKIE_KEY_ to blank. Note this also means that you must create a new customer. Or you can delete the old md5 password from DB and add your own.
Step 2 In the authentication.php file paste the following lines after line 6:
$customer = new Customer();
//$authentication = $customer->getByEmail(trim($email), trim($passwd));
$authentication = $customer->getByMd5(trim($email), trim($passwd)); //modified version of getByEmail if we are not accepting $passwd in cleartext but in md5.
/* Handle brute force attacks */
sleep(1);
if (!$authentication OR !$customer->id)
$errors[] = Tools::displayError('authentication failed');
else
{
$cookie->id_customer = intval($customer->id);
$cookie->customer_lastname = $customer->lastname;
$cookie->customer_firstname = $customer->firstname;
$cookie->logged = 1;
$cookie->passwd = $customer->passwd;
$cookie->email = $customer->email;
if (Configuration::get('PS_CART_FOLLOWING') AND (empty($cookie->id_cart) OR Cart::getNbProducts($cookie->id_cart) == 0))
$cookie->id_cart = intval(Cart::lastNoneOrderedCart(intval($customer->id)));
Module::hookExec('authentication');
if ($back = Tools::getValue('back'))
Tools::redirect($back);
//Tools::redirect('my-account.php'); //cut redirection to break infinite loop
}
The above code is what makes the user login using $email as username and $passwd as password in plaintext. The original code comes from the if (Tools::isSubmit('SubmitLogin')) function inside the authentication.php file.
Step 3 Paste the above code in the products.php file just under line 5
Step 4 In case you are sending $passwd directly in md5 format, here is the modified version of getByEmail()(customer.php):
public function getByMd5($email, $passwd = NULL)
{
$result = Db::getInstance()->GetRow('SELECT * FROM `'._DB_PREFIX_ .'customer` WHERE `active` = 1 AND `email` = \''.pSQL($email).'\' '.(isset($passwd) ? 'AND `passwd` = \''.pSQL(_COOKIE_KEY_.$passwd).'\'' : '').' AND `deleted` = 0');
if (!$result)
return false;
$this->id = $result['id_customer'];
foreach ($result AS $key => $value)
if (key_exists($key, $this))
$this->{$key} = $value;
return $this;
}
You can get access to the username/passwd either through the $_COOKIE[] function or through $_GET[]. Either way its a big security risk. Cookie reading can be placed in the index.php file.
This approach you have proposed is extremely insecure. A salt is required for password safety and should never be removed. Further more by authenticating a user with their MD5 hash you effectively voiding all protection that hashing passwords provides you. People hash passwords because attacks like SQL injection allow an attacker to obtain this hash which then needs to be cracked. In this scenario the attacker can grab the admin's hash and just login right away.
The correct way to do session sharing:
Create a simple table to store session state. In this case the Cryptgoraphic Nonce is a large random value used to reference the data.
'insert into session_state (sess,token) value ('.pSQL(serialize($_SESSION)).', '.$cryptographic_nonce.')'
When the browser is redirected to another shop give them a redirect like this:
header('location: https://some_other_shop/landing.php?token=$cryptographic_nonce');
When the new server gets this landing request it fetches the session state from the previous server:
$sess=http_get($_SERVER['HTTP_REFERER']."?token=$_GET[token]");
$_SESSION=unserialize($sess);
Note you might have to transfer the user's data in the database as well.

Resources