codeql实战练习-micro_service_seclab
C0cr 发表于 新疆 安全工具 276浏览 · 2024-11-30 04:31

不会的类要积极的去 CodeQL standard libraries 里面翻看。然后多积累,多记录。

构建数据库

项目地址

codeql database create ~/xxxxxx/micro-service-seclab-database --language="java" --command="mvn clean package -Dmaven.test.skip=true" --source-root=./micro-service-seclab/"

查看sql注入的点

RemoteFlowSource : A data flow source of remote user input.
https://codeql.github.com/codeql-standard-libraries/java/semmle/code/java/dataflow/FlowSources.qll/type.FlowSources$RemoteFlowSource.html

比如下面这个

@RequestMapping(value = "/one")    
public List<Student> one(@RequestParam(value = "username") String username) {    
    return indexLogic.getStudent(username);    
}
import java
 import semmle.code.java.dataflow.FlowSources
 import semmle.code.java.security.QueryInjection
 import DataFlow::PathGraph

 class VulConfig extends TaintTracking::Configuration {
      VulConfig() { this = "SqlInjectionConfig"}

     override predicate isSource(DataFlow::Node src) {
         src instanceof RemoteFlowSource
     }

     override predicate isSink(DataFlow::Node sink) {
         exists(Method method, MethodAccess call |
             method.hasName("query")
             and
             call.getMethod() = method and
             sink.asExpr() = call.getArgument(0)
         )
     }

 }

 from VulConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
 where config.hasFlowPath(source, sink)
 select source.getNode(), source, sink, "source"

删除int类型参数

这个方法的参数类型是 List<Long>,不会存在注入漏洞。
这说明我们的规则里,对于 List<Long> ,甚至 List<Integer> 类型都会产生误报,source 误把这种类型的参数涵盖了。
我们需要采用 isSanitizer 来消除这种情况。

三个类对应的类型如下

PrimitiveType > boolean, byte, short, char, int, long, float, and double.

BoxedType > Boolean, Byte, Short, Character, Integer, Long, Float, and Double.

NumberType > A (reflexive, transitive) subtype of java.lang.Number.

ParameterizedType 这个类,官方的一些解释。

A parameterized type is an instantiation of a generic type(泛型类), where each formal type variable has been replaced with a type argument.

For example, List<Number> is a parameterization of the generic type List<E>, where E is a type parameter.

override predicate isSanitizer(DataFlow::Node node) {
    node.getType() instanceof PrimitiveType or
    node.getType() instanceof BoxedType or
    node.getType() instanceof NumberType or
    exists(ParameterizedType pt| 
        node.getType() = pt and pt.getTypeArgument(0) instanceof NumberType
     )
}

解决漏报

我们发现,如下的SQL注入并没有被CodeQL捕捉到。

public List<Student> getStudentWithOptional(Optional<String> username) {
        String sqlWithOptional = "select * from students where username like '%" + username.get() + "%'";
        //String sql = "select * from students where username like ?";
        return jdbcTemplate.query(sqlWithOptional, ROW_MAPPER);
}

假如 Optional 这种类型的使用没有在 CodeQL 的语法库里,我们需要强制让 username 流转到username.get(),这样 username.get() 就变得可控了。这样应该就能识别出这个注入漏洞了。

import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.QueryInjection
import DataFlow::PathGraph

predicate isTaintedString(Expr expSrc, Expr expDest) {
    exists(Method method, MethodAccess call, MethodAccess call1|
        expSrc = call1.getArgument(0) and expDest = call and call.getMethod() = method
        and method.hasName("get") and method.getDeclaringType().toString() = "Optional<String>"
        and call1.getArgument(0).getType().toString() = "Optional<String>"
        )
}

class VulConfig extends TaintTracking::Configuration {
     VulConfig() { this = "SqlInjectionConfig"}

    override predicate isSource(DataFlow::Node src) {
        src instanceof RemoteFlowSource
    }

    override predicate isSink(DataFlow::Node sink) {
        exists(Method method, MethodAccess call |
            method.hasName("query")
            and
            call.getMethod() = method and
            sink.asExpr() = call.getArgument(0)  // sink.asExpr() 是一个方法,用于将一个 sink 转换成一个表达式。这个方法通常用于在查询中使用 sink,因为查询需要将 sink 转换成表达式才能进行分析。
        )
    }

    override predicate isSanitizer(DataFlow::Node node) {
        node.getType() instanceof PrimitiveType or
        node.getType() instanceof BoxedType or
        node.getType() instanceof NumberType or
        exists(ParameterizedType pt| 
            node.getType() = pt and pt.getTypeArgument(0) instanceof NumberType
         )
    }

    override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
        isTaintedString(node1.asExpr(), node2.asExpr())
    }

}

from VulConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select source.getNode(), source, sink, "source"

fastjson

import java
 import semmle.code.java.dataflow.FlowSources
 import semmle.code.java.security.QueryInjection
 import DataFlow::PathGraph

 class FastjsonVulConfig extends TaintTracking::Configuration {
     FastjsonVulConfig() { this = "fastjson" }

     override predicate isSource(DataFlow::Node src) {
         src instanceof RemoteFlowSource
     }

     override predicate isSink(DataFlow::Node sink) {
         exists(Method method, MethodAccess call|
             method.hasName("parseObject")
             and
             call.getMethod() = method and
             sink.asExpr() = call.getArgument(0)
             )
     }
 }

 from FastjsonVulConfig fastjsonVul, DataFlow::PathNode source, DataFlow::PathNode sink
 where fastjsonVul.hasFlowPath(source, sink)
 select source.getNode(), source, sink, "source"

SSRF

注意这个 RequestForgerySink 类。

A data flow sink for server-side request forgery (SSRF) vulnerabilities.

import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.QueryInjection
import DataFlow::PathGraph
import semmle.code.java.security.RequestForgeryConfig

class SSRFVulConfig extends TaintTracking::Configuration {
        SSRFVulConfig() { this = "SSRFVulConfig" }

    override predicate isSource(DataFlow::Node src) {
        src instanceof RemoteFlowSource
    }

    override predicate isSink(DataFlow::Node sink) {
    sink instanceof RequestForgerySink
    }
 }
 from SSRFVulConfig ssrfVulConfig, DataFlow::PathNode source, DataFlow::PathNode sink
 where ssrfVulConfig.hasFlowPath(source, sink)
 select source.getNode(), source, sink, "source"

ssrf 漏报处理1

漏报原因排查

但是其实ssrf 提供了5个都可以可以进行ssrf的,但是只排查出了3个。首先看看 two 这个路由

@RequestMapping(value = "/two")
public String Two(@RequestParam(value = "url") String imageUrl) {
    try {
        URL url = new URL(imageUrl);
        HttpResponse response = Request.Get(String.valueOf(url)).execute().returnResponse();
        return response.toString();
    } catch (IOException var1) {
        System.out.println(var1);
        return "Hello";
    }
}

imageUrl→url = new URL(imageUrl)→String.valueOf(url)→Request.Get(String.valueOf(url))

two 接口中的代码有问题的地方如下,用到了 String.valueOf(url),正常情况下程序不会觉得 String.valueOf() 方法返回的仍然是污点。因此我们需要修改 Config 中的 isAdditionalTaintStep 方法,将 java.net.URLString.valueOf(url) 绑定。

我们这里直接跟进RequestForgeryConfig 这个类看一看。

注意这个isAdditionalTaintStep。继续跟进propagatesTaint谓语。

处理方法1

我们这里直接修改 RequestForgery.qll 源码。主要是14到24行,还有 第 47 行。

/** Provides classes to reason about server-side request forgery (SSRF) attacks. */

import java
import semmle.code.java.frameworks.Networking
import semmle.code.java.frameworks.ApacheHttp
import semmle.code.java.frameworks.spring.Spring
import semmle.code.java.frameworks.JaxWS
import semmle.code.java.frameworks.javase.Http
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.frameworks.Properties
private import semmle.code.java.dataflow.StringPrefixes
private import semmle.code.java.dataflow.ExternalFlow

 class TypeStringLib extends RefType {
    TypeStringLib() { this.hasQualifiedName("java.lang", "String") }
  }

 class StringValue extends MethodAccess {
    StringValue(){
      this.getCallee().getDeclaringType() instanceof TypeStringLib and
      this.getCallee().hasName("valueOf")
    }
}

/**
 * A unit class for adding additional taint steps that are specific to server-side request forgery (SSRF) attacks.
 *
 * Extend this class to add additional taint steps to the SSRF query.
 */
class RequestForgeryAdditionalTaintStep extends Unit {
  /**
   * Holds if the step from `pred` to `succ` should be considered a taint
   * step for server-side request forgery.
   */
  abstract predicate propagatesTaint(DataFlow::Node pred, DataFlow::Node succ);
}

private class DefaultRequestForgeryAdditionalTaintStep extends RequestForgeryAdditionalTaintStep {
  override predicate propagatesTaint(DataFlow::Node pred, DataFlow::Node succ) {
    // propagate to a URI when its host is assigned to
    exists(UriCreation c | c.getHostArg() = pred.asExpr() | succ.asExpr() = c)
    or
    // propagate to a URL when its host is assigned to
    exists(UrlConstructorCall c | c.getHostArg() = pred.asExpr() | succ.asExpr() = c)
    or 
      //处理String.valueOf(URL)
    exists(StringValue c | c.getArgument(0) = pred.asExpr() | succ.asExpr() = c)
  }
}

private class TypePropertiesRequestForgeryAdditionalTaintStep extends RequestForgeryAdditionalTaintStep
{
  override predicate propagatesTaint(DataFlow::Node pred, DataFlow::Node succ) {
    exists(MethodAccess ma |
      // Properties props = new Properties();
      // props.setProperty("jdbcUrl", tainted);
      // Propagate tainted value to the qualifier `props`
      ma.getMethod() instanceof PropertiesSetPropertyMethod and
      ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "jdbcUrl" and
      pred.asExpr() = ma.getArgument(1) and
      succ.asExpr() = ma.getQualifier()
    )
  }
}

/** A data flow sink for server-side request forgery (SSRF) vulnerabilities. */
abstract class RequestForgerySink extends DataFlow::Node { }

private class DefaultRequestForgerySink extends RequestForgerySink {
  DefaultRequestForgerySink() { sinkNode(this, "request-forgery") }
}

/** A sanitizer for request forgery vulnerabilities. */
abstract class RequestForgerySanitizer extends DataFlow::Node { }

private class PrimitiveSanitizer extends RequestForgerySanitizer {
  PrimitiveSanitizer() {
    this.getType() instanceof PrimitiveType or
    this.getType() instanceof BoxedType or
    this.getType() instanceof NumberType
  }
}

private class HostnameSanitizingPrefix extends InterestingPrefix {
  int offset;

  HostnameSanitizingPrefix() {
    // Matches strings that look like when prepended to untrusted input, they will restrict
    // the host or entity addressed: for example, anything containing `?` or `#`, or a slash that
    // doesn't appear to be a protocol specifier (e.g. `http://` is not sanitizing), or specifically
    // the string "/".
    exists(this.getStringValue().regexpFind("([?#]|[^?#:/\\\\][/\\\\])|^/$", 0, offset))
  }

  override int getOffset() { result = offset }
}

/**
 * A value that is the result of prepending a string that prevents any value from controlling the
 * host of a URL.
 */
private class HostnameSantizer extends RequestForgerySanitizer {
  HostnameSantizer() { this.asExpr() = any(HostnameSanitizingPrefix hsp).getAnAppendedExpression() }
}

处理方法2

或者不改写lib,直接改写 ql 查询语句。

import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.ExternalFlow
import DataFlow::PathGraph
import semmle.code.java.security.RequestForgeryConfig

class TypeStringLib extends RefType {
  TypeStringLib() { this.hasQualifiedName("java.lang", "String") }
}

class StringValue extends MethodAccess {
  StringValue(){
    this.getCallee().getDeclaringType() instanceof TypeStringLib and
    this.getCallee().hasName("valueOf")
  }
}

private class MyRequestForgeryAdditionalTaintStep extends RequestForgeryAdditionalTaintStep {
  override predicate propagatesTaint(DataFlow::Node pred, DataFlow::Node succ) {
    // propagate to a URI when its host is assigned to
    exists(UriCreation c | c.getHostArg() = pred.asExpr() | succ.asExpr() = c)
    or
    // propagate to a URL when its host is assigned to
    exists(UrlConstructorCall c | c.getHostArg() = pred.asExpr() | succ.asExpr() = c)
    or 
    //处理String.valueOf(URL)
    exists(StringValue c | c.getArgument(0) = pred.asExpr() | succ.asExpr() = c)
  }
}

class SSRFVulConfig extends TaintTracking::Configuration {
  SSRFVulConfig() { this = "first_modifySSRF" }

  override predicate isSource(DataFlow::Node source) {
      source instanceof RemoteFlowSource and
      // Exclude results of remote HTTP requests: fetching something else based on that result
      // is no worse than following a redirect returned by the remote server, and typically
      // we're requesting a resource via https which we trust to only send us to safe URLs.
      not source.asExpr().(MethodAccess).getCallee() instanceof UrlConnectionGetInputStreamMethod
    }

   override predicate isSink(DataFlow::Node sink) {
      sink instanceof RequestForgerySink
   }

   override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
      any(RequestForgeryAdditionalTaintStep r).propagatesTaint(pred, succ)
    }


}
from SSRFVulConfig ssrfVulConfig, DataFlow::PathNode source, DataFlow::PathNode sink
where ssrfVulConfig.hasFlowPath(source, sink)
select source.getNode(), source, sink, "source"

ssrf 漏报处理2

还有一个漏的。

@RequestMapping(value = "/three")
public String Three(@RequestParam(value = "url") String imageUrl) {
    try {
        URL url = new URL(imageUrl);
        OkHttpClient client = new OkHttpClient();
        com.squareup.okhttp.Request request = new com.squareup.okhttp.Request.Builder().get().url(url).build();
        Call call = client.newCall(request);
        Response response = call.execute();
        return response.toString();
    } catch (IOException var1) {
        System.out.println(var1);
        return "Hello";
    }
}

com.squareup.okhttp.Request request = new com.squareup.okhttp.Request.Builder().get().url(url).build();

这种请求相对更复杂,因此需要自行构造规则。在这一条 ql 的语句当中有两个关键的定位锚点,一个是url(url),一个是 build()url() 确定是否引入污点,build() 确定sink的位置。结合这两者,进行检测 ql 的构造。先看一下这个的语法树。

这种链式结构调用在语法树中是包含的关系,当获取到最外层的 MethodAccess 时,可以使用 getAChildExpr() 方法返回其子语句,使用 getAChildExpr+() 可以递归返回全部子语句。结合前面说到的两个关键定位锚点,进行如下代码构造

最终。效果。7-20 行 ,56 行

import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.ExternalFlow
import DataFlow::PathGraph
import semmle.code.java.security.RequestForgeryConfig

MethodAccess url(MethodAccess ma,DataFlow::Node node){
  exists( MethodAccess mc | mc = ma.getAChildExpr()| if mc.getCallee().hasName("url") and mc.getArgument(0) = node.asExpr() then result = mc else result = url(mc,node)
  )
}

MethodAccess m(DataFlow::Node node){
  exists(
      MethodAccess ma | ma.getCallee().hasName("build") and ma.getCallee().getDeclaringType().hasName("Builder") |result = url(ma,node)
  )
}

class TypeStringLib extends RefType {
  TypeStringLib() { this.hasQualifiedName("java.lang", "String") }
}

class StringValue extends MethodAccess {
  StringValue(){
    this.getCallee().getDeclaringType() instanceof TypeStringLib and
    this.getCallee().hasName("valueOf")
  }
}

private class MyRequestForgeryAdditionalTaintStep extends RequestForgeryAdditionalTaintStep {
  override predicate propagatesTaint(DataFlow::Node pred, DataFlow::Node succ) {
    // propagate to a URI when its host is assigned to
    exists(UriCreation c | c.getHostArg() = pred.asExpr() | succ.asExpr() = c)
    or
    // propagate to a URL when its host is assigned to
    exists(UrlConstructorCall c | c.getHostArg() = pred.asExpr() | succ.asExpr() = c)
    or 
    //处理String.valueOf(URL)
    exists(StringValue c | c.getArgument(0) = pred.asExpr() | succ.asExpr() = c)
  }
}

class SSRFVulConfig extends TaintTracking::Configuration {
  SSRFVulConfig() { this = "first_modifySSRF" }

  override predicate isSource(DataFlow::Node source) {
      source instanceof RemoteFlowSource and
      // Exclude results of remote HTTP requests: fetching something else based on that result
      // is no worse than following a redirect returned by the remote server, and typically
      // we're requesting a resource via https which we trust to only send us to safe URLs.
      not source.asExpr().(MethodAccess).getCallee() instanceof UrlConnectionGetInputStreamMethod
    }

   override predicate isSink(DataFlow::Node sink) {
      sink instanceof RequestForgerySink or  
      //sink = URL对象
      exists (m(sink))
   }

   override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
      any(RequestForgeryAdditionalTaintStep r).propagatesTaint(pred, succ)
    }


}
from SSRFVulConfig ssrfVulConfig, DataFlow::PathNode source, DataFlow::PathNode sink
where ssrfVulConfig.hasFlowPath(source, sink)
select source.getNode(), source, sink, "source"

XXE

import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.ExternalFlow
import DataFlow::PathGraph

class XXEVulConfig extends TaintTracking::Configuration {
  XXEVulConfig(){
      this = "XXEVulConfig"
  }

  override predicate isSource(DataFlow::Node src) {
      src instanceof RemoteFlowSource
  }

  override predicate isSink(DataFlow::Node sink) {
      exists(Method method, MethodAccess call|
          method.hasName("parse") and
          call.getMethod() = method and
          sink.asExpr() = call.getArgument(0)
          )
  }
}

from XXEVulConfig xxeVulConfig, DataFlow::PathNode source, DataFlow::PathNode sink
where xxeVulConfig.hasFlowPath(source, sink)
select source.getNode(), source, sink, "source"
0 条评论
某人
表情
可输入 255