Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >某动态js加密cookie网站爬虫记录

某动态js加密cookie网站爬虫记录

原创
作者头像
用户7358413
修改于 2020-09-10 02:10:14
修改于 2020-09-10 02:10:14
4.2K0
举报

1.问题由来:由于公司新项目需求,需要从不同平台爬取大量与项目相关的数据,大多数平台没有反爬机制,只有一个站点布置了反爬。虽然可以爬取的平台很多,可以选择爬取其他平台的数据来代替,但是考虑到该平台的可用数据量很大,值得花时间做这个爬虫,同时也是受到好奇心的驱使,于是研究了该平台的反爬机制。以下将该站点称为h网站。

2.问题描述:h网站的的访问需要带上一个动态cookie才能访问成功。

  • 访问失败和访问成功的图示

访问失败,返回HTML源码

访问成功,返回HTML源码

  • 访问失败原因:每一次访问Request Header需要带上一个动态的cookie,如果请求里没有该cookie,则访问失败,cookie如图所示

其中最为关键的cookie是name为FSSBBIl1UgzbN7N80T 的cookie,该cookie是动态变化的,而其他的cookie:

例如:

FSSBBIl1UgzbN7N80S, JSESSIONID, site_id_cookie等这些都是不变的,而让FSSBBIl1UgzbN7N80T这个cookie产生动态变化的代码有两段,分别是:

<script type="text/javascript" charset="iso-8859-1" src="/4QbVtADbnLVIc/d.FxJzG50F.3e2af61.js" r='m'></script><script type="text/javascript" r="m">(function(){var _$1B=0,_$cb=[[2,4,0,6,3,1,5,7],[97,4,63,4,78,47,10,82,53,20,38,20,66,3,90,20,87,49,77,75,37,93,33,61,42,44,56,50,20,39,83,57,26,54,6,21,41,95,96,11,21,45,74,62,43,69,21,15,98,92,98,17,47,58,21,30,51,29,48,16,89,14,76,21,73,13,21,85,81,64,35,20,40,9,67,64,31,7,20,55,64,20,68,47,27,0,88,5,24,20,86,46,8,34,28,1,60,32,23,36,2,70,12,65,80,72,25,19,52,71,18,91,79,84,94,22,59,20],[32,1,0,31,0,21,33,7,18,27,22,15,5,23,20,22,26,12,30,17,11,28,8,28,2,4,19,10,19,9,19,29,19,16,25,19,6,19,3,24,13,14,22],[11,39,23,34,15,47,18,3,19,31,21,6,30,5,22,38,46,25,2,36,12,6,45,15,20,27,8,32,14,0,14,39,26,1,41,35,26,24,9,37,9,44,13,44,40,6,9,16,40,38,29,10,28,33,43,7,37,40,16,29,0,17,4,42,18],[24,25,13,6,30,27,31,8,34,29,10,26,25,7,23,21,25,0,14,21,28,20,22,2,32,5,11,35,28,1,17,33,36,15,32,12,35,9,1,25,4,16,19,18,8,3,25]];function _$MB(_$7U,_$5F){return _$e8.Math.abs(_$7U)%_$5F;}function _$x2(_$Qf){if(_$2B(_$Qf)){_$pc(_$Qf);}var _$6n=_$5M();var _$6n=_$Qf[_$MB(_$c2()+_$jX(),16)];_$ui(_$Qf);var _$6n=_$Qf[_$MB(_$Dh()+_$UF(),16)];_$Qf[_$MB(_$Qf[_$MB(_$c2()+_$jX(),16)],16)]=_$i1(_$Qf);return _$DW(_$Qf);}function _$2B(_$Qf){var _$6n=_$UF();var _$s2=_$5M();if(_$Lt()){_$Qf[_$MB(_$jX(),16)]=_$Xc();}_$Qf[11]=_$5M();_$Qf[7]=_$c2();var _$6n=_$Ri();var _$6n=_$zc();return _$fx(_$Qf);}function _$UF(){return 11}function _$5M(){return 1}function _$Lt(){return 4}function _$jX(){return 3}function _$Xc(){return 9}function _$Dh(){return 5}function _$c2(){return 13}function _$Ri(){return 14}function _$zc(){return 12}function _$fx(_$Qf){var _$6n=_$05();var _$U6=_$c2();_$Qf[_$MB(_$Y$(),16)]=_$Ri();_$Qf[_$MB(_$Dh(),16)]=_$UF();return _$5M();}function _$05(){return 7}function _$Y$(){return 0}function _$pc(_$Qf){_$x$(_$Qf);_$Qf[8]=_$Sd();var _$s2=_$jX();var _$s2=_$Xc();_$Qf[15]=_$Dh();var _$s2=_$Sd();var _$U6=_$Lt();return _$QL(_$Qf);}function _$x$(_$Qf){var _$6n=_$Ri();var _$U6=_$zc();_$Qf[_$MB(_$UF(),16)]=_$5M();_$Qf[7]=_$c2();var _$s2=_$Ri();var _$s2=_$zc();return _$5u();}function _$5u(){return 10}function _$Sd(){return 6}function _$QL(_$Qf){_$Qf[0]=_$Ri();var _$U6=_$UF();var _$U6=_$5M();_$Qf[7]=_$c2();return _$jX();}function _$ui(_$Qf) .....................})()</script>

第二段代码不妨称为bootstrap.js,bootstrap.js是动态变化,而且加密混淆过的。

  • 爬虫的难度

相比其他网站,该网站爬虫的难度在于每次访问需要带上所需的动态cookie,但是脱离了浏览器环境,产生cookie的js代码无法执行,而且js代码也是动态变化的,所以无法只在js环境里面执行一次代码,就一劳永逸。

3.解决方案

go有一个goja的执行js的库,相当于python的pyv8模块。我刚开始想的是调用goja,每次访问无论失败成功返回中都会附带动态js代码,所以可以用goja执行代码,获得cookie,带上cookie再访问。

但是随之而来的问题是,动态js代码中引用了window,document这样的浏览器环境中才有的全局变量。goja已经无法满足动态js的执行,到这里有一个解决办法就是使用chromedp库。

最近广泛使用的headless browser解决方案PhantomJS已经宣布不再继续维护,转而推荐使用headless chrome.那么headless chrome究竟是什么呢,Headless Chrome 是 Chrome 浏览器的无界面形态,可以在不打开浏览器的前提下,使用所有 Chrome 支持的特性运行你的程序。简而言之,除了没有图形界面,headless chrome具有所有现代浏览器的特性,可以像在其他现代浏览器里一样渲染目标网页,并能进行网页截图,获取cookie,获取html等操作。

想要在golang程序里使用headless chrome,需要借助一些开源库,实现和headless chrome交互的库有很多,这里选择chromedp,接口和Selenium类似,易上手。chromedp提供一种更快,更简单的方式来驱动浏览器 (Chrome, Edge, Safari, Android等)在 Go中使用Chrome Debugging Protocol 并且没有外部依赖 (如Selenium, PhantomJS等)。调用chromedp的Evaluate来执行相关js代码,在运行中还是报错。但是其实使用chromedp访问站点,跟在浏览器里访问是一样的,只是没有可视化的图形页面而已。利用chromedp访问后已经无需再执行动态js,访问的时候代码已经执行过了,此时cookie已经产生。所以只需要利用chromedp获取headless chrome里存储的cookie即可。

package mainimport (    "context"    "fmt"    "log"    "github.com/chromedp/chromedp")func main() {    var cookieBase string = "site_id_cookie=1; clientlanguage=zh_CN; "    ctx, cancel := chromedp.NewContext(context.Background())    defer cancel()    // run task list    var res string    err := chromedp.Run(ctx,        chromedp.Navigate(`http://www.hbsredcross.org.cn/`),        chromedp.WaitVisible(`body`),        chromedp.Evaluate(`document.cookie`, &res),    )    if err != nil {        log.Fatal(err)    }    fmt.Println(cookieBase + res)}

无论如何,问题的关键是明确的,在于获取动态cookie。所以我想既然从代码里生成cookie难度较大,那不如直接从浏览器那边获取cookie。

我使用的是chrome浏览器,本地存放cookie的文件是`C:\Users\…\AppData\Local\Google\Chrome\UserData\Default\Cookies(windows”。

但是因为cookie是动态变化的,所以还需要用一个定时刷新页面的脚本。定时刷新很简单,网上有可使用的js脚本,chrome也有定时刷新的扩展(例如:Super Auto Refresh Plus)。现在只需解决cookie文件读的问题。cookies文件是一个sqlite的数据库文件,cookies的table是:

CREATE TABLE cookies(    creation_utc INTEGER NOT NULL,    host_key TEXT NOT NULL,    name TEXT NOT NULL,    value TEXT NOT NULL,    path TEXT NOT NULL,    expires_utc INTEGER NOT NULL,    is_secure INTEGER NOT NULL,    is_httponly INTEGER NOT NULL,    last_access_utc INTEGER NOT NULL,    has_expires INTEGER NOT NULL DEFAULT 1,    is_persistent INTEGER NOT NULL DEFAULT 1,    priority INTEGER NOT NULL DEFAULT 1,    encrypted_value BLOB DEFAULT '',    samesite INTEGER NOT NULL DEFAULT -1,    source_scheme INTEGER NOT NULL DEFAULT 0,    UNIQUE (host_key, name, path)    )

go相关的库有

  • go-sqlite/sqlite3

使用sqlite3读取cookies这个db文件,根据cookie的host,name过滤获得需要的FSSBBIl1UgzbN7N80T即可。其中cookies中的字段,如果cookie value是加密的blob内容,那么value的值是放在encrypted_value字段,如果value没有加密,那么值放在value字段。爬取h平台所需cookie的value值是加密的,所以读取出来的encrypted_value还需要解密。chrome加密cookie在不同平台上的加密方法不同, Windows下加密采用DPAPI。

  • zellyn/kooky

有封装好的直接获得所需cookie的方法。

其他相关的库可以自行查找…

脚本:

1.Chrome在对Cookie的值保存之前会进行加密处理,并保存在数据库的encrypt_value字段中。在Windows系统中,Cookie加密采用的是系统提供的函数CryptProtectData,在解密的时候也需要调用系统提供的函数CryptUnprotectData。解密的Windows用户必须与加密的用户一致才能成功解密。不过系统提供的是C函数,go通过syscall库来实现对C函数的调用。

CryptUnprotectData需要7个参数:

  • pDataIn

一个指向DATA_BLOB结构体的指针,该DATA_BLOB内需存放被解密的数据。DATA_BLOB结构体内含两个成员:cbData,数据的所占用的字节数;pbData,指向数据所在内存的指针。

  • ppszDataDescr

描述该加密数据的信息,如果在进行加密操作的时候添加了描述,那么在解密的时候也能得到该描述信息。获得该描述后需要调用系统提供的LocalFree释放ppszDataDescr指向的内存。如果不需要,设为NULL即可。

  • pOptionalEntropy

一个指向含有密钥DATA_BLOB的指针,不过在进行Cookie加密时通常不会用到。

  • pvReserved

保留参数,设为NULL即可。

  • pPromptStruct

解密是一个有安全风险的操作,可能需要弹出风险提升,如果不需要弹出提示设置为NULL即可。

  • dwFlags

安全相关的标志,设置为0即可。

  • pDataOut

一个指向解密后的数据的DATA_BLOB,获得解密数据后需要调用系统提供的LocalFree函数释放pbData指向的内存。

package mainimport (    "fmt"    "os/user"    "syscall"    "unsafe"    "github.com/go-sqlite/sqlite3")func main() {    usr, _ := user.Current()    var cookiesFile string = usr.HomeDir + `\AppData\Local\Google\Chrome\User Data\Default\Cookies`    cookies, err := ReadChromeCookies(cookiesFile, "www.hbsredcross.org.cn", "FSSBBIl1UgzbN7N80T")    if err != nil {        fmt.Println(err)        return    }    if len(cookies) == 0 {        fmt.Println("visit www.hbsredcross.org.cn first")        return    }    fmt.Println(cookies[0])}var (    dllcrypt32      = syscall.NewLazyDLL("Crypt32.dll")    dllkernel32     = syscall.NewLazyDLL("Kernel32.dll")    procDecryptData = dllcrypt32.NewProc("CryptUnprotectData")    localFree       = dllkernel32.NewProc("LocalFree"))type data_blob struct {    cbData uint32    pbData *byte}type Cookie struct {    Domain string    Name   string    Value  string}func newBlob(d []byte) *data_blob {    if len(d) == 0 {        return &data_blob{}    }    return &data_blob{        pbData: &d[0],        cbData: uint32(len(d)),    }}func (b *data_blob) toByteArray() []byte {    d := make([]byte, b.cbData)    p := uintptr(unsafe.Pointer(b.pbData))    for i := 0; i < int(b.cbData); i++ {        p2 := (*byte)(unsafe.Pointer(p))        d[i] = *p2        p += unsafe.Sizeof(byte(0))    }    return d}func decrypt(data []byte) ([]byte, error) {    var outblob data_blob    r, _, err := procDecryptData.Call(uintptr(unsafe.Pointer(newBlob(data))), 0, 0, 0, 0, 0, uintptr(unsafe.Pointer(&outblob)))    if r == 0 {        return nil, err    }    defer localFree.Call(uintptr(unsafe.Pointer(outblob.pbData)))    return outblob.toByteArray(), nil}func decryptValue(encrypted []byte) (string, error) {    s, err := decrypt(encrypted)    if err != nil {        return ``, err    }    return string(s), nil}func ReadChromeCookies(filename string, domainFilter string, nameFilter string) ([]*Cookie, error) {    var cookies []*Cookie    db, err := sqlite3.Open(filename)    if err != nil {        return nil, err    }    defer db.Close()    err = db.VisitTableRecords("cookies", func(rowId *int64, rec sqlite3.Record) error {        cookie := &Cookie{}        if len(rec.Values) < 14 {            return fmt.Errorf("table 至少有14个字段", len(rec.Values))        }        domain, ok := rec.Values[1].(string)        if !ok {            return fmt.Errorf("domain不是string类型")        }        name, ok := rec.Values[2].(string)        if !ok {            return fmt.Errorf("name不是string类型")        }        value, ok := rec.Values[3].(string)        if !ok {            return fmt.Errorf("value不是string类型")        }        encrypted_value, ok := rec.Values[12].([]byte)        if !ok {            return fmt.Errorf("encrypted_value不是[]byte类型")        }        if domainFilter != "" && domain != domainFilter {            return nil        }        if nameFilter != "" && name != nameFilter {            return nil        }        cookie.Domain = domain        cookie.Name = name        if len(encrypted_value) > 0 {            decrypted, err := decryptValue(encrypted_value)            if err != nil {                return fmt.Errorf("decrypting cookie %v: %v", cookie, err)            }            cookie.Value = decrypted        } else {            cookie.Value = value        }        cookies = append(cookies, cookie)        return nil    })    if err != nil {        return nil, err    }    return cookies, nil}

2.Simple script to extract (encrypted) cookies out of Chrome OS X cookie store. Usage: ./cookiemonster domain.com

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
攻防|浏览器凭据获取 -- Cookies && Password
简介:近几年流行多因素认证(MFA),个人认为也是以后的趋势;进入某些网站只拿到账号密码是不行的,这时就体现出cookie的重要性了,利用cookie绕过多因素认证在以后会经常用到,所以本文来简单的分析一下cookie获取和利用的思路;
亿人安全
2024/04/12
9910
攻防|浏览器凭据获取 -- Cookies && Password
Python3 读取Chrome cookie
网上搜一下,读取cookie的基本都是这份代码。我也忘了是从那里抄来的了,这里贴一下 ,对于最新的chrome需要修改下路径:
obaby
2023/02/22
1.4K0
Chrome 80.X版本如何解密Cookies文件
最近遇到了一个头疼的问题,就是Chrome在2月份更新了版本 80.0.3987.122(正式版本) (64 位),以前写的抓取Cookies文件的脚本用不了,Chrome更新了加密算法,今天刚好解决了,分享出来大家一起交流学习下
HACK学习
2020/03/12
6K1
获取已控机器本地保存的RDP密码
本文就给大家聊一下关于获取已控机器本地保存的RDP密码的一些原理、思路、以及具体的实现方法。
鸿鹄实验室
2021/04/15
3.3K0
获取已控机器本地保存的RDP密码
抓取Chrome所有版本密码
在使用谷歌浏览器时,如果我们输入某个网站的账号密码,他会自动问我们是否要保存密码,以便下次登录的时候自动填写账号和密码
红队蓝军
2022/05/17
1.5K0
抓取Chrome所有版本密码
抓取Chrome所有版本密码
文章由团队成员编写,首发先知社区:https://xz.aliyun.com/t/9752
红队蓝军
2022/02/06
1.9K0
抓取Chrome所有版本密码
golang源码分析:chromedp
chromedp是go写的,支持Chrome DevTools Protocol 的一个驱动浏览器的库。https://github.com/chromedp/chromedp。随着前端spa应用的普及,传统的爬虫很难抓取到我们想要的内容,Chrome DevTools Protocol (CDP)提供了一个完整的浏览器接口,使得我们可以用浏览器一样的环境来模拟请求来抓取动态生成的网页。所谓 CDP 的协议,本质上是什么呢?本质上是基于 websocket 的一种协议。
golangLeetcode
2023/08/09
7580
golang源码分析:chromedp
chromedp模拟浏览器基础入门
广泛使用的headless browser解决方案PhantomJS已经宣布不再继续维护,转而推荐使用headless chrome
drunk_kk
2021/03/23
10K0
RSS Can:使用 Golang Rod 解析浏览器中动态渲染的内容:(四)
第四篇文章,来聊聊 Golang 生态中如何“遥控”浏览器,更简单、可靠的使用基于 CDP (Chrome DevTools Protocol)协议的浏览器作为容器,获取诸如微博、B 站 这类动态渲染内容信息,将它们转换为 RSS 订阅源。
soulteary
2023/03/05
2K0
RSS Can:使用 Golang Rod 解析浏览器中动态渲染的内容:(四)
RSS Can:使用 Golang Rod 解析浏览器中动态渲染的内容:(四)
第四篇文章,来聊聊 Golang 生态中如何“遥控”浏览器,更简单、可靠的使用基于 CDP (Chrome DevTools Protocol)协议的浏览器作为容器,获取诸如微博、B 站 这类动态渲染内容信息,将它们转换为 RSS 订阅源。
soulteary
2022/12/15
1.5K0
RSS Can:使用 Golang Rod 解析浏览器中动态渲染的内容:(四)
Go加密算法总结
它是一种数据编码方式,虽然是可逆的,但是它的编码方式是公开的,无所谓加密。本文也对Base64编码方式做了简要介绍。
iginkgo18
2020/12/22
1.7K0
Go 加密解密算法总结
加密解密在实际开发中应用比较广泛,常用加解密分为:“对称式”、“非对称式”和”数字签名“。
孤烟
2020/09/27
3K0
Golang:加密解密算法
在项目开发过程中,当操作一些用户的隐私信息,诸如密码,帐户密钥等数据时,往往需要加密后可以在网上传输.这时,需要一些高效地,简单易用的加密算法加密数据,然后把加密后的数据存入数据库或进行其他操作;当需要读取数据时,把加密后的数据取出来,再通过算法解密.
OwenZhang
2021/12/08
1.8K0
Golang:加密解密算法
Go每日一库之149:PDF处理相关库
使用qpdf进行强制解密,有些情况是可以解密成功的,但是有些情况也不一定能解密成功
luckpunk
2023/10/02
2.5K0
RSA 加密算法与 golang 代码实现
最近参与借贷业务的开发,接口传输过程中需要使用 RSA 加密算法对请求和返回进行加密,所以写了这篇博客。主要介绍 RSA 的基础知识和 golang 使用例子
_春华秋实
2023/11/27
4900
【JS 逆向百例】层层嵌套!某加速商城 RSA 加密
本文章中所有内容仅供学习交流,敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除!
K哥爬虫
2021/08/20
2.1K0
【JS 逆向百例】层层嵌套!某加速商城 RSA 加密
非对称加密的RSA算法如何通过golang来实现?
上一篇文章我们讲了golang实现AES的方式,这里我们来讲一下RSA算法如何通过golang实现。需要注意的是rsa本身不支持大文件的加密,我们需要分段切割进行加解密。下面我们来看下代码。
公众号-利志分享
2022/04/25
6390
golang使用JWX进行认证和加密
最近看了一个名为go-auth的库,它将JWT作为HTTP cookie对用户进行验证,但这个例子中缺少了对JWT的保护,由此进行了一些针对JWX的研究。
charlieroro
2023/02/25
1.1K0
「Go工具箱」web中想做到cookie值安全?securecookie库的使用和实现原理
大家好,我是渔夫子。「Go学堂」新推出“Go工具箱”系列,意在给大家分享使用go语言编写的、实用的、好玩的工具。
Go学堂
2022/11/09
5600
RSA加密解密(无数据大小限制,php、go、java互通实现)
RSA加解密中必须考虑到的密钥长度、明文长度和密文长度问题。明文长度需要小于密钥长度,而密文长度则等于密钥长度。因此当加密内容长度大于密钥长度时,有效的RSA加解密就需要对内容进行分段。
双鬼带单
2019/07/30
5K0
相关推荐
攻防|浏览器凭据获取 -- Cookies && Password
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档