Symfony/MySQL: full text search syntax error for special chars - mysql

I've got a problem with full-text search query. It is being sent from the inside of Symfony (2.8) controller, if that's important. The code is:
// not processed text (apart from multiple spaces) from form:
$searchTerm = preg_replace('!\s+!', ' ', trim($data['query']));
// for full-text search:
$searchTermPhrase = str_replace(' ', '* ', $searchTerm) . '*';
$entityManager = $this->getDoctrine()->getManager();
// as far as I remember, query taken from StackOverflow after some tests
$statement = $entityManager->getConnection()
->prepare(
"SELECT COUNT(id) AS books_count
FROM book
WHERE
MATCH(title) AGAINST (:searchTermPhrase IN BOOLEAN MODE) > 5
AND active = true
");
$statement->execute(array('searchTermPhrase' => $searchTermPhrase));
$titlesCount = $statement->fetch();
$titlesCount = $titlesCount['books_count'];
Now, it works great. Most of the time. But if user puts some special chars inside, for example (title), "title" etc., it fails:
SQLSTATE[42000]: Syntax error or access violation:
1064 syntax error, unexpected $end, expecting
FTS_TERM or FTS_NUMB or '*'
I don't get it, I use parametrized query, it should automatically take care of such cases...? I don't want to end up stripping all non-alphanumeric characters, or testing, which exactly cause problems, but I'm out of ideas.

Looks like this is InnoDB issue, see this report https://bugs.mysql.com/bug.php?id=72605. I've faced with same issue, changed engine to MyISAM it started to work

Well, as mentioned, got the same problem. I looked at our code and it is clear that BOOLEAN MODE causes this problems.
First of all: "title" should work, while "title should throw the error above.
Take a look at all the boolean operators: https://dev.mysql.com/doc/refman/8.0/en/fulltext-boolean.html
With this mode you can provide great search experience by letting the user search for apples but not bananas: +apple -banana. But wrongly entered Parenthesis or Quotes break it logic.
Question is: is there a regex or function to detect if the search term is valid for boolean mode? Else it should default to natural language mode.

Related

SQL injection with no spaces

I'm trying to exploit an SQL injection on a website (in the name of Science, of course). After some tests I found how the back-end works. The SQL query is formed like this:
$query = 'SELECT * FROM TABLE WHERE ID ='.$segment
where $segment is the second path segment from the URL. In case of http://vict.im/menu/10 it equals 10. Symbols /, # and everything after them is ignored, so the previous link, http://vict.im/menu/10/blah-blah and http://vict.im/menu/10#blah-blah give the same result.
The problem here is that the segment-parser doesn't URLdecode() the segment. If I send .../menu/30 ; it will be encoded to .../menu/30%20;, and MySQL will interpret it as remainder of division, returning us result where ID = 10. By the same reason + is not replaced for whitespace, it works as an operator.
So, it's needed to make an injection that doesn't contain any symbols usually encoded by web browsers. For example, .../menu/(10)or(1=1), boolean-based injection .../menu/9+(USER()='Smith') and .../menu/CONCAT('1','0') work fine.
How can I explain this situation to Sqlmap? Is there a tamper script for this? Are there any other ways to bypass this "protection"?
P.S. It seems following symbols can be used: ! $ & ' ( ) * + , : ; = ? # [ ] plus mixalpha-numeric.

Trick to use variable in match against mysql

Please first read my question,and then you will find out it is not a duplicate of other question.
I'm using sphinx search for 98% of search,but need to use match against for just one query.
As we know from mysql documentation that AGAINST only takes string.The search string must be a literal string, not a variable or a column name.
But I have found this link http://bugs.mysql.com/bug.php?id=66573 ,which says it is possible.But I'm not sure how to use that in my case.
Here is my code
$sqli="SELECT busi_title,category FROM `user`.`user_det`";
$queryi=mysqli_query($connecti,$sqli);
if(mysqli_num_rows($queryi)>0){
while($rowi=mysqli_fetch_assoc($queryi)){
$busi_title=$rowi['busi_title'];
$category=$rowi['category'];
}
}else{
echo "OH NO";
}
$sqlj="SELECT * FROM `user`.`user_det` WHERE MATCH(student) AGAINST('$busi_title','$category')";
$queryj=mysqli_query($connecti,$sqlj);
if(mysqli_num_rows($queryj)>0){
..............................
..............................
}else{
foreach ( $res["matches"] as $doc => $docinfo ) {
................................
...............................
}
}
MATCH() AGAINST() is giving error,as it supposed to be.How to use that trick of that link in this case.I don't know the use of #word:= of that link.
Thanks in advance.
That link doesn't show a trick to get around a limitation of MySQL. It's a bug report demonstrating an incorrect statement in the MySQL documentation. The statement in the documentation has now been corrected.
The reason you're getting an error is because you're sending two parameters to AGAINST and it only accepts one. You can use a MySQL variable in AGAINST which is what the bug report is about, but this has nothing to do with the PHP variable that you're using.
EDIT
Upon reading your response, I rather suspect that you have your syntax backwards.
SELECT * FROM `user`.`user_dets` WHERE MATCH(busi_title, category) AGAINST('student')
But note this from the documentation:
The MATCH() column list must match exactly the column list in some FULLTEXT index definition for the table, unless this MATCH() is IN BOOLEAN MODE. Boolean-mode searches can be done on nonindexed columns, although they are likely to be slow.
If you don't have a Fulltext index, you'll actually want this:
SELECT * FROM `user`.`user_dets` WHERE `busi_title` LIKE '%student%' OR `category` LIKE '%student%'
When they say "The search string must be a literal string, not a variable or a column name" does not mean you cannot use variable to create your Query String.
So it is OK to make your query very simple.
Your WHERE could be this:
WHERE `student` = $busi_title OR `student` = $category

Avoid syntax error warnings when using string interpolation in SQL query generation

When using string interpolation, sprintf or generally any form of dynamically creating an SQL query string, PhpStorm usually trips up. For example:
$placeholders = join(', ', array_fill(0, count($ids), '?'));
$stmt = $db->prepare("SELECT * FROM foo WHERE bar IN ($placeholders)");
$stmt->execute($ids);
or:
$db->prepare(sprintf('INSERT .. (%s) ..', $foo))
These understandably trigger some sort of SQL syntax error warning in PhpStorm. Is there any way to suppress those without outright disabling SQL language parsing?
I found the definitive answer to this problem from Jetbrains Blog:
http://blog.jetbrains.com/phpstorm/2014/11/database-language-injection-configuration/
You need to add \%\w+. in your Tools > Database section.
You can have basic SQL parsing and syntax checking by following these two steps:
Set the dialect to Generic. In File -> Settings set the following:
Zoom
Then, disable the SQL dialect detection inspection:
Zoom
Confirm with Ok and you're done.
Of course it would be great if PHPStorm could resolve SQL Strings with variable substitution, but sadly that's not the case (yet).
The only solution I've come up with is to add a newline before the SQL like:
$sql = "\nINSERT INTO ...";
PhpStorm thinks it's a plain-old-string now and will not try to parse it as SQL.

SQL reserved word in an $array resulting in Error

I'm having a problem when I try to insert an array that has a reserved word on it.
It's really wierd I might say, take a look:
$sql="INSERT INTO sites (cat_id, cat_title, cat_parent, title, image, site_name, description) VALUES ('$_POST[cat_id]','$_POST[cat_title]','$_POST[cat_parent]','$title','$image','$site_name','$description')";
The array is comming from a opengraph fetch that I created but it's not important, the question is that sometimes when the array $title, for example, or $image has a reserved word like "use" on it, the sql return the error "Error: You have an error in your SQL syntax; check the manual that corresponds...."
so that's the case:
when the array $title = http://techcrunch.com/2012/08/28/flipboard-hits-20-million-users-3-billion-flips-per-month/ <---- it don't work and I receive the error above.
when the array is $title = http://techcrunch.com/2012/08/27/google-nexus-7-is-now-available-in-france-germany-and-spain/ <---- it works fine!
so I think that probably because theres a 'reserved word' in the array $title sometimes (or any other array that I'm using) that is returning the error... So theres any way that I could protect my arrays from this error?
Thanks! :)
EDIT:
SOLUTION
Ok! I followed the #dystroy and #tadman advice and used PDO instead of the regular mysql connection... I don't know if I'm totally secure against SQL injetion or attacks but it solve my problem with the reserved words. Now I can insert whatever content I have in an $array to the database.
If someone end up here with the same problem, that's what I did (please complain if you guys find any awkwardness):
$dbh = new PDO("mysql:host=MYHOST;dbname=MYDATABASE", "USER", "PASS");
$query = "INSERT INTO sites (cat_id, cat_title, cat_parent, title, image, site_name, description) VALUES (:cat_idpost, :cat_titlepost, :cat_parentpost, :titlepost, :imagepost, :site_namepost, :descriptionpost)";
$stmt = $dbh->prepare($query);
$stmt->bindParam(':cat_idpost', $cat_id);
$stmt->bindParam(':cat_titlepost', $cat_title);
$stmt->bindParam(':cat_parentpost', $cat_parent);
$stmt->bindParam(':titlepost', $titlepost);
$stmt->bindParam(':imagepost', $imagepost);
$stmt->bindParam(':site_namepost', $site_namepost);
$stmt->bindParam(':descriptionpost', $descriptionpost);
$cat_id = $_POST['cat_id'];
$cat_title = $_POST['cat_title'];
$cat_parent = $_POST['cat_parent'];
$titlepost = $title;
$imagepost = $image;
$site_namepost = $site_name;
$descriptionpost = $description;
$stmt->execute();
Thank you guys! :D
Always use prepared statement to insert strings in a database. Never simply concatenate them.
The problem isn't only reserverd words but all kind of errors (or attacks) due to special values or characters. Don't try to escape all this yourself : The database will handle that for you if you use a prepared statement.
Look at those samples based on today's recommended library for PHP/MySQL (PDO) : http://www.php.net/manual/en/pdo.prepared-statements.php
Escape reserved words like title with accents

Why does my INSERT sometimes fail with "no such field"?

I've been using the following snippet in developements for years. Now all of a sudden I get a DB Error: no such field warning
$process = "process";
$create = $connection->query
(
"INSERT INTO summery (process) VALUES($process)"
);
if (DB::isError($create)) die($create->getMessage($create));
but it's fine if I use numerics
$process = "12345";
$create = $connection->query
(
"INSERT INTO summery (process) VALUES($process)"
);
if (DB::isError($create)) die($create->getMessage($create));
or write the value directly into the expression
$create = $connection->query
(
"INSERT INTO summery (process) VALUES('process')"
);
if (DB::isError($create)) die($create->getMessage($create));
I'm really confused ... any suggestions?
It's always better to use prepared queries and parameter placeholders. Like this in Perl DBI:
my $process=1234;
my $ins_process = $dbh->prepare("INSERT INTO summary (process) values(?)");
$ins_process->execute($process);
For best performance, prepare all your often-used queries right after opening the database connection. Many database engines will store them on the server during the session, much like small temporary stored procedures.
Its also very good for security. Writing the value into an insert string yourself means that you must write the correct escape code at each SQL statement. Using a prepare and execute style means that only one place (execute) needs to know about escaping, if escaping is even necessary.
Ditto what Zan Lynx said about placeholders. But you may still be wondering why your code failed.
It appears that you forgot a crucial detail from the previous code that worked for you for years: quotes.
This (tested) code works fine:
my $thing = 'abcde';
my $sth = $dbh->prepare("INSERT INTO table1 (id,field1)
VALUES (3,'$thing')");
$sth->execute;
But this next code (lacking the quotation marks in the VALUES field just as your first example does) produces the error you report because VALUES (3,$thing) resolves to VALUES (3,abcde) causing your SQL server to look for a field called abcde and there is no field by that name.
my $thing = 'abcde';
my $sth = $dbh->prepare("INSERT INTO table1 (id,field1)
VALUES (3,$thing)");
$sth->execute;
All of this assumes that your first example is not a direct quote of code that failed as you describe and therefore not what you intended. It resolves to:
"INSERT INTO summery (process) VALUES(process)"
which, as mentioned above causes your SQL server to read the item in the VALUES set as another field name. As given, this actually runs on MySQL without complaint and will fill the field called 'process' with NULL because that's what the field called 'process' contained when MySQL looked there for a value as it created the new record.
I do use this style for quick throw-away hacks involving known, secure data (e.g. a value supplied within the program itself). But for anything involving data that comes from outside the program or that might possibly contain other than [0-9a-zA-Z] it will save you grief to use placeholders.