朋友圈投票活动-刷票案例实现与分析

1.声明

本文只讨论技术范畴内的刷票行为。


2.案例描述

某商城(以下简称A商城)在微信平台上举办了一场在线投票活动,微信用户可通过活动链接访问到投票页面,对喜欢的作品进行投票;每个微信帐号每天只能给单个作品投1张选票


3.漏洞分析

表面上看,A商城已经对投票活动进行了反作弊处理,因为限制了每个微信用户每天只能投一张票。如果用户都是正常地通过微信访问这个投票服务进行投票的话,的确是能起到预期效果的。

然而,如果查看投票页面的原始地址,即按住页面向下拖动,会发现屏幕顶端显示为"本网页由XXX提供"(这里的"XXX"并不是"mp.weixin.qq.com"),而是A商城的域名。也就是说,这个投票活动的程序是运行在A商城的服务器上面的。

基于以上分析,可以推断出用户投票操作的网络拓扑结构示意图应该是这样的:
朋友圈投票活动-刷票案例实现与分析_第1张图片

微信用户访问投票页面时,微信服务器只是进行了请求转发,具体的投票计数与校验都是在A商城的服务器上的。

那么,商城是怎么来区分投票用户的呢?

这里就涉及到微信公众平台OpenID的概念了。官方对OpenID的解释是:加密后的微信号,每个用户对每个公众号的OpenID是唯一的。要验证这一点也很容易,只需要通过采用多个微信账号进行投票,并对投票过程进行网络抓包(这一步是最关键的点,我用Charles进行的抓包,这里是关于Charles抓包的教程),这里主要是为了获取第三方的请求参数

基于这一点,微信公众平台在转发投票请求时,会在POST参数中包含用户的OpenID;A商城在接收到投票的POST请求后,通过查询当前OpenID是否在当天已经投过票,就可以阻止单一用户重复投票的行为了。

然而,这里面却存在一个很大的漏洞,A商城只能判断OpenID是否出现了重复,但是却无法校验OpenID的有效性,因为它是无法调用微信服务器来对这个OpenID进行校验的。


4.实现思路
  • 抓包获取到请求参数,模拟Https请求
  • 生成随机位数的Openid
  • 为防止服务器拦截请求,随机时间后向服务器发送请求
  • 动态模拟不同的设备,即修改User-Agent,否则,服务端可以较为容易地识别作弊行为

5.作弊与反作弊

看到这里,也许有的同学心中窃喜,以后投票都可以采用这种方式“刷票”了么?

很遗憾,当然不是。其实本文中案例的漏洞是很低级的,只是,当前的确还存在不少比例的投票活动是采用这种模式。

要判断一个投票活动是否可以采用这种方式来作弊也很简单。采用本文中的方法,若查看到活动的网址是非微信官方的,而且整个投票过程也没有额外的校验,那么实现作弊的可能性就很大了;再通过抓包看下通讯交互过程,并用网络请求工具修改参数后重新请求下,即可验证是否真的可以作弊了。

不过,活动举办方可以通过一些手段,大大提高作弊的门槛:

  • 要求投票用户先关注活动举办方的公众号,然后调用微信官方的投票功能;
  • 要求投票用户在投票活动举办方的网站上进行注册(手机号验证、实名验证)

不管采用这两种方式中的哪一种,本文中的“刷票”方法就完全失效了


6.代码实现

本案例只提供实现的流程,不提供刷票的网站及参数

    /*
     * @auther: Ragty
     * @describe: Get请求远程接口
     * @param: [url, parameters]
     * @return: java.lang.String
     * @date: 2019/1/16
     */
    public static String sendGet(String url, Map<String, String> parameters) {

        String result="";
        BufferedReader in = null;// 读取响应输入流
        StringBuffer sb = new StringBuffer();// 存储参数
        String params = "";// 编码之后的参数

        try {
            // 编码请求参数
            if(parameters.size()==1){
                for(String name:parameters.keySet()){
                    sb.append(name).append("=").append(java.net.URLEncoder.encode(parameters.get(name), "UTF-8"));
                }
                params=sb.toString();
            }else{
                for (String name : parameters.keySet()) {
                    sb.append(name).append("=").append(java.net.URLEncoder.encode(parameters.get(name), "UTF-8")).append("&");
                }
                String temp_params = sb.toString();
                params = temp_params.substring(0, temp_params.length() - 1);
            }

            String full_url = url + "?" + params;
            System.out.println("请求链接为:"+full_url);
            // 创建URL对象
            URL connURL = new URL(full_url);
            // 打开URL连接
            HttpURLConnection httpConn = (HttpURLConnection) connURL.openConnection();
            // 设置通用属性
            httpConn.setRequestProperty("Accept", "*/*");
            httpConn.setRequestProperty("Connection", "Keep-Alive");
            httpConn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)");
            // 建立实际的连接
            httpConn.connect();
            // 响应头部获取
            Map<String, List<String>> headers = httpConn.getHeaderFields();
            // 遍历所有的响应头字段
            for (String key : headers.keySet()) {
                System.out.println(key + "\t:\t" + headers.get(key));
            }
            // 定义BufferedReader输入流来读取URL的响应,并设置编码方式
            in = new BufferedReader(new InputStreamReader(httpConn.getInputStream(), "UTF-8"));
            String line;
            // 读取返回的内容
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            try {
                if (in != null) { in.close(); }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return result ;

    }



    /*
     * @auther: Ragty
     * @describe: Post请求远程接口
     * @param: [url, parameters]
     * @return: java.lang.String
     * @date: 2019/1/16
     */
    public static String sendPost(String url, Map<String, String> parameters) {

        String result = "";// 返回的结果
        BufferedReader in = null;// 读取响应输入流
        PrintWriter out = null;
        StringBuffer sb = new StringBuffer();// 处理请求参数
        String params = "";// 编码之后的参数

        try {
            // 编码请求参数
            if (parameters.size() == 1) {
                for (String name : parameters.keySet()) {
                    sb.append(name).append("=").append(java.net.URLEncoder.encode(parameters.get(name), "UTF-8"));
                }
                params = sb.toString();
            } else {
                for (String name : parameters.keySet()) {
                    sb.append(name).append("=").append(java.net.URLEncoder.encode(parameters.get(name), "UTF-8")).append("&");
                }
                String temp_params = sb.toString();
                params = temp_params.substring(0, temp_params.length() - 1);
            }

            // 创建URL对象
            URL connURL = new URL(url);
            // 打开URL连接
            HttpURLConnection httpConn = (HttpURLConnection) connURL.openConnection();
            // 设置通用属性
            httpConn.setRequestProperty("Accept", "*/*");
            httpConn.setRequestProperty("Connection", "Keep-Alive");
            httpConn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)");
            // 设置POST方式
            httpConn.setDoInput(true);
            httpConn.setDoOutput(true);
            // 获取HttpURLConnection对象对应的输出流
            out = new PrintWriter(httpConn.getOutputStream());
            // 发送请求参数
            out.write(params);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应,设置编码方式
            in = new BufferedReader(new InputStreamReader(httpConn.getInputStream(), "UTF-8"));
            String line;
            // 读取返回的内容
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null) { out.close(); }
                if (in != null) { in.close(); }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return result;
    }




    /**
    * @Author Ragty
    * @Description 生成OpenId
    * @Date   2019/11/14 18:17
    * @Exception
    */
    public static String createOpenId(int length) {
        String val = "";
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num"; // 输出字母还是数字

            if ("char".equalsIgnoreCase(charOrNum)) // 字符串
            {
                int choice = random.nextInt(2) % 2 == 0 ? 65 : 97; // 取得大写字母还是小写字母
                val += (char) (choice + random.nextInt(26));
                val += (char) (choice + random.nextInt(26));
            } else if ("num".equalsIgnoreCase(charOrNum)) // 数字
            {
                val += String.valueOf(random.nextInt(10));
            }
        }
        return val;
    }




    /**
    * @Author Ragty
    * @Description  进行一次投票
    * @Date   2019/11/14 18:24
    * @Exception
    */
    public static void voteOnce(String voteid, String voteUrl) {
        Map<String,String> request = new HashMap<String,String>();
        String openId = createOpenId(22);

        //这里放请求参数
        request.put("name","ragty");
        request.put("openid",openId);
        String result = sendPost(voteUrl,request);
    }




    /**
    * @Author Ragty
    * @Description  莫得感情的投票机器(这里加个随机模拟)
    * @Date   2019/11/14 18:26
    * @Exception
    */
    public static void voteMachine(String voteId, int voteNumber, String voteUrl) throws InterruptedException{

        Random random =  new Random();

        for (int i=1; i<=voteNumber; i++) {
            voteOnce(voteId,voteUrl);
            System.out.println("进行第"+i+"次投票,投票成功 ");
            int randomTime = random.nextInt(5000);
            System.out.println("下次投票时间间隔为:"+randomTime/1000);
            Thread.sleep(randomTime);
        }

        System.out.println("投票结束,本次共投"+voteNumber+"票");
    }
    
    
    
     public static void main(String[] args) throws InterruptedException{
       voteMachine("11",10,"https://www.baidu.com");
    }
    

你可能感兴趣的:(计算机网络)