Nginx_lua 100参数绕过原理详解

一、环境搭建
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++

这里画了一个图

本文来源于: https://xz.aliyun.com/t/9390

相关推荐

记一次靠猜的.net代码审计拿下目标

0x00写在前面 在一次授权的实战测试中,需要拿到某OA的权限,经过top500的姓名+top100的密码,爆破出来几个账户,有了账户,进入oa内部,通过上传很轻松的就拿到了shell,但是客户不满足于此,要求找到未授权的RCE.有了she

网络空间搜索引擎的区别

网络空间搜索引擎的区别 ### fofa fofa是白帽汇推出的网络空间测绘引擎。白帽汇是一家专注于网络空间测绘与前沿技术研究的互联网安全公司,主要从事网络安全产品开发与服务支撑等相关工作,为国家监管部门及政企用户提供综合性整体解决方案,有

D-Link路由器漏洞研究分享

0x0 前言 D-Link DIR-816 A2是中国台湾友讯(D-Link)公司的一款无线路由器。攻击者可借助‘datetime’参数中的shell元字符利用该漏洞在系统上执行任意命令。 0x1 准备 固件版本 1.10B05:http:

主流WebShell工具流量层分析

很多人都说冰蝎好用,流量加密的,可是流量加密在哪里,很多人可能还是懵懵懂懂的,于是就分析记录了一下,大佬勿喷。 AntSword流量分析 蚁剑有一个非常好用的扩展功能为编码器和解码器,利用此功能可以自定义加密,这里分析的是默认的加密方式,在

CVE-2019-12422 Apache Shiro RememberMe Padding Oracle

前置知识 CBC模式 首先我们可以看一下CBC模式的流程图 初始化向量IV和第一组明文XOR后得到的结果作为新的IV和下一组明文XOR,按这样循环下去就得到结果。解密是加密的逆过程,也就是密文被Key解密为中间值,然后中间值与IV进行XOR

CC链 1-7 分析

一、简介 Apache Commons 是对 JDK 的拓展,包含了很多开源的工具,用于解决平时编程经常会遇到的问题。Apache Commons 当中有一个组件叫做 Apache Commons Collections,封装了 Java

记一次 getshell 过程

前言 兜兜转转最终拿到了 shell ,但是发现大佬已经在前一个小时 getshell 了,记录一下我是怎么发现的过程。 未授权测试是违法的,仅供学习交流。 过程 打开网站查看源代码,发现成功登录后会跳转到 f0.html 文件 没登录直接

GitHub SSTI靶场 wp

本人小白,师傅们勿喷 payload主要有两种形式: 一种是获取os来执行命令 {{''.__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['popen']('

ThinkPHP 6 反序列化漏洞

ThinkPHP6 反序列化漏洞 环境W tp6.0 apache php7.3 漏洞分析 反序列化漏洞需要存在 unserialize() 作为触发条件,修改入口文件 app/controller/Index.php 注意tp6的url访

记一次详细的代码审计

前言 本篇是对极致CMSv1.7漏洞的一些新的发现,先从MVC开始到漏洞的发掘利用 MVC篇 首先,先打开index.php <?php // +-----------------------------------------------

Yii2反序列化RCE 新POP链

Yii反序列化漏洞 0x搭建环境 首先利用composer安装yii2框架 composer create-project yiisoft/yii2-app-basic yii2 yii2 version <= 2.0.41(GitHub最