SQL注入之语义分析

作者: print("") 分类: 编译原理 发布时间: 2025-05-12 21:12

一、语义分析介绍

1.1 介绍

此次分析的语义分析模块使用的是 https://github.com/wallarm/libdetection

libdetection 原理是通过用户输入的字符串 使用词法提取的方式生成词法的Token 并标记好

当前Token的危险等级、和操作符,发送给语法分析, 再由语法分析进行提起符合预设的规则判断为攻击行为。

1.2 与正则、指纹库方案对比

名称
优点
缺点
语义分析
1.准确率高:深入理解 SQL 语句的语义,能准确识别复杂的注入模式,不易被绕过。例如对于利用编码、变形等手段隐藏的注入语句也有较好的检测效果。
2. 适应性强:可以适应不同类型的数据库和 SQL 语法,不局限于特定的规则。
3. 可检测复杂逻辑:能够检测包含复杂逻辑和嵌套结构的注入攻击,如多重条件组合、子查询注入等。
1.性能开销大:需要对 SQL 语句进行深度解析和语义理解,计算资源消耗大,处理速度相对较慢,在高并发场景下可能成为性能瓶颈。
  1. 实现复杂:需要专业的知识和大量的开发工作来构建语义分析引擎,维护成本较高。
  2. 依赖数据质量:分析结果依赖于训练数据和规则的质量,如果数据不全面或规则不准确,可能导致误判或漏判。
正则表达式
1.实现简单:语法相对简单,易于编写和理解,开发成本低。可以快速实现基本的 SQL 注入检测规则。
  1. 执行速度快:对于简单的模式匹配,正则表达式的执行效率较高,能够快速处理大量的输入。
  2. 灵活性高:可以根据不同的需求灵活编写和调整匹配规则。
1.规则维护困难:随着攻击手段的不断变化,需要不断更新和完善正则表达式规则,否则容易出现漏判。
  1. 误判率较高:对于一些正常的 SQL 语句,如果其中包含与注入模式相似的字符序列,可能会导致误判。
  2. 难以处理复杂情况:对于复杂的 SQL 注入模式,如经过编码、变形或嵌套的注入语句,正则表达式可能无法准确识别。
Libinjection指纹库
1.轻量级:代码简洁,资源占用少,对系统性能影响小,适合在资源受限的环境中使用。
  1. 检测速度快:采用了高效的算法,能够快速对输入进行检测,响应时间短。
  2. 开源且易用:是开源库,有丰富的文档和示例,方便开发者集成到自己的项目中。
1.规则有限:预定义的规则可能无法覆盖所有的 SQL 注入场景,对于一些新型的攻击手段可能检测能力不足。
  1. 定制性较差:虽然可以进行一定程度的定制,但相比于语义分析和正则表达式,定制的灵活性相对较低。
  2. 缺乏上下文理解:主要基于模式匹配,对 SQL 语句的语义理解不够深入,可能会出现误判或漏判的情况。

二、架构

2.1 框架图


2.2 执行流程图


三、源码分析

3.1 处理的总流程图

3.2 detect_init 初始化

初始化现有的所有的一个解析器模块
int
detect_parser_init(void)
{
    int rc = 0;
     // 初始化红黑树存储结构
    RB_INIT(&detect_parsers);
    // 顺序加载内置解析器模块
    TRYLOAD(rc, detect_parser_sqli);
    TRYLOAD(rc, detect_parser_pt);
    TRYLOAD(rc, detect_parser_bash);
done:
    // 任一模块加载失败时执行全局清理
    if (rc) {
        detect_parser_deinit();
    }
    return (rc);
}
例如:detect_parser_sqli
每个模块都是detect_parser 结构体
struct detect_parser detect_parser_sqli = {
    .name = {CSTR_LEN("sqli")},
    .open = detect_sqli_open,
    .close = detect_sqli_close,
    .start = detect_sqli_start,
    .stop = detect_sqli_stop,
    .add_data = detect_sqli_add_data,
};

// 模块的结构体
// 每个对象都是一个函数指针。这样就可以方便的使用入口函数调用模块内的函数
struct detect_parser {
    struct detect_str name;

    detect_parser_init_func init;
    detect_parser_deinit_func deinit;

    detect_parser_open_func open;
    detect_parser_close_func close;
    detect_parser_set_options_func set_options;

    detect_parser_start_func start;
    detect_parser_stop_func stop;
    detect_parser_add_data_func add_data;
};

3.3 detect_open 初始化模块上下文

这里调用的是SQL 那么调用就是sql的detect_parser_open_func 函数指针 最终调用到detect_sqli_open
static struct detect *
// 创建并初始化SQL注入检测器实例
detect_sqli_open(struct detect_parser *parser)
{   
    // 初始化一个detect 结构体
    struct detect *detect;
    unsigned i;
    // 
    detect = malloc(sizeof(*detect));
    //初始化检测器基础结构,设置解析器指针
    detect_instance_init(detect, parser);
    // 设置上下文数量(对应枚举SQLI_CTX_LAST的值)
    detect->nctx = SQLI_CTX_LAST;

    // 为所有上下文分配内存
    detect->ctxs = malloc(detect->nctx * sizeof(*detect->ctxs));  // 申请了6块 detect_ctx  指针内存的地址
    for (i = 0; i < detect->nctx; i++) {
        // 每个上下文地址都是detect_ctx 这个结构体指针
        // detect_ctx 结构体包含了  detect_ctx_desc    detect_ctx_result 这两个结构体
        struct sqli_detect_ctx *ctx;
        ctx = calloc(1, sizeof(*ctx));
        ctx->base.desc = (struct detect_ctx_desc *)&sqli_ctxs[i].desc;   // 这里例如第一个就是data  
        ctx->base.res = &ctx->res;  
        detect_ctx_result_init(ctx->base.res);  // 初始化检测结果结构体
        ctx->type = i;  
        ctx->ctxnum = i;
        ctx->detect = detect;  // 指向detect 结构体
        ctx->var_start_with_num = sqli_ctxs[i].var_start_with_num;  // 是否变量以数字开头

        // 这里存储的是sqli_detect_ctx 结构体。
        // 因为sqli_detect_ctx 中第一个成员就是detect_ctx 结构体 所以等价于detect_ctx
        detect->ctxs[i] = (void *)ctx;
    }
    // 返回初始化好的检测器
    return (detect);
}

3.4 detect_start 给上下文变量赋值

内置了5种检测类型
static const struct {
    struct detect_ctx_desc desc;
    enum sqli_parser_tokentype start_tok;
    bool var_start_with_num;
} sqli_ctxs[] = {
    // clang-format off
    [SQLI_CTX_DATA] = {
        .desc = {.name = {CSTR_LEN("data")}},
        .start_tok = TOK_START_DATA,  //上下文的开始
        .var_start_with_num = false,
    },
    [SQLI_CTX_IN_STRING] = {
        .desc = {.name = {CSTR_LEN("str")}},
        .start_tok = TOK_START_STRING, //表示SQL字符串上下文的开始
        .var_start_with_num = false,
    },
    [SQLI_CTX_RCE] = {
        .desc = {.name = {CSTR_LEN("rce")}, .rce = true},
        .start_tok = TOK_START_RCE, //表示远程命令执行上下文的开始
        .var_start_with_num = false,
    },
    [SQLI_CTX_DATA_VAR_START_WITH_NUM] = {
        .desc = {.name = {CSTR_LEN("data_num")}},
        .start_tok = TOK_START_DATA, //数据上下文的起始令牌
        .var_start_with_num = true,
    },
    [SQLI_CTX_IN_STRING_VAR_START_WITH_NUM] = {
        .desc = {.name = {CSTR_LEN("str_num")}},
        .start_tok = TOK_START_STRING,  //字符串上下文的起始令牌
        .var_start_with_num = true,
    },
    [SQLI_CTX_RCE_VAR_START_WITH_NUM] = {
        .desc = {.name = {CSTR_LEN("rce_num")}, .rce = true},
        .start_tok = TOK_START_RCE, //远程命令执行上下文的起始令牌
        .var_start_with_num = true,
    },
};
detect_sqli_start 函数都会给每个上下文进行初始化。给这个上下文最开始的一个状态。
这里使用了detect_sqli_push_token 进行写入状态
static int
detect_sqli_start(struct detect *detect)
{
    unsigned i;
    // 遍历所有上下文
    for (i = 0; i < detect->nctx; i++) {
        struct sqli_detect_ctx *ctx = (void *)detect->ctxs[i];
        // 如果当前上下文已经完成,则跳过
        if (ctx->res.finished){
            printf("ctx %u finished\n", i);
            continue;
        }
            
        // yypstate_new  
        ctx->pstate = sqli_parser_pstate_new();
        sqli_lexer_init(&ctx->lexer);
        if (detect_sqli_push_token(ctx, sqli_ctxs[ctx->type].start_tok, NULL) != 0)
            break;
    }
    return (0);
}

四、词法分析

re2c 代码如下
https://github.com/wallarm/libdetection/blob/master/lib/sqli/sqli_lexer.re2c

4.1 detect_add_data 添加Token到语法分析中

首先是通过sqli_get_token 来获取到上下文的的一个Token 那么这个Token 是怎么产生的。如下
static int
detect_sqli_add_data(struct detect *detect, const void *data, size_t siz, bool fin)
{
    unsigned i;
    union SQLI_PARSER_STYPE token_arg;
    int rv = 0;
    // 遍历所有上下文
    for (i = 0; i < detect->nctx; i++) {
        // 打印一下i对应的start_tok
        printf("[DEBUG] 开始检测上下文: %u\n", i);
        struct sqli_detect_ctx *ctx = (void *)detect->ctxs[i];
        // 
        int token;
         // 如果当前上下文的解析已经完成,则跳过该上下文
        if (ctx->res.finished)
            continue;
        sqli_lexer_add_data(ctx, data, siz, fin);
        do {
            memset(&token_arg, 0, sizeof(token_arg)); // 清空token_arg结构体
            token = sqli_get_token(ctx, &token_arg);

done:
    return (rv);
}

4.2 Token 产生的过程

以用户输入1′ union select 1,2,3,4 — 为例子
ctx->lexer.instring 为true的状态下
最开始进入到词法分析中
<> {
 // 检查是否在字符串处理模式
  if (ctx->lexer.instring) {
     // 如果在字符串中,设置为字符串处理状态
      YYSETCONDITION(sqli_INSTRING);
      arg->data.flags = SQLI_DATA_NOSTART;
      detect_buf_init(&ctx->lexer.buf, MINBUFSIZ, MAXBUFSIZ);
      goto sqli_INSTRING;
  }
   // 如果不在字符串中,设置为初始状态
  YYSETCONDITION(sqli_INITIAL);
  goto sqli_INITIAL;
}

4.2.1 处理1

那么会跳转到INSTRING 节点处理1
<INSTRING> "''"|'""'|'``' {
      detect_buf_add_char(&ctx->lexer.buf, DETECT_RE2C_YYCURSOR(&ctx->lexer.re2c)[-1]);
      goto sqli_INSTRING;
  }
<INSTRING> ['"`] => INITIAL {
  YYSETSTATE(-1);
  printf("INSTRING\n");
  RET_DATA(DATA, ctx, arg);
}
<INSTRING> ']' => INITIAL {
  YYSETSTATE(-1);
  RET_DATA(NAME, ctx, arg);
}
<INSTRING,SQUOTE,DQUOTE,BQUOTE,IQUOTE> [\x00] {
      if (ctx->lexer.re2c.fin && ctx->lexer.re2c.tmp_data_in_use &&
          ctx->lexer.re2c.pos >= ctx->lexer.re2c.tmp_data + ctx->lexer.re2c.tmp_data_siz) {
          YYSETCONDITION(sqli_INITIAL);
          DETECT_RE2C_YYCURSOR(&ctx->lexer.re2c)--;
          arg->data.flags |= SQLI_DATA_NOEND;
          YYSETSTATE(-1);
          RET_DATA(DATA, ctx, arg);
      }
      detect_buf_add_char(&ctx->lexer.buf, DETECT_RE2C_YYCURSOR(&ctx->lexer.re2c)[-1]);
      DETECT_RE2C_UNUSED_BEFORE(&ctx->lexer.re2c);
      goto yy0;
  }
  <INSTRING,SQUOTE,DQUOTE,BQUOTE,IQUOTE> .|[\n] {
      detect_buf_add_char(&ctx->lexer.buf, DETECT_RE2C_YYCURSOR(&ctx->lexer.re2c)[-1]); // 获取刚匹配的字符、加入到
      DETECT_RE2C_UNUSED_BEFORE(&ctx->lexer.re2c); // 标记当前字符已处理
      goto yy0;
  }
  <INSTRING,SQUOTE,DQUOTE,BQUOTE,IQUOTE> [^] {
      DETECT_RE2C_YYCURSOR(&ctx->lexer.re2c)--;
      YYSETSTATE(-1);
      RET_DATA_ERROR(ctx);
      RET(ctx, TOK_ERROR);
  }
那么根据匹配规则。会匹配到 <INSTRING,SQUOTE,DQUOTE,BQUOTE,IQUOTE> .|[\n] 这个接口
这个节点会将1 加入到缓冲区中。然后跳回到初始节点。

4.2.2 处理’

现在已经回到了初始化节点。那么又会走到sqli_INSTRING 这个函数。继续处理INSTRING 那么此刻INSTRING节点就是这几条。匹配规则。则会匹配到
<INSTRING> ['"`] => INITIAL {
  YYSETSTATE(-1);
  printf("INSTRING\n");
  RET_DATA(DATA, ctx, arg);
}
这里就会返回一个内容为1 Token为263 的值。对应的是TOK_DATA
并切换到INITIAL 节点

4.2.3 处理 空格

因为已经到了INITIAL 节点。那么处理空格就到了如下的一个块上去了
<INITIAL> whitespace {
  DETECT_RE2C_UNUSED_BEFORE(&ctx->lexer.re2c);
  goto sqli_INITIAL;
}
处理完空格后还是在INITIAL 节点中

4.2.4 处理union

因为INITIAL 节点上面有这个union 的token 就返回了291 这里并且设置了SQLI_KEY_INSTR 指令
<INITIAL> 'UNION'/key_end {
         printf("UNION\n");
          KEYNAME_SET_RET(ctx, arg, UNION, SQLI_KEY_INSTR);
      }

4.2.5 处理select

<INITIAL> 'SELECT'/key_end {
          KEYNAME_SET_RET(ctx, arg, SELECT, SQLI_KEY_READ|SQLI_KEY_INSTR);
      }

4.2.6 处理1

现在还是处于INITIAL 节点匹配到的就是
<INITIAL> '\\'|[0-9] {
printf("NUMBER222\n");
  if (ctx->var_start_with_num) {
      YYSETCONDITION(sqli_NUMBER_OR_VAR);
      detect_buf_init(&ctx->lexer.buf, MINBUFSIZ, 256);
      detect_buf_add_char(&ctx->lexer.buf, DETECT_RE2C_YYCURSOR(&ctx->lexer.re2c)[-1]);
      goto sqli_NUMBER_OR_VAR;
  } else {
      YYSETCONDITION(sqli_NUMBER);
      detect_buf_init(&ctx->lexer.buf, MINBUFSIZ, 256);
      detect_buf_add_char(&ctx->lexer.buf, DETECT_RE2C_YYCURSOR(&ctx->lexer.re2c)[-1]);
      goto sqli_NUMBER;
  }
}
这里1 已经被写入缓存了
然后跳到了sqli_NUMBER_OR_VAR 那么此刻的值是,
那么就会跳转到
<NUMBER_OR_VAR,NUMBER,DECIMAL,EXP_OR_VAR,EXP> [^] => INITIAL {
      DETECT_RE2C_YYCURSOR(&ctx->lexer.re2c)--;
      YYSETSTATE(-1);
      RET_DATA(NUM, ctx, arg);
  }
这里就会返回TOK_NUM 266 并且到INITIAL 这个节点

4.2.7 处理,号

因为已经在INITIAL 节点了 那么,号会跳转到
self = [,\.();=:{}~]; // 所有可能的SQL操作符字符
<INITIAL> self {
  printf("SELF\n");
  arg->data.value.str = (char *)&selfsyms[DETECT_RE2C_YYCURSOR(&ctx->lexer.re2c)[-1]];
  arg->data.value.len = 1;
  arg->data.flags = SQLI_KEY_INSTR;
  arg->data.tok = DETECT_RE2C_YYCURSOR(&ctx->lexer.re2c)[-1];
  RET(ctx, DETECT_RE2C_YYCURSOR(&ctx->lexer.re2c)[-1]);
}
这个节点直接返回ASCII 编码 44

4.2.7 处理2, 同理如上

返回266 和44

4.2.8 处理–

opchar = [!^&|%+\-*/<>];
<INITIAL> opchar => OPERATOR {
    printf("OPERATOR\n");
  detect_buf_init(&ctx->lexer.buf, MINBUFSIZ, MAXBUFSIZ);
  detect_buf_add_char(&ctx->lexer.buf, DETECT_RE2C_YYCURSOR(&ctx->lexer.re2c)[-1]);
  goto sqli_OPERATOR;
}
<INITIAL> '-' => MINUS {
    detect_buf_init(&ctx->lexer.buf, MINBUFSIZ, MAXBUFSIZ);
    detect_buf_add_char(&ctx->lexer.buf, DETECT_RE2C_YYCURSOR(&ctx->lexer.re2c)[-1]);
    goto sqli_MINUS;
}
这里有两个匹配的、那个在前面就会走那个。这里会走opchar 的组。下一跳为OPERATOR
<OPERATOR> '-' {
          assert(ctx->lexer.buf.data.len > 0);
          if (ctx->lexer.buf.data.str[ctx->lexer.buf.data.len - 1] != '-')
              goto opchar_generic;
          ctx->lexer.buf.data.len--;
          YYSETCONDITION(sqli_DASHCOMMENT);
          YYSETSTATE(-1);
          if (!ctx->lexer.buf.data.len) {
              detect_buf_deinit(&ctx->lexer.buf);
              goto sqli_DASHCOMMENT;
          }
          goto operator_done;
      }
这里因为不是结尾了。所以会走到DASHCOMMENT
<DASHCOMMENT> [\x00] => INITIAL {
          // TODO: create UNCLOSED_COMMENT key

      DETECT_RE2C_YYCURSOR(&ctx->lexer.re2c)--;
      goto sqli_INITIAL;
  }
触发结尾符。然后状态返回INITIAL最后触发结束符
<INITIAL> [\x00]|'-'[\x00]|'/'[\x00] {
         printf("INITIAL -");
          if (ctx->lexer.re2c.fin && ctx->lexer.re2c.tmp_data_in_use &&
              ctx->lexer.re2c.pos >= ctx->lexer.re2c.tmp_data + ctx->lexer.re2c.tmp_data_siz) {
              return (0);
          }
           printf("yy0 -");
          goto yy0;
      }
这里是没有返回的。

4.2.9 统计

最终得到的Token序列如下
对应的TOK
Token 值
1
TOK_DATA
263
UNION
TOK_UNION
291
SELECT
TOK_SELECT
313
空格
1
266
,
44
44
2
TOK_NUM
266
,
44
44
3
TOK_NUM
266
,
44
44
4
TOK_NUM
266
空格
整体向语法检测的输出了如下的Token
[DEBUG] 词法单元: 263
[DEBUG] 词法单元: 291
[DEBUG] 词法单元: 313
[DEBUG] 词法单元: 266
[DEBUG] 词法单元: 44
[DEBUG] 词法单元: 266
[DEBUG] 词法单元: 44
[DEBUG] 词法单元: 266
[DEBUG] 词法单元: 44
[DEBUG] 词法单元: 266

五、语法分析

yacc 代码如下
https://github.com/wallarm/libdetection/blob/master/lib/sqli/sqli_parser.y

5.1 LALR(1)算法介绍

这里采用的LALR(1)算法 它属于自下而上的分析方法
这里使用一个示例文法举例子

E -> E + T
E -> T
T -> T * F
T -> F
F -> ( E )
F -> id
自下而上分析过程示例
假设输入的符号串是 id + id * id,下面是使用该文法进行自下而上分析(移进 – 归约)的步骤:
步骤 栈状态 输入符号串 动作
1 $ id + id * id $ 移进 id
2 $ id + id * id $ 归约 F -> id
3 $ F + id * id $ 归约 T -> F
4 $ T + id * id $ 归约 E -> T
5 $ E + id * id $ 移进 +
6 $ E + id * id $ 移进 id
7 $ E + id * id $ 归约 F -> id
8 $ E + F * id $ 归约 T -> F
9 $ E + T * id $ 移进 *
10 $ E + T * id $ 移进 id
11 $ E + T * id $ 归约 F -> id
12 $ E + T * F $ 归约 T -> T * F
13 $ E + T $ 归约 E -> E + T
14 $ E $ 接受
每个上下文都会有一个初始的状态。
因为在detect_sqli_start 函数中。
static int
detect_sqli_start(struct detect *detect)
{
    unsigned i;
    // 遍历所有上下文
    for (i = 0; i < detect->nctx; i++) {e_new  
        ctx->pstate = sqli_parser_pstate_new();
        sqli_lexer_init(&ctx->lexer);
        if (detect_sqli_push_token(ctx, sqli_ctxs[ctx->type].start_tok, NULL) != 0)
            break;
    }
}

这个为初始的状态。后续的上下文都是从这个初始的状态开启。进行匹配语法的。
还是用用户输入1′ union select 1,2,3,4 — 为例子
ctx->lexer.instring 为true的状态下
[DEBUG] 词法单元: 263  TOK_DATA
[DEBUG] 词法单元: 291  TOK_UNION
[DEBUG] 词法单元: 313  TOK_SELECT
[DEBUG] 词法单元: 266  TOK_NUM
[DEBUG] 词法单元: 44
[DEBUG] 词法单元: 266  TOK_NUM
[DEBUG] 词法单元: 44
[DEBUG] 词法单元: 266  TOK_NUM
[DEBUG] 词法单元: 44
[DEBUG] 词法单元: 266   TOK_NUM

5.2 处理 263 TOK_DATA

data:     TOK_DATA
这里就直接返回了。因为解析器需要更多令牌,继续处理

5.3 处理 263 TOK_UNION

在处理TOK_UNION 之前。需要向上去找到TOK_DATA 最顶层的规则(文法)
data_name:  data
        |   TOK_NAME
        |   TOK_DATA2
        |   TOK_TABLE
        |   TOK_BINARY
        |   TOK_OPEN
        |   TOK_LANGUAGE
        |   TOK_PERCENT
        | '{'[u1] TOK_NAME[name] noop_expr '}'[u2] {
            YYUSE($u1);
            $$ = $name;
            YYUSE($u2);
        }
        /* Tokens-as-identifiers here */
        ;
找到了data_name 为什么找到data_name 因为 可以是由一个单独的data 来代表。那么就可以用这个data_name 继续往上找
colref_exact2 因为data_name 单独表示colref_exact2
colref_exact2:
        data_name
通过colref_exact2 继续向上找
colref_exact: colref_exact2
        | data_name[dname] ':'[u1] colref_exact2 {
             printf("经过 colref_exact21111\n");
            sqli_token_data_destructor(&$dname);
            YYUSE($u1);
        }
        ;
可以找到colref_exact
noop_expr: expr_common
        | logical_expr
        | colref_exact{
这里logical_expr 可以单独代表noop_expr 那么就可以继续往上找
expr_cont:
        | noop_expr after_exp_cont_op_noexpr after_exp_cont 
        | noop_expr where_opt after_exp_cont_op_noexpr after_exp_cont
        ;
处理UNION
union_tk:
          TOK_UNION {printf("经过 union_tk\n");}
        | TOK_INTERSECT
        | TOK_EXCEPT
        ;

union_c: union_tk[tk] {
     printf("经过 union_c\n");
            sqli_token_data_destructor(&$tk);
        }
        ;

5.4 处理 313 TOK_SELECT

select:   TOK_SELECT[tk] select_args into_opt from_opt
          where_opt select_after_where {
            sqli_store_data(ctx, &$tk);
        }
        | TOK_SELECT[tk] select_args from_opt into_opt
          where_opt select_after_where {
            sqli_store_data(ctx, &$tk);
        }
        | select union_c all_distinct_opt select_parens
        | select union_c all_distinct_opt execute
        ;
这里因为需要传递参数。所以需要等待用户传递的参数

5.4 处理 266 TOK_NUM

noop_expr: expr_common{ printf("noop_expr->expr_common\n");}
        | logical_expr{ printf("noop_expr->logical_expr\n");}
        | colref_exact{ printf("noop_expr->colref_exact\n");}
        | colref_asterisk{ printf("noop_expr->colref_asterisk\n");}
        | TOK_NUM {
            printf("noop_expr->TOK_NUM\n");
            sqli_token_data_destructor(&$TOK_NUM);
        }
        | noop_expr post_exprs
        ;
往上追溯noop_expr
select_arg:
          noop_expr alias_opt
        ;
因为alias_opt 是可以为空的。符合select_arg
那么
TOK_SELECT[tk] select_args from_opt into_opt
这条规则就符合from_opt into_opt where_opt select_after_where 这些都是可以为空的。
那么就成立select 这条规则。
debug 如下
[DEBUG] 词法单元: 263 Value: 1
TOK_DATA
[DEBUG] 词法单元: 291 Value: UNION
经过 colref_exact2
 sqli_token_data_destructor Value: 1
经过 colref_exact: colref_exact2
noop_expr->colref_exact
11dada
经过 union_tk
经过 union_c
 sqli_token_data_destructor Value: UNION
[DEBUG] 词法单元: 313 Value: SELECT
[DEBUG] 词法单元: 266 Value: 1
noop_expr->TOK_NUM
 sqli_token_data_destructor Value: 1
[DEBUG] 词法单元: 44 Value: ,
select_arg->noop_expr
经过 select_list
[DEBUG] 词法单元: 266 Value: 1
noop_expr->TOK_NUM
 sqli_token_data_destructor Value: 1
select_arg->noop_expr
经过 27222
经过 select2
sqli_store_data: unknown token 313
sqli_store_key1: READ
sqli_store_key2: SELECT
detect_ctx_result_store_token1: READ
sqli_store_key1: INSTR
sqli_store_key2: SELECT
detect_ctx_result_store_token1: INSTR
 sqli_token_data_destructor Value: SELECT ---------------------------end----------------


六、总结

1. 这个思路可以参考,实际应用当中会有很大的误报需要进行调整词法分析和语法分析

2. 性能较弱

3. 需要增加打分选项

4. 规约冲突 移进冲突较多、需要细化 

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注