为了顺应当前形势和更好的发展,黑基网已于9月19日正式更名为【安基网】,域名更换为www.safebase.cn,请卸载旧的APP并安装新的APP,给您带来不便,敬请理解!谢谢

黑基Web安全攻防班
安基网 首页 IT技术 电脑技术 查看内容

国外的MYSQL注入教程 

2004-11-15 06:16| 投稿: computer

摘要: (来自Projectmoose第4月杂志) [] - [ 1 - Simple SQL Security by...
(来自Projectmoose第4月杂志) [] - [ 1 - Simple SQL Security by Netjester. ] - []Contact: netjester@zoite.net / irc.zoite.netI don't know about you, oh most knowledgable of SQL users, but when I learnt how to build a database driven website from various tutorials around the Internet, I never came across the phrase "SQL Injection"... in fact, I never came across any SQL security concepts at all. The purpose of this article is to inform the SQL programmer of the dangers of SQL injection, and ways to potentially minimize the severity of any exploits in your code.All the examples in this article are going to be using PHP. The reason for this choice is that PHP seems to be the server-side solution of choice - it's free and powerful. I'm also using MySQL, as it's all I've ever used. I think all the examples here should be applicable across different database servers, but I have mentioned the particular database servers affected where necessary. I assume knowledge of PHP database functions To start with, I'll create a database and a couple of tables, so I can demonstrate how a certain security risk could be exploited, and to what means. Rather than make a graphical representation of these tables, I'll write it in insertion SQL statements, so you can stick them on your own server and test it yourself. After all, the best way to learn is to get stuck in.####################### Database structure #######################CREATE DATABASE sqlsecdemo;CREATE TABLE Users (INT UserID NOT NULL AUTO_INCREMENT PRIMARY KEY, TEXT Username NOT NULL, TEXT Password NOT NULL);INSERT INTO Users SET Username="netjester", Password="blahblah123";INSERT INTO Users SET Username="someoneelse", Password="letmein";INSERT INTO Users SET Username="MrsThePlague", Password="God";CREATE TABLE Messages (INT MessageID NOT NULL AUTO_INCREMENT PRIMARY KEY, INT RelUserID NOT NULL, TEXT Message);INSERT INTO Messages SET RelUserID=1, Message="Hi everybody.";INSERT INTO Messages SET RelUserID=2, Message="What account number shall I have this large amount of money sent to?";INSERT INTO Messages SET RelUserID=3, Message="B825KM32-F please ";CREATE TABLE SomeMoreInfo (INT InfoID NOT NULL AUTO_INCREMENT PRIMARY KEY, TEXT Info);INSERT INTO SomeMoreInfo SET Info="Some info here";INSERT INTO SomeMoreInfo SET Info="Even more information.";INSERT INTO SomeMoreInfo SET Info="Information overload.";Now we have this foundation, I can start off by showing you the most basic of attacks. Consider this PHP statement:$result=pg_query($db,"SELECT Message FROM Messages WHERE RelUserID=".$_GET['uid']." ORDER BY MessageID");This may appear in a script that displays all messages posted by a certain user, reached from a page with a list of users to choose from. The URL for a page which displays all of someoneelse's posted messages would be something like http://www.sqlinjection.com/postedby.php?uid=2 so the full SQL statement, with substituted variables would be:SELECT Message FROM Messages WHERE RelUserID=2 ORDER BY MessageIDNow consider this URL: http://www.sqlinjection.com/postedb...20Messages%20--If you were to replace the %20's with spaces, as %20 is simply a URL-encoded space, you would see that the SQL statement sent to the server now reads like this:SELECT Message FROM Messages WHERE RelUserID=2; DELETE * FROM Messages -- ORDER BY MessageIDThe semicolon ends the first SQL statement, and the server is now ready for another... which we provide. The -- is the SQL comment syntax - all text following that will be ignored by the server. This would be included by an attacker in case there are more search clauses or ordering directives and such following "WHERE RelUserID=2", which there are. If it was left out, the attackers injected SQL statement would probably be invalid.MySQL is not vulnerable to this attack, as it only allows 1 statement per query, for exactly this reason. PostgreSQL is vulnerable however, and probably others too, as they allow multiple SQL statements per query.Protecting against this type of attack is fairly simple, and can be done in two ways. Ideally, both ways should be implemented. The first step is to add quote marks around the user-defined variable being comapared to RelUserID, like this:$result=pg_query($db,"SELECT Message FROM Messages WHERE RelUserID='".$_GET['uid']."' ORDER BY MessageID");So, when our attacker tries the URL above, the SQL statements becomes:SELECT Message FROM Messages WHERE RelUserID='2; DELETE * FROM Messages --' ORDER BY MessageIDThe result of this is to put all the users input into a string, which will then be compared against RelUserID. However, an attacker still has a way around this. Consider the effect of an attacker simply adding a ' to his input, thus unquoting the string, and then again we are at his or her mercy. This is quote possible:http://www.sqlinjection.com/postedby.php?uid=2';%20DELETE%20*%20FROM%20Messages%20--Our SQL statement then becomes:SELECT Message FROM Messages WHERE RelUserID='2'; DELETE * FROM Messages --' ORDER BY MessageIDSo it looks like we've made absolutely no progress at all. They key is to add backslashes to the user's input before each occcurrence of ' or ". This way, a user can no longer open or close a string. With this implemented, the above URL generates the SQL statement below:SELECT Message FROM Messages WHERE RelUserID='2'; DELETE * FROM Messages --' ORDER BY MessageIDBecause of the escaping of the quote, the string compared to the RelUserID is now "2'; DELETE * FROM Messages --", and the attack is now neutralised once again.Another precaution that can be taken against this kind of attack is simple data validation. As RelUserID is defined in the database as being an INT, comparing text to it seems illogical. So first of all, check that the value of $_GET['uid'] is in fact a number, and doesn't contain anything except digits. If it does, you can promptly stop execution and inform the user he or she is trying to do things that perhaps they shouldn't be doing. Here's a PHP function which will help you do that, and the way I'd implement it in the above example.function nj_isInteger($checkString) {if($checkString!='' && ereg("^[0-9]*$",$checkString)) {return true;} else {return false;}}if(!nj_isInteger($_GET['uid']))exit('User inputted UID was not an integer.');So there we have a good basis for some slightly more advanced SQL injection attacks.Now, imagine you wanted a page to select and display everything from a particular table. Which table it is the user decides. So it might look like this:mysql_query($db,"SELECT * FROM ".$_GET['table']);Here, injection can be achieved in much the same way. If the user gave the script the value "users WHERE Username='netjester'", they would be presented with my password. Which is bad.The way to protect against injection further than the table to select from, you again, add quotes around the value you're adding, and escape all quotes input by the user.However, an important lesson is to be learnt here. If you wish to give a user a choice such as which table will be queried, decide which tables you want to allow the user to access, and then, for example, put them in a PHP array, and use the particular array element required using a number provided by the user. That way, a user can only indirectly insert those table names you specify in the array.Ok, now how about we return to the postedby.php idea for another example.<?php/* http://www.sqlinjection.com/big/security/hole/index.php */$db=mysql_connect('localhost','root','rootpass123');mysql_select_db('sqlsecdemo');$sql='SELECT '.$_GET['toselect'].' FROM Messages';$result=mysql_query($db,$sql);while($row=mysql_fetch_array($result)) {echo $row['Message'].'<br>';}?>On first glance, one could be forgiven for thinking that even though this page allows a user to perform pretty much any SELECT query they like on our database, they still wouldn't be able to see their results. This is, however, not true, because of SQL's AS keyword. If you do not know what AS is for, it will be in any good SQL tutorial. So, consider this URL:http://www.sqlinjection.com/big/sec...0FROM%20Users--Because the password field has been given the name "Message", every time the script goes to print a supposedly public message, it actually prints one of our users passwords!The rule here is again to delimit any user input with quotes, and escape any quotes within that input. Better yet, give the user an indirect choice of what to add to the SQL statement, so you still have 100% control of what goes in and out of the server. Here's an example of a PHP script that uses indirection.<?php// indirect.php$selectTables=array('Messages','SomeMoreInfo');if(isset($_GET['action']) && $_GET['action']=='show') {$sql='SELECT * FROM '.$selectTables[$_GET['tableid']];$db=mysql_connect('localhost','dbuser','dbpass') or die('Couldn't connect');mysql_select_db('sqlsecdemo') or die('Couldn't select sqlsecdemo database');$result=mysql_query($sql,$db);while($row=mysql_fetch_array($result)) {foreach($row as $k => $v) {echo $k.': '.$v."<br>\n";}echo "<br>\n";}} else {for($i=0; $i<sizeof($selectTables); $i++) {echo '<a href="indirect.php?action=show&tableid='.$i.'">'.$selectTables[$i].'</a><br>';}}?>This is a very rough-and-ready script, just for demonstration purposes. As you can see, even though the first part of the SQL query is dynamically created from the user's choices, the user still has no way to add their own SQL code to the statement. They are simply allowed to choose from a set of predefined options.Now, this last section is specific to MySQL only. I have no idea about how users and privileges are defined on any other database servers, if at all. If you want to implement similar techniques on other servers, you will have to look into it yourself.I see a lot of people write PHP scripts where they make their everyday database calls after connecting to the database as root. As any UNIX enthusiast will know, root is not the account a client should be using to perform everyday tasks. It has unlimited power, and so, there is simply no room for error. Instead, less privileged users should be created by the root user, and instead these should be used for everyday database queries. In UNIX, this practice is both for security reasons and for safety reasons (to prevent a user accidentally deleting the universe). Here it is mainly for security reasons.For your average PHP driven website, four different types of query are likely to be needed. These are SELECT, INSERT, UPDATE and DELETE. We can create 4 different users for each type of query, with privileges allowing them to perform only queries of that type. Why would we want to do this? Well, let's take a moment to think about the statistics of it. The majority of your web applications MySQL queries will be SELECT queries. If there was an exploitable piece of code in your application using a SELECT query, made by a database user with the power to alter and delete information in the database the consequences would be very grave. If, however, the database user used to make the SELECT call only had the ability to read the database, the potential for damage is greatly reduced.There are two ways to set up MySQL database users and their permissions. The first is by using the GRANT and REVOKE commands provides by the MySQL environment, and the second is to manually edit the 'mysql' database tables. I go for the latter, as the syntax outline for GRANT and REVOKE in the MySQL manual basically scares me. And also I like to have a deeper understanding of what is actually going on with my server.So fire up your text-based MySQL client. If you don't know how to use this, you should really learn, because reading any further without understand that will just confuse you, and also it is a very useful tool during the development cycle. Hit up your 'mysql' database, and then do 'DESCRIBE user;' to see what the user table fields are. Here's the output of that command:+-----------------+-----------------+------+-----+---------+-------+| Field | Type | Null | Key | Default | Extra |+-----------------+-----------------+------+-----+---------+-------+| Host | char(60) binary | | PRI | | || User | char(16) binary | | PRI | | || password | char(16) | | | | || Select_priv | enum('N','Y') | | | N | || Insert_priv | enum('N','Y') | | | N | || Update_priv | enum('N','Y') | | | N | || Delete_priv | enum('N','Y') | | | N | || Create_priv | enum('N','Y') | | | N | || Drop_priv | enum('N','Y') | | | N | || Reload_priv | enum('N','Y') | | | N | || Shutdown_priv | enum('N','Y') | | | N | || Process_priv | enum('N','Y') | | | N | || File_priv | enum('N','Y') | | | N | || Grant_priv | enum('N','Y') | | | N | || References_priv | enum('N','Y') | | | N | || Index_priv | enum('N','Y') | | | N | || Alter_priv | enum('N','Y') | | | N | |+-----------------+-----------------+------+-----+---------+-------+The 'Host' field is for specifying a hostmask for the client connecting to the server. This can include a wildcard (%). If set to 'localhost', only connections from the same machine are allowed. This means a hacker elsewhere on the Internet can't connect to your server and start guessing at passwords - even if he or she has the right password, the connection will still be refused.The 'User' field is the name of the user. This can also be a wildcard, and will then apply to any user connecting to the server not specifying a username, but if you're wanting a secure server, you won't want to do this.The 'password' field contains a hash produced by using the built in 'PASSWORD' function in MySQL.The subsequent fields either contain 'Y' or 'N', depending on what you set them too. By default they are set to 'N' (do not allow the user this privilege).First of all I'd recommend you delete all the entries except for the root user with the localhost mask.Now let's set up a group of users for the previously mentioned database tasks.INSERT INTO user SET Host="localhost", User="selectuser", password=PASSWORD("passforselectuser");INSERT INTO user SET Host="localhost", User="deleteuser", password=PASSWORD("passfordeleteuser");INSERT INTO user SET Host="localhost", User="insertuser", password=PASSWORD("passforinsertuser");INSERT INTO user SET Host="localhost", User="updateuser", password=PASSWORD("passforupdateuser");You may have noticed I didn't set any priviledges with these commands, so all the privileges has defaulted to 'N'. This is because the 'user' table has global scope, ie. a delete privilege here means a user can delete anything from any database in the server. Instead, we set the privileges in the privilege tables with a narrower scope.Enter the 'db' table. This let's us set privileges for specific databases for specific users. A 'DESCRIBE db' produces the following:+-----------------+-----------------+------+-----+---------+-------+| Field | Type | Null | Key | Default | Extra |+-----------------+-----------------+------+-----+---------+-------+| Host | char(60) binary | | PRI | | || Db | char(64) binary | | PRI | | || User | char(16) binary | | PRI | | || Select_priv | enum('N','Y') | | | N | || Insert_priv | enum('N','Y') | | | N | || Update_priv | enum('N','Y') | | | N | || Delete_priv | enum('N','Y') | | | N | || Create_priv | enum('N','Y') | | | N | || Drop_priv | enum('N','Y') | | | N | || Grant_priv | enum('N','Y') | | | N | || References_priv | enum('N','Y') | | | N | || Index_priv | enum('N','Y') | | | N | || Alter_priv | enum('N','Y') | | | N | |+-----------------+-----------------+------+-----+---------+-------+The 'Host' and 'User' fields have the same meaning as in the 'user' table. The 'Db' field specifies which database subsequent privileges in the record are referring to. So, let's give our users the permissions they need. It's important to mention here that ALL the users should be given the SELECT privilege. This is because the SELECT privilege is needed for WHERE clauses, as in such cases information must be read from the database.INSERT INTO db SET Host="localhost", Db="sqlseqdemo", User="selectuser", Select_priv="Y";INSERT INTO db SET Host="localhost", Db="sqlseqdemo", User="deleteuser", Select_priv="Y", Delete_priv="Y";INSERT INTO db SET Host="localhost", Db="sqlseqdemo", User="insertuser", Select_priv="Y", Insert_priv="Y";INSERT INTO db SET Host="localhost", Db="sqlseqdemo", User="updateuser", Select_priv="Y", Update_priv="Y";Now we have our limited privilege users set up and ready to go. But first, we need to reload the information from the privilege tables into MySQL so that the changes we have made take effect. This can be done in two ways. The first is to simply shutdown and restart the server, and the second is to use mysqladmin or the mysql client.To do it with mysqladmin:shell> mysqladmin -u root -p flush-privilegesPassword: *********To do it from the mysql client, first login as the root user, then do:mysql> FLUSH PRIVILEGES;The new database users are now ready to be used. By sticking to these users, any SQL injection holes in your web applications can have their severity greatly decreased.That's it for this article, I hope you learnt something about the dangers and preventative measures surrounding SQL and MySQL.Resources:http://www.mysql.com/ - The official MySQL website.http://www.mysql.com/doc/en/index.html - Searchable MySQL manual with user comments.http://www.php.net/ - The official PHP website.http://www.php.net/manual/en/ - The PHP manual, with user comments

小编推荐:欲学习电脑技术、系统维护、网络管理、编程开发和安全攻防等高端IT技术,请 点击这里 注册黑基账号,公开课频道价值万元IT培训教程免费学,让您少走弯路、事半功倍,好工作升职加薪!



免责声明:本文由投稿者转载自互联网,版权归原作者所有,文中所述不代表本站观点,若有侵权或转载等不当之处请联系我们处理,让我们一起为维护良好的互联网秩序而努力!联系方式见网站首页右下角。


鲜花

握手

雷人

路过

鸡蛋

相关阅读

发表评论

最新评论

最新

返回顶部