← 返回文章列表

深度解析:注册安全分析报告:黑马程序员

前言由于网站注册入口容易被黑客攻击,存在如下安全问题:暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞所以大部分网站及App都采取图形验证码或滑动验证码等交互解

前言

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

暴力破解密码,造成用户信息泄露

短信盗刷的安全问题,影响业务及导致用户投诉

带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞

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

一、 黑马程序员PC端注册入口

简介:黑马程序员是传智教育(股票代码:003032)旗下高端IT教育品牌,传智教育是国内首个实现A股IPO的教育企业。依托于传智雄厚的教育资源,秉承传智教育“为中华民族伟大复兴而讲课,为千万学生少走弯路而著书”的使命,致力于培养高精尖数字化人才,为国家建设现代化经济体系的战略目标培养科技人才。

二、 安全性分析报告:

采用腾讯的智能验证,包含点击和滑动验证,容易被模拟器绕过甚至逆向后暴力攻击,滑动拼图识别率在 95% 以上。

三、 测试方法:

前端界面分析,这是腾讯v2版本,特点是矩形缺口,滑块和其它图形混合在一起,需要做切割处理,智能版本为头几次直接过,无需滑动,我们采用模拟器的方式,关键点主要模拟器交互、距离识别和轨道算法3部分。

1. 模拟器交互部分

public RetEntity send

(

WebDriver driver, String areaCode, String phone

)

{

RetEntity retEntity

=

new RetEntity

(

)

;

WebElement phoneElemet, loginElemet

;

try

{

driver.get

(

INDEX_URL

)

;

// 点击注册

driver.findElement

(

By.className

(

"user_dl"

))

.findElement

(

By.tagName

(

"a"

))

.click

(

)

;

Thread.sleep

(

1000

)

;

WebElement iframe

=

ChromeDriverManager.waitElement

(

driver, By.id

(

"user_dl"

)

,

500

)

;

if

(

iframe

==

null

)

{

System.out.println

(

"user_zc|timeout!!!"

)

;

return

null

;

}

driver.switchTo

(

)

.frame

(

iframe

)

;

// 输入手机号

phoneElemet

=

driver.findElement

(

By.id

(

"userphone"

))

;

phoneElemet.clear

(

)

;

for

(

int i

=

0

;

i

<

phone.length

(

)

;

i++

)

{

char c

=

phone.charAt

(

i

)

;

phoneElemet.sendKeys

(

c +

""

)

;

}

Thread.sleep

(

2

*

1000

)

;

// 点击获取验证码

loginElemet

=

ChromeDriverManager.waitElement

(

driver, By.id

(

"TencentCaptcha"

)

,

500

)

;

loginElemet.click

(

)

;

Thread.sleep

(

2

*

1000

)

;

// 计算移动距离

RetEntity ret

=

tClient.moveExec

(

driver

)

;

if

(

ret.getRet

(

)

==

-1

)

{

System.out.println

(

"moveExec ret="

+ ret

)

;

return

null

;

}

// send msg ret

Thread.sleep

(

2000

)

;

iframe

=

ChromeDriverManager.waitElement

(

driver, By.id

(

"user_dl"

)

,

1

)

;

if

(

iframe

==

null

)

{

System.out.println

(

"user_zc|timeout!!!"

)

;

return

null

;

}

driver.switchTo

(

)

.frame

(

iframe

)

;

Thread.sleep

(

3000

)

;

String str

=

(

loginElemet

!=

null

)

? loginElemet.getAttribute

(

"value"

)

:

null

;

if

(

str

!=

null

)

{

retEntity.setMsg

(

str

)

;

}

if

(

str.contains

(

"重新发送"

))

{

retEntity.setRet

(

0

)

;

}

return

retEntity

;

}

catch

(

Throwable e

)

{

StringBuffer er

=

new StringBuffer

(

"send() "

+ e.toString

(

)

+

"

\n

"

)

;

for

(

StackTraceElement elment

:

e.getStackTrace

(

))

er.append

(

elment.toString

(

)

+

"

\n

"

)

;

logger.error

(

er.toString

(

))

;

System.out.println

(

er.toString

(

))

;

retEntity.setRet

(

-1

)

;

retEntity.setMsg

(

e.toString

(

))

;

return

retEntity

;

}

}

/**

* v2 版本

*

* @param driver

* @return

*/

public RetEntity moveExec

(

WebDriver driver

)

{

File bFile

=

null

;

File sFile

=

null

;

RetEntity retEntity

=

new RetEntity

(

)

;

retEntity.setRet

(

-1

)

;

try

{

// 获取到验证区域

WebElement iframe

=

ChromeDriverManager.waitElement

(

driver, By.id

(

"tcaptcha_iframe_dy"

)

,

100

)

;

if

(

iframe

==

null

)

{

System.out.println

(

"moveExec() tcaptcha_iframe|timeout!!!"

)

;

retEntity.setRet

(

-99

)

;

retEntity.setMsg

(

"tcaptcha_iframe|timeout!!"

)

;

return

retEntity

;

}

driver.switchTo

(

)

.frame

(

iframe

)

;

String bgUrl

=

null

;

System.out.println

(

"get bgUrl begin ........"

)

;

for

(

int i

=

0

;

i

<

10

;

i++

)

{

// 获取带阴影的背景图

WebElement wegSlideBg

=

ChromeDriverManager.waitElement

(

driver, By.id

(

"slideBg"

)

,

300

)

;

String cssValue

=

(

wegSlideBg

!=

null

)

? wegSlideBg.getCssValue

(

"background-image"

)

:

null

;

bgUrl

=

(

cssValue

!=

null

&&

cssValue.contains

(

"

\"

"

))

? cssValue.split

(

"

\"

"

)

[

1

]

:

null

;

System.out.println

(

" |-i="

+ i +

",bgUrl="

+ bgUrl

)

;

if

(

bgUrl

!=

null

&&

bgUrl.length

(

)

>

0

)

{

break

;

}

else

{

sleep

(

500

)

;

}

}

// 获取带阴影的背景图

if

(

bgUrl

==

null

)

{

retEntity.setMsg

(

"bgUrl="

+ bgUrl

)

;

return

retEntity

;

}

Long

time

=

System.currentTimeMillis

(

)

;

// 获取小图 URL

(

替换img_index

=

1

img_index

=

0

)

String slUrl

=

bgUrl.replaceAll

(

"img_index=1"

,

"img_index=0"

)

;

bFile

=

new File

(

dataPath +

time

+

"-b.png"

)

;

sFile

=

new File

(

dataPath +

time

+

"-s.png"

)

;

Map

<

String, byte

[

]

>

retMap

=

getTwoImg

(

driver, bgUrl, slUrl, bFile, sFile

)

;

if

(

retMap

!=

null

)

{

byte

[

]

bigBytes

=

retMap.get

(

"big"

)

;

byte

[

]

smallBytes

=

retMap.get

(

"small"

)

;

String distanceStr

=

null, width

=

null

;

String ckSum

=

GenChecksumUtil.genChecksum

(

bigBytes

)

;

String

[

]

outArray

=

openCv2.getOpenCvDistance

(

ckSum, bigBytes, smallBytes,

"tencent_v2"

,

3

)

;

distanceStr

=

(

outArray

!=

null

&&

outArray.length

>=

2

)

? outArray

[

1

]

:

null

;

width

=

(

outArray

!=

null

&&

outArray.length

>=

2

)

? outArray

[

0

]

:

null

;

Double left

=

27.0

*

672

/

340

;

// 起点距左边距离

Double act

=

(

Double.parseDouble

(

distanceStr

)

- left - Double.parseDouble

(

width

))

*

340.0

/

672.0

;

Integer distance

=

act.intValue

(

)

;

System.out.println

(

"moveExec() distance("

+ distanceStr +

")="

+ distance

)

;

if

(

distance

==

null

||

distance

<=

0

)

{

return

retEntity

;

}

WebElement moveElemet

=

ChromeDriverManager.waitElement

(

driver, By.className

(

"tc-slider-normal"

)

,

500

)

;

sleep

(

500

)

;

// 滑动

ActionMove.move

(

driver, moveElemet, distance

)

;

sleep

(

400

)

;

// 滑动结果

String gtInfo

=

ChromeDriverManager.waitElement

(

driver, By.id

(

"statusSuccess"

)

,

100

)

.getText

(

)

;

if

(

gtInfo

==

null

||

""

.equals

(

gtInfo

))

{

sleep

(

200

)

;

gtInfo

=

ChromeDriverManager.waitElement

(

driver, By.id

(

"statusError"

)

,

100

)

.getText

(

)

;

}

System.out.println

(

"moveExec() gtInfo="

+ gtInfo

)

;

// driver.switchTo

(

)

.frame

(

oldWin

)

;

if

(

gtInfo.contains

(

"验证成功"

))

{

retEntity.setRet

(

0

)

;

retEntity.setMsg

(

"moveExec:"

+ gtInfo

)

;

}

else

if

(

gtInfo.contains

(

"再试一次"

)

||

gtInfo.contains

(

"恍惚了"

)

||

gtInfo.contains

(

"半路丢了"

))

{

retEntity.setRet

(

-1

)

;

retEntity.setMsg

(

"失败"

)

;

}

else

{

retEntity.setMsg

(

gtInfo

)

;

}

}

else

{

logger.error

(

"retMap="

+ retMap

)

;

retEntity.setMsg

(

"retMap="

+ retMap

)

;

}

// 切回主页面

driver.switchTo

(

)

.defaultContent

(

)

;

return

retEntity

;

}

catch

(

Exception e

)

{

StringBuffer er

=

new StringBuffer

(

"moveExec() "

+ e.toString

(

)

+

"

\n

"

)

;

for

(

StackTraceElement elment

:

e.getStackTrace

(

))

er.append

(

elment.toString

(

)

+

"

\n

"

)

;

logger.error

(

er.toString

(

))

;

System.out.println

(

er.toString

(

))

;

retEntity.setRet

(

-99

)

;

retEntity.setMsg

(

er.toString

(

))

;

return

retEntity

;

}

finally

{

if

(

retEntity.getRet

(

)

==

0

)

{

System.out.println

(

"moveExec() del file..."

)

;

if

(

bFile

!=

null

)

bFile.delete

(

)

;

if

(

sFile

!=

null

)

sFile.delete

(

)

;

}

}

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

;

}

}

3. 轨道生成及移动算法

/**

* 根据距离获取滑动轨迹

*

* @param distance需要移动的距离

* @return

*/

public static List

<

Integer

>

getTrack

(

int distance

)

{

List

<

Integer

>

track

=

new ArrayList

<

Integer

>

(

)

;

// 移动轨迹

List

<

Integer

[

]

>

list

=

getXyTrack

(

distance

)

;

for

(

Integer

[

]

m

:

list

)

{

track.add

(

m

[

0

]

)

;

}

return

track

;

}

/**

* 双轴轨道生成算法,主要实现平滑加速和减速

*

* @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

(

))

;

}

}

4. OpenCv 轮廓匹配测试样例:

四丶结语

黑马程序员主要做技术培训为主要业务的网站,应该有不少技术专家, 对机器识别应该有所了解, 但采用的是通俗的滑动验证产品, 该产品稳定并且市场占有率很高, 在一定程度上提高了用户体验, 但安全性在机器学习的今天, 已经无法应对攻击了,并且正是由于该产品通俗, 所以在网上破解的文章和教学视频也是大量存在,并且经过验证滑动产品很容易被破解, 所以除了滑动验证方式, 花样百出的产品层出不穷,但本质就是牺牲用户体验来提高安全。

很多人在短信服务刚开始建设的阶段,可能不会在安全方面考虑太多,理由有很多。

比如:“

需求这么赶,当然是先实现功能啊

”,“

业务量很小啦,系统就这么点人用,不怕的

” , “

我们怎么会被盯上呢,不可能的

”等等。

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

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

#安全短信#

戳这里→

康康你手机号在过多少网站注册过!!!

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

>>相关阅读

《腾讯防水墙滑动拼图验证码》

《百度旋转图片验证码》

《网易易盾滑动拼图验证码》

《顶象区域面积点选验证码》

《顶象滑动拼图验证码》

《极验滑动拼图验证码》

《使用深度学习来破解 captcha 验证码》

《验证码终结者-基于CNN+BLSTM+CTC的训练部署套件》