浅析java中File.getPath()方法引发命令执行漏洞的成因

2013-10-23 16:11:46 10 1389 1


这个版块有权限,就发这边吧,分析不到位的地方还请大牛提出宝贵意见,转载请注名出处,土司首发

今天看到某童鞋的提问,大概意思是某执行命令处代码有没有办法利用,详见https://www.t00ls.net/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);
代码片段2:
                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");
代码片段3:
                if (lineCount == null ) lineCount = "1000";
                try {
                        intLineCount = Integer.parseInt(lineCount);
                        if ( intLineCount > MAXLINE ) {
                                intLineCount = MAXLINE;
                                lineCount = String.valueOf(MAXLINE);
                        }
                } catch (Exception e) {
                        intLineCount = 1000;
                }   
我们来先看这段:intLineCount = Integer.parseInt(lineCount),虽然lineCount是可控的且是String类型,但是这一句直接将lineCount转换成int类型,没法用;
再看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);
    }
源码片段2(摘自File类):
    /**
     * 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;
    }
源码片段3(摘自FileSystem类):
    /**
     * 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);
且看这句this.path = fs.normalize(pathname);,normalize是抽象类FileSystem中的抽象方法.
    我们知道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 == '\\') ? '/' : '\\';
    }
请看关键代码2(摘自Win32FileSystem类):
        /* 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次

要评论?请先  登录  或  注册