使用从用户输入的字符串作为格式控件是什么意思,为什么它是安全漏洞?

时间:2017-04-03 03:03:15

标签: java

我在Deitel和Deitel的书中读到了这个错误预防技巧(3.1), Java:如何编程。我不明白这意味着什么。我只知道它正在讨论下面的代码行:

// display the name stored in object myAccount
System.out.printf("Name in object myAccount is:%n%s%n", myAccount.getName());

该段如下:

  

永远不要将格式控件用作从用户输入的字符串。当方法System.out.printf在其第一个参数中计算格式控制字符串时,该方法根据该字符串中的转换说明符执行任务。如果格式控制字符串是从用户获得的,则恶意用户可以提供由System.out.printf执行的转换说明符,可能导致安全漏洞。

1 个答案:

答案 0 :(得分:3)

严格地说,使用用户输入的字符串进行格式控制将是这样的:

String format=getFromUser(...);
System.out.printf(format, arg1, arg2, arg3...);

这可能是非常有害的,因为除了@JohnKugelman建议的编码注入之外,我可以想到四种简单方法,其中恶意用户可以通过format string破坏安全性:

  • 最简单的方法是输入错误的掩码,以便在运行时抛出WrongFormatConversion
    @Test
    public void wrongMask()
    {
        String s="january";
        System.out.printf("%)/$#", s);
    }
  • 输入一个高参数索引,以便在运行时抛出MissingFormatArgumentException
    @Test
    public void highArgumentIndex()
    {
        String s="january";
        System.out.printf("%1000$s%n", s);
    }
  • 通过输入一个较高的场宽,这样一条巨大的线将被输出,这样做是不切实际的:
    @Test
    public void highFieldWidth()
    {
        String s="january";
        System.out.printf("%1000000s%n", s);
    }
  • 同上,但为数字参数指定零填充高宽度,这样填充操作会消耗很长时间,也可能耗费高内存(当然因为填充算法不是为了处理大字符串,所以,它的设计并不是最佳的。)
    @Test
    public void highArgumentWidth()
    {
        int n=12;
        System.out.printf("%01000000d%n", n);
    }

此测试推迟了几乎一分钟才能在我的计算机上执行。尝试使用足够高的宽度,并且在延迟几分钟后你可能会得到OutOfMemoryError

(通过输入字符串的简单预处理可以轻松避免代码注入以覆盖所有双引号,但这些缺陷属于语义性,因此它们更难找到并避免)

但这些都不适用于您的行,因为您的格式字符串是硬编码的,而不是来自用户的输入。

更新

要了解从最终用户接受控制格式字符串的危险程度,您最好重复上面的测试集,但是在交互模式下:

    public static void main(String[] args)
    {
        String format=args[0];
        int n=12;
        System.out.printf(format, n);
    }

然后,想象自己是一个试图打破这个程序的黑客。使用这些建议值执行它:

  • %)/$#
  • %1000$d
  • %1000000d
  • %01000000d

结论:格式字符串必须由程序员组成,而不是由最终的交互式用户组成。