西关注:原文来自数据库黑客大曝光—— 数据库服务器防护术,真正要研究数据库安全的可以买一本看看,支持正版。本文来自第十四章。
Sybase的攻击和防御是一个广泛的主题,因此本章试图提取问题的本质,并演示一些有趣的攻击和场景。在防御方面,如果可能的话,可以采取很多措施以增加攻击的难度。本章涉及了很多防御问题。
但是首先需要定位Sybase服务器并确定其配置。
14.1 发现目标
攻击Sybase服务器的第一步是在网络中定位服务器。本节描述了多种定位Sybase服务器的方法。
14.1.1 扫描Sybase
正如前面已经注意到的,Sybase通常监听一 些众所周知的TCP端口—— 5000-5004、8181和8182。配置Sybase监听不同的端口是很容易的,但是这些众所周知的端口真的帮了大忙。使用端口扫描工具,例如 Fyodor的nMap(http://www.insecure.org/nmap),是通过特定已知的开放端口定位主机的最好的方式。
如果可以在网络内远程访问Windows注册表,检查ODBC数据源将非常有用。在
HKEY_LOCAL_MACHINESoftwareODBC
内仅仅搜索SybaseServerName和NetworkAddress就可以看到主机名、IP地址和配置在有疑问的主机上的任何ODBC数据源的TCP端口。
如果公司有LDAP基础结构,也可以使用LDAP查询。
14.1.2 Sybase版本号
Sybase身份验证失败的响应数据包内包含了服务器的主(major)版本号和次(minor)版本号,因此嗅探身份验证失败的数据包可以获得版本号。该数据包看起来就像如下所示:
Ethernet Header
…
IP Header
…
TCP Header
Source port: 5000
Dest port: 1964
Flags: 0×18 (ACK PSH )
…
Raw Data
04 01 00 4e 00 00 00 00 ad 14 00 06 05 00 00 00 ( N )
0a 73 71 6c 20 73 65 72 76 65 72 0c 05 00 00 e5 ( sql server )
23 00 a2 0f 00 00 01 0e 05 5a 5a 5a 5a 5a 00 01 (# ZZZZZ )
00 0e 00 4c 6f 67 69 6e 20 66 61 69 6c 65 64 2e ( Login failed. )
0a 00 00 00 00 fd 02 00 02 00 00 00 00 00 ( )
紧跟在字符串“sql server”后面的4个字节是版本号—— 0×0c=12,0×05=5,因此该主机的版本号是12.5.0.0。用这种方式取得版本号并不是故事的全部—— 需要去验证并select @@version以得到那些信息—— 至少能得到某种暗示。发送前述数据包的服务器实际上正在运行ASE 12.5.1。
利用轻微删简的身份验证数据包有可能得到Sybase服务器的版本号。根据我们的试验,即使设置了身份验证日志选项,被删简的身份验证尝试也不会被记入日志。这就很好了,因为我们并不真的要尝试身份验证;我们只想在错误响应中获得服务器的版本信息。
为了可以把失败与成功的身份验证尝试记录到日志中,执行如下命令:
sp_configure ‘log audit logon failure’, 1
sp_configure ‘log audit logon success’, 1
在本章的末尾您可以找到实现一个简化工具的C源代码,该代码实现了通过删简的身份验证数据包来获得Sybase版本。
14.1.3 窥探身份验证
在默认的“即开即用”(out of the box)配置内,Sybase以明文形式在网络上传递口令。这是一个如此明显和著名的安全风险,以至于几乎所有的公司都曾采用某种缓解措施—— 或者采用Sybase的建议并部署一种更高级的身份验证方式,例如Kerberos,或者使用加密的IPSec通道或类似措施。虽然如此,默认配置偶尔还 是会出现,因此要留意从Sybase客户机到普通Sybase服务器端口5000-5004的通信量,那里也许很可能就有明文的口令。
因为支持多数本地数据库身份验证机制,也有可能 发动中间人(man-in-the-middle)攻击。当攻击者假装是数据库服务器时,就会出现该场景。通常,攻击者将不得不侵入一台DNS或WINS 服务器才能这么做,但是这依赖于网络内的名字解析基础结构,也许可以直接攻击。
14.2 攻击Sybase
本节讨论攻击Sybase服务器的技术。这些技术可应用于多种情况;例如,在“SQL注入”下列举的几个技术与攻击者可以发出任意SQL查询的每个情况都相关。
14.2.1 Sybase中的SQL注入
Sybase在SQL注入方面有一个特有的问 题,这部分地是因为它共享了基于Microsoft SQL Server的ancestral代码。因为Microsoft平台上的SQL注入已经被研究得非常透彻,并且因为Sybase共享了很多相同的性质,这 使得Microsoft SQL Server相当容易遭受SQL注入攻击(成批查询、全面subselect支持、非常有帮助的错误消息),即使攻击者对Sybase了解不多,也非常有 可能“找到附近的路”。此外,Sybase提供了一整套新功能,该功能可被攻击者在SQL注入攻击的上下文内利用,Java集成就是一个非常典型的示例。
本节提供了简短的SQL注入的最新技术资料,评估被到处宣扬的Sybase环境内的Microsoft SQL Server攻击技术的有效性,然后研究一些Sybase特有的技术,例如Java-In-SQL和通过proxy表进行的文件系统交互。
在深入涉及SQL注入机制之前,先简短地讨论一 下严重性和有效范围。如果Sybase服务器(和XP服务)正以低特权用户运行,Web应用程序用于连接的Sybase用户是低特权的,并且全面安装了最 新的补丁,那么就从根本上降低了SQL注入的实际影响。但这仍是个严重的问题,因为攻击者仍然可以对数据做应用程序可以做的每件事情,但是降低了攻击者把 数据库服务器作为进入内部网络的登陆场的可能性。
我们将在本章的后续部分对防御做大体上的讨论。
14.2.2 SQL注入基础
为了恰当地讨论SQL注入,需要一个可以充分演 示该问题的应用程序样例。通常人们最关心Web应用程序内的SQL注入,因此我们将用一个非常简单的Web应用程序做样例。对于应用程序样例在决定技术平 台时存在困难,因为Sybase支持很多种机制。因为Java是Sybase策略的一个关键部分,一个小型的基于Servlet的Java Web应用程序可能是恰当的样例。
下面是一个小型Java Servlet样例的源代码,该程序在Sybase默认数据库pubs2内按照包含特定搜索字符串的标题查询书籍。可以将其安装在任何支持Servlet的Web服务器上,例如Tomcat。
import java.io.*;
import java.lang.*;
import java.net.*;
import java.sql.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.sybase.jdbc2.jdbc.*;
public class BookQuery extends HttpServlet
{
public void init(ServletConfig config) throws.ServletException
{
super.init(config);
}
public void destroy(){}
protected void processRequest(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
PrintWriter out = response.getWriter();
try
{
response. setContentType (“text/html”);
out.println(“<html><headxtitle>Book Title Search
Results</title></head>”);
out.println(“<bodyxhl>Search results</hl>”) ;
Class.forName(“corn.Sybase.jdbc2.jdbc.SybDriver”);
Connection con = DriverManager.getConnection(“jdbc:
Sybase:Tds:sybtest:5000″,”sa”, “sapassword”);
Statement stmt = con.createStatement();
String search = request.getParameter(“search”);
ResultSet rs = stmt.executeQueryt”select * from
pubs2..titles where UPPER(title) like UPPER(‘%” + search + “%’)”);
int numberOfColumns = rs.getMetaData().getColumnCount();
rs.next();
out.println(“<TABLE border=l>”);
while( !rs.isAfterLast())
{
out.print(“<TR>”) ;
for(int i = 1; i <= numberOf Columns; i++)
{
out.print(“<TD>”);
out.print(rs.getString(i));
out.print(“</TD>”);
}
out.print(“</TR>”);
rs.next();
}
rs.close();
out.println(“</TABLE>”);
out.println(“</body>”);
out.println(“</html>”);
}
catch( SQLException e )
{
while( e != null )
{
out.println(e);
e = e.getNextException();
}
}
catch( Exception e )
{
out.printin(“Exception:” + e);
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse
response)
throws ServletException, IOException
{
processRequest(request, response);
}
protected void do Post(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
processRequest(request