pascal-lab / Tai-e

An easy-to-learn/use static analysis framework for Java
https://tai-e.pascal-lab.net/docs/index.html
GNU Lesser General Public License v3.0
1.44k stars 174 forks source link

When using methods such as Map.get(), if the return value is an array, the elements in the array will not be tainted. #102

Closed springkill closed 2 months ago

springkill commented 6 months ago

Description

I used the following configuration:

  - { method: "<org.apache.catalina.connector.Request: java.util.Map getParameterMap()>", from: base, to: result }

When I use the return value of Map.get() directly as a sink, the taint is captured, but when I use the elements of the returned array, the taint is not added. The reality is that the specific problem is: When I was analysing the tomcat application, I needed to use the org.apache.catalina.connector.RequestFacade.getParameterMap() method, and the obtained ParameterMap was tainted with taints, and the array obtained by ParameterMap.get() also The array obtained by ParameterMap.get() is also tainted, but when I remove an element from the array, the taint disappears. image image image I tried adding TRANSFER to the methods in the two images above, but still not getting the desired results.

  - { method: "<org.apache.catalina.connector.RequestFacade: java.util.Map getParameterMap()>", from: base, to: base.request }
  - { method: "<org.apache.catalina.connector.Request: java.lang.String[] getParameterValues(java.lang.String)>", from: base, to: "result[*]" }

Is there any way to fix this?

zhangt2333 commented 6 months ago

When I was analysing the tomcat application, I needed to use the org.apache.catalina.connector.RequestFacade.getParameterMap() method, and the obtained ParameterMap was tainted with taints, and the array obtained by ParameterMap.get() also The array obtained by ParameterMap.get() is also tainted, but when I remove an element from the array, the taint disappears.

I can't clearly understand what you're saying. And there are so many things circled in the chart that I don't know which to focus on. Would you be so kind as to illustrate with examples/current behavior/expected behavior?

springkill commented 6 months ago

The problem I'm trying to solve is, when the return value of the Map.get() method is an array, how do I mark the elements of the returned array with taints? For example, how do I make the elements in the "values" array in the doPost() method carry tags?

package org.owasp.benchmark.testcode;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(value="/cmdi-00/BenchmarkTest00004")
public class BenchmarkTest00004 extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");

        java.util.Map<String,String[]> map = request.getParameterMap();
        String param = "";
        if (!map.isEmpty()) {
            String[] values = map.get("BenchmarkTest00004");
            if (values != null) param = values[0];
        }

        org.owasp.benchmark.helpers.ThingInterface thing = org.owasp.benchmark.helpers.ThingFactory.createThing();
        String bar = thing.doSomething(param);

        String cmd = "";
        String osName = System.getProperty("os.name");
        if (osName.indexOf("Windows") != -1) {
            cmd = org.owasp.benchmark.helpers.Utils.getOSCommandString("echo");
        }

        Runtime r = Runtime.getRuntime();

        try {
            Process p = r.exec(cmd + bar);
            org.owasp.benchmark.helpers.Utils.printOSCommandResults(p, response);
        } catch (IOException e) {
            System.out.println("Problem executing cmdi - TestCase");
            response.getWriter().println(
              org.owasp.esapi.ESAPI.encoder().encodeForHTML(e.getMessage())
            );
            return;
        }
    }

}
zhangt2333 commented 6 months ago

I don't have your test environment and don't know how to reproduce your problem, in addition, you don't provide enough usable information (e.g. points-to-set, analysis options, complete analyzed program and complete taint-config.yml). All of the above makes it difficult for me to think, validate and answer your question.

Please offer the following infomation (otherwise, I don't know how to proceed.):

springkill commented 5 months ago

OK, I will describe my problem in detail. Here's the test code:

package org.owasp.benchmark.testcode;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(value="/cmdi-00/BenchmarkTest00016")
public class BenchmarkTest00016 extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");

        java.util.Map<String,String[]> map = request.getParameterMap();
        String param = "";
        if (!map.isEmpty()) {
            String[] values = map.get("BenchmarkTest00016");
            if (values != null) param = values[0];
        }

        String bar = doSomething(request, param);

        String cmd = "";
        String osName = System.getProperty("os.name");
        if (osName.indexOf("Windows") != -1) {
            cmd = org.owasp.benchmark.helpers.Utils.getOSCommandString("echo");
        }

        Runtime r = Runtime.getRuntime();

        try {
            Process p = r.exec(cmd + bar);
            org.owasp.benchmark.helpers.Utils.printOSCommandResults(p, response);
        } catch (IOException e) {
            System.out.println("Problem executing cmdi - TestCase");
            response.getWriter().println(
              org.owasp.esapi.ESAPI.encoder().encodeForHTML(e.getMessage())
            );
            return;
        }
    }  // end doPost

    private static String doSomething(HttpServletRequest request, String param) throws ServletException, IOException {

        String bar = param;

        return bar; 
    }
}

At line 24 of this code, I use getParameterMap() to get map, the source method is doPost and the tain is marked in org.apache.catalina.connector.RequestFacade which implements HttpServletRequest. So if I want to propagate the taint, I need to use RequestFacade.getParameterMap() as a transfer. Like this:

  - { method: "<org.apache.catalina.connector.RequestFacade: java.util.Map getParameterMap()>", from: base, to: result }

And in line 28 of this test code, I uses the Map.get() method to get the value of a param, the value obtained is of array type, but since the return value of Map.get() is of type Object, there is no way for me to propagate the taint by using to: "result [*]" , I can only use to: result. Like this:

  - { method: "<org.apache.catalina.util.ParameterMap: java.lang.Object get(java.lang.Object)>", from: base, to: result }

After I did this, the taint propagated to the map object on line 24 and from the map object to the values array on line 28, but not to the elements inside thevalues array.

There is the source code of org.apache.catalina.connector.RequestFacade.getParameterMap().

public Map<String,String[]> getParameterMap() {
        checkFacade();

        if (Globals.IS_SECURITY_ENABLED) {
            return AccessController.doPrivileged(new GetParameterMapPrivilegedAction());
        } else {
            return request.getParameterMap();
        }
    }

In line 7 of the above code request.getParameterMap() is used, where request is a filed of RequestFacade of type org.apache.catalina.connector.Request. There is the source code of org.apache.catalina.connector.Request.getParameterMap():

public Map<String,String[]> getParameterMap() {

        if (parameterMap.isLocked()) {
            return parameterMap;
        }

        Enumeration<String> enumeration = getParameterNames();
        while (enumeration.hasMoreElements()) {
            String name = enumeration.nextElement();
            String[] values = getParameterValues(name);
            parameterMap.put(name, values);
        }

        parameterMap.setLocked(true);

        return parameterMap;

    }

I try to usr getParameterValues() as transfer to tain the array in map, like this :

  - { method: "<org.apache.catalina.connector.Request: java.lang.String[] getParameterValues(java.lang.String)>", from: base, to: "result[*]" }

But ultimately the tainflow is still undetectable. Above is what I mentioned: how to pass taints to elements inside an array when the return value type of Map.get() is an array?

tai-e.log tai-e-plan.tml:

- id: pta
  options:
    cs: 1-obj
    only-app: false
    implicit-entries: true
    distinguish-string-constants: all
    merge-string-objects: false
    merge-string-builders: false
    merge-exception-objects: false
    handle-invokedynamic: false
    propagate-types:
    - reference
    advanced: zipper
    dump: false
    dump-ci: false
    dump-yaml: false
    expected-file: null
    reflection-inference: solar
    reflection-log: null
    taint-config: config/common/taint-config.yml
    plugins:
    - pascal.taie.analysis.pta.plugin.taint.TomcatHandler
    time-limit: -1

pascal.taie.analysis.pta.plugin.taint.TomcatHandler:

package pascal.taie.analysis.pta.plugin.taint;

import pascal.taie.World;
import pascal.taie.analysis.pta.core.cs.context.Context;
import pascal.taie.analysis.pta.core.cs.element.CSMethod;
import pascal.taie.analysis.pta.core.heap.HeapModel;
import pascal.taie.analysis.pta.core.heap.Obj;
import pascal.taie.analysis.pta.core.solver.EntryPoint;
import pascal.taie.analysis.pta.core.solver.Solver;
import pascal.taie.analysis.pta.core.solver.SpecifiedParamProvider;
import pascal.taie.analysis.pta.plugin.Plugin;
import pascal.taie.ir.IR;
import pascal.taie.ir.exp.Var;
import pascal.taie.language.classes.JClass;
import pascal.taie.language.classes.JMethod;
import pascal.taie.language.type.Type;

import java.util.List;

import static pascal.taie.analysis.pta.core.heap.Descriptor.ENTRY_DESC;

public class TomcatHandler implements Plugin {
    private Solver solver;
    private TaintManager manager;

    private boolean isCalled;

    final JClass requestFacade = World.get().getClassHierarchy().getClass("org.apache.catalina.connector.RequestFacade");
    final JClass responseFacade = World.get().getClassHierarchy().getClass("org.apache.catalina.connector.ResponseFacade");

    @Override
    public void setSolver(Solver solver) {
        this.solver = solver;
        manager = new TaintManager(solver.getHeapModel());
    }

    @Override
    public void onStart() {
        List<JClass> list = solver.getHierarchy().applicationClasses().toList();
        for (JClass jClass : list) {
            String a = jClass.getName();
            if (a.matches("^(javax\\.servlet\\.ServletRequest).+$")) {
                System.out.println("found: " + a);
            }
            HeapModel heapModel = solver.getHeapModel();
            jClass.getAnnotations().forEach(annotation -> {
                        if (annotation.getType().matches("javax.servlet.annotation.WebServlet")) {
                            jClass.getDeclaredMethods().forEach(jMethod -> {
                                if (jMethod.getName().matches("\\b(doGet|doPost|doPut|doDelete)\\b")) {
                                    Type requestFacadeType = jMethod.getParamType(0);
                                    Type responseFacadeType = jMethod.getParamType(1);
                                    String requestFacadeAlloc = "<" + requestFacadeType.toString() + ">";
                                    String responseFacadeAlloc = "<" + responseFacadeType.toString() + ">";
                                    Obj mockRequest = heapModel.getMockObj(ENTRY_DESC, requestFacadeAlloc, requestFacade.getType(), jMethod);
                                    Obj mockResponse = heapModel.getMockObj(ENTRY_DESC, responseFacadeAlloc, responseFacade.getType(), jMethod);
                                    Obj mockServlet = heapModel.getMockObj(ENTRY_DESC, "<http-controller>", jClass.getType());
                                    SpecifiedParamProvider paramProvider = new SpecifiedParamProvider.Builder(jMethod)
                                            .addThisObj(mockServlet)
                                            .addParamObj(0, mockRequest)
                                            .addParamObj(1, mockResponse)
                                            .build();
                                    solver.addEntryPoint(new EntryPoint(jMethod, paramProvider));
                                }
                            });
                        }
                    }
            );
        }
    }

    @Override
    public void onNewCSMethod(CSMethod csMethod) {

        JMethod method = csMethod.getMethod();
        Context context = csMethod.getContext();
        boolean isDoMethod = method.getName().matches("\\b(doGet|doPost|doPut|doDelete)\\b");
        if (isDoMethod) {
//            System.out.println("find"+method.toString());
            IR ir = method.getIR();
            Var param = ir.getParam(0);
            SourcePoint sourcePoint = new ParamSourcePoint(method, new IndexRef(IndexRef.Kind.VAR, 0, null));
            Obj taint = manager.makeTaint(sourcePoint, param.getType());
            solver.addVarPointsTo(context, param, taint);
        }
    }
}

You can download the full source code from here: https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core/9.0.85

Thanks!

springkill commented 3 months ago

Hi, it's been a long time since I received a reply, is this question still being followed?

zhangt2333 commented 3 months ago

Sorry for the delay. Could you please share your entire Tai-e project with me, either via file upload or by providing a link to your GitHub repository? This will allow me to reproduce the issue with one-click.