一、环境搭建
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++
这里画了一个图