文章目录
一、SQL注入原理
SQL注入漏洞主要形成的原因是在数据交互中,前端的数据传入到后台处理时,没有做严格的判断,导致其传入的"数据"拼接到SQL语句中后,被当作SQL语句的一部分执行,从而导致数据库受损(被脱库、被删除、甚至整个服务器权限沦陷)。
示例如下:
//如下是利用一段登录验证时查询数据库的代码login.php:
//连接到数据库
$conn = mysql_connect("localhost","username","password");
//使用输入动态创建 SQL 语句
$query = "SELECT userid FROM Users WHERE user = '$_GET["user"]' " .
"AND password = '$_GET["password"]'";
//对数据库执行查询
$result = mysql_query($query);
//检查从数据库返回了多少条记录
$rowcount = mysql_num_rows($result);
//如果返回一条记录,那么验证必定是有效的,因此将用户导航到 admin 页面
if ($rowcount !=0) { header("Location: admin.php");
//如果没有返回记录,那么验证必定是无效的
else { die('Incorrect username or password, please try again.')}
在输入用户名user和密码passwd时,login.php会动态的创建一条SQL语句在数据库中进行查询。
SELECT userid FROM Users WHERE user='user' AND password='passwd';
正常情况下,这个流程是没有问题的,数据库中存在user和passwd,页面登陆成功;不存在时,返回Incorrect username or password, please try again。但是在这个过程中,代码执行的结果始终为0或1
。如果我们此时利用数据去破坏0或1
这个查询逻辑,那么就会造成SQL注入。如下对password参数进行:
SELECT userid FROM Users WHERE user='user' AND password='passwd'or '1'='1';
这个sql查询语句由于(or 1=1),导致恒为1,所以此时无论用户名输入什么,这个查询语句都会返回1,导致可以直接登录。
二、Web应用的工作原理
数据库驱动的Web应用通常都包含一个后台数据库和很多Web页面,这些页面中包含了使用某种编程语言编写的服务器端脚本,而这些脚本则能够根据 Web 页面与用户的交互从数据库中提取特定的信息。
数据库驱动的 Web 应用通常包含三层:表示层(Web 浏览器或呈现引擎)、逻辑层(如 C#、ASP、.NET、PHP、JSP 等编程语言)和存储层(如 Microsoft SQL Server、MySQL、Oracle 等数据库)。Web 浏览器(表示层,如 Internet Explorer、Safari、Firefox等)向中间层(逻辑层)发送请求,中间层通过查询、更新数据库(存储层)来响应该请求。
1、三层web架构
表示层:是应用的最高层,它显示服务相关的信息,并通过将结果输出到浏览器/客户端层和网络上的所有其他层来与应用架构的其他层进行通信。
逻辑层:是从表示层剥离出来的,作为单独的一层,它通过执行细节处理来控制应用的功能。
数据层:包括数据库服务器,用于对信息进行存储和检索。数据层保证数据独立于应用服务器或业务逻辑。将数据作为单独的一层还可以提高程序的可扩展性和性能。
下图中,Web 浏览器(表示层)向中间层(逻辑层)发送请求,中间层通过查询、更新数据库(存储层)响应该请求。三层架构中一条最基本的规则是:表示层不应直接与数据层通信。在三层架构中,所有通信都必须经过中间层。从概念上看,三层架构是一种线性关系。
1、用户激活Web浏览器并连接到URL;
2、位于逻辑层的Web服务器从文件系统中加载脚本并将其传递给脚本引擎,脚本引擎负责解析并执行脚本;
3、脚本使用数据库连接程序打开存储层连接并对数据库执行SQL语句;
4、数据库将数据返回给数据库连接程序,后者将其传递给逻辑层的脚本引擎;
5、逻辑层再将Web页面以HTML格式返回给表示层的用户的Web 浏览器之前,执行相关的应用或业务逻辑规则;
6、用户的Web浏览器呈现 HTML并借助代码的图形化表示展现给用户。
所有操作都将在数秒内完成,并且对用户是透明的。
2、N层应用程序开发范式
N层架构中的应用服务器负责将 API(应用编程接口)提供给业务逻辑和业务流程以供程序使用。可以根据需要引入其他的 Web 服务器。此外,应用服务器可以与多个数据源通信,包括数据库、大型机以及其他旧式系统。
下图描绘了一种简单的 4 层架构。Web浏览器(表示层)向中间层(逻辑层)发送请求,后者依次调用由位于应用层的应用服务器提供的 API,应用层通过查询、更新数据库(存储层)来响应该请求。
1、用户激活Web浏览器并连接到URL;
2、位于逻辑层的Web服务器从文件系统中加载脚本并将其传递给脚本引擎,脚本引擎负责解析并执行脚本;
3、脚本调用由位于应用层的应用服务器提供的API;
4、应用服务器使用数据库连接程序打开存储层连接并对数据库执行SQL语句;
5、数据库将数据返回给数据库连接程序;
6、应用服务器在将数据返回给Web服务器之前先执行相关的应用或业务逻辑规则;
7、Web服务器在将数据以HTML格式返回给表示层的用户的Web浏览器之前先执行最后的有关逻辑。
8、用户的Web浏览器呈现HTML并借助代码的图形化表示展现给用户。
所有操作都将在数秒内完成,并且对用户是透明的。
分层架构的基本思想是将应用分解成多个逻辑块(或层),其中每一层都分配有通用或特定的角色。各个层可以部署在不同的机器上,或者部署于同一台机器上,但实际在概念上是彼此分离的。使用的层越多,每一层的角色就越具体。将应用的职责分成多个层能使应用更易于扩
展,可以更好地为开发人员分配开发任务,提高应用的可读性和组件的可复用性。
三、SQL注入的产生原因
SQL是访问Microsoft SQL Server、Oracle、MySQL、Sybase 和 Informix(以及其他)数据库服务器的标准语言。大多数 Web 应用都需要与数据库进行交互,并且大多数Web应用编程语言(如ASP、C#、.NET、Java和PHP)均提供了可编程的方法来与数据库连接并进行交互。如果Web应用开发人员无法保证在将从Web表单、cookie及输入参数等收到的值传递给SQL查询(该查询在数据库服务器上执行)之前已经对其进行过验证,那么通常会出现SQL注入漏洞。如果攻击者能够控制发送给SQL查询的输入,并且能够操纵该输入将其解析为代码而非数据,那么攻击者就很有可能在后台数据库执行该代码。
示例如下:
//在 PHP 中动态构造 SQL 语句的字符串
$query = "SELECT * FROM table WHERE field = '$_GET["input"]'";
//在.NET 中动态构造 SQL 语句的字符串
query = "SELECT * FROM table WHERE field = '" +
request.getParameter("input") + " ' ";
如果在将输入传递给动态创建的语句之前,未对代码进行验证或者编码,那么攻击者会将sql语句作为输入提供给应用并将sql语句传递给数据库加以执行。
经过上述代码构造如下SQL语句:
SELECT * FROM TABLE WHERE FIELD = 'input'
此过程中可能产生如下问题导致SQL注入问题:
1、转义字符处理不当
SQL 数据库将单引号字符(')解析成代码与数据间的分界线:单引号外面的内容均是需要运行的代码,而用单引号引起来的内容均是数据。因此,只需在 URL 或 Web 页面(或应用)的字段中输入一个单引号,就能快速识别出 Web 站点是否会受到 SQL 注入攻击。下面是一个应用的源代码,它将用户输入直接传递给动态创建的 SQL 语句:
//构造动态 SQL 语句
$SQL = "SELECT * FROM table WHERE field = '$_GET["input"]'";
//执行 SQL 语句
$result = mysql_query($SQL);
//检查从数据库返回了多少条记录
$rowcount = mysql_num_rows($result);
//迭代返回的记录集
$row = 1;
while ($db_field = mysql_fetch_assoc($result)) {
if ($row <= $rowcount){
print $db_field[$row] . "<BR>";
$row++;
}
}
如果将一个单引号字符作为该程序的输入,那么可能会出现下列错误中的一种。具体出现
何种错误取决于很多环境因素,比如编程语言、使用的数据库以及采用的保护和防御技术:
Warning: mysql_fetch_assoc(): supplied argument is not a valid MySQL
result resource
我们还可能会收到下列错误,通过这些错误我们可以来构造SQL语句:
You have an error in your SQL syntax; check the manual that corresponds
to your MySQL server version for the right syntax to use near ''VALUE''
出现该错误是因为单引号字符被解析成了字符串分隔符。运行时执行的 SQL 查询在语法上存在多个字符串分隔符,所以数据库抛出异常。在 SQL 注入攻击中,攻击者使用该字符 “转义”开发人员的查询以便构造自己的查询并加以执行。
单引号字符并不是唯一的转义字符。比如在 Oracle 中,空格( )、双竖线(| |)、逗号(,)、点
号(.)、(*/)以及双引号字符(")均具有特殊含义。例如:
1、管道字符[|]用于为一个值追加一个函数,函数将被执行,函数的结果将转换并与前面的值连接。
http://www.victim.com/id=1 || utl_inaddr.get_host_address(local)--
2、星号后跟一个正斜线,用于结束注释或 Oracle 中的优化提示
http://www.victim.com/hint=*/ from dual—
无论是进行攻击还是防御,熟悉数据库的各种特性都是非常重要的。例如在 SAP MAX DB
3、(SAP DB)中,开始定界符是由一个小于符号和一个感叹号组成的:
http://www.victim.com/id=1 union select operating system from sysinfo.
version--<!
2、类型处理不当
如下脚本接收一个数字参数($userid)并显示该用户的信息。假定该查询的参数是整数,因此写的时候没有加单引号:
//构造动态 SQL 语句
$SQL = "SELECT * FROM table WHERE field = $_GET["userid"]"
//执行 SQL 语句
$result = mysql_query($SQL);
//检查从数据库返回了多少条记录
$rowcount = mysql_num_rows($result);
//迭代返回的记录集
$row = 1;
while ($db_field = mysql_fetch_assoc($result)) {
if ($row <= $rowcount) {
print $db_field[$row] . "<BR>";
$row++;
}
}
MySQL 提供了一个名为 LOAD_FILE
的函数,它能够读取文件并将文件内容作为字符串返回。要使用该函数,必须保证读取的文件位于数据库服务器主机上,然后将文件的完整路径作为输入参数传递给函数。调用该函数的用户还必须拥有FILE权限。如果将下列语句作为输入,那么攻击者便会读取/etc/passwd 文件中的内容,该文件中包含系统用户的属性和用户名:
1 UNION ALL SELECT LOAD_FILE('/etc/passwd')--
MySQL 还包含一个内置命令,可使用该命令来创建系统文件并进行写操作。还可使用下列命令向 Web 根目录写入一个Webshell以便安装一个可远程交互访问的Webshell:
1 UNION SELECT "<? system($_REQUEST['cmd']); ?>" INTO OUTFILE
"/var/www/html/victim.com/cmd.php" –
要想执行 LOAD_FILE 和 SELECT INTO OUTFILE
命令,易受攻击应用所使用的 MySQL用户就必须拥有FILE权限(FILE 是一种管理员权限)。例如,root 用户在默认情况下拥有该权限,攻击者的输入直接被解析成了SQL语法,所以攻击者没必要使用单引号字符来转义查询。
3、查询语句组装不当
有时需要使用动态SQL语句对某些复杂的应用进行编码,因为在程序开发阶段可能还不知道要查询的表或字段(或者还不存在)。比如与大型数据库交互的应用,这些数据库在定期创建的表中存储数据。
下面是一个应用的源代码,它将用户输入直接传递给动态创建的 SQL 语句,脚本使用应用产生的值作为输入,输入是一个表名加三个列名,之后显示员工信息。该程序允许用户选择他希望返回的数据。例如,用户可以选择一个员工并查看其工作明细、日工资或当月的效能图。由于应用已经产生了输入,因而开发人员会信任该数据。不过,该数据仍可被用户控制,因为它是通过 GET 请求提交的。攻击者可使用自己的表和字段数据来替换应用产生的值。
//构造动态 SQL 语句
$SQL = "SELECT". $_GET["column1"]. ",". $_GET["column2"]. ",".
$_GET["column3"]. "FROM". $_GET["table"];
//执行 SQL 语句
$result = mysql_query($SQL);
//检查从数据库返回了多少条记录
$rowcount = mysql_num_rows($result);
//迭代返回的记录集
$row = 1;
while ($db_field = mysql_fetch_assoc($result)) {
if ($row <= $rowcount) {
print $db_field[$row] . "<BR>";
$row++;
}
}
如果攻击者操纵 HTTP 请求并使用值 users 替换表名,使用 user、password 和 Super_priv
字段替换应用产生的列名,那么他便可以显示系统中数据库用户的用户名和口令。下面是他在使用
应用时构造的 URL:
http://www.victim.com/user_details.php?table=users&column1=user&column2
=password&column3=Super_priv
如果注入成功,那么将会返回user、password、Super_priv数据而非时间安排数据。
4、错误处理不当
错误处理不当最常见的问题是将详细的内部错误消息(如数据库转储、错误代码等)显示给用户或攻击者,这些错误消息会泄露不应该显示的实现细节,而这些细节会为攻击者提供与网站潜在缺陷相关的重要线索。
例如,攻击者可利用详细的数据库错误消息来提取信息,从而知道如何修改或构造注入以避开开发人员的查询,并得知如何操纵数据库以便取出附加数据的信息,或者在某些情况下转储数据库(Microsoft SQL Server)中所有数据的信息。
下面是一个使用C#语言编写的ASP.NET应用示例,它使用Microsoft SQL Server数据库服务器作为后台(该数据库提供了非常详细的错误消息)。当用户从下拉列表中选择一个用户标识符时,脚本会动态产生并执行一条 SQL 语句:
private void SelectedIndexChanged(object sender,System.EventArgs e )
{
//创建一条 Select 语句,查询 id 值与 Value 属性相匹配的记录
string SQL;
SQL = "SELECT * FROM table ";
SQL += "WHERE ID=" + UserList.SelectedItem.Value + "";
//定义 ADO.NET 对象
OleDbConnection con = new OleDbConnection(connectionString);
OleDbCommand cmd = new OleDbCommand(SQL, con);
OleDbDataReader reader;
//尝试打开数据库并读取信息
try
{
con.Open();
reader = cmd.ExecuteReader();
reader.Read();
lblResults.Text = "<b>" + reader["LastName"];
lblResults.Text += " ," + reader["FirstName"] + "</b><br>";
lblResults.Text += "ID: " + reader["ID"] + "<br>";
reader.Close();
}
catch (Exception err)
{
lblResults.Text = "Error getting data. " ;
lblResults.Text += err.Message;
}
finally
{
con.Close();
}
}
如果攻击者想操纵 HTTP 请求并希望使用自己的SQL句来替换预期的 ID 值,可以使用信息量非常大的 SQL 错误消息来获取数据库中的值。
例如,如果攻击者输入下列查询,那么执行SQL语句时会显示信息量非常大的SQL错误消息,其中包含了Web应用所使用RDBMS版本:
' and 1 in (SELECT @@version) –
下面是返回的错误信息:
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 (Intel 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、多个提交处理不当
白名单:是一种除了白名单中的字符外,禁止使用其他字符的技术。
黑名单:是一种除了黑名单中的字符外,其他字符均允许使用的技术。
使用不可接受字符列表的潜在风险是:定义列表时很可能会忽视某个不可接受的字符或者忘记该字符一种或多种可选的表示方式。
大型 Web 开发项目会出现这样的问题:有些开发人员会遵循这些建议并对输入进行验证,而其他开发人员则不以为然。对于开发人员、团队甚至公司来说,彼此独立工作的情形并不少见,很难保证项目中的每个人都遵循相同的标准。例如,在评估应用的过程中,经常会发现几乎所有输入均进行了验证,但坚持找下去的话,就会发现某个被开发人员忘记验证的输入。
应用程序开发人员还倾向于围绕用户来设计应用,他们尽可能使用预期的处理流程来引导用户,认为用户将遵循他们已经设计好的逻辑顺序。例如,当用户已到达一系列表单中的第三个表单时,他们会期望用户肯定已完成了第一个和第二个表单。但实际上,借助直接的URL乱序来请求资源,能够非常容易地避开预期的数据流程。以下面应用为例:
// 处理表单 1
if ($_GET["form"] = "form1"{
//参数是否是一个字符串?
if (is_string($_GET["param"])) {
//获取字符串的长度并检查是否在指定的范围内?
if (strlen($_GET["param"]) < $max){
//将字符串传递给一个外部校验函数
$bool = validate(input_string , $_GET["param"]);
if ($bool = true) {
//继续处理
}
}
}
}
//处理表单 2
if ($_GET["form"] = "form2"){
//由于第一个表单已经验证过参数,因为这里无须再验证参数
$SQL = "SELECT * FROM TABLE WHERE ID = $_GET["param"]";
//执行 SQL 语句
$result = mysql_query($SQL);
//检查从数据库返回了多少条记录
$rowcount = mysql_num_rows($result);
$row = 1;
//迭代返回的记录集
while ($db_field = mysql_fetch_assoc($result)) {
if ($row <= $rowcount) {
print $db_field[$row] . "<BR>";
$row++;
}
}
}
由于第一个表单已经进行过输入验证,因此该应用程序的开发人员没有想到第二个表单也需要验证输入。攻击者将直接调用第二个表单而不使用第一个表单,或是简单地向第一个表单提交有效数据,然后操纵要向第二个表单提交的数据。下面的第一个 URL 会失败,因为需要验证输入。第二个 URL 则会引发成功的 SQL 注入攻击,因为输入未作验证:
[1] http://www.victim.com/form.php?form=form1¶m=' SQL Failed --
[2] http://www.victim.com/form.php?form=form2¶m=' SQL Success --
6、数据库配置问题
数据库带有很多默认的用户预安内容。
SQL Server使用sa作为数据库系统管理员。
Oracle再创建数据库时通常会创建SYS、SYSTEM、DBSNMP、OUTLN用户等。
MySQL使用root和anonymous用户账户。
有些系统和数据库管理员在安装数据库服务器时允许以root、SYSTEM、Administrator特权账户执行操作。
每一种类型的数据库服务器都施加了自己的访问控制模型,它们为用户账户分配多种权限来禁止、拒绝、授权、支持数据访问和(或)执行内置存储过程、功能或特性。不同类型的数据库服务器默认还支持通常超出需求但能够被攻击者修改的功能(xp_cmdshell、OPENROWSET、LOAD_FILE、ActiveX 以及 Java 支持等)。
应用开发人员在编写程序代码时,通常使用某个内置的权限账户来连接数据库,而不是根据程序需要来创建特定的用户账户。这些功能强大的内置账户可以在数据库上执行很多与程序需求无关的操作。当攻击者利用应用中的 SQL 注入漏洞并使用授权账户连接数据库时,他可以在数据库上使用该账户的权限执行代码。Web 应用开发人员应与数据库管理员协同工作,以保证程序的数据库访问在最低权限模型下运行,同时应针对程序的功能性需求适当地分离授权角色。
不过,要实现上述目标,攻击者需要了解可以获取哪些附加内容、目标机器安装了哪些其他数据库、存在哪些其他的表以及哪些有吸引力的字段!攻击者在利用 SQL 注入漏洞时,通常会尝试访问数据库的元数据。元数据是指数据库内部包含的数据,比如数据库或表的名称、列的数据类型或访问权限。有时也使用数据字典和系统目录等其他项来表示这些信息。
1、MySQL 服务器(5.0及之后的版本)的元数据位于INFORMATION_SCHEMA虚拟数据库中,
可通过SHOW DATABASES 和 SHOW TABLES 命令访问。
所有 MySQL 用户均有权访问该数据库中的表,但只能查看表中那些与该用户访问权限相对应的对象的行。
2、SQL Server 的原理与 MySQL 类似,可通过 INFORMATION_SCHEMA 或系统表(sysobjects、sysindexkeys、sysindexes、syscolumns、systypes 等)及(或)系统存储过程来访问元数据。
SQL Server 2005 引入了一些名为“sys.*”的目录视图,并限制用户只能访问拥有相应访问权限的对象。
所有的 SQL Server 用户均有权访问数据库中的表并可以查看表中的所有行,而不管用户是否对表或所查阅的数据拥有相应的访问权限。
3、Oracle 提供了很多全局内置视图来访问 Oracle 的元数据(ALL_TABLESALL_TAB_COLUMNS
等)。这些视图列出了当前用户可访问的属性和对象。
此外,以 USER_开头的视图只显示当前用户拥有的对象(例如,更加受限的元数据视图);
以 DBA_开头的视图显示数据库中的所有对象(例如,用于数据库示例且不受约束的全局元数据视图)。
DBA_元数据函数需要有数据库管理员(DBA)权限。下面是这些语句的示例:
①Oracle 语句,列举当前用户可访问的所有表:
SELECT OWNER, TABLE_NAME FROM ALL_TABLES ORDER BY TABLE_NAME;
②MySQL 语句,列举当前用户可访问的所有表和数据库:
SELECT table_schema, table_name FROM information_schema.tables;
③MSSQL 语句,使用系统表列举所有可访问的表:
SELECT name FROM sysobjects WHERE xtype = 'U';
④MSSQL 语句,使用目录视图列举所有可访问的表:
SELECT name FROM sys.tables;
参考:《SQL注入攻击与防御(第2版》----Justin Clarke