重定向
状态码3xx
存在问题的代码段
@GetMapping("/redirect")
public String redirect(@RequestParam("url") String url) {
return "redirect:" + url;
}
用户访问/redirect
路径时,redirect
方法会获取web
请求中的url
参数内容,并使用springboot
中的redirect
控制器,重定向到用户想要前往的界面
复现
http://127.0.0.1:8080/urlRedirect/redirect?url=http://www.baidu.com
网站会直接打开百度界面,burpsuite中则为302重定向状态码
@RequestMapping("/setHeader")
@ResponseBody
public static void setHeader(HttpServletRequest request, HttpServletResponse response) {
String url = request.getParameter("url");
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301 redirect
response.setHeader("Location", url);
}
访问/setHeader
路由时,服务端会接受url参数内容,设置网站响应状态码为301,并将网站相应的Location头(用于指向重定向的目标url)设置为url内容,并且没有任何过滤
复现
http://localhost:8080/urlRedirect/setHeader?url=http://www.baidu.com
网站显示为baidu界面,burpsuite显示的内容为301状态码
@RequestMapping("/sendRedirect")
@ResponseBody
public static void sendRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {
String url = request.getParameter("url");
response.sendRedirect(url);
}
代码段采用Java Servlet API
中的redirect
方法,用于将HTTP请求重定向到指定的URL(302)。
复现
http://127.0.0.11:8080/urlRedirect/sendRedirect?url=http://www.baidu.com
网站显示为百度界面,burpsuite显示的内容为302状态码
安全的代码写法
@RequestMapping("/forward")
@ResponseBody
public static void forward(HttpServletRequest request, HttpServletResponse response) {
String url = request.getParameter("url");
RequestDispatcher rd = request.getRequestDispatcher(url);
try {
rd.forward(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
从用户请求中获取url参数内容,设置RequestDispatcher对象,将用户的请求发送到url地址中,调用forward方法将请求和响应对象转发到url中,由于RequestDispatcher是用来在服务器端进行请求的内部处理和转发,所以只能在同源网站内进行跳转,无法跳转到外部网站
复现
http://127.0.0.1:8080/urlRedirect/forward?url=/urlRedirect/redirect
burpsuite中请求包,首先有一个重定向的数据包,然后请求重定向后的路由
如果是外部网站,状态码为200,路由,界面显示均无变化,并且idea中会显示请求转发到错误页面时出现了问题。
@RequestMapping("/sendRedirect/sec")
@ResponseBody
public void sendRedirect_seccode(HttpServletRequest request, HttpServletResponse response)
throws IOException {
String url = request.getParameter("url");
if (SecurityUtil.checkURL(url) == null) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("url forbidden");
return;
}
response.sendRedirect(url);
}
}
接受url参数,使用check方法过滤,白名单黑名单位置在url/url_safe_domain.xml
<safedomains>//白名单
<!-- 支持一级域名或多级域名 -->
<domain>joychou.com</domain>
<domain>joychou.org</domain>
<domain>test.joychou.org</domain>
<domain>localhost</domain>
</safedomains>
<!-- 支持一级域名或多级域名 -->
<blockdomains>//黑名单名单
<domain>baidu.com</domain>
<domain>evil.joychou.org</domain>
</blockdomains>
check方法,假设传入的参数为http://test.joychou.org
public static String checkURL(String url) {
if (null == url){ //检测是否为空
return null;
}
//读取文件中的黑白名单
ArrayList safeDomains = WebConfig.getSafeDomains();//白名单
ArrayList blockDomains = WebConfig.getBlockDomains();//黑名单
try {
String host = gethost(url);//test.joychou.org 获取host值
// 检测是否为http/https
if (!isHttp(url)) {
return null;
}
// 如果满足黑名单返回null 将host与黑名单中进行比对,有则null
if (blockDomains.contains(host)){
return null;
}
//检测host属性值是否有黑名单后缀,用于去除黑名单域名与其子域名
for(String blockDomain: blockDomains) {
if(host.endsWith("." + blockDomain)) {
return null;
}
}
// 支持一级域名 白名单
if (safeDomains.contains(host)){
return url;
}
// 支持多级域名 白名单
for(String safedomain: safeDomains) {
if(host.endsWith("." + safedomain)) {
return url;
}
}
return null;
} catch (NullPointerException e) {
logger.error(e.toString());
return null;
}
}
当用户传入的为白名单的网址则进行跳转,否则输出url forbidden
,