Java 关于爬取网站数据遇到csrf-token的分析与解决

问题描述

在爬取某网站的时候遇到了问题,因为网站的避免CSRF攻击机制,无法获取到目标页面数据,而是跳转到一个默认页面。

关于CSRF

1、CSRF tokens是如何工作(详情请点击查引用源站点)

1、服务器发送给客户端一个token。
2、客户端提交的表单中带着这个token。
3、如果这个token不合法,那么服务器拒绝这个请求。

2、java 站点应对CSRF参考(详情请点击查引用源站点)
3、Laravel中如何避免CSRF攻击(详情请点击查引用源站点)

爬取站点分析

1、在http头信息可见X-CSRF-Token字段
这里写图片描述
2、X-CSRF-Token来源,meta标签截图
这里写图片描述

解决思路

1、获取请求前,先去请求一次这个链接地址的Host,在这一次请求中获取Token,以及Cookie
2、用获取到的Token和Cookie作为头信息去请求传入链接地址
3、相当于多请求了一次这个请求链接地址的Host,必须要注意的是确定获取的Host返回头信息中包含有Set-Cookie字段,以及返回的内容中有包含Token的mate标签,存在这两个条件的时候,这个解决流程才可能实现

实现代码

1、获取Cookie与Token

package dto.ajax;

import org.apache.commons.lang3.StringUtils;
import tool.GetUrlData;
import tool.RegexFinder;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;

/**
 * Created by yuyu on 2018/3/23.
 * 获取处理CSRF需要的相关信息
 * 原理:
 *  每个用户Session生成了一个CSRF Token,
 *  该Token可用于验证登录用户和发起请求者是否是同一人,
 *  如果不是则请求失败。
 */
public class CsrfToken {

    String cookie;//用于请求的站点的cokie
    String csrf_token;//用于请求站点的密钥

    /**
     * 接收一个网站的host,设置接口请求需要的数据
     * 1、获取网站请求回来的Set-Cookie字段
     * 2、然后获取内容中的密钥
     *      <meta content="密钥" name="csrf-token" />
     *
     * @param url
     */
    public CsrfToken(String url) {
        //校验传输安全
        if(StringUtils.isNotBlank(url)){
            try{
                //设置请求的头信息.获取url的host
                String host=new URL(url).getHost();
                URLConnection connection= GetUrlData.getConnection("http://"+host,null);
                //获取请求回来的信息
                String data = GetUrlData.getStringByConnection(connection);
                //匹配token
                String metaRegex="<meta(.*?)\\/>";
                String tokenRegex="content=\"(.*?)\"";
                String tokenReplace="content=\"|\"";
                String tokenName="csrf-token";//mate中的名称
                //获取mate头信息
                List<String> mate=RegexFinder.getAllToList(metaRegex ,data);
                for (String info :mate){
                    if (info.indexOf(tokenName)>0){
                        //取出对应的密钥
                        this.csrf_token=RegexFinder.findOneByReplaceEmpty(tokenRegex
                                ,info,tokenReplace);
                    }
                }
                //获取cookie头信息
                this.cookie = connection.getHeaderField("Set-Cookie");
                //胡罗内容以外的字段
                String regexCookie="\\S+=(.*?);";
                if (this.cookie!=null){
                    this.cookie=RegexFinder.findOne(regexCookie,this.cookie);
                }

            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    public String getCookie() {
        return cookie;
    }

    public void setCookie(String cookie) {
        this.cookie = cookie;
    }

    public String getCsrf_token() {
        return csrf_token;
    }

    public void setCsrf_token(String csrf_token) {
        this.csrf_token = csrf_token;
    }
}

2、正则工具类

package tool;

import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by yy on 2017/12/19.
 * 用于正则的工具类
 */
public class RegexFinder {

    /**
     * 匹配一个数据与正则,返回匹配的数据
     *
     * @param regex
     * @param info
     * @return
     * @throws Exception
     */
    public  static String findOne(String regex,String info){
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(info);
        if(matcher.find()){
            return matcher.group();
        }
        return null;
    }

    /**
     * 匹配一个数据与正则,返回匹配的数据
     *
     * @param regex
     * @param info
     * @return
     * @throws Exception
     */
    public  static String findOneByReplaceEmpty(String regex,String info,
                                                            String replace){
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(info);
        if(matcher.find()){
            return matcher.group().replaceAll(replace,"");
        }
        return null;
    }

    /**
     * 获取正则匹配全部可能,到list数据
     * @param regex
     * @param info
     * @return 成功返回一个
     */
    public  static List<String> getAllToList(String regex,String info){
        //数据安全校验
        if(StringUtils.isEmpty(regex)||StringUtils.isEmpty(info)){
            return null;
        }
        List<String> back=new ArrayList<String>();
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(info);
        while (matcher.find()){
            back.add(matcher.group());
        }
        return back;
    }

    /**
     * 获取正则匹配全部可能,到list数据,替换字符中得数据
     * @param regex
     * @param info
     * @param replace
     * @return
     */
    public  static List<String> getAllToListByReplaceEmpty(String regex,String info
            , String replace) {
        //数据安全校验
        if(StringUtils.isEmpty(regex)||StringUtils.isEmpty(info)){
            return null;
        }
        List<String> back=new ArrayList<String>();
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(info);
        while (matcher.find()){
            back.add(matcher.group().replaceAll(replace,""));
        }
        return back;
    }
}

3、爬取调用逻辑

package tool;

import dto.ajax.CsrfToken;
import org.apache.commons.lang3.StringUtils;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;


/**
 * Created by yy on 2017/10/17.
 * 获取url信息的工具类
 */
public class GetUrlData {
    /**
     * 破解X-CSRF-Token 跨站请求伪造限制
     * @param url
     * @return
     * @throws Exception
     */
    public static String getX_CSRF_Token(String url) throws Exception{

        CsrfToken  csrfToken=new CsrfToken(url);
        //设置头信息
        Map<String,String> args=new HashMap<String, String>();
        args.put("Cookie",csrfToken.getCookie());
        args.put("X-CSRF-Token",csrfToken.getCsrf_token());
        args.put("X-Requested-With","XMLHttpRequest");
        args.put("Accept","application/json, text/javascript, */*; q=0.01");

        URLConnection connection=GetUrlData.getConnection(url,args);
        //获取数据
        return GetUrlData.getStringByConnection(connection);
    }

    /**
     * 根据传入的数据获取URLConnection对象
     * @param url
     * @param mapArgs
     * @return
     * @throws Exception
     */
    public static URLConnection getConnection(String url, Map<String,String> mapArgs)throws Exception{
        //设置请求的头信息
        URL urlInfo = new URL(url);
        URLConnection connection = urlInfo.openConnection();
        //设置传入的头信息
        if (mapArgs!=null){
            for(String key:mapArgs.keySet()){
                connection.addRequestProperty(key,mapArgs.get(key));
            }
        }
        //设置默认头信息
        connection.addRequestProperty("Host", urlInfo.getHost());
        connection.addRequestProperty("Connection", "keep-alive");
        connection.addRequestProperty("Cache-Control", "max-age=0");
        connection.addRequestProperty("Upgrade-Insecure-Requests", "1");
        //表示用户不愿意目标站点追踪用户个人信息。
        connection.addRequestProperty("DNT", "1");
        //强制要求缓存服务器在返回缓存的版本之前将请求提交到源头服务器进行验证。
        connection.addRequestProperty("Pragma", "no-cache");
        connection.addRequestProperty("Accept", "*/*");
        connection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36");
        connection.addRequestProperty("Referer", "http://"+urlInfo.getHost());
        connection.connect();

        return connection;
    }

    /**
     * 将获取的链接读取对应的数据
     * @param connection
     * @return
     */
    public static String getStringByConnection(URLConnection connection) throws Exception{
        //定义返回数据的格式
        InputStreamReader input = new InputStreamReader(connection.getInputStream(),"UTF-8");
        BufferedReader reader = new BufferedReader(input);
        StringBuilder data = new StringBuilder();
        String str;
        while ((str = reader.readLine()) != null) {
            data.append(str);
        }
        //关闭操作流
        reader.close();
        input.close();
        return data.toString();
    }
}

4,测试代码

@Test
    public void testGet(){
        String url = "需要请求的地址";
        try{
            System.out.println(GetUrlData.getX_CSRF_Token(url));
        }catch (Exception e){
            e.printStackTrace();
        }

    }

运行结果

1、正常获取时无法获取目标页面,拿到的时默认页面数据截图
这里写图片描述
2、运行以上的代码时获取的数据截图
这里写图片描述
这里写图片描述

总结

1、不同的站点有不同的处理机制,所以以上的代码只是一个解决的思路,并不是适合所有的爬取
2、有些站点请求的Host并不返回Set-Cookie字段,这时候需要找到返回该字段的请求来获取
3、以上的代码只对于特定的mate标签的起作用,要是应对不同的站点,需要因用不同的对策获取

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章