Is it possible for to sql inject a ZEND_DB_TABLE_ABSTRACT method?
like for example
$this->insert();
edit for a more clearer explanation
Post values are :
'username' = 'admin';
'password' = '1;Drop table users;'
Here is the insert statement in the controller:
public function InsertAction() {
$postValues = $this->_request->getPost();
$usersTable = new Application_Models_DbTable_Users();
$username = $postValues['username'];
$password = $postValues['password'];
$data = array('username'=>$username,'password'=>$password);
$users->insert($data);
}
Yes, it is possible, but in the usual uses of insert() it's not probable. Unless you are using Zend_Db_Expr, you should be safe, because insert() uses prepared statements.
See this post from Bill Karwin for other methods and details.
Check the manual of Zend Zend_Db_Table
It will show you who you can create your own method.
Related
How can I truncate a table with CakePHP 3.x
I get the truncate query by this:
$this->Coupons->schema()->truncateSql($this->Coupons->connection());
but what is the best practice to execute it
This code working well, thanks to #ndm for his comment that helped the answer to be better.
//In Coupons Controller
$this->Coupons->connection()->transactional(function ($conn) {
$sqls = $this->Coupons->schema()->truncateSql($this->Coupons->connection());
foreach ($sqls as $sql) {
$this->Coupons->connection()->execute($sql)->execute();
}
});
In CakePHP4 you can use the following code to truncate a table:
$table = $this->Coupons;
$sqls = $table->getSchema()->truncateSql($table->getConnection());
foreach ($sqls as $sql) {
$table->getConnection()->execute($sql)->execute();
}
I tested following and it's worked:
$connection = $this->Coupons->getConnection();
$connection->query('TRUNCATE coupons');
Reference: https://book.cakephp.org/4/en/orm/database-basics.html#executing-queries
Read more here: https://book.cakephp.org/4/en/orm/database-basics.html#using-transactions
Ok so basically I need to convert this regular sql statement to the syntax joomla uses via
https://api.joomla.org/11.4/Joomla-Platform/Database/JDatabaseQuery.html
here is my statement
SET #myunsubid = (
SELECT subid
FROM aqbi8_acymailing_subscriber s
WHERE s.email = 'email#email.co.nz'
);
SELECT #myunsubid;
UPDATE aqbi8_acymailing_listsub a
SET a.`status` = 1
WHERE a.subid = #myunsubid AND a.listid = 232
So id like it to be like
$db->set(#myunsubid = ( $db->select($db->quoteName('subid') )
$db->from($db->quoteName('aqbi8_acymailing_subscriber s') )
$db->where($db->quoteName('s.email') = 'email#email.co.nz')
)
$db->update($db->quoteName('aqbi8_acymailing_listsub a'))
$db->set($db->quoteName('a.status') = 1)
$db->where ($db->quoteName('a.subid') = #myunsubid AND $db->quoteName('a.listid') = 232 )
But this isnt quite right. please help!
I actually figured it out got it to work like this.
$db = &JDatabase::getInstance($option);
$query = $db->getQuery(true);
// make a variable for subID
$query->select($db->quoteName(array('subid')));
$query->from($db->quoteName('aqbi8_acymailing_subscriber'));
$query->where($db->quoteName('email') . " = '" . $email ."'");
$db->setQuery($query);
$db->execute();
$test = $db->loadObjectList();
print_r( $test );
$myid = $test[0]->subid;
$query->clear();
// // Create Database query
$fields = $db->quoteName('status') . ' = 1';
$conditions = array(
$db->quoteName('subid') . ' = ' . $myid,
$db->quoteName('listid') . ' = ' . $listid
);
// // update query
$query->update($db->quoteName('aqbi8_acymailing_listsub'))->set($fields)->where($conditions);
$db->setQuery($query);
$db->execute();
You don't need to make two trips to the database, you can write a subquery into your UPDATE's WHERE condition (no mysql variables or table aliases are necessary).
Raw Query:
UPDATE aqbi8_acymailing_listsub
SET status = 1
WHERE listid = 232
AND subid = (
SELECT subid
FROM aqbi8_acymailing_subscriber
WHERE `email` = 'email#email.co.nz'
)
Tested Code:
$db = JFactory::getDBO();
try {
$subquery = $db->getQuery(true)
->select('subid')
->from('#__acymailing_subscriber')
->where("email = 'email#email.co.nz'");
$query = $db->getQuery(true)
->update("#__acymailing_listsub")
->set("status = 1")
->where(["listid = 232", "personid = ($subquery)"]); // or make 2 where() calls
echo $query->dump(); // if you want to see; *during development ONLY
$db->setQuery($query);
$db->execute();
if ($affrows = $db->getAffectedRows()) {
JFactory::getApplication()->enqueueMessage("Updated. Rows affected: $affrows", 'success');
} else {
JFactory::getApplication()->enqueueMessage("Logic Error", 'error');
}
} catch (Exception $e) {
JFactory::getApplication()->enqueueMessage("Query Syntax Error: " . $e->getMessage(), 'error'); // never show getMessage() to public
}
It is not clear if any of your values are coming from users/untrusted sources, so be sure to follow good practices when writing variables into your queries -- like casting integers with (int) and calling $db->quote() on string values.
If you want to see a complex/convoluted UPDATE query with several other tables and techniques blended in, here is a comprehensive post: https://joomla.stackexchange.com/a/22916/12352
Please DON'T USE JDatabase Object to update Joomla tables, when there's an API available for the extension.
Whilst I appreciate the OP's question is pertaining to how to update the joomla database using the joomla database object (JDatabase), I propose a safer and more robust method, the "ACYMailing API".
"BUT WHY?", I hear you ask...
Good question!!!
There are 2 pitfalls in updating the joomla database directly - be it on the command-line, in a GUI such as MySQL Workbench or PHPMyAdmin, or even with the Joomla Database Object. Simply put, they both concern compatibility - 1. regarding third party integrations, and 2. concerning the future compatibility of your code. In a nutshell, whenever there's a an API for interacting with a component, I'd use it, over JDatabase every time to future proof your code, and ensure that all pre and post save, update, delete... ...move, and publish plugin events take care of your integrations, just as if you'd performed the action authentically.
To elaborate on these points a bit...
Most Joomla extensions (core and 3rd-party) make use of Joomla's powerful plugin architecture. By doing so, extensions can perform actions at key points in the application's life cycle. For example, after deleting a record from a table belonging to component1, delete related records from a table relating to compnent2. Therefore, one run's the risk of breaking the behaviour/functionality of the component in question - i.e. ACY Mailing, as in your case. Potentially, other core/3rd-party extensions that rely on ACY's data, that would otherwise, get updated through onAfterSave() or onAfterDelete() plugin events, as they will not get called.
There's a big risk that your code to break with future Joomla/ACY Mailing updates, if/when the table structure changes.
OK, so how do we use the API?
The following example code displays everything that you should need to update a subscription record. Each step explains the code, which for reference, is summarised in doc and inline comments in the code itself. To begin, navigate to the file where you are entering your code, then...
STEP BY STEP
STEP 1: Check the existence of ACY Mailing by attempting to include it's helper class, as follows. N.B. If the include_once() fails, you should see the echo statement, indicating that ACY Mailing IS NOT installed.
// load the ACY Mailing helper - bail out if not
if(!include_once(rtrim(JPATH_ADMINISTRATOR, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_acymailing' . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'helper.php')){
echo 'This code can not work without the AcyMailing Component';
return false;
}
STEP 2: Set-up your parameters by inputting values into the following 3 variables. See examples in code comments.
// array $lists An array of integer IDs (primary keys) of the lists you want the user to be subscribed to (can be empty).
// e.g. array(2,4,6)
$lists = array();
// array $unsubs An array of integer IDs (primary keys) of the lists you want the user to be un-subscribed from (can be empty).
// e.g. array(2,4,6)
$unsubs = array();
// string $userID Numeric Joomla User or user e-mail. For example: '42' or 'name#domain.com'
$userID = '';
STEP 3: Add the following code to find the ACY Mailing user, from the Joomla User ID/Email address passed in to the ->subid() method, and bail out if not found.
// instantiate the ACY Mailing Subscriber (user) Class
$user = acymailing_get('class.subscriber');
// find the ACY Mailing user id (subid) from the joomla ID or email address set in $userID
$subID = $user->subid($userID);
// No ACY Mailing user/subscriber?
if(empty($subID))
return; // bail out
STEP 4: Add the following code to check, and setup the data for any of the subscriptions/unsubscriptions you've configured to update ($lists and $unsubs arrays). If any found, they will be updated. If not found, return.
// create an array to store data in
$data = array();
// Set up new newsletter subscriptions from the $lists array()
if(!empty($lists)) foreach($lists as $listId)
$data[$listId] = array("status" => 1);
// Set up un-subscriptions from the $unsubs array()
if(!empty($unsubs)) foreach($unsubs as $listId)
$data[$listId] = array('status' => 0);
// no data, bail out...
if(empty($data))
return; //there is nothing to do...
// update the user's subscription records, creating/removing subscriptions/unsubsriptions accordingly
$user->saveSubscription($subID, $data);
I wanto to sipmly iaterate the result in view. Problem is that it do not accually work. What is missing? I read the documentatnion but they do not give a full ansver how to retrieve data by cusotm query. For me as a beinnger is hard to understend what do next ? I mean about this query I know how to pass it to view by viewmodel et. Please Help.
$config = $this->getServiceLocator()->get('config');
$adapter = new \Zend\Db\Adapter\Adapter($config[db]);
$sql = new Sql($adapter);
$select = $sql->select();
$select->from('brokerzy');
$select->where(array('broker_status' => 'publish'));
$stm = $sql->prepareStatementForSqlObject($select);
$results = $stm->execute();
First, you probably shouldn't be doing queries in your controller, that's bad practice. These queries should be moved behind a service interface.
However, to answer your question (if I understand it), to pass the results of your query to a view, you should use a view model. It would look something like this.
//Your Code
$results = $stm->execute();
$viewModel = new \Zend\View\Model\ViewModel();
$viewModel->results = $results;
return $viewModel;
I use RedBeanPHP 3.5.1 for ORM in my MVP project (powered by Nette FW).
I need to get ID of the last inserted element, that is owned by element from another table. Below you can find method representing functionality which I just described:
public function createSite($userId, $siteName, $feedUrl, $reloadTime, $reloadRate){
$site = R::dispense('site');
$site->user_id = $userId;
$site->name = $siteName;
$site->feed = $feedUrl;
$site->reload_time = $reloadTime;
$site->reload_rate = $reloadRate;
$user = R::load('user', $userId);
$user->ownSite[] = $site;
$id = R::store($user);
return $id;
}
Now I would assume that line
$id = R::store($user);
would store site ID into $id variable since it is owned by already existing user. Instead of that it fills variable with user ID that I have no further use for.
So my question is: How do I get last inserted ID of owned bean that was just created by calling R::store() method on parent (just loaded) bean? Is there an implementation on this in RedBean or do I have to do this manually?
I browsed every corner of RedBeanPHP project web but so far no luck.
Thanks for possible suggestions, guys.
Using common sense I finally figured out how to solve this elegantly and since no one answered my question so far let my just do that myself.
Since R::store($user) is capable of storing both $user and $site, there is misleadingly no need to store $site object manually.
But if you need to get last inserted id of owned bean, there is really no harm in doing so. By storing $site object framework will do the exact same thing and on top of that it returns resired id.
So the correct method implementation looks like this:
public function createSite($userId, $siteName, $feedUrl, $reloadTime, $reloadRate){
$site = R::dispense('site');
$site->user_id = $userId;
$site->name = $siteName;
$site->feed = $feedUrl;
$site->reload_time = $reloadTime;
$site->reload_rate = $reloadRate;
$user = R::load('user', $userId);
$user->ownSite[] = $site;
$id = R::store($site);
R::store($user);
return $id;
}
So in conclusion, hats off to RedBeanPHP ORM FW and I sincerely hope this helps people with similar problem in the future.
There is a function called R::findLast('...')
$last_record = R::findLast('...');
Not sure if this would have been a correct answer 7 years ago but at least now there is no need to do any kind of extra work:
$shop = R::dispense( 'shop' );
$shop->name = 'Antiques';
$vase = R::dispense( 'product' );
$vase->price = 25;
$shop->ownProductList[] = $vase
R::store( $shop );
echo $vase->$id; // <-- yes, id which was created by database is present here
After a lot of recommendation from others I have decided to make the switch from mysql_ to PDO. I started looking at PDO literally around 15 minutes ago and I'm stuck trying to convert this line of code into PDO format.
function verify_user($username, $recover_password) {
return (mysql_result(mysql_query("SELECT COUNT(`user_id`) FROM `users` WHERE `username` = '$username' AND `password_recovery` = '$recover_password'"), 0) == 1) ? true : false;
}
I have looked at a couple of tutorials and as far as I can work out I can do the actual query with this code:
$verify_user = "SELECT COUNT(`user_id`) FROM `users` WHERE `username` = '$username' AND `password_recovery` = '$recover_password'";
$result = $con->prepare($verify_user);
$result->execute();
The problem I am having is the second part of the line of code - the mysql_result. Now that the query has run I have no idea how to return true or false using PDO. I'd appreciate any help. Thanks!
Updated:
$result = $con->prepare("SELECT COUNT(`user_id`) FROM `users` WHERE `username` = :username AND `password_recovery` = :recover_password");
$result->bindParam(':username', $username, PDO::PARAM_STR);
$result->bindParam(':password_recovery', $recover_password, PDO::PARAM_STR);
$result->execute();
From reading that page you provided it would be:
$result = $con->prepare("SELECT COUNT(`user_id`) FROM `users` WHERE `username` = :username AND `password_recovery` = :recover_password");
$result->bindParam(':username', $username, PDO::PARAM_STR);
$result->bindParam(':password_recovery', $recover_password, PDO::PARAM_STR);
$result->execute();
return ($con->fetch($result) == 1) ? true : false;
I'm probably miles out but I appreciate the help you've given me :) I'll do a couple more searches.
I would write the function this way:
function verify_user($username, $recover_password) {
$sql = "SELECT COUNT(`user_id`) AS count FROM `users`
WHERE `username` = ? AND `password_recovery` = ?";
$stmt = $con->prepare($sql);
$stmt->execute(array($username, $recover_password));
while ($row = $stmt->fetch()) { } /* should be exactly one row anyway */
return $row["count"] == 1;
}
There's no need to use bind_param(), since you can just pass values in an array argument to execute(). And there's no need to specify the parameter type (that's actually ignored, at least in the MySQL PDO driver).
Also be careful to do error-checking. The prepare() and execute() functions return false on error. Many things can cause an error. You could misspell a column name. Your database connection may lack the right database privileges. Someone could drop the table.
FWIW, proper error-checking is important when using the mysql_* and mysqli_* API's too, but it seems that few people do it right.
In the above code, I don't show checking the return values because I've made an assumption that we've enabled exceptions when we created the PDO connection.
$con->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
That relieves us of having to write code to check the return values every time, but it means that an error will cause our application to go "white-screen". It's best practice to handle the exceptions in the caller function, and display some friendly error screen.