freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

某平台登录和支付接口分析与脚本实现
2023-08-16 17:26:10

站点数据已脱敏,使用 xxx 表示
本文基于黑箱分析,部分内容非有效动作,仅作为排错以及验证过程展示

目标数据获取流程(用户态)

用户登录

https://xxx/plat/login?synAccessSource=h5&loginFrom=h5&type=logout

image

跳转到主界面

https://xxx/plat/shouyeUser

image

然后点击付款

https://xxx/plat/pay?nodeId=15&loginFrom=h5

image

要获取的数据为40778129653692506121即 查看数字 那部分内容
而条形码与二维码的解析内容也是这串数字

过程分析

数据包分析

image

(已过滤资源与 js 文件)
其中标红数据包(id 37,68)初步判断为关键有效项
下面分别分析

数据包 id:37

image

认证包,POST 请求携带用户名与密码,返回包含关键参数access_token

数据包 id:68

image

websocket 连接请求
仅有单向通讯,客户端向服务器发消息,传输数据为

image

其中userId内容正是我们需要的支付码

推测支付码为客户端本地生成(可能是将用户 id 与时间进行加密后作为支付码),再发送给服务器进行登记使用

代码审阅

由于网页 js 文件进行了压缩,仅有单行,不方便调试,于是先保存到本地。

文件结构

├─ 付款码.html
└─ 付款码_files
        app.892a663e.css
        app.b45024b7.js.下载
        cardpay.svg
        chunk-0a3f7412.5241d975.js.下载
        chunk-0b9fab22.d78e378c.js.下载
        chunk-10eb720c.7887c8cc.css
        chunk-10eb720c.cb614830.js.下载
        chunk-1bfe6064.69249a75.css
        chunk-1bfe6064.9ebc95bb.js.下载
        chunk-2d0cf502.d3b50d1b.js.下载
        chunk-2d21a7f5.caf0d7aa.js.下载
        chunk-2d21d0c2.b51727b1.js.下载
        chunk-2d22673a.f40a8a36.js.下载
        chunk-2d22a11c.9403ebcb.js.下载
        chunk-4518e542.6dba3aad.js.下载
        chunk-4cc56ecd.865baaef.css
        chunk-4cc56ecd.dec2ba16.js.下载
        chunk-5dde23d5.3ff654ee.js.下载
        chunk-5dde23d5.c5454cce.css
        chunk-76ee7552.59423d06.js.下载
        chunk-fa993c88.7cdb37fb.css
        chunk-fa993c88.cf44569d.js.下载
        chunk-fe09330e.143bcc46.css
        chunk-fe09330e.2ac3e7c4.js.下载
        chunk-vendors.4c0400c5.css
        chunk-vendors.95895f96.js.下载
        loading.gif
        paycode.000ea068.js.下载

Edge 浏览器Ctrl+S保存,发现依赖项文件夹 付款码_files 内的 js 文件都增加了.下载后缀来防止误执行

使用 cmd 命令(PowerShell 不可用)去除.下载后缀

ren _.js.下载 _.

数据溯源

根据 WebSocket 内容首先在文件夹内搜索userId,寻找数据源头

image

排查到引用了this.qrcode变量(chunk-fa993c88.cf44569d.js L1936)
继续溯源搜索qrcode

image
qrcode有 4 处赋值语句,而这 4 处处理流程基本相似,下面对其一处(chunk-fa993c88.cf44569d.js L1795)进行分析

image

进入判断后
o = this.codes.barcode
然后 L1777 进行一次过滤,L1783 删除首元素,如果仍有元素,则将首元素赋值给this.qrcode(L1795)

网页调试

为验证猜想,L1767 与 L1801 打断点debugger

var e = this;debugger;

}, 300));debugger;

Fiddler 开启 AutoResponder 用本地修改过的文件替换网页原先的 chunk-fa993c88.cf44569d.js 文件(注意浏览器 DevTools 开启禁用缓存)

image

可惜刷新页面后,并没有停止在断点处,可能是没有通过此处进行赋值,
于是继续测试之前 4 处赋值语句中的其他位置

image
(已加入调试语句)

campusCardAfter函数内断点会在每次页面刷新时调用,

image

能够看到a中存放的正是我们想要的支付码,其数据源是this.codes.barcode

继续探查barcode变量,发现并没有明显的赋值过程(有也只是类似先复制出一个barcode对象,然后进行处理,再重新赋值给barcode

但是找到这部分代码(chunk-fa993c88.cf44569d.js L1708-L1710)

codes: localStorage.getItem("barcode")
        ? JSON.parse(localStorage.getItem("barcode"))
        : [],

发现本地存储中存有barcode变量,里面存放着支付码信息。(实际上应该先清空localStorage,再进行测试)

localStorage.barcode = [
    {
        expires: 172800,
        barcode: [
            "40556116680367244682",
            "40427500396723912935",
            "40532966131530106739",
            "40616042672184885167",
            "40292904039749786708",
            "40467887275518089314",
            "40090871415248895341",
            "40274724330740455508",
            "40026998262309530846",
            "40074626173082122138",
            "40289421604037124807",
            "40020004343507194680",
            "40990309021750279243",
            "40336601800307009738",
            "40752092092809061117",
            "40018889369138192361",
            "40525889113799032635",
            "40844062976732763809",
            "40775401545052749823",
            "40946320320688595353",
        ],
        time: 1692168476261,
        icon: "cardpay",
        id: 1,
    },
];

在读取本地存储前打断点,然后删除存储后继续运行(或者删除后强制刷新页面)
发现出现了一个新的网络包,在之前的抓包中未出现过,正是我们需要的支付码数据包。

image)

脚本实现

由上述分析,需要至少发送三个数据包来模拟客户端进行操作

  1. 认证请求包

  2. paycode 请求包

  3. websocket 通讯包

python 实现

代码已脱敏,不可运行,仅供参考

import requests
import json
import time
import websockets
import asyncio


async def send_qrcode(qrcode):
    async with websockets.connect(
        "wss://xxx/websocket/mobile_service_platform/qrcode_ykt/payCode"
    ) as ws:
        ws_data = {
            "appCode": "mobile_service_platform",
            "module": "qrcode_ykt",
            "userId": qrcode,
        }
        s = json.dumps(ws_data)
        await ws.send(s)
        print("send", s)


if __name__ == "__main__":
    cookies = ""

    # token
    url = "https://xxx/berserker-auth/oauth/token"
    headers = {
        "Connection": "keep-alive",
        "Content-Length": "126",
        "sec-ch-ua": '"Not/A)Brand";v="99", "Microsoft Edge";v="115", "Chromium";v="115"',
        "Content-Type": "application/x-www-form-urlencoded",
        "sec-ch-ua-mobile": "?0",
        "Authorization": "Basic bW9iaWxlX3NlcnZpY2VfcGxhdGZvcm06bW9iaWxlX3NlcnZpY2VfcGxhdGZvcm1fc2VjcmV0",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.203",
        "sec-ch-ua-platform": '"Windows"',
        "Accept": "*/*",
        "Origin": "https://xxx",
        "Sec-Fetch-Site": "same-origin",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Dest": "empty",
        "Referer": "https://xxx/plat/login?type=logout&type=login&synAccessSource=h5&loginFrom=h5",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
    }
    data = "username=XXXXXX&password=XXXXXX&grant_type=password&scope=all&loginFrom=h5&logintype=sno&device_token=h5&synAccessSource=h5"
    session = requests.session()
    session.headers.clear()
    session.headers.update(headers)
    response = session.post(url=url, data=data, verify=False)
    cookies = session.cookies
    content = json.loads(response.content)
    print("token", content["access_token"])
    time.sleep(3)

    # qrcode
    url1 = "https://xxx/berserker-app/ykt/tsm/batchGetBarCodeGet?account=95633&payacc=%23%23%23&paytype=1&synAccessSource=h5"
    headers1 = {
        "Connection": "keep-alive",
        "sec-ch-ua": '"Not/A)Brand";v="99", "Microsoft Edge";v="115", "Chromium";v="115"',
        "synjones-auth": "bearer " + content["access_token"],
        "sec-ch-ua-mobile": "?0",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.203",
        "sec-ch-ua-platform": '"Windows"',
        "Accept": "*/*",
        "Sec-Fetch-Site": "same-origin",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Dest": "empty",
        "Referer": "https://xxx/plat/pay?nodeId=15&loginFrom=h5",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
    }
    session1 = requests.session()
    session1.cookies.update(cookies)
    session1.headers.clear()
    session1.headers.update(headers1)
    response1 = session1.get(url=url1, verify=False)
    content1 = json.loads(response1.content)
    qrcode = content1["data"]["barcode"][0]
    print("qrcode", qrcode)
    time.sleep(3)

    # websocket
    asyncio.run(send_qrcode(qrcode))


js 实现(油猴)

代码已脱敏,不可运行,仅供参考

// ==UserScript==
// @name         example.com
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       BrokenClient
// @match        *://example.com/*
// @match        *://*.example.com/*
// @icon         none
// @run-at       document-end
// @grant        GM_xmlhttpRequest
// @grant        GM_info
// @require      https://cdn.jsdelivr.net/npm/qrcodejs2@0.0.2/qrcode.min.js
// @connect      xxx
// ==/UserScript==

(function () {
    "use strict";

    let token = "";
    let qrcode = "";

    //console.log(GM_info);
    //alert(GM_info.version)
    async function main() {
        token = await get_token();
        console.log("token", token);
        //alert(token);
        qrcode = await get_qrcode(token);
        console.log("qrcode", qrcode);
        //alert(qrcode);
        qrcode_img.makeCode(qrcode);

        let ws_data = {
            appCode: "mobile_service_platform",
            module: "qrcode_ykt",
            userId: "40778129653692506121",
        };
        ws_data["userId"] = qrcode;
        let ws = new WebSocket(
            "wss://xxx/websocket/mobile_service_platform/qrcode_ykt/payCode"
        );
        ws.onopen = (params) => {
            let s = JSON.stringify(ws_data);
            console.log("send", s);
            ws.send(s);
            //alert(s);
        };
        console.log("main over");
    }

    document.getElementsByTagName("div")[0].remove();
    let div_qrcode = document.createElement("div");
    div_qrcode.id = "qrcode";
    div_qrcode.style = "position:absolute;width:80%;height:60wh;top:5rem;";
    document.body.appendChild(div_qrcode);
    const qrcode_img = new QRCode("qrcode", {
        text: "qrcode",
        width: 0.8 * window.innerWidth,
        height: 0.8 * window.innerWidth,
    });
    main();
})();

function get_token() {
    return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
            method: "POST",
            url: "https://xxx/berserker-auth/oauth/token",
            headers: {
                Connection: "keep-alive",
                "Content-Length": "126",
                "sec-ch-ua":
                    '"Not/A)Brand";v="99", "Microsoft Edge";v="115", "Chromium";v="115"',
                "Content-Type": "application/x-www-form-urlencoded",
                "sec-ch-ua-mobile": "?0",
                Authorization:
                    "Basic bW9iaWxlX3NlcnZpY2VfcGxhdGZvcm06bW9iaWxlX3NlcnZpY2VfcGxhdGZvcm1fc2VjcmV0",
                "User-Agent":
                    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.203",
                "sec-ch-ua-platform": '"Windows"',
                Accept: "*/*",
                Origin: "https://xxx",
                "Sec-Fetch-Site": "same-origin",
                "Sec-Fetch-Mode": "cors",
                "Sec-Fetch-Dest": "empty",
                Referer:
                    "https://xxx/plat/login?type=logout&type=login&synAccessSource=h5&loginFrom=h5",
                "Accept-Encoding": "gzip, deflate, br",
                "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
            },
            data: "username=xxxxxx&password=xxxxxx&grant_type=password&scope=all&loginFrom=h5&logintype=sno&device_token=h5&synAccessSource=h5",
            timeout: 5000,
            onload: (response) => {
                let j = JSON.parse(response.responseText);
                let token = j["access_token"];
                resolve(token);
            },
            onerror: (e) => {
                console.log(e);
            },
        });
    });
}

function get_qrcode(token) {
    return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
            method: "GET",
            url: "https://xxx/berserker-app/ykt/tsm/batchGetBarCodeGet?account=95633&payacc=%23%23%23&paytype=1&synAccessSource=h5",
            headers: {
                Connection: "keep-live",
                "sec-ch-ua":
                    '"Not/A)Brand";v="99", "Microsoft Edge";v="115", "Chromium";v="115"',
                "synjones-auth": "bearer " + token,
                "sec-ch-ua-mobile": "?0",
                "User-Agent":
                    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.203",
                "sec-ch-ua-platform": '"Windows"',
                Accept: "*/*",
                "Sec-Fetch-Site": "same-origin",
                "Sec-Fetch-Mode": "cors",
                "Sec-Fetch-Dest": "empty",
                Referer: "https://xxx/plat/pay?nodeId=15&loginFrom=h5",
                "Accept-Encoding": "gzip, deflate, br",
                "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
            },
            timeout: 5000,
            onload: (response) => {
                let j = JSON.parse(response.responseText);
                let qrcode = j["data"]["barcode"][0];
                resolve(qrcode);
            },
            onerror: (e) => {
                console.log(e);
            },
        });
    });
}

js 效果:

打开 example.com,页面会自动处理,并生成一个可以使用的支付二维码

image

补充

移动端使用油猴脚本:
实测该js脚本不能在Via,X浏览器上使用,其搭载的扩展执行器版本过低,推荐使用狐猴浏览器安装油猴插件管理器来实现脚本运行。

结语

如有建议或优化思路,欢迎评论指正或私信
如果本篇文章对你有帮助,请不要吝啬你的点赞。

# web安全 # javascript # 登录 # 网络数据包
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录