第1章什么是SQL注入11 1.转义字符处理不当 $QL数据库将单引号字符(‘)解析成代码与数据间的分界线:假定单引号外面的内容均是 需要运行的代码,而用单引号引起来的内容均是数据。因此,只需简单地在URL或Wb页面 (或应用)的字段中输入-一个单引号,就能快速识别出Wb站点是否会受到SQL注入攻击。下 面是一个非常简单的应用的源代码,它将用户输入直接传递给动态创建的SQL语句: /build dynamic SOL statement SSQL "SELECT FROM table WHERE field 'GET["input"]'"; /execute sql statement Sresult mysql_query(SSQL); /check to see how many rows were returned from the database Srowcount mysql_num_rows(Sresult) /iterate through the record set returned Srow 1; while (Sdb field mysql fetch assoc(Sresult)){ if (Srow <Srowcount){ print $db_field〔srow】,"<BR>"; Srow++; 如果将一个单引号字符作为该程序的输入,则可能会出现下列错误中的一种。具体出现何 种错误取决于很多环境因素,比如编程语言、使用的数据库以及采用的保护和防御技术。 Warning:mysql_fetch_assoc():supplied argument is not a valid MySQL result resource 我们还可能会收到下列错误,这些错误提供了关于如何构造SQL语句的有用信息: You have an error in your SoL syntax;check the manual that corresponds to your MySQL server version for the right syntax to use near "VALUE" 出现该错误是因为单引号字符被解析成了字符串分隔符。运行时执行的$QL查询在语法 上存在错误(它包含多个字符串分隔符),所以数据库抛出异常。$QL数据库将单引号字符看作 特殊字符(字符串分隔符)。在$QL注入攻击中,攻击者使用该字符“转义”开发人员的查询 以便构造自己的查询并加以执行。 单引号字符并不是唯一的转义字符。比如在Oracle中,空格()、双竖线()、逗号()、点 号()、(*)以及双引号字符(“)均具有特殊含义。例如: --The pipe [I]character can be used to append a function to a value. --The function will be executed and the result cast and concatenated. http://www.victim.com/id=1 I utl_inaddr.get_host_address(local)-- --An asterisk followed by a forward slash can be used to terminate a --comment and/or optimizer hint in Oracle http://www.victim.com/hint=*/from dual--
12SQL注入攻击与防御 2.类型处理不当 到目前为止,部分读者可能认为要避免被$QL注入利用,只需对输入进行验证、消除单 引号字符就足够了。确实,很多Wb应用开发人员已经陷入这样一种思维模式。我们刚才讲 过,单引号字符会被解析成字符串分隔符并作为代码与数据间的分界线。处理数字数据时,不 需要使用单引号将数字数据引起来,否则,数字数据会被当作字符串处理。 下面是一个非常简单的应用的源代码,它将用户输入直接传递给动态创建的$QL语句。 该脚本接收一个数字参数($userid)并显示该用户的信息。假定该查询的参数是整数,因此写的 时候没有加单引号。 /build dynamic SQL statement SSQL "SELECT FROM table WHERE field '$GET["userid"]" /execute sql statement sresult mysql_query(SSQL); /check to see how many rows were returned from the database Srowcount mysql_num_rows(Sresult); //iterate through the record set returned Srow 1; while ($db_field mysql_fetch assoc(Sresult))( if (Srow <Srowcount) print $db field[Srow]."<BR>"; $row十+: MySQL提供了一个名为LOAD FILE的函数,它能够读取文件并将文件内容作为字符串 返回。要使用该函数,必须保证读取的文件位于数据库服务器主机上,然后将文件的完整路径 作为输入参数传递给函数。调用该函数的用户还必须拥有FLE权限。如果将下列语句作为输 入,那么攻击者便会读取/etc/passwd文件中的内容,该文件中包含系统用户的属性和用户名: 1 UNION ALL SELECT LOAD_FILE('/etc/passwd')-- 提示: MySQL还包含一个内置命令,可使用该命令来创建系统文件并进行写操作。还可使用下 列命令向Web根目录写入一个Web shell以便安装一个可远程交互访问的Web shell: 1 UNION SELECT "<?system($_REQUEST['cmd']);?>INTO OUTFILE "/var/www/html/victim.com/cmd.php"-- 要想执行LOAD FILE和SELECT INTO OUTFILE命令,易受攻击应用所使用的MySQL用 户就必须拥有FLE权限(FLE是一种管理员权限).例如,Oot用户在默认情况下拥有该权限。 攻击者的输入直接被解析成了$QL语法,所以攻击者没必要使用单引号字符来转义查询。 下列代码更加清晰地说明了构造的SQL语句:
第1章什么是SQL注入13 SELECT FROM TABLE WHERE USERID 1 UNION ALL SELECT LOAD_FILE ('/etc/passwd')-- 3.查询集处理不当 有时需要使用动态SQL语句对某些复杂的应用进行编码,因为在程序开发阶段可能还不 知道要查询的表或字段(或者还不存在)。比如与大型数据库交互的应用,这些数据库在定期创 建的表中存储数据。还可以虚构一个应用,它返回员工的时间安排数据。将每个员工的时间安 排数据以包含当月的数据格式(比如2008年1月,其格式为employee employee-id01012008) 输入到新的表中。Wb开发人员应该支持根据查询执行的日期来动态创建查询语句。 下面是一个非常简单的应用的源代码,它将用户输入直接传递给动态创建的SQL语句, 该示例说明了上述问题。脚本使用应用产生的值作为输入,输入是一个表名加三个列名,之后 显示员工信息。该程序允许用户选择他希望返回的数据。例如,用户可以选择一个员工并查看 其工作明细、日工资或当月的效能图。 由于应用已经产生了输入,因而开发人员会信任该数据。不过,该数据仍可被用户控制, 因为它是通过GET请求提交的。攻击者可使用自己的表和字段数据来替换应用所产生的值。 /build dynamic SQL statement SSQL "SELECT $_GET ["column1"],$_GET["column2"],GET ["column3"]FROM GET["table"]"; /execute sql statement sresult mysql_query(SSQL); /check to see how many rows were returned from the database Srowcount mysql_num_rows (Sresult); /iterate through the record set returned Srow 1; while (Sdb field mysql fetch assoc(Sresult))( if (Srow <Srowcount) print $db_field[srow]."<BR>"; SrOW++; 如果攻击者操纵HTTP请求并使用users替换表名,使用user、password和Super_priv字 段替换应用产生的列名,那么他便可以显示系统中数据库用户的用户名和口令。下面是他在使 用应用时构造的URL: .http://www.victim.com/user_details.php?table-users&column1-user&column2-password column3=Super_priv 如果注入成功,那么将会返回下列数据而非时间安排数据。虽然这是一个计划好的例子, 但现实中很多应用都是以这种方式构建的。我已经不止一次碰到过类似的情况
14SQL注入攻击与防御 I user I password Super priv + I root *2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19 y sqlinjection *2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19 I N I owned *2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19N 4.错误处理不当 错误处理不当会为Wb站点带来很多安全方面的问题。最常见的问题是将详细的内部错 误消息(如数据库转储、错误代码等)显示给用户或攻击者。这些消息会泄露从来都不应该显示 的实现上的细节。这些细节会为攻击者提供与网站潜在缺陷相关的重要线索。例如,攻击者可 使用详细的数据库错误消息来提取如何修改或构造注入来避开开发人员查询的信息,并得知如 何操纵数据库以便取出附加数据的信息或者在某些情况下转储数据库中所有数据的信息。 下面是一个简单的使用C#语言编写的ASP .NET应用示例,它使用Microsoft SQL Server 数据库服务器作为后台(因为该数据库提供了非常详细的错误消息)。当用户从下拉列表中选择 一个用户标识符时,脚本会动态产生并执行一条SQL语句: private void SelectedIndexChanged(object sender,System.EventArgs e //Create a Select statement that searches for a record /matching the specific id from the Value property. string SQL; SQL "SELECT FROM table " SQL +"WHERE ID-"UserList.SelectedItem.Value +"" /Define the ADO.NET obiects. OleDbConnection con new oleDbConnection(connectionstring); oleDbCommand cmd new oleDbCommand(SQL,con); oleDbDataReader reader; /Try to open database and read information. try con.open () reader -cmd.ExecuteReader(); reader.Read(); lblResults.Text "<b>"reader["LastName"]; lbiResults.Text +=","reader["FirstName"]+"</b><br>"; 1blResults.Text "ID:reader["ID"]+"<br>"; reader.close(); 1 catch (Exception err) lblResults.Text "Error getting data." lblResults.Text +-err.Message; finally con.Close();
第1章什么是SQL注入15 如果攻击者想操纵HTTP请求并希望使用自己的SQL语句来替换预期的D值,则可以使用 信息量非常大的$QL错误消息来获取数据库中的值。例如,如果攻击者输入下列查询,那么执 行SQL语句时会显示信息量非常大的SQL错误消息,其中包含了Wb应用所使用的RDBMS 版本: 'and 1 in (SELECT @@version) 虽然这行代码确实捕获了错误条件,但它并未提供自定义的通用错误消息。相反,攻击者 可以通过操纵应用和错误消息来获取信息。第4章会详细介绍攻击者使用、滥用该技术的过程 及场景。下面是返回的错误信息: Microsoft OLE DB Provider for ODBC Drivers error '80040e07' [Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value 'Microsoft SQL Server 2000 -8.00.534 (Inter X86)Nov 19 2001 13:23:50 Copyright (c)1988-2000 Microsoft Corporation Enterprise Edition on Windows NT 5.0 (Build 2195:Service Pack 3)'to a column of data type int. 5.多个提交处理不当 白名单(white listing)是一种除了白名单中的字符外,禁止使用其他字符的技术。用于验证 输入的白名单方法是指为特定输入创建一个允许使用的字符列表,这样列表外的其他字符均会 遭到拒绝。建议使用与黑名单(black list)截然不同的白名单方法。黑名单(black listing)是一种除 了黑名单中的字符外,其他字符均允许使用的技术。用于验证输入的黑名单方法是指创建能被 恶意使用的所有字符及其相关编码的列表并禁止将它们作为输入。现实中存在非常多的攻击类 型,它们能够以多种方式呈现,要想有效维护这样一个列表是一项非常繁重的任务。使用不可 接受字符列表的潜在风险是:定义列表时很可能会忽视某个不可接受的字符或者忘记该字符一 种或多种可选的表示方式。 大型Wb开发项目会出现这样的问题:有些开发人员会遵循这些建议并对输入进行验证, 而其他开发人员则不以为然。对于开发人员、团队甚至公司来说,彼此独立工作的情形并不少 见,很难保证项目中的每个人都遵循相同的标准。例如,在评估应用的过程中,经常会发现几 乎所有输入均进行了验证,但坚持找下去的话,就会发现某个被开发人员忘记验证的输入。 应用开发人员还倾向于围绕用户来设计应用,他们尽可能使用预期的处理流程来引导用 户,认为用户将遵循他们已经设计好的逻辑顺序。例如,当用户已到达一系列表单中的第三个 表单时,他们会期望用户肯定己完成了第一个和第二个表单。但实际上,借助直接的URL乱 序来请求资源,能够非常容易地避开预期的数据流程。以下面这个简单的应用为例: /process form 1 if (GET["form"]="forml"( /is the parameter a string? if (is string($GET["param"])) /get the length of the string and check if it is within the /set boundary? if (strlen($GET["param"])<Smax){