Nginx_lua 100参数绕过原理详解

作者: print("") 分类: 信息安全 发布时间: 2021-04-01 19:55

一、环境搭建

Nginx_lua 安装

https://github.com/openresty/lua-nginx-module#installation

wget 'https://openresty.org/download/nginx-1.19.3.tar.gz'
 tar -xzvf nginx-1.19.3.tar.gz
 cd nginx-1.19.3/

 # tell nginx's build system where to find LuaJIT 2.0:
 export LUAJIT_LIB=/path/to/luajit/lib
 export LUAJIT_INC=/path/to/luajit/include/luajit-2.0

 # tell nginx's build system where to find LuaJIT 2.1:
 export LUAJIT_LIB=/path/to/luajit/lib
 export LUAJIT_INC=/path/to/luajit/include/luajit-2.1

 # Here we assume Nginx is to be installed under /opt/nginx/.
 ./configure --prefix=/opt/nginx \
         --with-ld-opt="-Wl,-rpath,/path/to/luajit/lib" \
         --add-module=/path/to/ngx_devel_kit \
         --add-module=/path/to/lua-nginx-module

 # Note that you may also want to add `./configure` options which are used in your
 # current nginx build.
 # You can get usually those options using command nginx -V

 # you can change the parallism number 2 below to fit the number of spare CPU cores in your
 # machine.
 make -j2
 make install

安装完之后可以在nginx.conf 写入配置。可以动态在Nginx 层面进行过滤和调度

这里使用一个很简单的方式来展示绕过的原理

  location = /api2 {
        content_by_lua_block {
            tmp=''
            for i,v in pairs(ngx.req.get_uri_args()) do
              if type(i)=='string' then 
                tmp=tmp..i..' '
              end
            end
            ngx.header.content_type = "application/json;"
          	ngx.status = 200
          	ngx.say(tmp)
            ngx.exit(200)
        }
    }

这里是意思是访问/api2 然后返回get的所有参数。默认他是接受100个参数。当超过100个参数的时候会默认不会记录。这样达成了一个绕过的一个方式。演示如下:

首先先发送两个id 过去试试

那么试试id1->id100

a=''
for i in range(1,102):
   a=a+'id'+str(i)+'=11&'
print(a)
GET /api2?id1=11&id2=11&id3=11&id4=11&id5=11&id6=11&id7=11&id8=11&id9=11&id10=11&id11=11&id12=11&id13=11&id14=11&id15=11&id16=11&id17=11&id18=11&id19=11&id20=11&id21=11&id22=11&id23=11&id24=11&id25=11&id26=11&id27=11&id28=11&id29=11&id30=11&id31=11&id32=11&id33=11&id34=11&id35=11&id36=11&id37=11&id38=11&id39=11&id40=11&id41=11&id42=11&id43=11&id44=11&id45=11&id46=11&id47=11&id48=11&id49=11&id50=11&id51=11&id52=11&id53=11&id54=11&id55=11&id56=11&id57=11&id58=11&id59=11&id60=11&id61=11&id62=11&id63=11&id64=11&id65=11&id66=11&id67=11&id68=11&id69=11&id70=11&id71=11&id72=11&id73=11&id74=11&id75=11&id76=11&id77=11&id78=11&id79=11&id80=11&id81=11&id82=11&id83=11&id84=11&id85=11&id86=11&id87=11&id88=11&id89=11&id90=11&id91=11&id92=11&id93=11&id94=11&id95=11&id96=11&id97=11&id98=11&id99=11&id100=11&id101=11 HTTP/1.1
Host: 192.168.1.70
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36
Connection: close

返回从1-100

id76 id74 id64 id62 id60 id61 id5 id73 id71 id14 id91 id15 id20 id22 id12 id66 id13 id32 id10 id31 id33 id11 id92 id21 id84 id93 id85 id67 id30 id83 id58 id3 id88 id59 id98 id68 id69 id81 id48 id49 id8 id9 id25 id24 id26 id16 id79 id17 id36 id35 id18 id89 id99 id29 id28 id100 id97 id96 id95 id94 id6 id38 id90 id87 id39 id86 id19 id82 id80 id42 id56 id78 id52 id77 id75 id57 id44 id53 id7 id54 id72 id50 id1 id46 id55 id70 id51 id65 id23 id2 id40 id37 id4 id43 id47 id27 id41 id45 id34 id63 

但是没有id101 那么这个id101 哪里去了呢?

那么看看ngx.req.get_uri_args() 这个函数是怎么实现的

二、源码解析

参考文章:

https://blog.csdn.net/liujiyong7/article/details/37692027

src/ngx_http_lua_module.c为模块主入口文件

注册函数的写法有统一的格式:

static int
ngx_http_lua_ngx_req_get_method(lua_State *L)
{
    int                      n;
    ngx_http_request_t      *r;
    n = lua_gettop(L);
    if (n != 0) {
        return luaL_error(L, "only one argument expected but got %d", n);
    }
    r = ngx_http_lua_get_req(L);//从lua全局变量得到request结构体指针,见4.2.2
    if (r == NULL) {
        return luaL_error(L, "request object not found");
    }
    ngx_http_lua_check_fake_request(L, r);//检查r合法性
 
    lua_pushlstring(L, (char *) r->method_name.data, r->method_name.len);//将method压栈
    return 1;
}

注册get_uri_args 在

所有的nginx api for lua注册在lua-nginx-module/src/ngx_http_lua_util.c:ngx_http_lua_inject_ngx_api 函数中

与request有关的注册在lua-nginx-module/src/ngx_http_lua_util.c: ngx_http_lua_inject_req_api 函数中

ngx_http_lua_inject_ngx_api 函数

static void
ngx_http_lua_inject_ngx_api(lua_State *L, ngx_http_lua_main_conf_t *lmcf,
    ngx_log_t *log)
{
    lua_createtable(L, 0 /* narr */, 113 /* nrec */);    /* ngx.* */

    lua_pushcfunction(L, ngx_http_lua_get_raw_phase_context);
    lua_setfield(L, -2, "_phase_ctx");

    ngx_http_lua_inject_arg_api(L);

    ngx_http_lua_inject_http_consts(L);
    ngx_http_lua_inject_core_consts(L);

    ngx_http_lua_inject_log_api(L);
    ngx_http_lua_inject_output_api(L);
    ngx_http_lua_inject_string_api(L);
    ngx_http_lua_inject_control_api(log, L);
    ngx_http_lua_inject_subrequest_api(L);
    ngx_http_lua_inject_sleep_api(L);

    ngx_http_lua_inject_req_api(log, L);
    ngx_http_lua_inject_resp_header_api(L);
    ngx_http_lua_create_headers_metatable(log, L);
    ngx_http_lua_inject_shdict_api(lmcf, L);
    ngx_http_lua_inject_socket_tcp_api(log, L);
    ngx_http_lua_inject_socket_udp_api(log, L);
    ngx_http_lua_inject_uthread_api(log, L);
    ngx_http_lua_inject_timer_api(L);
    ngx_http_lua_inject_config_api(L);

    lua_getglobal(L, "package"); /* ngx package */
    lua_getfield(L, -1, "loaded"); /* ngx package loaded */
    lua_pushvalue(L, -3); /* ngx package loaded ngx */
    lua_setfield(L, -2, "ngx"); /* ngx package loaded */
    lua_pop(L, 2);

    lua_setglobal(L, "ngx");

    ngx_http_lua_inject_coroutine_api(log, L);
}

ngx_http_lua_inject_req_api 函数

void
ngx_http_lua_inject_req_api(ngx_log_t *log, lua_State *L)
{
    /* ngx.req table */

    lua_createtable(L, 0 /* narr */, 23 /* nrec */);    /* .req */

    ngx_http_lua_inject_req_header_api(L);
    ngx_http_lua_inject_req_uri_api(log, L);
    ngx_http_lua_inject_req_args_api(L);
    ngx_http_lua_inject_req_body_api(L);
    ngx_http_lua_inject_req_socket_api(L);
    ngx_http_lua_inject_req_misc_api(L);

    lua_setfield(L, -2, "req");
}

看着应该是ngx_http_lua_inject_req_uri_api 和 ngx_http_lua_inject_req_args_api 比较像 跟踪一下这两个函数

ngx_http_lua_inject_req_uri_api

void
ngx_http_lua_inject_req_uri_api(ngx_log_t *log, lua_State *L)
{
    lua_pushcfunction(L, ngx_http_lua_ngx_req_set_uri);
    lua_setfield(L, -2, "set_uri");
}

ngx_http_lua_inject_req_args_api

ngx_http_lua_inject_req_args_api(lua_State *L)
{
    lua_pushcfunction(L, ngx_http_lua_ngx_req_set_uri_args);
    lua_setfield(L, -2, "set_uri_args");

    lua_pushcfunction(L, ngx_http_lua_ngx_req_get_post_args);
    lua_setfield(L, -2, "get_post_args");
}}

这里只有set_uri_args 和get_post_args 并没有找到get_uri_args

这里陷入了深深的沉思

全局搜索下只有ngx_http_lua_ffi_req_get_uri_args 这一个函数是相关的 。

这个函数在src/ngx_http_lua_args.c

三、查看get_post_args 这个函数过程

首先看一下get_post_args 这个一个过程吧

注册为get_post_args 那么nginx内部的调用方式为ngx.req.get_post_args

lua_pushcfunction(L, ngx_http_lua_ngx_req_get_post_args);
lua_setfield(L, -2, "get_post_args");

ngx_http_lua_ngx_req_get_post_args 函数体

static int
ngx_http_lua_ngx_req_get_post_args(lua_State *L)
{
    ngx_http_request_t          *r;
    u_char                      *buf;
    int                          retval;
    size_t                       len;
    ngx_chain_t                 *cl;
    u_char                      *p;
    u_char                      *last;
    int                          n;
    int                          max;

    n = lua_gettop(L);

    if (n != 0 && n != 1) {
        return luaL_error(L, "expecting 0 or 1 arguments but seen %d", n);
    }

    if (n == 1) {
        max = luaL_checkinteger(L, 1);
        lua_pop(L, 1);

    } else {
        max = NGX_HTTP_LUA_MAX_ARGS;
    }

    r = ngx_http_lua_get_req(L);
    if (r == NULL) {
        return luaL_error(L, "no request object found");
    }

    ngx_http_lua_check_fake_request(L, r);

    if (r->discard_body) {
        lua_createtable(L, 0, 0);
        return 1;
    }

    if (r->request_body == NULL) {
        return luaL_error(L, "no request body found; "
                          "maybe you should turn on lua_need_request_body?");
    }

    if (r->request_body->temp_file) {
        lua_pushnil(L);
        lua_pushliteral(L, "request body in temp file not supported");
        return 2;
    }

    if (r->request_body->bufs == NULL) {
        lua_createtable(L, 0, 0);
        return 1;
    }

    /* we copy r->request_body->bufs over to buf to simplify
     * unescaping query arg keys and values */

    len = 0;
    for (cl = r->request_body->bufs; cl; cl = cl->next) {
        len += cl->buf->last - cl->buf->pos;
    }

    dd("post body length: %d", (int) len);

    if (len == 0) {
        lua_createtable(L, 0, 0);
        return 1;
    }

    buf = ngx_palloc(r->pool, len);
    if (buf == NULL) {
        return luaL_error(L, "no memory");
    }

    lua_createtable(L, 0, 4);

    p = buf;
    for (cl = r->request_body->bufs; cl; cl = cl->next) {
        p = ngx_copy(p, cl->buf->pos, cl->buf->last - cl->buf->pos);
    }

    dd("post body: %.*s", (int) len, buf);

    last = buf + len;

    retval = ngx_http_lua_parse_args(L, buf, last, max);

    ngx_pfree(r->pool, buf);

    return retval;
}

上述的关键的在于

max = NGX_HTTP_LUA_MAX_ARGS;

找到定义的NGX_HTTP_LUA_MAX_ARGS 默认为100

ifndef NGX_HTTP_LUA_MAX_ARGS
	define NGX_HTTP_LUA_MAX_ARGS 100
endif

然后走到了ngx_http_lua_parse_args 这个函数

int
ngx_http_lua_parse_args(lua_State *L, u_char *buf, u_char *last, int max)
{
    u_char                      *p, *q;
    u_char                      *src, *dst;
    unsigned                     parsing_value;
    size_t                       len;
    int                          count = 0;
    int                          top;

    top = lua_gettop(L);

    p = buf;

    parsing_value = 0;
    q = p;

    while (p != last) {
        if (*p == '=' && ! parsing_value) {
            /* key data is between p and q */

            src = q; dst = q;

            ngx_http_lua_unescape_uri(&dst, &src, p - q,
                                      NGX_UNESCAPE_URI_COMPONENT);

            dd("pushing key %.*s", (int) (dst - q), q);

            /* push the key */
            lua_pushlstring(L, (char *) q, dst - q);

            /* skip the current '=' char */
            p++;

            q = p;
            parsing_value = 1;

        } else if (*p == '&') {
            /* reached the end of a key or a value, just save it */
            src = q; dst = q;

            ngx_http_lua_unescape_uri(&dst, &src, p - q,
                                      NGX_UNESCAPE_URI_COMPONENT);

            dd("pushing key or value %.*s", (int) (dst - q), q);

            /* push the value or key */
            lua_pushlstring(L, (char *) q, dst - q);

            /* skip the current '&' char */
            p++;

            q = p;

            if (parsing_value) {
                /* end of the current pair's value */
                parsing_value = 0;

            } else {
                /* the current parsing pair takes no value,
                 * just push the value "true" */
                dd("pushing boolean true");

                lua_pushboolean(L, 1);
            }

            (void) lua_tolstring(L, -2, &len);

            if (len == 0) {
                /* ignore empty string key pairs */
                dd("popping key and value...");
                lua_pop(L, 2);

            } else {
                dd("setting table...");
                ngx_http_lua_set_multi_value_table(L, top);
            }

            if (max > 0 && ++count == max) {
                lua_pushliteral(L, "truncated");

                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                               "lua hit query args limit %d", max);
                return 2;
            }

        } else {
            p++;
        }
    }

    if (p != q || parsing_value) {
        src = q; dst = q;

        ngx_http_lua_unescape_uri(&dst, &src, p - q,
                                  NGX_UNESCAPE_URI_COMPONENT);

        dd("pushing key or value %.*s", (int) (dst - q), q);

        lua_pushlstring(L, (char *) q, dst - q);

        if (!parsing_value) {
            dd("pushing boolean true...");
            lua_pushboolean(L, 1);
        }

        (void) lua_tolstring(L, -2, &len);

        if (len == 0) {
            /* ignore empty string key pairs */
            dd("popping key and value...");
            lua_pop(L, 2);

        } else {
            dd("setting table...");
            ngx_http_lua_set_multi_value_table(L, top);
        }
    }

    dd("gettop: %d", lua_gettop(L));
    dd("type: %s", lua_typename(L, lua_type(L, 1)));

    if (lua_gettop(L) != top) {
        return luaL_error(L, "internal error: stack in bad state");
    }

    return 1;
}

如上代码。读取等于号之前的作为key 然后& 之前的作为value 。然后进行保存到内存中然后进行判断是否大于等于max

获取key

            src = q; dst = q;

            ngx_http_lua_unescape_uri(&dst, &src, p - q,
                                      NGX_UNESCAPE_URI_COMPONENT);

            dd("pushing key %.*s", (int) (dst - q), q);

            /* push the key */
            lua_pushlstring(L, (char *) q, dst - q);

            /* skip the current '=' char */
            p++;

            q = p;
            parsing_value = 1;

value

	src = q; dst = q;

	ngx_http_lua_unescape_uri(&dst, &src, p - q,
							  NGX_UNESCAPE_URI_COMPONENT);

	dd("pushing key or value %.*s", (int) (dst - q), q);

	/* push the value or key */
	lua_pushlstring(L, (char *) q, dst - q);

判断长度是否等于max

  if (max > 0 && ++count == max) {
                lua_pushliteral(L, "truncated");

                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
                               "lua hit query args limit %d", max);
                return 2;
            }

但是现在还有一个疑问就是get_uri_args 这个怎么获取的呢? 如上是获取了get_post_args

四、get_uri_args

参考大量的代码发现。他这个是内置的一个格式ngx_http_lua_ffi 开头。我也没有找到他内部怎么注册流程。

例如:

ngx_http_lua_ffi_encode_base64

ngx_http_lua_ffi_unescape_uri

ngx_http_lua_ffi_time

暂时没有找到他的内部注册的逻辑。这里先不做讨论了。如果有大佬可以指出哪里是注册流程话记得艾特一下我

ngx_http_lua_ffi_req_get_uri_args 代码如下

int
ngx_http_lua_ffi_req_get_uri_args(ngx_http_request_t *r, u_char *buf,
    ngx_http_lua_ffi_table_elt_t *out, int count)
{
    int                          i, parsing_value = 0;
    u_char                      *last, *p, *q;
    u_char                      *src, *dst;

    if (count <= 0) {
        return NGX_OK;
    }

    ngx_memcpy(buf, r->args.data, r->args.len);

    i = 0;
    last = buf + r->args.len;
    p = buf;
    q = p;

    while (p != last) {
        if (*p == '=' && !parsing_value) {
            /* key data is between p and q */

            src = q; dst = q;

            ngx_http_lua_unescape_uri(&dst, &src, p - q,
                                      NGX_UNESCAPE_URI_COMPONENT);

            dd("saving key %.*s", (int) (dst - q), q);

            out[i].key.data = q;
            out[i].key.len = (int) (dst - q);

            /* skip the current '=' char */
            p++;

            q = p;
            parsing_value = 1;

        } else if (*p == '&') {
            /* reached the end of a key or a value, just save it */
            src = q; dst = q;

            ngx_http_lua_unescape_uri(&dst, &src, p - q,
                                      NGX_UNESCAPE_URI_COMPONENT);

            dd("pushing key or value %.*s", (int) (dst - q), q);

            if (parsing_value) {
                /* end of the current pair's value */
                parsing_value = 0;

                if (out[i].key.len) {
                    out[i].value.data = q;
                    out[i].value.len = (int) (dst - q);
                    i++;
                }

            } else {
                /* the current parsing pair takes no value,
                 * just push the value "true" */
                dd("pushing boolean true");

                if (dst - q) {
                    out[i].key.data = q;
                    out[i].key.len = (int) (dst - q);
                    out[i].value.len = -1;
                    i++;
                }
            }

            if (i == count) {
                return i;
            }

            /* skip the current '&' char */
            p++;

            q = p;

        } else {
            p++;
        }
    }

    if (p != q || parsing_value) {
        src = q; dst = q;

        ngx_http_lua_unescape_uri(&dst, &src, p - q,
                                  NGX_UNESCAPE_URI_COMPONENT);

        dd("pushing key or value %.*s", (int) (dst - q), q);

        if (parsing_value) {
            if (out[i].key.len) {
                out[i].value.data = q;
                out[i].value.len = (int) (dst - q);
                i++;
            }

        } else {
            if (dst - q) {
                out[i].key.data = q;
                out[i].key.len = (int) (dst - q);
                out[i].value.len = (int) -1;
                i++;
            }
        }
    }

    return i;

首先呢。他这个也是获取一个key 和一个value 的过程。然后判断一下是否是i==count

i 这个地方是一个整数。每次设置好值之后i++

这里画了一个图

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

发表回复

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