《Java安全编码标准》一2.1 IDS00-J净化穿越受信边界的非受信数据

2.1 IDS00-J净化穿越受信边界的非受信数据

许多程序会接受来自未经验证的用户数据、网络连接,以及其他来自于非受信域的不可信数据,然后它们会将这些数据(经过修改或不经修改)穿过受信边界送到另一个受信域中。通常,这些数据是以字符串的形式出现的,并且它们会有既定的内部数据结构,这种数据结构必须能够被子系统所解析。同时,必须净化这些数据,因为子系统可能并不具备处理恶意输入信息的能力,这些未经净化数据输入很有可能包含注入攻击。
值得注意的是,程序必须对传递给命令行解释器或者解析器的所有字符串数据进行净化,这样才能保证解析器处理后或者解释器处理后的字符串是无害的。
许多命令行解释器和解析器提供了自己的数据净化和数据验证的方法。当存在这样的方法时,应当优先考虑它们,因为相对而言,自定义的净化方法会忽略一些特殊的情况,或者会忽略在解析器中所隐含着的那些复杂性。另一个问题是,当有新的功能添加到命令行解释器或者添加到解析器软件的时候,自定义的处理方法可能并不能得到很好的维护。

2.1.1 SQL注入

当初始的SQL查询被修改成另一个完全不同形式的查询的时候,就会出现SQL注入漏洞。执行这一被修改过的查询,可能会导致信息泄露或者数据被修改。防止SQL注入漏洞的主要方法是,净化并验证非受信输入,同时采用参数化查询的方法。
假设一个数据库具有用户名和密码数据,它可以用这些数据来对系统用户进行认证。每个用户名使用长度为8的字符串表示,密码使用长度为20的字符串表示。
一个用来验证用户的SQL命令如下所示:

SELECT * FROM db_user WHERE username='<USERNAME>' AND
????????????????????????????password='<PASSWORD>'

如果它可以返回任何记录,那么意味着用户名和密码是合法的。
然而,如果攻击者能够替代和中的任意字符串,它们可以使用下面的关于< USERNAME >的字符串进行SQL注入:

validuser' OR '1'='1

当将其注入到命令时,命令就会变成:

SELECT * FROM db_user WHERE username='validuser' OR '1'='1' AND
password=<PASSWORD>

如果validuser是一个有效的用户名,那么这条选择语句会选择出表中的validuser?记录。这个操作中不会使用密码,因为username='validuser'的判断条件为真;因此,不会检查那些在OR后面的条件。所以,只要在OR后面的部分是一个语法正确的SQL表达式,攻击者就可以由此获得validuser的访问权限。
同样,攻击者可以为提供字符串:

' OR '1'='1

这将会产生以下的命令:

SELECT * FROM db_user WHERE username='' AND password='' OR '1'='1'

这一次,‘1’=‘1’是永远成立的,它使得用户名和密码的验证是无效的,并且攻击者可以不需要正确的用户名或密码就能登录。

2.1.2 不符合规则的代码示例

在这个不符合规则的代码示例中,系统使用JDBC代码来认证用户。密码通过char数组传入,建立数据库连接,然后进行哈希编码。
遗憾的是,这段代码会出现SQL注入问题,因为在SQL语句中,sqlString?允许输入未经净化的输入参数。如前所述的攻击场景将再次出现。

class Login {
??public Connection getConnection() throws SQLException {
????DriverManager.registerDriver(new
??????????com.microsoft.sqlserver.jdbc.SQLServerDriver());
????String dbConnection =?
??????PropertyManager.getProperty("db.connection");
????// can hold some value like
????// "jdbc:microsoft:sqlserver://<HOST>:1433,<UID>,<PWD>"
????return DriverManager.getConnection(dbConnection);
??}

??String hashPassword(char[] password) {
????// create hash of password
??}

??public void doPrivilegedAction(String username, char[] password)
?????????????????????????????????throws SQLException {
????Connection connection = getConnection();
????if (connection == null) {
??????// handle error
????}
????try {
??????String pwd = hashPassword(password);

??????String sqlString = "SELECT * FROM db_user WHERE username = '"?
?????????????????????????+ username +
?????????????????????????"’ AND password = '" + pwd + "’";
??????Statement stmt = connection.createStatement();
??????ResultSet rs = stmt.executeQuery(sqlString);

??????if (!rs.next()) {
????????throw new SecurityException(
??????????"User name or password incorrect"
????????);
??????}

??????// Authenticated; proceed
????} finally {
??????try {
????????connection.close();
??????} catch (SQLException x) {
????????// forward to handler
??????}
????}
??}
}

2.1.3 符合规则的方案(PreparedStatement)

幸运的是,在JDBC类库中,提供了能够构建SQL命令并且处理非受信数据的API。在java.sql.PreparedStatement类中,可以对输入字符串进行转义,如果使用正确的话,可以防止SQL注入。下面的例子显示了基于组件的净化过程:
这个符合规则的方案修改了doPrivilegedAction()方法,使用PreparedStatement类来代替?java.sql.Statement。并且这段代码会验证username参数的长度,防止如果提交一个长用户名的时候可能会出现的攻击。

public void doPrivilegedAction(
??String username, char[] password
) throws SQLException {
??Connection connection = getConnection();
??if (connection == null) {
????// Handle error
??}
??try {
????String pwd = hashPassword(password);

????// Ensure that the length of user name is legitimate
????if ((username.length() > 8) {
??????// Handle error
????}

????String sqlString =?
??????"select * from db_user where username=? and password=?";
????PreparedStatement stmt = connection.prepareStatement(sqlString);
????stmt.setString(1, username);
????stmt.setString(2, pwd);
????ResultSet rs = stmt.executeQuery();
????if (!rs.next()) {
??????throw new SecurityException("User name or password incorrect");
????}

????// Authenticated, proceed
??} finally {
????try {
??????connection.close();
????} catch (SQLException x) {
??????// forward to handler
????}
??}
}

通过使用PreparedStatement?类的set*()方法,可以进行强类型检查。这样可以减少SQL注入漏洞,因为自动化例程会正确地转义双引号内的输入数据。需要注意的是,那些将数据插入数据库的查询也会使用PreparedStatement?。
XML注入
由于XML语言具有平台无关性、灵活性和相对简洁的特点,它适用于从远端过程调用到系统化存储、交换以及获取数据的各种场合。然而,因为XML的多功能性,所以XML也是被广泛攻击的对象。其中一种攻击称为XML注入。
如果用户有能力使用结构化XML文档作为输入,那么他能够通过在数据字段中插入XML标签来重写这个XML文档的内容。当XML解析器对这些标签进行解析和归类的时候,会将它们作为可执行的内容,从而导致重写许多数据成员。
下面是一段从一个在线商店应用中摘取出来的XML代码,主要用来查询后台数据库。用户可以指定该次购买的物品的数量。

<item>
??<description>Widget</description>
??<price>500.0</price>
??<quantity>1</quantity>
</item>

恶意用户可以在quantity?域中输入以下字符串来代替一个简单的数字:

1</quantity><price>1.0</price><quantity>1

结果会导致生成如下XML文档:

<item>
??<description>Widget</description>
??<price>500.0</price>
??<quantity>1</quantity><price>1.0</price><quantity>1</quantity>
</item>

通过用于XML的简单API(SAX)解析器(org.xml.sax?和javax.xml.parsers.SAXParser)可以解释该XML文件,这时第二个price域会覆盖第一个price域,从而使商品的价格被设置为$1。甚至于存在这样的可能,攻击者可以构造这样一个攻击:插入特殊字符,比如插入注释块或者CDATA分隔符,从而就可以扭曲XML文档正常表达的意思。

2.1.4 不符合规则的代码示例

在下面这段不符合规则的代码示例中,一个客户方法使用简单的字符串链接来创建一个XML查询,然后将其发送到服务器。在这时就有可能出现XML注入问题,因为这个方法并没有进行任何输入验证。

private void createXMLStream(BufferedOutputStream outStream,?
????????????????????????????String quantity) throws IOException {
??String xmlString;
??xmlString = "<item>\n<description>Widget</description>\n" +
??????????????"<price>500.0</price>\n" +
??????????????"<quantity>" + quantity + "</quantity></item>";
??outStream.write(xmlString.getBytes());
??outStream.flush();
}

2.1.5 符合规则的方案(白名单)

根据特定的数据和接收这些数据的命令解释器或者解析器的情况,必须使用一个适当的方法来净化这些非受信的用户输入。这个符合规则的方案使用白名单来净化输入数据。在这个方案中,处理方法要求输入的数字必须为0~9。

private void createXMLStream(BufferedOutputStream outStream,?
?????????????????????????????String quantity) throws IOException {
??// Write XML string if quantity contains numbers only.
??// Blacklisting of invalid characters can be performed?
??// in conjunction.

??if (!Pattern.matches("[0-9]+", quantity)) {
????// Format violation
??}

??String xmlString = "<item>\n<description>Widget</description>\n" +
?????????????????????"<price>500</price>\n" +
?????????????????????"<quantity>" + quantity + "</quantity></item>";
??outStream.write(xmlString.getBytes());
??outStream.flush();
}

2.1.6 符合规则的方案(XML模板)

一个更为通用的检查XML以防止注入的方法是,可以使用文档类型定义(Document Type Definition,DTD)或模板(schema)来进行验证。模板必须严格定义,从而防止通过注入的方式将一个合法的XML文档变成错误的文档。以下是一个用来验证XML文档片段的合适的模板:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="item">
??<xs:complexType>
????<xs:sequence>
??????<xs:element name="description" type="xs:string"/>
??????<xs:element name="price" type="xs:decimal"/>
??????<xs:element name="quantity" type="xs:integer"/>
????</xs:sequence>
??</xs:complexType>
</xs:element>
</xs:schema>

在schema.xsd文件中可以得到XML模板。在这个符合规则的方案中,可以采用这个模板来防止XML注入。它同时需要使用CustomResolver?类来防止XXE攻击。关于这个类和所谓的XXE攻击,可以参见如下代码示例中的描述。

private void createXMLStream(BufferedOutputStream outStream,
?????????????????????????????String quantity) throws IOException {
??String xmlString;
??xmlString = "<item>\n<description>Widget</description>\n" +
??????????????"<price>500.0</price>\n" +
??????????????"<quantity>" + quantity + "</quantity></item>";
??InputSource xmlStream = new InputSource(
????new StringReader(xmlString)
??);

??// Build a validating SAX parser using our schema
??SchemaFactory sf
????= SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
??DefaultHandler defHandler = new DefaultHandler() {
?????public void warning(SAXParseException s)
???????throws SAXParseException {throw s;}
?????public void error(SAXParseException s)
???????throws SAXParseException {throw s;}
?????public void fatalError(SAXParseException s)
???????throws SAXParseException {throw s;}
????};
??StreamSource ss = new StreamSource(new File("schema.xsd"));
??try {
????Schema schema = sf.newSchema(ss);
????SAXParserFactory spf = SAXParserFactory.newInstance();
????spf.setSchema(schema);
????SAXParser saxParser = spf.newSAXParser();
????// To set the custom entity resolver,
????// an XML reader needs to be created
????XMLReader reader = saxParser.getXMLReader();?
????reader.setEntityResolver(new CustomResolver());
????saxParser.parse(xmlStream, defHandler);
??} catch (ParserConfigurationException x) {
????throw new IOException("Unable to validate XML", x);
??} catch (SAXException x) {
????throw new IOException("Invalid quantity", x);
??}

??// Our XML is valid, proceed
??outStream.write(xmlString.getBytes());
??outStream.flush();
}

当XML可能已经载入还未处理的输入数据时,一般情况下使用XML模板或者DTD验证XML。如果还没有创建这样的XML字符串,那么在创建XML之前处理输入,这种方式性能
较高。
XML外部实体攻击(XXE)
一个XML文档可以从一个被称为实体的很小的逻辑块开始动态构建。实体可以是内部的、外部的或基于参数的。外部实体允许将外部文件中的XML数据包含进来。
根据XML W3C 4.4.3小节的建议?[W3C 2008]:“包含所需的验证”部分。
当一个XML处理器找到一个已经解析过的实体的引用的时候,为了验证这个文档,处理器必须包含它的替代字段。如果该实体是外部的,而且处理器不打算验证这个XML文档,那么处理器可以(但不必)包含该实体的替代字段。
攻击者通过操作实体的URI,使其指向特定的在当前文件系统中保存的文件,从而造成拒绝服务攻击或者程序崩溃,比如,指定/dev/random或者/dev/tty作为输入URI。这可能永久阻塞程序或者造成程序崩溃。这就称为XML外部实体攻击(XML external entity, XXE)。因为包含来作为外部实体的替代文本并不是必需的,并不是所有的XML解析器都存在这样的外部实体攻击的安全漏洞。

2.1.7 不符合规则的代码示例

下面这个不符合规则的代码示例尝试对evil.xml文件进行解析,并报告相关错误,然后退出。然而,SAX或者DOM(Document Object Model, 文档对象模型)解析器会尝试访问在SYSTEM属性中标识的URL,这意味着它将读取本地/dev/tty文件的内容。在POSIX系统中,读取这个文件会导致程序阻塞,直到可以通过计算机控制台得到输入数据为止。结果是,攻击者可以使用这样的恶意XML文件来导致系统挂起。

class XXE {
?private static void receiveXMLStream(InputStream inStream,
??????????????????????????????????????DefaultHandler defaultHandler)
????throws ParserConfigurationException, SAXException, IOException {
???SAXParserFactory factory = SAXParserFactory.newInstance();
???SAXParser saxParser = factory.newSAXParser();
???saxParser.parse(inStream, defaultHandler);
?}

?public static void main(String[] args)
????throws ParserConfigurationException, SAXException, IOException {
???receiveXMLStream(new FileInputStream("evil.xml"),
????????????????????new DefaultHandler());
?}
}

如果evil.xml文件中包含以下文本,程序会受到远程XXE攻击。

<?xml version="1.0"?>
<!DOCTYPE foo SYSTEM "file:/dev/tty">
<foo>bar</foo>

如果包含在异常里的信息是敏感的,则这个不符合规则的代码示例同样违反了规则ERR06-J。

2.1.8 符合规则的方案(EntityResolver)

这个符合规则的方案定义了一个CustomResolver类?,这个类实现了org.xml.sax.EntityResolver接口。它可以让SAX应用定制对外部实体的处理。setEntityResolver()方法可以将对应的SAX驱动实例注册进来。这个定制的处理器使用的是一个为外部实体定义的简单的白名单。当输入不能解析任何指定的、安全的实体源路径的时候,resolveEntity()方法会返回一个空的InputSource?对象。结果是,当解析恶意输入时,这个由自定义的解析器返回的空的InputSource对象会抛出java.net.MalformedURLException异常。需要注意的是,你必须创建一个XMLReader对象,以便通过这个对象来设置自定义的实体解析器。
下面是一个基于组件的净化示例。

class CustomResolver implements EntityResolver {
??public InputSource resolveEntity(String publicId, String systemId)
????throws SAXException, IOException {

????// check for known good entities
????String entityPath = "/home/username/java/xxe/file";
????if (systemId.equals(entityPath)) {
??????System.out.println("Resolving entity: " + publicId +
?????????????????????????" " + systemId);
??????return new InputSource(entityPath);
????} else {
??????return new InputSource(); // Disallow unknown entities
????????????????????????????????// by returning a blank path
????}
??}
}

class XXE {
??private static void receiveXMLStream(InputStream inStream,
???????????????????????????????????????DefaultHandler defaultHandler)
??????throws ParserConfigurationException, SAXException, IOException {
????SAXParserFactory factory = SAXParserFactory.newInstance();
????SAXParser saxParser = factory.newSAXParser();

????// To set the Entity Resolver, an XML reader needs to be created
????XMLReader reader = saxParser.getXMLReader();
????reader.setEntityResolver(new CustomResolver());
????reader.setErrorHandler(defaultHandler);

????InputSource is = new InputSource(inStream);
????reader.parse(is);
??}

??public static void main(String[] args)
??????throws ParserConfigurationException, SAXException, IOException {
????receiveXMLStream(new FileInputStream("evil.xml"),?
?????????????????????new DefaultHandler());
??}
}

2.1.9 风险评估

如果不在系统处理或存储用户输入之前净化数据,会导致注入攻击。

相关的漏洞 CVE-2008-2370描述了在Aapache Tomat 从4.1.0到4.1.37, 5.5.0 到 5.5.26以及 6.0.0 到6.0.16这些版本中的安全漏洞。当使用RequestDispatcher时,Tomcat会进行路径标准化,然后从URI中移除查询字串,这样会导致攻击者可以进行远程目录遍历攻击,并且在请求参数中通过..(两个点)来读取任意文件。

2.1.10 相关规范

2.1.11 参考书目

时间: 2024-10-30 11:32:38

《Java安全编码标准》一2.1 IDS00-J净化穿越受信边界的非受信数据的相关文章

《Java安全编码标准》一导读

前 言 在Java编程语言中,关键的安全编码要素是采用良好的文档和强制的编码规范.本书提供了在Java语言中的一系列安全编码规则.这些规则的目标是消除不安全的编码实践,因为这些不安全的因素会导致可利用的漏洞.如果应用这些安全编码标准,可以帮助设计出安全的.可靠的.健壮的.有弹性的.可用性和可维护性高的高质量系统,并且这些安全编码规范还可作为评估源代码质量特性的一个指标(不管使用的是手动的还是自动化的过程).对于使用Java编程语言开发的软件系统,这个编码规范具有广泛的影响. 目 录 第1章 概述

《Java安全编码标准》一1.2 注入攻击

1.2 注入攻击 当一个组件从超出该组件受信边界的外部数据源接收数据的时候,这些数据可能是恶意的,并且会导致注入攻击,如图1-1所示. 程序必须采取以下几个步骤,从而通过受信边界而收到的数据确保是适当的并且是没有恶意的.这些步骤包括: 验证(Validation):验证是指这样一个过程,该过程可以保证输入的数据处在预先设定好的有效的程序输入范围之内.这就要求这些输入符合类型和数值范围的要求,并且对各个类别和子系统来说,输入不会发生变化. 净化(Sanitization):在许多情况下,数据是从另

《Java安全编码标准》一1.1 错位的信任

1.1 错位的信任 软件程序往往包含多个组件作为子系统,其中每个组件会在一个或多个受信域中运行.例如,一个组件可以访问文件系统,但不允许访问网络,而另一组件可以访问网络,但不能访问文件系统.非信任解耦(distrustful decomposition)及权限分离(privilege separation) [Dougherty 2009]是安全设计模式的例子,它意味着首先需要减少需要授权的代码的数量,这就要在设计系统时,使用不互信的组件,并保证组件在特定的权限下运行.当软件组件需要遵循安全策略

《Java安全编码标准》一1.11 小结

1.11 小结 尽管作为一种相对安全的语言,Java语言及其类库还是在很大程度上存在着一些编程问题,从而造成系统安全漏洞.如果假设Java本身提供的功能特性可以减少一般的软件问题,并足够Java程序安全得不需要进行进一步检测,那么我们就大错特错了.因为任何在软件实现中出现的缺陷都会产生严重的安全影响,绷紧安全性这根弦是非常关键的,这样,当我们进行系统开发和部署时,就可以避免出现软件的安全漏洞问题.为了减少因为编程错误所带来的安全漏洞的可能性,Java开发人员应当遵循本编码标准中的安全编码规则,并

《Java安全编码标准》一2.11 IDS10-J不要拆分两种数据结构中的字符串

2.11 IDS10-J不要拆分两种数据结构中的字符串 在历史遗留系统中,常常假设字符串中的每一个字符使用8位(一个字节,Java中的byte).而Java语言使用16位表示一个字符(Java中的Char类型).遗憾的是,不管是Java的byte类型还是char类型数据,都不能表示所有的Unicode字符.许多字符串使用例如UTF-8编码的方式存储和通信,而在这种编码中,字符长度是可变的. 当Java字符串以字符数组的方式存储时,它可以用一个字节数组来表示,字符串里的一个字符可以用两个连续的或更

《Java安全编码标准》一2.13 IDS12-J在不同的字符编码中无损转换字符串数据

2.13 IDS12-J在不同的字符编码中无损转换字符串数据 在String对象之间进行转换时,如果涉及不同的编码类型,可能会导致数据丢失. 根据Java API[API 2006] 对?String.getBytes(Charset)方法的描述: 该方法总会替代那些错误格式的输入和不可映射的字符序列,把它们替换成这些字符的字节数组. 当必须将一个String转化为字节数组时,例如写入一个文件,并且在这个字符串中含有不可映射的字符序列的时候,就必须进行正确的字符编码. 2.13.1 不符合规则的

《Java安全编码标准》一2.7 IDS06-J从格式字符串中排除用户输入

2.7 IDS06-J从格式字符串中排除用户输入 对Java格式字符串的解释要比对在像C语言这样的语言中更严格[Seacord 2005].当任何转换参数不能匹配相应的格式符时,标准类库实现会抛出一个相应的异常.这种方法降低了被恶意利用的可能性.然而,恶意用户输入可以利用格式字符串,并且造成信息泄露或者拒绝服务.因此,不能在格式字符串中使用非受信来源的字符串. 2.7.1 不符合规则的代码示例 这个不符合规则的代码示例展示了可能出现信息泄露的问题.它将信用卡的失效日期作为输入参数并将其用在格式字

《Java安全编码标准》一2.3 IDS02-J在验证之前标准化路径名

2.3 IDS02-J在验证之前标准化路径名 根据Java API[API 2006]文档对java.io.File类的描述:一个路径名,不管是抽象的还是字符串形式的,可以是相对路径也可以是绝对路径.使用绝对路径名,因为在定位一个路径表示的文件时,已经不需要其他信息了,因而可以认为是完整的.相比之下,一个相对路径名必须要增加其他的路径信息才能进行解释.绝对路径名或者相对路径名会包含文件链接,比如符号(软)链接.硬链接.快捷方式.影子.别名和联名.这些文件链接在文件验证操作进行之前必须完全解析.例

《Java安全编码标准》一1.5 拒绝服务

1.5 拒绝服务 拒绝服务攻击试图使计算机的资源不可获得,或者对需要使用该计算机资源的用户来说,会造成资源不足的情况.通常这种攻击是持续服务的服务器系统需要重点关注的,它与桌面应用程序有很大区别:然而,拒绝服务的问题可以出现在所有类别的应用上. 1.5.1 资源耗尽型的拒绝服务 拒绝服务可能出现在,相对于输入数据需要的资源消耗来说,使用比例上更为巨大的资源.通过客户端软件检查资源是否被过度消耗,并希望用户来处理与资源相关的问题是不合理的.但是,存在这样的客户端软件,它们可以检查那些可能会导致持久