如何安全地解析字符串?

时间:2011-10-09 18:30:25

标签: java string security parsing

我们知道使用字符串连接来形成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#中)?一种方法是验证转义字符的输入。还有什么吗?

6 个答案:

答案 0 :(得分:3)

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

在您的情况下,您假设文件路径是用户应该写的地方。

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

所以是这一行:

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

确实是一个漏洞

这个概念也适用于C ++

答案 1 :(得分:3)

这里最简单的解决方案是拥有可接受字符的白名单。修改原始代码(包括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"): 或者其他一些,但那些依赖于容易改变的环境变量。

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

答案 2 :(得分:2)

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

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

更新了答案以提高清晰度。

答案 3 :(得分:2)

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

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

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

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

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

答案 4 :(得分:2)

如果您看到类似的代码,请运行。

一些问题:

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

链接:目录中的文件系统链接可能链接到别处。

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

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

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

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

因此,尽量避免使用外人创建的文件名。如果你真的需要,我建议使用紧密的白名单。

答案 5 :(得分:1)

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