如何安全地解析字符串?

3
我们知道使用字符串拼接来形成 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。它们能在通用上下文中应用吗? - InvalidBrainException
6个回答

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"):之类的方法,但这些方法依赖于易于更改的环境变量。我尽力详尽地说明了问题,希望对您有所帮助。

这确实非常详尽,感谢您的解释。 - InvalidBrainException
我以前从未使用过Pattern和Matcher类,所以这对我来说是需要消化的内容。作为Java的新手,并且看到几乎每种情况都有一个类,我希望能够有一个标准解决方案来过滤用户输入。 - InvalidBrainException
在我所接触的任何编程语言中,用户输入验证始终是通过正则表达式实现的。解决方案1始终是白名单,解决方案2是黑名单。 https://www.owasp.org/index.php/Category:OWASP_Java_Project 获取更多与Java相关的材料。 - avgvstvs
哦,如果我的答案是你在这里看到的最好的,随意点击“接受”(复选框)。 - avgvstvs

3
任何用户输入都应被视为“可疑的”。在您的情况下,您假设文件路径是用户应该编写的地方。用户可以传递任何文件路径并修改(如果程序具有权限)您没有预期的文件。所以是的,这行代码:
FileOutputStream file = new FileOutputStream(directoryName + "/" + filePath);

的确存在漏洞

这个概念同样适用于C++


这个概念适用于任何编程语言 ;) - Voo
Java通常以用户权限运行,因此它只能在用户被允许的地方进行写操作。然而,有一个非常流行的桌面操作系统直到最近才默认允许用户做任何事情。 - user439793

2
由于文件名中有几个保留字符,您可能需要搜索用户提供的路径。您也可能想检查字符串是否包含../:/等内容,这将让用户篡改“主目录”路径。我建议在使用给定字符串之前使用正则表达式验证其有效性。如果验证失败,只需终止操作并让用户知道输入有问题即可,而不要尝试修复它。
如果不知道自己在做什么,文件结构可能非常复杂,而字符不是唯一的问题,正如其他答案中所述。在各种文件系统中,哪些文件名是有效的是不同的。旧的FAT系统最多允许8个字符,而Windows使用的新NTFS允许最多255个字符。
更新的答案以提供更清晰的信息。

所以我认为一个有用的验证是检查斜杠和反斜杠并拒绝这样的输入。像退格键这样的转义字符也会带来安全风险吗?在Java和C#中是否有一些处理这个问题的优雅的输入验证类? - InvalidBrainException
1
我们的代码中已经存在一个安全漏洞。因为以 ../ 开头是不够的。foo/../../../privateStuff 是一个完全正常的路径。C:/Windows 也是如此。而且,根据你如何修复它(只是用空白替换 ../?),你会遇到其他问题,例如 ..././doh。然后还有像 NTFS 上的 ADS 这样的东西(虽然我不知道 Java 是否允许这样做?)等等。所以,真正的最佳想法是不要自己修复它。 - Voo
实际上,我并没有提出修复问题,而是让用户知道验证失败了。当然,仅检查字符串的开头是不够的;这是我的错误。 - Marcus

2
这个问题与SQL注入问题非常不同。在SQL注入问题中,恶意输入的参数可以用于在特权安全上下文中执行命令,因为执行命令的数据库用户通常具有完全访问权限,可以写入数据库中的行。
在您提供的示例中,关键问题是“以什么用户身份执行Java代码?”如果您将此代码执行为CGI脚本,则Web服务器进程用户可以写入的任何文件或目录都容易受到攻击。如果您只是从命令行运行此代码,则实际上取决于操作系统(而不是Java代码)来保护用户不应该能够写入的文件/目录。
如果您的意图只允许代码写入用户的目录,则提供的其他答案是正确的。但是,我可以想象许多情况下可能不是这种情况。例如,也许您正在编写一些代码以自动编辑/etc目录中的文件。
简而言之,您需要考虑代码将在其中执行的上下文,以及该上下文提供的安全性,以及您需要在该上下文中自己的代码中提供的安全性。
PS-您通常不希望假设“ /”是您的目录分隔符。 Java提供了File.separator常量来实现此目的。

File.separator 是指定文件分隔符的技术正确方式。然而,I/O层会自动更改/和\以适应底层平台。这更多是一个风格问题,我也更喜欢使用 File.separator - user439793

2
如果你看到这样的代码,请立即停止运行。
一些问题:
目录遍历攻击:传统上,文件系统混淆了 UI 和 API。我们使用带文件路径但没有办法清晰地表述特定名称的语言。在典型操作系统中,".. "将允许移动到目录结构上方(不一定在路径的开头)。请注意,多个字符可能会作为目录分隔符。
链接:目录内的文件系统链接可能链接到其他位置。
NUL 字符:如果您尝试指定后缀,例如文件扩展名,那么零字节将截断路径。
Shell 转义: 您可能会发现问题是由于 shell 代码试图在创建之前或稍后解释文件路径而引起的。
现有文件:如果文件已经存在,会发生什么?
磁盘使用:如果数据是由用户提供的,您是否检查它不是巨大的?
因此,尽量避免使用外部创建的文件名。如果您真的需要,我建议应用一个严格的字符白名单。

1

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


谢谢,我不知道那个。但实际上我并不是在谈论操作系统创建的用户目录。我正在制作一个小程序,如果您以Bob的身份登录,所有与Bob相关的数据都存储在C:\SomeFolder\Bob\中,因此我无法享受操作系统提供的权限管理功能。 - InvalidBrainException
1
然后,使用正则表达式处理文件名。http://regexlib.com/Search.aspx?k=file+name - madhead
那个网站看起来非常有用。在我处理正则表达式的所有年份中,我希望我能找到那个网站。谢谢你。 - InvalidBrainException
我只是太懒了,现在没有写一个文件名正则表达式 :) - madhead

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接