首页 > 其他分享 >【福建医科大学附属第一医院-注册安全分析报告】

【福建医科大学附属第一医院-注册安全分析报告】

时间:2024-10-24 22:47:03浏览次数:3  
标签:String int move 附属 toString 注册 医科大学 new Mat

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

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

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

一、 福建医科大学附属第一医院PC 注册入口

简介:福建医科大学附属第一医院(以下简称“医院”)创建于1937年,是福建省首家公立西医医院。历经抗战的硝烟,告别动荡的岁月,百折不挠的附一人秉承“践行立德树人,守护生命安全,推动医学进步,促进人民健康”的使命,将医院建设成为福建省集医疗、教学、科研于一体的大型综合性三级甲等医院。医院综合实力雄厚,是福建省高水平医院,并被国家发改委、国家卫健委确定为首批全国疑难病症诊治能力提升工程项目医院和中国罕见病协作网福建省牵头单位。在国家卫健委公布的三级公立医院绩效考核中,连续5年荣获A+,分别位居全国第37名、第44名、第42名、第42名、第32名。医院荣登2023年研究型医院排行榜全国第33名,在2024年Nature发布的自然指数榜单中,位列全球医疗机构第284位,居福建省第一。与复旦大学附属华山医院共建的复旦大学附属华山医院福建医院(福建医科大学附属第一医院滨海院区)为全国首批10家国家区域医疗中心。

1. 注册引导页

在这里插入图片描述
在这里插入图片描述

2. 会员注册页面

在这里插入图片描述

二、 安全性分析报告:

福建医科大学附属第一医院采用的是自己研发的滑动验证码,容易被模拟器绕过甚至逆向后暴力攻击,滑动拼图识别率在 95% 以上。

在这里插入图片描述

三、 测试方法:

前端界面分析,这是福建医科大学附属第一医院自己研发的滑动验证码,网上没有现成的教学视频,但形式都差不多,没什么难度 , 这次还是采用模拟器的方式,关键点主要模拟器交互、距离识别和轨道算法3部分 。

在这里插入图片描述

  1. 模拟器交互部分

private static String INDEX_URL = "https://jk.fyyy.com:10081/module/backstage/pcweb/#/login";

	@Override
	public RetEntity send(WebDriver driver, String areaCode, String phone) {
		try {
			RetEntity retEntity = new RetEntity();
			driver.get(INDEX_URL);

			WebElement tabElement = ChromeDriverManager.waitElement(driver, By.xpath("//div/span[contains(text(),'暂无账号?点击注册')]"), 20);
			tabElement.click();			

			// 输入手机号
			Thread.sleep(1000);
			WebElement phoneElement = driver.findElement(By.xpath("//input[contains(@placeholder,'请输入手机号码')]"));
			phoneElement.sendKeys(phone);

			// 密码
			Thread.sleep(1000);
			WebElement passElement = driver.findElement(By.xpath("//input[contains(@placeholder,'设置密码')]"));
			passElement.sendKeys("Abc123456");

			WebElement passConfirmElement = driver.findElement(By.xpath("//input[contains(@placeholder,'确认密码')]"));
			passConfirmElement.sendKeys("Abc123456");
			Thread.sleep(500);

			List<WebElement> findElements = driver.findElements(By.xpath("//button/span"));
			WebElement sendElement = null;
			for (WebElement web : findElements) {
				String text = web.getText();
				if (text != null && text.contains("获取验证码")) {
					web.click();
					sendElement = web;
					break;
				}

			}
			Thread.sleep(2000);
			WebElement moveElemet = ChromeDriverManager.waitElement(driver, By.id("slider-move-btn"), 20);

			// 1 获取带阴影的背景图
			byte[] bigBytes = GetImage.callJsById(driver, "bg-img");
			// 2 获取小图
			byte[] smallBytes = GetImage.callJsById(driver, "slider-img");
			// 3 计算匹配到的位置
			String ckSum = GenChecksumUtil.genChecksum(bigBytes);
			Map<String, Double> openResult = cv2.getOpenCvDistance(ckSum, bigBytes, smallBytes, "Fyyy", 0);
			if (openResult == null || openResult.size() < 2) {
				System.out.println("ckSum=" + ckSum + "->openResult=" + openResult);
				return null;
			}
			Double r = 260.0 / 590.0;
			BigDecimal disD = new BigDecimal(openResult.get("minX") * r).setScale(0, BigDecimal.ROUND_HALF_UP);
			int distance = disD.intValue();
			// 4 滑动
			ActionMove.move(driver, moveElemet, distance);
			Thread.sleep(2000);

			String msg = (sendElement != null) ? sendElement.getText() : null;
			retEntity.setMsg(msg);
			if (msg != null && msg.contains("后重新获取")) {
				retEntity.setRet(0);
			}
			return retEntity;
		} catch (Exception e) {
			System.out.println("phone=" + phone + ",e=" + e.toString());
			for (StackTraceElement ele : e.getStackTrace()) {
				System.out.println(ele.toString());
			}
			return null;
		} finally {
			driver.manage().deleteAllCookies();
		}
	}

	

2. 距离识别


/**
	 * 
	 * @param ckSum
	 * @param bigBytes
	 * @param smallBytes
	 * @param factory
	 * @return { width, maxX }
	 */

	public String[] getOpenCvDistance(String ckSum, byte bigBytes[], byte smallBytes[], String factory, int border) {
		try {
			String basePath = ConstTable.codePath + factory + "/";
			File baseFile = new File(basePath);
			if (!baseFile.isDirectory()) {
				baseFile.mkdirs();
			}
			// 小图文件
			File smallFile = new File(basePath + ckSum + "_s.png");
			FileUtils.writeByteArrayToFile(smallFile, smallBytes);
			// 大图文件
			File bigFile = new File(basePath + ckSum + "_b.png");
			FileUtils.writeByteArrayToFile(bigFile, bigBytes);
			// 边框清理(去干扰)
			byte[] clearBoder = (border > 0) ? ImageIOHelper.clearBoder(smallBytes, border) : smallBytes;
			File tpFile = new File(basePath + ckSum + "_t.png");
			FileUtils.writeByteArrayToFile(tpFile, clearBoder);

			String resultFile = basePath + ckSum + "_o.png";
			return getWidth(tpFile.getAbsolutePath(), bigFile.getAbsolutePath(), resultFile);
		} catch (Throwable e) {
			logger.error("getMoveDistance() ckSum=" + ckSum + " " + e.toString());
			for (StackTraceElement elment : e.getStackTrace()) {
				logger.error(elment.toString());
			}
			return null;
		}
	}

	/**
	 * Open Cv 图片模板匹配
	 * 
	 * @param tpPath
	 *            模板图片路径
	 * @param bgPath
	 *            目标图片路径
	 * @return { width, maxX }
	 */
	private String[] getWidth(String tpPath, String bgPath, String resultFile) {
		try {
			Rect rectCrop = clearWhite(tpPath);
			Mat g_tem = Imgcodecs.imread(tpPath);
			Mat clearMat = g_tem.submat(rectCrop);

			Mat cvt = new Mat();
			Imgproc.cvtColor(clearMat, cvt, Imgproc.COLOR_RGB2GRAY);
			Mat edgesSlide = new Mat();
			Imgproc.Canny(cvt, edgesSlide, threshold1, threshold2);
			Mat cvtSlide = new Mat();
			Imgproc.cvtColor(edgesSlide, cvtSlide, Imgproc.COLOR_GRAY2RGB);
			Imgcodecs.imwrite(tpPath, cvtSlide);

			Mat g_b = Imgcodecs.imread(bgPath);
			Mat edgesBg = new Mat();
			Imgproc.Canny(g_b, edgesBg, threshold1, threshold2);
			Mat cvtBg = new Mat();
			Imgproc.cvtColor(edgesBg, cvtBg, Imgproc.COLOR_GRAY2RGB);

			int result_rows = cvtBg.rows() - cvtSlide.rows() + 1;
			int result_cols = cvtBg.cols() - cvtSlide.cols() + 1;
			Mat g_result = new Mat(result_rows, result_cols, CvType.CV_32FC1);
			Imgproc.matchTemplate(cvtBg, cvtSlide, g_result, Imgproc.TM_CCOEFF_NORMED); // 归一化平方差匹配法
			// 归一化相关匹配法
			MinMaxLocResult minMaxLoc = Core.minMaxLoc(g_result);
			Point maxLoc = minMaxLoc.maxLoc;
			Imgproc.rectangle(cvtBg, maxLoc, new Point(maxLoc.x + cvtSlide.cols(), maxLoc.y + cvtSlide.rows()), new Scalar(0, 0, 255), 1);
			Imgcodecs.imwrite(resultFile, cvtBg);
			String width = String.valueOf(cvtSlide.cols());
			String maxX = String.valueOf(maxLoc.x + cvtSlide.cols());
			System.out.println("OpenCv2.getWidth() width=" + width + ",maxX=" + maxX);
			return new String[] { width, maxX };
		} catch (Throwable e) {
			System.out.println("getWidth() " + e.toString());
			logger.error("getWidth() " + e.toString());
			for (StackTraceElement elment : e.getStackTrace()) {
				logger.error(elment.toString());
			}
			return null;
		}
	}

	public Rect clearWhite(String smallPath) {
		try {
			Mat matrix = Imgcodecs.imread(smallPath);
			int rows = matrix.rows();// height -> y
			int cols = matrix.cols();// width -> x
			System.out.println("OpenCv2.clearWhite()  rows=" + rows + ",cols=" + cols);
			Double rgb;
			double[] arr;
			int minX = 255;
			int minY = 255;
			int maxX = 0;
			int maxY = 0;
			Color c;
			for (int x = 0; x < cols; x++) {
				for (int y = 0; y < rows; y++) {
					arr = matrix.get(y, x);
					rgb = 0.00;
					for (int i = 0; i < 3; i++) {
						rgb += arr[i];
					}
					c = new Color(rgb.intValue());
					int b = c.getBlue();
					int r = c.getRed();
					int g = c.getGreen();
					int sum = r + g + b;
					if (sum >= 5) {
						if (x <= minX)
							minX = x;
						else if (x >= maxX)
							maxX = x;
						if (y <= minY)
							minY = y;
						else if (y >= maxY)
							maxY = y;
					}
				}
			}

			int boder = 1;
			if (boder > 0) {
				minX = (minX > boder) ? minX - boder : 0;
				maxX = (maxX + boder < cols) ? maxX + boder : cols;
				minY = (minY > boder) ? minY - boder : 0;
				maxY = (maxY + boder < rows) ? maxY + boder : rows;
			}

			int width = (maxX - minX);
			int height = (maxY - minY);
			System.out.println("openCv2 minX=" + minX + ",minY=" + minY + ",maxX=" + maxX + ",maxY=" + maxY + "->width=" + width + ",height=" + height);
			Rect rectCrop = new Rect(minX, minY, width, height);
			return rectCrop;
		} catch (Throwable e) {
			StringBuffer er = new StringBuffer("clearWrite() " + e.toString() + "\n");
			for (StackTraceElement elment : e.getStackTrace()) {
				er.append(elment.toString() + "\n");
			}
			logger.error(er.toString());
			System.out.println(er.toString());
			return null;
		}
	}

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(50);
			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 < 5) {
					Thread.sleep(5 - 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());
		}
	}


4. 图片比对结果测试样例:
在这里插入图片描述

四丶结语

福建医科大学附属第一医院(以下简称“医院”)创建于1937年,是福建省首家公立西医医院。历经抗战的硝烟,告别动荡的岁月,百折不挠的附一人秉承“践行立德树人,守护生命安全,推动医学进步,促进人民健康”的使命,将医院建设成为福建省集医疗、教学、科研于一体的大型综合性三级甲等医院。医院综合实力雄厚,是福建省高水平医院,并被国家发改委、国家卫健委确定为首批全国疑难病症诊治能力提升工程项目医院和中国罕见病协作网福建省牵头单位。与复旦大学附属华山医院共建的复旦大学附属华山医院福建医院(福建医科大学附属第一医院滨海院区)为全国首批10家国家区域医疗中心。医院作为涉及民生的重点事业单位,技术实力较弱采取采购产品的方式,采购了一家智业健康的软件产品,这家公司研发的滑动验证产品, 在一定程度上提高了用户体验, 不过随着图形识别技术及机器学习能力的提升,所以在网上破解的文章和教学视频也是大量存在,并且经过验证的确有效, 所以除了滑动验证方式, 花样百出的产品层出不穷,但本质就是牺牲用户体验来提高安全。

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

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

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

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

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

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

标签:String,int,move,附属,toString,注册,医科大学,new,Mat
From: https://blog.csdn.net/weixin_44549063/article/details/143220960

相关文章

  • navicat安装包注册机激活教程
    title:navicat安装包注册机激活教程tags:-navicatcategories:-navicat安装navicat,傻瓜式安装后,进行后面的步骤,记录好安装路径,后面要用1)下载激活程序!注意X64的系统要选择X64的软件!如:navicat-keygen-for-x642)软件包解压,里面的2个文件navicat-keygen-for-x64,3)以管理......
  • 第6章 综合案例——商城的注册页面
    制作全球汇商城会员注册页面,运用多种表单元素整合。CSS代码如下:*{ margin:0;/*所有元素外边距为0*/ padding:0; /*所有元素内边距为0*/}/*设置页面整体样式*/body{ font-size:12px......
  • 织梦dedecms无法使用会员系统,注册、登录、管理会员时提示错误怎么办?怎么解决
    问题描述:无法使用会员系统,注册、登录、管理会员时提示错误。解决方案:检查会员设置:确保DedeCMS的会员设置正确。检查邮件配置:确保邮件配置正确,能够发送验证邮件。检查验证码设置:确保验证码设置正确,验证码功能正常。检查数据库连接:确保数据库连接配置正确,数据库服务正常运行......
  • Windows基础(三):注册表与自启动
     https://bu1.github.io/2021/10/30/Windows%E5%9F%BA%E7%A1%80(%E4%B8%89)%EF%BC%9A%E6%B3%A8%E5%86%8C%E8%A1%A8%E4%B8%8E%E8%87%AA%E5%90%AF%E5%8A%A8/ 本文简要介绍了Windows的注册表以及自启动管理。注册表注册表是控制计算机硬件和软件的一整套数据。如果对注册表熟......
  • 注册表项也可以用于进程启动时自动加载DLL.自动加载DLL的选项。
    AppInit_DLLs用于在每个用户界面线程启动时加载指定的DLL。以下是一些常见的用途和注意事项:DLL路径:可以指定一个或多个DLL的完整路径。多DLL加载:可以通过逗号分隔加载多个DLL。影响性能:加载的DLL可能影响应用程序的启动速度和系统性能。安全风险:不安全或恶......
  • 全网免费API-免注册登录
    前言所有的链接都不需要注册登录,点开即用标题:狗图描述:想要别人的狗吗?DogAPI-Theinternet'sbiggestcollectionofopensourcedogpictures.Fetchingyouover20,000dogimagesaccessiblebyover120breeds.链接:https://dog.ceo/dog-api/------------------......
  • 企业域名注册:如何选择最适合的域名邮箱?
    企业域名申请流程有哪些?个人域名和企业域名的区别?企业域名注册不仅是品牌建设的重要一环,更是企业形象和专业性的体现。选择一个合适的域名邮箱,能够有效提升企业的专业形象,增强客户信任度。烽火将详细探讨企业域名注册的关键步骤。企业域名注册:域名后缀在进行企业域名注册时......
  • 人工智能编程助手MarsCode注册和安装步骤
    人工智能编程助手MarsCode注册和安装步骤字节最近推出了人工智能编程助手MarsCode,功能非常强大。在IDEA中安装和使用MarsCode的步骤如下:一、注册MarsCode账号    注册地址:https://www.marscode.cn/events/s/iSMPHK8a/二、在Idea中安装插件    点击菜单“File”——......
  • 建立一个简单的注册登录界面遇到的问题
    一、数据库连接在实现用户登录功能时,我的目标是通过MySQL数据库中的数据表进行账号和密码的验证。然而,我遇到了连接数据库不正确或账号密码匹配失败的问题。为了解决这些问题,我采取了以下步骤:检查数据库连接信息:仔细核对数据库的URL、数据库名称和用户密码,确保它们的正确性。......
  • 免费试用、快速上云,注册还能100%中奖,竟有这种好事?!
    活动时间内完成注册亚马逊云科技账号,即可获得以下福利免费试用,带你体验快速上云热门场景,带你手把手轻松搭建51CTO博客专属福利,100%获得精美礼品罗技无线键鼠套装绿联充电宝50元京东E卡丰厚奖品等你来拿!中奖率100%!活动时间:即日起至10月31日24时51CTO博客福利领取流程:在活动有效期内......