声明:
该文章为学习使用,严禁用于商业用途和非法用途,违者后果自负,由此产生的一切后果均与作者无关
瑞数动态安全 Botgate(机器人防火墙)以“动态安全”技术为核心,通过动态封装、动态验证、动态混淆、动态令牌等技术对服务器网页底层代码持续动态变换,增加服务器行为的“不可预测性”,实现了从用户端到服务器端的全方位“主动防护”,为各类 Web、HTML5 提供强大的安全保护。















// 代理器封装
function getEnv(proxy_array) {
    for(var i=0; i 

 
- 运行zj.js,会发现在获取window.top时报错了,在控制台输出window.top得知 window.top = window,在代码顶部补上window.top



 - 运行zj.js,会发现程序卡window.clearInterval在方法,该方法是清除定时器,把window.clearInterval、setTimeout、setInterval方法置空后再运行还是卡在window.clearInterval,这时候就要分析控制台打印出的值,先把常见的值为undefined的补上



 - 运行zj.js,会发现在获取document.createElement时报错了,document.createElement是用来创建标签的,且标签值是div,在代码顶部补上document.createElement


 - 运行zj.js,会发现在获取document.createElement时又报错了,找到打印信息最后的位置,会看到报错代码

 - 在浏览器call断点处,点击进入该方法内部会找到虚拟文件,在虚拟文件内搜索 
      
       
        
        
          d 
         
        
       
         d 
        
       
     d[_$$i[85]],会找到该方法的位置,在该行打上断点,点击跳过断点,会进入刚才的断点,把代码信息在控制输出,会发现是获取div下的i标签,获取出的结果是 length:0,在代码顶部补上该代码



 - 运行zj.js,document.getElementsByTagName报错,找到打印信息最后的位置,会看到报错代码,在虚拟文件内搜索KaTeX parse error: Expected group after '_' at position 4: av[_̲cL],会发现很多KaTeX parse error: Expected group after '_' at position 4: av[_̲cL],全部打上断点,点击跳过断点,会进入其中的断点,在控制台打印出KaTeX parse error: Expected group after '_' at position 4: av[_̲cL]的值,如果不是getElementsByTagName就点击跳过断点,如果是就停下,找到后悬浮到参数上会发现是script标签



 - 在document中补上getElementsByTagName,点击运行会发现确实是script标签,把返回的值打印在控制台,会发现是两个script和一个length=1,在代码顶部补上该代码,具体script要补什么代码还有根据实际代码往下分析,点击跳过断点,会再次进入该断点,打印出该断点的信息,会发现是第二个script标签中getAttribute传了个r参数,返回了m,getAttribute是获取该标签上的属性,在代码顶部补上该代码,再点击断点,同样会进入该断点,打印出该断点的信息输出和刚才的是获取了第一个script标签上的r属性,并返回了m,在代码顶部补上该代码




 - 运行zj.js,到scrtpt-1中获取r属性时报错了,找到打印信息最后的位置,发现是缺少removeChild,清除cookie,刷新页面,找到刚才KaTeX parse error: Expected group after '_' at position 4: av[_̲cL]断点位置,点击跳过断点,因为刚才的报错是在script获取属性之后,所以断点到获取属性的时候停下,在循环顶部打上断点,可以看作用域上的_$ay,来确定断点是否到获取script属性

 - 点击跳过断点,观察作用域,会看到 script、parentElement,这是获取script的父级head,再继续点击跳过断点就会看到head、removeChild,继续点击跳过断点会看到script、removeChild,说明刚才的removeChild报错是由script[1].parentElement.removeChild引起的,在代码顶部补上该代码,在调试这个removeChild错误的时候会发现很难下断点,当遇见这种清况看作用域就能看到代码的执行清空,后面会遇到script[0]报removeChild会采用另一种方法




 - 运行zj.js,到scrtpt-1中获取r属性时报错了,找到打印信息最后的位置,发现是缺少removeChild,修改代理数组在后面加上script[0],再运行zj.js,就可以看到script[0].parentElement是undefined,在顶部补上代码




 - 运行zj.js,获取window.attachEvent属性时报错,这里需要注意上面的打印信息window.addEventListener,一般这里会环境监测,如果window.addEventListener存在执行window.addEventListener,不存在执行window.attachEvent,node环境不存在addEventListener,所以会报window.attachEven的错误,但是window.addEventListener下是存在的,所以只要补window.addEventListene就行,找到打印信息最后的位置,搜索KaTeX parse error: Expected group after '_' at position 4: h3[_̲il[4]],在判断出打上断点,会进入window.addEventListener,在代码顶部补上代码



 - 运行zj.js,会发现document .getElementsByTagName失败,在浏览器控制台打印document .getElementsByTagName(‘meta’),会发现是两个meta对象和length,在代码顶部补上代码



 - 运行zj.js,会发现获取meta标签时又报错了,找到打印信息最后的位置,在虚拟文件内搜索KaTeX parse error: Expected group after '_' at position 4: do[_̲$i[19]],并在搜索到的位置打上断点,打印断点的信息,会发现获取的是第二个meta的r属性,在顶部补上代码




 - 运行zj.js,会发现获取meta-1中的的r属性是报错,找到打印信息最后的位置,发现是缺少removeChild,修改代理数组增加meta[1],再运行zj.js,就可以看到meta[1].parentNode是undefined,在顶部补上代码,




 - 运行zj.js,会发现不再报meta错误,不过这里meta[1]标签中的content是一定要补上的


 - 重复运行zj.js,按照上面的方法补全环境
 - 补完环境,修改zj.js,再运行,会发现已经拿到1EzPGwRUoQaWT

 
 
七、python代码验证结果
 
- 修改zj.js,因为meta中的content、还有js代码都是动态生成的,所以要用字符暂时占位,分别注释掉之前content的值、 
      
       
        
         
          
         
           t 
          
         
        
          s 
         
        
          = 
         
        
          w 
         
        
          i 
         
        
          n 
         
        
          d 
         
        
          o 
         
        
          w 
         
         
         
           [ 
          
         
           ′ 
          
         
        
       
         _ts = window[' 
        
       
     ts=window[′_ts’]、js,用meta_content、ts_code、js_code代替


 - 修改zj.py


 - 点击运行zj.py,会发现第二个请求报400,那是因为瑞数会检测当前执行的文件是哪一个

 - 修改zj.js,再运行fdc.py,会发现还是没获取成功,但是之前的cookie确实已经拿到,这就要考虑还有其他环境没补上


 - 修改zj.js,打开之前注释的meta[1]下的content、js等代码,把补环境中有if、else判断的console,放在else中



 - 运行zj.js,先找出getElementsByTagName、createElement未补的环境,搜索getElementsByTagName发现base是需要补的,搜索createElement发现form、input是需要补的,而且input创建了三次,本文就不讲base和input怎么补的,只需补form就可以,在代码顶部补上代码



 - 修改代理数组,加上form,运行zj.js,会发现设置form时需要id、action


 - 找到之前补meta的时候的断点KaTeX parse error: Expected group after '_' at position 4: av[_̲cL],点击跳过断点,直到在作用域中看到createElement、form,在虚拟文件内搜索KaTeX parse error: Expected group after '_' at position 4: av[_̲cL](,搜索到的都打上断点,点击跳过断点,仔细观察作用域中的信息,直到看到appendChild、body、form关键字说明要把创建好的form标签插入到body中,查看form信息会找到id、action,在顶部补上form,如果想补input可以继续点击跳过断点,就会看到向form中插入input,至于input插入三次id分别是username、password、innerText



 - 注释掉之前content的值、 
      
       
        
         
          
         
           t 
          
         
        
          s 
         
        
          = 
         
        
          w 
         
        
          i 
         
        
          n 
         
        
          d 
         
        
          o 
         
        
          w 
         
         
         
           [ 
          
         
           ′ 
          
         
        
       
         _ts = window[' 
        
       
     ts=window[′_ts’]、js,在运行zj.py,会发现数据获取成功



 
 
八、瑞数其他接口加密
 
如果cookie生成之后,有其他接口参数加密,直接使用明文参数一样可以调用成功,因为参数加密是瑞数加密,所以这里解密也是瑞数自己解密的;
测试请求/doc/queryDocList,通过XHR请求拦截,找到明文信息,修改zj.py,请求/doc/queryDocList,会发现即使是明文数据也可以获取成功



 
九、最终代码
 
- zj.js
 
 
delete __filename
delete __dirname
var getEnv = require('./jsProxy')
//补window
window = global
window.top = window
window.clearInterval = function () {
}
window.addEventListener = function (event) {
    console.log('window中addEventListener接受的值是:', event)
}
//补document
div = {
    getElementsByTagName: function (tag) {
        if (tag == 'i') {
            return {length: 0}
        } else {
            console.log('div中getElementsByTagName接受的值是', tag)
            return {}
        }
    }
}
script = {
    0: {
        getAttribute: function (attr) {
            console.log('script-0中getAttribute接受的值是', attr)
            if (attr == 'r') {
                return 'm'
            }
        },
        parentElement: {
            removeChild: function (tag) {
                console.log('script-1中的removeChild接受的值:', tag)
            }
        }
    },
    1: {
        getAttribute: function (attr) {
            console.log('script-1中getAttribute接受的值是', attr)
            if (attr == 'r') {
                return 'm'
            }
        },
        parentElement: {
            removeChild: function (tag) {
                console.log('script-1中的removeChild接受的值:', tag)
            }
        }
    },
    length: 2
}
meta = {
    0: {},
    1: {
        getAttribute: function (attr) {
            if (attr == 'r') {
                return 'm'
            } else {
                console.log('meta-1中getAttribute接受的值是', attr)
                return {}
            }
        },
        parentNode: {
            removeChild: function (tag) {
                console.log('meta-1中removeChild接受的值是:', tag)
            }
        },
        content: 'meta_content'
    },
    length: 2
}
form = {
    action: 'https://ucenter.miit.gov.cn/login.jsp',
    id: '__Zm9ybS5pZAo__',
}
document = {
    createElement: function (tag) {
        if (tag == 'div') {
            return div
        } else if (tag == 'form') {
            return form
        }
    },
    appendChild: function (tag) {
        console.log('document中appendChild接受的值是', tag)
        return tag || {};
    },
    removeChild: function (tag) {
        console.log('document中removeChild接受的值是', tag)
        return {}
    },
    getElementsByTagName: function (tag) {
        if (tag == 'script') {
            return script
        } else if (tag == 'meta') {
            return meta
        } else {
            console.log('document中getElementsByTagName接受的值是', tag)
            return {}
        }
    },
    getElementById: function (id) {
        console.log('document中的getElementById接受的值是:', id)
        if (id == 'root-hammerhead-shadow-ui') {
            return null
        }
    }
}
//补location
location = {
    href: "http://www.chinastock.com.cn/newsite/cgs-services/stockFinance/businessAnnc.html",
    origin: "http://www.chinastock.com.cn",
    protocol: "http:",
    host: "www.chinastock.com.cn",
    hostname: "www.chinastock.com.cn",
    pathname: "/newsite/cgs-services/stockFinance/businessAnnc.html"
}
setTimeout = function () {
}
setInterval = function () {
}
proxy_array = ['window', 'document', 'location', 'navigator', 'history', 'screen', 'target', 'script[0]', 'meta[1]', 'form']
getEnv(proxy_array)
'ts_code'
'js_code'
function get_cookie() {
    var EzPGwRUoQaWT = document.cookie.split('1EzPGwRUoQaWT=')[1].split(';')[0]
    return EzPGwRUoQaWT
}
console.log(get_cookie())
 
- zj.py
 
 
import requests
from lxml import etree
import execjs
headers = {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
    "Accept-Language": "en,zh-CN;q=0.9,zh;q=0.8",
    "Cache-Control": "no-cache",
    "Pragma": "no-cache",
    "Proxy-Connection": "keep-alive",
    "Referer": "http://www.chinastock.com.cn/newsite/cgs-services/stockFinance/businessAnnc.html",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
}
cookies = {
    # "aliyungf_tc": "d0a79762e3a5a1d8fae1f5815c7c8dfee488fd8bb3fe2e271ca7f6b32c4891e4",
    # "acw_tc": "ac11000117016103207903338ebe3da6d7f7013a812d4181d1c81f244b0c5e",
    # "1EzPGwRUoQaWS": "5Sd_7hNDdsibBtUr2KtaorX.oMnUzRBCHryQ5jRWd5MgFf4243PnXoAZTfhrUTSeItypKYhnttDwtt.8PaZXHhA",
    # "1EzPGwRUoQaWT": ".sHyUJtIBQzbbxLniztzoQdA3t20ZaSihfzHSypZM.4g_l7L7FR9B9dAic0hpBh2LoAoyddu75D2fGnxHgxjxvjwzNBjeXFFjmZ9KqNVuffkH3LGfYGIpCKE1zqsINGThR9wd2mY9o87Pu.89nXVRA_j2d.6nedwflKSAAkPCuL6TwVemW_lu.1lelL3uAUjcr2bpV2EQ2pdu6txVpYO1MieHfUtzKoxl2b6pSJgnPA"
}
url = "http://www.chinastock.com.cn/newsite/cgs-services/stockFinance/businessAnnc.html"
request_session = requests.session()
request_session.headers.update(headers)
request_session.cookies.update(cookies)
def get_ts():
    response = request_session.get(url)
    cookies['acw_tc'] = response.cookies['acw_tc']
    cookies['aliyungf_tc'] = response.cookies['aliyungf_tc']
    cookies['1EzPGwRUoQaWS'] = response.cookies['1EzPGwRUoQaWS']
    request_session.cookies.update(cookies)
    html = etree.HTML(response.text)
    ts_code = html.xpath('//script[1]/text()')[0]
    js_src = html.xpath('//script[2]/@src')[0]
    meta_content = html.xpath('//meta[2]/@content')[0]
    return ts_code, js_src, meta_content
ts_code, js_src, meta_content = get_ts()
def get_js():
    js_url = f'http://www.chinastock.com.cn{js_src}'
    response = request_session.get(js_url)
    request_session.cookies.update(cookies)
    return response.text
js_code = get_js()
with open('zj.js', 'r', encoding='utf-8') as js_file:
    js_text = js_file.read()
    js_text = js_text.replace('meta_content', meta_content)
    js_text = js_text.replace("'ts_code'", ts_code)
    js_text = js_text.replace("'js_code'", js_code)
    js = execjs.compile(js_text)
    cookies['1EzPGwRUoQaWT'] = js.call('get_cookie')
    request_session.cookies.update(cookies)
    response = request_session.get(url)
    response.encoding = 'utf-8'
    # print(response.text)
    # print(response)
get_data_par = {
    'catName': "yhgg_融资融券公告",
    'dayLimit': "9000",
    'pageNo': 2,
    'pageSize': "50",
}
get_data_url = 'http://www.chinastock.com.cn/website2020/doc/queryDocList'
response = request_session.get(get_data_url, params=get_data_par)
print(response.text)
print(response)