【问题标题】:How to parse strings safely?如何安全地解析字符串?
【发布时间】:2011-10-09 18:30:25
【问题描述】:

我们知道,使用字符串连接来形成 SQL 查询会使程序容易受到 SQL 注入的攻击。我通常通过使用我正在使用的任何数据库软件的 API 提供的参数功能来解决这个问题。

但我没有听说这是常规系统编程中的问题。考虑以下代码作为程序的一部分,该程序允许用户仅写入其私有目录中的文件。

Scanner scanner = new Scanner(System.in);
String directoryName = "Bob";
String filePath = null;
String text = "some text";

System.out.print("Enter a file to write to: ");
filePath = scanner.nextLine();

// Write to the file in Bob's personal directory for this program (i.e. Bob/textfile.txt)
FileOutputStream file = new FileOutputStream(directoryName + "/" + filePath);
file.write(text.getBytes());

倒数第二行是漏洞吗?如果是这样,如何使程序更安全(特别是在 Java、C++ 和 C# 中)?一种方法是验证转义字符的输入。还有什么?

【问题讨论】:

  • @HovercraftFullOfEels:我想这就是我要找的词。 Java 官方教程似乎暗示准备好的语句是 SQL 特有的。它们可以应用于通用上下文吗?

标签: java string security parsing


【解决方案1】:

这里最简单的解决方案是设置可接受字符的白名单。修改你的原始代码(包括 Java 约定,因为你说你是新人......)

package javawhitelist;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JavaWhiteListExample {

    public static void main(String[] args) throws IOException {

        Scanner scanner = new Scanner(System.in); 
        String directoryName = "Bob"; 
        String filePath = null; 
        FileWriter stream = null;
        String text = "some text";  
        System.out.print("Enter a file to write to: "); 
        filePath = scanner.nextLine();  
        String WHITELIST = "[^0-9A-Za-z]+";
        Pattern p = Pattern.compile(WHITELIST);
        Matcher m = p.matcher(filePath);

        //You need to do m.find() because m.matches() looks for COMPLETE match
        if(m.find()){ 
            //reject input.
            System.out.println("Invalid input.");
        }else{
            // Write to the file in Bob's home directory (i.e. Bob/textfile.txt) 
            try{
                File toWrite = new File(directoryName + File.separator + filePath);

                if(toWrite.canWrite()){
                    stream = new FileWriter(toWrite);
                    stream.write(text);
                }   
            }catch(FileNotFoundException e){
                e.printStackTrace();
            }catch(IOException e){
                e.printStackTrace();
            }finally{
                if(stream != null){
                    stream.close();
                }
            }

        }
    }
}

任何 JVM 的默认实现都以用户的所有访问权限运行。使用File.canWrite() 方法将有助于确保用户不会覆盖他/她没有权限的文件。最安全的解决方案(指定文件的确切位置)将使用 com.sun.security.auth.module.UnixSystem.getName() 并用它来构建 /home/$USER 目录名称的一部分。一些解决方案可能会告诉您使用 System.getProperty("user.home"): 或类似的,但那些依赖于容易改变的环境变量。

我试图做到彻底,希望这会有所帮助。

【讨论】:

  • 这确实相当彻底,谢谢你的解释。
  • 我以前从未使用过Pattern 和Matcher 类,所以这是我要消化的东西。作为 Java 新手,看到几乎每种情况都有一个类,我希望有一个过滤用户输入的标准解决方案。
  • 在我使用过的任何语言中,始终使用正则表达式实现用户输入验证。解决方案 1 始终是白名单,解决方案 2 始终是黑名单。 owasp.org/index.php/Category:OWASP_Java_Project 获取更多特定于 Java 的材料。
  • 哦,如果我的回答是您在这里看到的最好的答案,请随意点击“接受”。 (复选框。)
【解决方案2】:

用户的任何输入都应被视为“可疑

在您的情况下,您假设文件路径位于用户应该写入的位置。

用户可以传递任何文件路径并修改(如果程序具有权限)您不期望的文件。

所以是的行:

FileOutputStream file = new FileOutputStream(directoryName + "/" + filePath);

确实是一个漏洞

这个概念也适用于 C++

【讨论】:

  • 这个概念绝对适用于任何编程语言;)
  • Java 通常在用户权限下运行,因此它应该只能在用户被允许的地方写入。然而,有一个非常流行的桌面操作系统直到最近才默认允许用户做任何事情。
【解决方案3】:

这个问题与 SQL 注入问题完全不同。在 SQL 注入问题中,恶意输入的参数可用于在特权安全上下文中执行命令,因为执行命令的 database 用户通常具有全权访问权限来写入数据库中的行.

在您提供的示例中,关键问题是“Java 代码将以什么用户身份执行?”。例如,如果您将此代码作为 CGI 脚本执行,那么 Web 服务器进程的用户可以写入的任何文件或目录都是易受攻击的。如果您只是从命令行运行它,那么实际上取决于操作系统(而不是 Java 代码)来保护用户不应写入的文件/目录。

如果您的意图是只允许代码写入用户的目录,那么提供的其他答案都是正确的。但是,我可以设想许多情况可能并非如此。例如也许您正在编写一些代码来自动编辑 /etc 目录中的文件。

简而言之,您需要考虑执行代码的上下文,该上下文将提供什么安全性,以及您需要在该上下文内的自己的代码中提供什么安全性。

PS - 您通常不想假设“/”是您的目录分隔符。 Java 为此提供了 File.separator 常量。

【讨论】:

  • File.separator 是指定文件分隔符的技术正确方法。但是,I/O 层会自动将 / 和 \ 更改为适合底层平台。不过,这更像是一个风格问题,我也更喜欢File.separator
【解决方案4】:

如果你看到这样的代码,运行。

一些问题:

目录遍历攻击:传统上,文件系统会混淆 UI 和 API。我们有这种带有文件路径的语言,但没有办法清楚地说明特别的名称。在典型的操作系统上.. 将允许向上移动目录结构(不一定在路径的开头)。另请注意,多个字符可能用作目录分隔符。

链接:目录中的文件系统链接可能会链接到其他地方。

NUL 字符:如果您尝试指定后缀,例如作为文件扩展名,零字节将截断路径。

Shell 转义:您可能会发现尝试解释文件路径的 shell 代码存在问题,无论是在创建之前还是之后。

现有文件:如果文件存在,会发生什么?

光盘使用情况:如果数据是用户提供的,您是否检查过它不是很大?

因此,请尽量避免使用外部创建的文件名。如果你真的需要,我建议应用严格的字符白名单。

【讨论】:

    【解决方案5】:

    由于文件名中有多个reserved characters,您可能需要搜索用户给出的路径。您可能还想检查字符串是否不包含../:/ 等,这会让用户篡改“主目录”路径。我建议在使用之前使用正则表达式来验证给定的字符串。如果验证失败,只需中止操作并让用户知道输入有问题,而不是尝试修复它。

    如其他答案所述,如果一个人不知道自己在做什么并且字符不是唯一的问题,那么文件结构可能会非常复杂。哪些文件名有效,在各种文件系统中是不同的。旧的 FAT 系统有最多 8 个字符的限制,而 Windows 使用的新 NTFS 最多允许 255 个字符。

    更新了答案以更加清晰。

    【讨论】:

    • 所以我想一个有用的验证是检查斜杠和反斜杠并拒绝这样的输入。像退格这样的转义字符也会带来安全风险吗? Java 和 C# 中是否有某种输入验证类可以优雅地处理这个问题?
    • 我们已经在您的代码中发现了一个安全漏洞。因为从../ 开始是不够的。 foo/../../../privateStuff 是一条非常好的路径。 C:/Windows 也是如此。并且取决于您如何修复它(只需将../ 替换为空?)您会遇到其他问题,例如。 ..././doh。然后是 NTFS 上的 ADS(不知道 java 是否允许这样做?)等等。所以最好的主意是不要自己修复它。
    • 其实我并没有建议解决问题,而是让用户知道验证是否失败。当然,只检查字符串的开头是不够的;我的错。
    【解决方案6】:

    您可以使用System.getProperty("user.home") 获取用户目录。如果您的程序在该用户下运行,并且用户权限得到正确管理,那么预计不会出现问题。您还可以使用另一个属性 - file.separator 获取路径分隔符。最后,还有File.canRead()File.canWrite() 方法。

    【讨论】:

    • 谢谢,我不知道。但实际上我不是在谈论操作系统创建的用户目录。我正在制作一个小程序,如果您以 Bob 身份登录,与 Bob 有关的所有数据都存储在 C:\SomeFolder\Bob\ 中,因此我无法享受操作系统提供的权限管理功能。
    • 然后,对文件名使用正则表达式。 regexlib.com/Search.aspx?k=file+name
    • 那个网站看起来非常有用。这些年来我不得不处理正则表达式,我希望我遇到过那个。谢谢。
    • 我只是懒惰,现在自己写一个文件名的正则表达式:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-04
    • 1970-01-01
    相关资源
    最近更新 更多