java代码审计 命令注入 及 修复建议
命令注入:
是指通过提交恶意构造的参数破坏命令语句结构,从而达到执行恶意命令的目的。
在Web应用中,有时候会用到一些命令执行的函数,如php中system、exec、shell_exec等,当对用户输入的命令没有进行限制或者过滤不严导致用户可以执行任意命令时,就会造成命令执行漏洞。
命令注入漏洞主要表现为以下两种形式:
1、攻击者能够算改程序执行的命令:攻击者直接控制了所执行的命令。
2、攻击者能够复改命今的执行环境:攻击者间接控制了所执行的命令。
在这种情况下,我们着重关注第一种情况,即攻击者控制所执行命令的可能性。这种形的 Command Injection漏洞在以下情况下发生:
1.数据从不可信赖的数据源进入应用程序
2.数据被用作代表应用程序所执行命令的字符串,或字符串的部分
3.通过命令的执行,应用程序会授予攻击者一种原本不该拥有的特权或能力
例1:下面这段来自系统实用程序的代码根据系统属性 APPHOME 来决定其安装目录,然后根据指定目录的相对路径执行一个初始化脚本。
... String home= System. getProperty( "APPHOME"); String cmd = home + INITCMD; java.lang Runtime. getRuntme( exec(cmd));//执行操作命令 ...
例1中的代码使得攻击者可通过修改系统属性 APPHOME而指向一个包含恶意版本 INITOMD的其他路径,从而提高自己右应用程序中的权限,继而随心所欲地执行命令。
由于程序不会验证从环境中读取的值,所以如果攻击者能够控制系统属性 APPHOME的值,他们就能耗应用程序去运行恶意代码从而取得系统控制权。
例2:下面的代码来自一个管理web应用程序,旨在使用户能够使用一个围绕rman实用程序的批处理文件封装器来启动 Oracle数据库备份,然后运行一个 clearup.bat.脚本来删除一些临时文件。
脚本 manDB.bat接受单个命令行参数,该参数指定了要执行的备份类型。由于访间数据库受限,所以应用程序执行备份 需要具有较高权限的用户。
...
Sting btype = request.getparameter("backuptype");
Sting cmd= new Strng("cmd. exe /K
\'c: \util\\ramnDB.bat "+btype+" &&c:\\util\\clearup.bat\"")
System. Runtime. getRuntime().exec(cmd);
...
这里的问题是:程序没有对读取自用户的 backtype参数做任何验证,通常情况下 Runtime,exec()函数不会执行多条命令,但在这种情况下,程序会首先运行 cmd.exe shell,从而可以通过调用一次Runtime.exec()来执行多条命令。一且调用了该shell,它即会允许执行用两个与号分的多条命令。
如果攻击者传了一个形式为 "&&c:\\dbms\\*.*”的字符串,那么应用程序将随程序指定的其他命令起执行此命令。由于该应用程序的特性,运行该应用程序需要具备与数库进行交互所需的权限,这就意味着攻主者注入的任何命令都将道过这些权限得以运行。
例3:下面的代码来自于一个web应用程字,该应用程序就允许用户访问一个可在系统中更新用户密码的接口,在特定的网络环境中更新密码时,其中的一个步骤就是在/var/yp 目录中运行 make命令,下面显示了此步骤的代码:
...
System. Runtime. getRuntime().exec(make);
...
这里的问题在于程序没有在的构造中指定一个绝对路径,并且没能在执行 Runtime. exeo()调用前清除它的环境变量。如果取击者能够修改&path变量,把指向名为make恶意二制代码,程序就会在其指定的环境下执行,然后加载该恶意二进制代码,而非原本期理的代码,由于应用程字自身的特性,运行该应用程序要具备执行系统操作所需的权限,这意味着攻击者会利用这些权限执行自己的make,从而可能导致攻击者完全控制系统。
例4:以下代码可从 Android Intent中读取要执行的命令
...
String[] cmds= this.getIntent().getstringArrayExtra("commands");
Process p= Runtime. getRuntime(exec("su"));
DataOutputStream os = new DataOutputStream(p.getOutputStream());
for (String cmd: cmds){
os.wnteBytes(cmd+"\n");
}
os.writeBytes("ext\n");
os.flush();
经过root的设备上,恶意应用程序会强迫受取击应用程序使用超用户权限执行任意命令。
修复建议:
应当禁止用户直接控制由程序执行的命令。在用户的输入会影响命令执行的情兄下,应将用户输入限制为从预定的安全命令集合中进行选择。如果输入中出现了恶意的内容传递到命令执行函数的值将默认从安全命令集合中选择,或者程序将拒绝执行任何命令。
在需要将用户的输入用作程序命令中的参数时,由于合法的参数集合实在很大,或是难以跟踪,使得这个方法通常都不切实际。开发者通常的做法是使用黑名单。在输入之前,黑名单会有选择的拒绝或避免潜在的危险字符。但是,任何个定义不安全内容的列表都很可能是不完整的,并且会严重地依赖于执行命令的环境。更的方法是组建一份白名单,允许其中的字符出现在输入中,并只接受完全由这些经认可的字符组成的输入。
攻击者可以通过修改程序运行命令的环境来间接控制这些命令的执行。我们不应当完全信赖环境,还需采取预防指施,防止攻击者利用某些控制环境的手段进行攻击。无论何时,只要有可能,都应由用程序来控制命令,并使用绝对路径执行命令。如果编译时尚不了解路径(如在跨平台应用程序中),应该在执行过程中利用可信赖的值构建一个绝对路径。应对照一系列定义有效值的常量,仔细的检查从配置文件或者环境中读取的命令值和路径。
有时还可以执行其他检验,以检查这些来源是否已被恶意篡改。例如,如果一个配置文件为可写,程序可能会拒绝运行。如果能够预先得知有关要执行的人进制代码的信息,程序就会进行检测,以检验这个二进制代码的合法性。
如果个二进制代码始终属于某个特定的用户,或者被指定了一组特定的访问权限,这些属性就会在执行二进制代码通过程序进行检验。
尽管可能无法完全阻止强大的攻击者为了控制程序执行的命令而对系统进行的攻击,但只要程序执行外部命令,就务必使用最小授权原则:不给予超过执行该命令所必需的权限。
代码参考
如下的代码案例中,获取路径的值是从本地的属性文件当中获取,本人觉得已经写得很好啦,
但按照修复建议 如上,要求即使是从本地属性文件获取的值 也要 “仔细的检查从配置文件或者环境中读取的命令值和路径”,要求很吹毛求疵啦,
如果可以 也可以增加 对输入值的过滤与限制。
TestController.java
//拿PDF转换案例举例 代码如下
... //调用 openoffice服务线程 String sofficePath= PropertiesUtil. getValue("sofficePath"); String openofficeport= Propertiesutil. getvalue("openofficeport"); ...
... String command = sofficePath +"-headless -accept=\"socket,host=127.0.0.1, port="+ openofficeport+ ";urp; \"-nofirststarwizard"; ps = Runtime.getRuntime().exec( command); ...
PropertiesUtil.java
//属性文件 WEB-INF/properties 对象 private static Properties aosConfigProp = new Praperties(); static{ //初始加载properties文件 InputStream in=null; try{ String webinfPatH = HttpUtil.getAbsolutePath("WEB-INF"); String fileName = webInfPath+File.separator +"properties"; File file = new File(fileName); if (!file.exists()){ logger.error(webInfPath. concat(“目录下无propertie配置文件"); }else { in= new FileInputStream(file); aosConfigProp.load(in); } }catch (Exception e) t logger.error(”获取MEB-INF/ properties配置文件信息异常",e); }finally{ if(in!=null){ try{ in.close(); }catch (IOException e){ // TODO Auto-generated catch block e.printstackTrace(); } } } } //获取指定参数 public static String getvalue(String name){ return aosConfigProp.getProperty(name,""); }