论报文加密加签场景下如何高效的进行渗透测试

news2024/11/26 20:21:08

前言

最新的测试中,经常遇到HTTP报文加密/加签传输的情况,这导致想要查看和修改明文报文很不方便。

之前应对这种情况我们有几种常见的办法解决,比如使用burpy插件、在Burp上下游使用mitmproxy进行代理等,但这些使用起来不太方便,并且偶尔遇到python和java间加密算法上的一些小差异,需要调试很久。

因此在想是否可以做一款Burp插件来解决这些问题,而且使用上要简单高效。在看了Burp新版接口 Montoya API 的介绍后,发现可以满足我们的需求,并且还有一些意想不到的发现,比如它可以满足 动态密钥 的场景。

原理

正常情况下,我们通过Burp代理进行渗透测试时,流量的流转情况如图所示。

图片

而通过Burp的 Montoya API 我们可以分别做到 Request/Response 在 Client/Burp/Server 流转时实现自己对其的处理逻辑,像图中这样。

图片

Burp Proxy :Burp中的Proxy模块,即UI中看到的Proxy那样,负责代理以及流量记录等。
Burp Http:Burp中的Http模块,负责请求最终从Burp出去、响应刚到达Burp时的处理等。

而要实现我们的需求,很显然只需要在图中①,②,③,④进行不同的处理逻辑即可:

①:HTTP请求从客户端到达Burp时被触发。在此处完成请求解密的代码就可以在Burp中看到明文的请求报文。
②:HTTP请求从Burp将要发送到Server时被触发。在此处完成请求加密的代码就可以将加密后的请求报文发送到Server。
③:HTTP请求从Server到达Burp时被触发。在此处完成响应解密的代码就可以在Burp中看到明文的响应报文。
④:HTTP请求从Burp将要发送到Client时被触发。在此处完成响应加密的代码就可以将加密后的响应报文返回给Client。

设计思路

逻辑成立,实现开始。

请求过滤

据过去遇到的情况,同一个系统可能出现部分流量加密的情况、甚至还有根据某个请求头的不同,不加密或者使用不同的加密方式。
想要支持这些情况,采用host白名单,url白名单的方案有其弊端,因此我引入了表达式的方式,你可以通过实现一个表达式,来过滤哪些请求需要/不需要处理,比如我们默认的表达式为:

!request.isStaticExtension() && request.host=='192.168.1.4'

表达式采用JS的语法,执行完成会返回一个 boolean 类型,用于判断是否过滤。

这条的意思是指请求不能是静态后缀,且host必须是192.168.1.4,在使用过程中用户可以根据自己需求修改,以应对各种特殊的场景。

核心实现

还是根据经验来,我想要的实现需要满足以下条件:

  1. 可以根据某个请求头的不同,使用不同的加密方式。

  2. 可以满足在加密加签同时存在、加密时算法组合的情况。

  3. 最好可以使用java中的加解密库,避免不同语言算法实现上的小差异。

调研了现在一些常见的实现思路,有的通过配置,但配置总会有不足;有的可以让用户自定义代码后在Repeater模块中右键使用,很灵活但无法做到对代理请求/响应自动解密。

综合考虑了上述方式的缺点,借鉴了其优点,提炼出自己的方案。

我将该功能命名为 HttpHook ,四个阶段分别为 hookRequestToBurphookRequestToServer, hookResponseToBurp, hookResponseToClient。他们分别接收请求或响应对象,由用户对其进行自定义处理。四个阶段分别有多种实现方式:

Python:通过python代码实现四个函数,原理为将python编译并在jvm中运行。
JS:通过js代码实现四个函数,原理为将js编译并在jvm中运行。
Java:通过java代码实现四个函数,原理为动态加载。
Grpc:通过实现Grpc服务实现四个rpc接口,算是兜底方案。

这四种实现方式,可以分为两类:

Grpc :你需要用其他语言实现Grpc Server,并自行通过三方库实现对应 Hook 接口 应有的功能。
Code :你需要用支持的方式编写对应语言的脚本,在脚本中组合、调用项目中的DataObjects和Util,实现对应 Hook 函数 应有的功能。

它们各有优缺点:

Grpc:优点是跨语言能力强,运行兼容性强;缺点是学习成本稍高、依赖IO -> 可能存在性能问题、不同语言算法间可能存在兼容性问题,在动态密钥的情况下很难实现需求。
Code:优点是可以与JVM交互调用Java中的加解密库 -> 对Java来说没有算法兼容性的问题;缺点是需要熟悉项目自带的DataObjects和Utils,并且可能存在运行兼容性的问题。

上手难度

看了上边的实现,你发现居然还要我写代码,好麻烦。确实,不可否认,我们为了覆盖复杂的场景,采用了写代码这种适应性最强的方式。

因此我们采用尽量多而全的自带示例,来减少了上手难度。项目中涵盖了大量的示例,如AES-CBCAES-ECB, AES-GCM, RSA, SM2 等常见算法(后续也会持续扩充),对于这些算法,可以做到开箱即用,至于更复杂的场景,你需要熟悉这些示例中的代码,灵活运用,从而解决问题。

效果演示

github地址:https://github.com/outlaws-bai/Galaxy

常规情况

这块看文章顶部的 gif 即可,更加直观,也比较简单,我们详细演示动态密钥情况下的使用。

动态密钥

示例编写

首先,我们写一个简单用于测试的客户端(index.html)和服务端(server.py),加密算法使用AES-CBC

index.html:在用户打开页面时,请求 /api/getSecret,获取加密使用的 key 和 iv,之后用户登录(/api/login)的请求和响应都会加密。

前言

最新的测试中,经常遇到HTTP报文加密/加签传输的情况,这导致想要查看和修改明文报文很不方便。

之前应对这种情况我们有几种常见的办法解决,比如使用burpy插件、在Burp上下游使用mitmproxy进行代理等,但这些使用起来不太方便,并且偶尔遇到python和java间加密算法上的一些小差异,需要调试很久。

因此在想是否可以做一款Burp插件来解决这些问题,而且使用上要简单高效。在看了Burp新版接口 Montoya API 的介绍后,发现可以满足我们的需求,并且还有一些意想不到的发现,比如它可以满足 动态密钥 的场景。

原理

正常情况下,我们通过Burp代理进行渗透测试时,流量的流转情况如图所示。

图片

而通过Burp的 Montoya API 我们可以分别做到 Request/Response 在 Client/Burp/Server 流转时实现自己对其的处理逻辑,像图中这样。

图片

Burp Proxy :Burp中的Proxy模块,即UI中看到的Proxy那样,负责代理以及流量记录等。
Burp Http:Burp中的Http模块,负责请求最终从Burp出去、响应刚到达Burp时的处理等。

而要实现我们的需求,很显然只需要在图中①,②,③,④进行不同的处理逻辑即可:

①:HTTP请求从客户端到达Burp时被触发。在此处完成请求解密的代码就可以在Burp中看到明文的请求报文。
②:HTTP请求从Burp将要发送到Server时被触发。在此处完成请求加密的代码就可以将加密后的请求报文发送到Server。
③:HTTP请求从Server到达Burp时被触发。在此处完成响应解密的代码就可以在Burp中看到明文的响应报文。
④:HTTP请求从Burp将要发送到Client时被触发。在此处完成响应加密的代码就可以将加密后的响应报文返回给Client。

设计思路

逻辑成立,实现开始。

请求过滤

据过去遇到的情况,同一个系统可能出现部分流量加密的情况、甚至还有根据某个请求头的不同,不加密或者使用不同的加密方式。
想要支持这些情况,采用host白名单,url白名单的方案有其弊端,因此我引入了表达式的方式,你可以通过实现一个表达式,来过滤哪些请求需要/不需要处理,比如我们默认的表达式为:

!request.isStaticExtension() && request.host=='192.168.1.4'

表达式采用JS的语法,执行完成会返回一个 boolean 类型,用于判断是否过滤。

这条的意思是指请求不能是静态后缀,且host必须是192.168.1.4,在使用过程中用户可以根据自己需求修改,以应对各种特殊的场景。

核心实现

还是根据经验来,我想要的实现需要满足以下条件:

  1. 可以根据某个请求头的不同,使用不同的加密方式。

  2. 可以满足在加密加签同时存在、加密时算法组合的情况。

  3. 最好可以使用java中的加解密库,避免不同语言算法实现上的小差异。

调研了现在一些常见的实现思路,有的通过配置,但配置总会有不足;有的可以让用户自定义代码后在Repeater模块中右键使用,很灵活但无法做到对代理请求/响应自动解密。

综合考虑了上述方式的缺点,借鉴了其优点,提炼出自己的方案。

我将该功能命名为 HttpHook ,四个阶段分别为 hookRequestToBurphookRequestToServer, hookResponseToBurp, hookResponseToClient。他们分别接收请求或响应对象,由用户对其进行自定义处理。四个阶段分别有多种实现方式:

Python:通过python代码实现四个函数,原理为将python编译并在jvm中运行。
JS:通过js代码实现四个函数,原理为将js编译并在jvm中运行。
Java:通过java代码实现四个函数,原理为动态加载。
Grpc:通过实现Grpc服务实现四个rpc接口,算是兜底方案。

这四种实现方式,可以分为两类:

Grpc :你需要用其他语言实现Grpc Server,并自行通过三方库实现对应 Hook 接口 应有的功能。
Code :你需要用支持的方式编写对应语言的脚本,在脚本中组合、调用项目中的DataObjects和Util,实现对应 Hook 函数 应有的功能。

它们各有优缺点:

Grpc:优点是跨语言能力强,运行兼容性强;缺点是学习成本稍高、依赖IO -> 可能存在性能问题、不同语言算法间可能存在兼容性问题,在动态密钥的情况下很难实现需求。
Code:优点是可以与JVM交互调用Java中的加解密库 -> 对Java来说没有算法兼容性的问题;缺点是需要熟悉项目自带的DataObjects和Utils,并且可能存在运行兼容性的问题。

上手难度

看了上边的实现,你发现居然还要我写代码,好麻烦。确实,不可否认,我们为了覆盖复杂的场景,采用了写代码这种适应性最强的方式。

因此我们采用尽量多而全的自带示例,来减少了上手难度。项目中涵盖了大量的示例,如AES-CBCAES-ECB, AES-GCM, RSA, SM2 等常见算法(后续也会持续扩充),对于这些算法,可以做到开箱即用,至于更复杂的场景,你需要熟悉这些示例中的代码,灵活运用,从而解决问题。

效果演示

github地址:https://github.com/outlaws-bai/Galaxy

常规情况

这块看文章顶部的 gif 即可,更加直观,也比较简单,我们详细演示动态密钥情况下的使用。

动态密钥

示例编写

首先,我们写一个简单用于测试的客户端(index.html)和服务端(server.py),加密算法使用AES-CBC

index.html:在用户打开页面时,请求 /api/getSecret,获取加密使用的 key 和 iv,之后用户登录(/api/login)的请求和响应都会加密。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
    <script src="https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/crypto-js.js"></script>
</head>
<body>
    <h2>Login</h2>
    <form id="loginForm">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username"><br><br>
        <label for="password">Password:</label>
        <input type="password" id="password" name="password"><br><br>
        <button type="button" onclick="login()">Login</button>
    </form>
    <div id="userInfo" style="margin-top: 20px;">
    </div>
    <script>
        let secretKey = '';
        let iv = '';
        async function getSecret() {
            try {
                const response = await fetch('/api/getSecret');
                const data = await response.json();
                secretKey = data.key;
                iv = data.iv;
            } catch (error) {
                console.error('Error fetching the secret:', error);
            }
        }
        function encrypt(data, key, iv) {
            const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
            const ivUtf8 = CryptoJS.enc.Utf8.parse(iv);
            const encrypted = CryptoJS.AES.encrypt(JSON.stringify(data), keyUtf8, { iv: ivUtf8, mode: CryptoJS.mode.CBC });
            return encrypted.toString();
        }
        function decrypt(data, key, iv) {
            const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
            const ivUtf8 = CryptoJS.enc.Utf8.parse(iv);
            const decrypted = CryptoJS.AES.decrypt(data, keyUtf8, { iv: ivUtf8, mode: CryptoJS.mode.CBC });
            return JSON.parse(CryptoJS.enc.Utf8.stringify(decrypted));
        }
        async function login() {
            const username = document.getElementById('username').value;
            const password = document.getElementById('password').value;
            const data = {
                username: username,
                password: password
            };
            const encryptedData = encrypt(data, secretKey, iv);
            try {
                const response = await fetch('/api/login', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ data: encryptedData })
                });
                const result = await response.json();

                const decryptedResult = decrypt(result.data, secretKey, iv);

                if (decryptedResult.success) {
                    displayUserInfo(decryptedResult.user);
                } else {
                    alert('Login failed: ' + decryptedResult.message);
                }
            } catch (error) {
                console.error('Error during login:', error);
            }
        }
        function displayUserInfo(user) {
            const userInfoDiv = document.getElementById('userInfo');
            userInfoDiv.innerHTML = `
                <h3>User Information</h3>
                <p><strong>Username:</strong> ${user.username}</p>
                <p><strong>Email:</strong> ${user.email}</p>
                <p><strong>Full Name:</strong> ${user.fullName}</p>
            `;
        }
        window.onload = getSecret;
    </script>
</body>
</html>

server.py:共有三个接口,/ 返回index.html 给到游览器渲染页面;/api/getSecret 获取AES-CBC加解密过程中需要的keyiv/api/login 获取客户端输入数据后加密、发送请求、最后将结果解密展示到游览器。

# pip install fastapi pycryptodome
import json
import base64
import string
import random
import uvicorn
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from fastapi import FastAPI
from pydantic import BaseModel
from fastapi.responses import HTMLResponse, JSONResponse

app = FastAPI()

users_db = {
    "testuser": {
        "username": "testuser",
        "password": "testpassword",
        "email": "testuser@example.com",
        "fullName": "Test User",
    }
}


class LoginRequest(BaseModel):
    data: str


def encrypt(data: dict, key: bytes, iv: bytes) -> str:
    cipher = AES.new(key, AES.MODE_CBC, iv)
    padded_data = pad(json.dumps(data).encode("utf-8"), AES.block_size)
    encrypted = cipher.encrypt(padded_data)
    return base64.b64encode(encrypted).decode("utf-8")


def decrypt(data: str, key: bytes, iv: bytes) -> dict:
    encrypted_data = base64.b64decode(data)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size)
    return json.loads(decrypted_data.decode("utf-8"))


def generate_string(length: int) -> str:
    return "".join(
        random.choice(string.digits + string.ascii_letters) for _ in range(length)
    )


@app.get("/api/getSecret")
async def get_secret():
    key = generate_string(32)
    iv = generate_string(16)
    app.state.secret_key = key
    app.state.secret_iv = iv
    return {"key": app.state.secret_key, "iv": app.state.secret_iv}


@app.post("/api/login")
async def login(request: LoginRequest):
    key = app.state.secret_key.encode()
    iv = app.state.secret_iv.encode()

    login_data = decrypt(request.data, key, iv)

    username = login_data.get("username")
    password = login_data.get("password")

    if username in users_db and users_db[username]["password"] == password:  # type: ignore
        user_info = users_db[username]  # type: ignore
        encrypted_response = encrypt({"success": True, "user": user_info}, key, iv)
        return JSONResponse(content={"data": encrypted_response})
    else:
        encrypted_response = encrypt(
            {"success": False, "message": "Invalid username or password"}, key, iv
        )
        return JSONResponse(content={"data": encrypted_response})


@app.get("/", response_class=HTMLResponse)
async def index():
    with open("index.html", "r", encoding="utf-8") as file:
        html_content = file.read()
    return HTMLResponse(content=html_content)


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

然后我们启动服务:python server.py

正常情况下,客户端首先从服务端加载keyiv

图片

在页面上输入数据登录时,请求和响应被加密了。

图片

尝试解密

Burp安装Galaxy后,配置下方的java脚本并启动服务,来完成需求

import org.m2sec.core.utils.*;
import org.m2sec.core.models.*;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;

public class Test {

    private Logger log;
    private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
    private byte[] secret;
    private byte[] iv;
    private static final Map<String, Object> paramMap = new HashMap<>();
    private static final String jsonKey = "data";

    private final ThreadLocal<Boolean> flag = ThreadLocal.withInitial(() -> false);

    public Test(Logger log) {
        this.log = log;
    }

    public Request hookRequestToBurp(Request request) {
        flag.set(false);
        if (request.getPath().endsWith("/api/getSecret")) {
            log.info("match update key & iv api.");
            flag.set(true);
        } else if (request.getMethod().equalsIgnoreCase("POST")) {
            byte[] encryptedData = getData(request.getContent());
            byte[] data = decrypt(encryptedData);
            request.setContent(data);
        } else {
            log.info("request666: {}", request);
        }
        return request;
    }

    public Request hookRequestToServer(Request request) {
        if (!flag.get()) {
            byte[] data = request.getContent();
            byte[] encryptedData = encrypt(data);
            byte[] body = toData(encryptedData);
            request.setContent(body);
        }
        return request;
    }

    public Response hookResponseToBurp(Response response) {
        if (!flag.get()) {
            byte[] encryptedData = getData(response.getContent());
            byte[] data = decrypt(encryptedData);
            response.setContent(data);
        }
        return response;
    }

    public Response hookResponseToClient(Response response) {
        if (flag.get()) {
            Map<?, ?> bodyMap = JsonUtil.jsonStrToMap(new String(response.getContent()));
            String secret1 = ((String) bodyMap.get("key"));
            String iv1 = ((String) bodyMap.get("iv"));
            secret = secret1.getBytes();
            iv = iv1.getBytes();
            paramMap.put("iv", iv);
            log.info("update key & iv: {}, {}", secret1, iv1);
        } else {
            byte[] data = response.getContent();
            byte[] encryptedData = encrypt(data);
            byte[] body = toData(encryptedData);
            response.setContent(body);
        }
        return response;
    }

    public byte[] decrypt(byte[] content) {
        return CryptoUtil.aesDecrypt(ALGORITHM, content, secret, paramMap);
    }

    public byte[] encrypt(byte[] content) {
        return CryptoUtil.aesEncrypt(ALGORITHM, content, secret, paramMap);
    }

    public byte[] getData(byte[] content) {
        return CodeUtil.b64decode((String) JsonUtil.jsonStrToMap(new String(content)).get(jsonKey));
    }

    public byte[] toData(byte[] content) {
        HashMap<String, Object> jsonBody = new HashMap<>();
        jsonBody.put(jsonKey, CodeUtil.b64encodeToString(content));
        return JsonUtil.toJsonStr(jsonBody).getBytes();
    }
}

再次刷新页面,触发动态密钥变换,然后输入账号和密码点击登录,就可以看到请求和响应都被自动解密了,之后对明文进行测试即可。

图片

总结

总结下插件的特点吧。Galaxy

  1. 简单高效:用户不需要启动多余的本地服务,配置成功后可以自动对报文进行加解密。

  2. 上手容易:通用算法已有示例,能做到开箱即用。

  3. 灵活:可以使用Python、JS、Java、Grpc多种方式实现。

  4. 支持面广:如加密算法组合、自定义算法、动态密钥等均可以支持,

更多网络安全优质免费学习资料与干货教程加v

送渗透工具、技术文档、书籍,面试题、视频(基础到进阶。环境搭建,HTML,PHP,MySQL基础学习,信息收集,SQL注入,XSS,CSRF,暴力破解等等)、应急响应笔记、学习路线。

申明:本账号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,

所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1962205.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LSTM详解总结

LSTM&#xff08;Long Short-Term Memory&#xff09;是一种用于处理和预测时间序列数据的递归神经网络&#xff08;RNN&#xff09;的改进版本。其设计初衷是为了解决普通RNN在长序列训练中出现的梯度消失和梯度爆炸问题。以下是对LSTM的详细解释&#xff0c;包括原理、公式、…

面向非结构化数据的知迟抽取

文章目录 实体抽取关系抽取事件抽取大量的数据以非结构化数据(即自由文本)的形式存在,如新闻报道、科技文献和政府文件等,面向文本数据的知识抽取一直是广受关注的问题。在前文介绍的知识抽取领域的评测竞赛中,评测数据大多属于非结构化文本数据。本节将对这一类知识抽取技…

Prometheus-部署

Prometheus-部署 Server端安装配置部署Node Exporters监控系统指标监控MySQL数据库监控nginx安装grafana Server端安装配置 1、上传安装包&#xff0c;并解压 cd /opt/ tar xf prometheus-2.30.3.linux-amd64.tar.gz mv prometheus-2.30.3.linux-amd64 /usr/local/prometheus…

【音频识别】十大数据集合集,宝藏合集,不容错过!

本文将为您介绍10个经典、热门的数据集&#xff0c;希望对您在选择适合的数据集时有所帮助。 1 RenderMe-360 发布方&#xff1a; 上海人工智能实验室 发布时间&#xff1a; 2023-05-24 简介&#xff1a; RenFace是一个大规模多视角人脸高清视频数据集&#xff0c;包含多样的…

2024年最强网络安全学习路线,详细到直接上清华的教材!

关键词&#xff1a;网络安全入门、渗透测试学习、零基础学安全、网络安全学习路线 首先咱们聊聊&#xff0c;学习网络安全方向通常会有哪些问题前排提示&#xff1a;文末有CSDN官方认证Python入门资料包 &#xff01; 1、打基础时间太长 学基础花费很长时间&#xff0c;光语…

Redis内存管理

文章目录 Redis内存管理删除策略淘汰策略LRU算法 Redis内存管理 长期把Redis做缓存用&#xff0c;总有一天Redis内存总会满的。有没有思考过这个问题&#xff0c;Redis内存满了会怎么样&#xff1f;在redis.conf中把Redis内存设置为1个字节&#xff0c;做一个测试&#xff1a;…

【随机链表的复制】python刷题记录

R3-哈希表 参考k神题解 哈希表法&#xff1a; """ # Definition for a Node. class Node:def __init__(self, x: int, next: Node None, random: Node None):self.val int(x)self.next nextself.random random """class Solution:def copy…

“打破常规:评估八股文对工作的真正影响“

“八股文”在实际工作中是助力、阻力还是空谈&#xff1f; 作为现在各类大中小企业面试程序员时的必问内容&#xff0c;“八股文”似乎是很重要的存在。但“八股文”是否能在实际工作中发挥它“敲门砖”应有的作用呢&#xff1f;有IT人士不禁发出疑问&#xff1a;程序员面试考什…

基于深度学习的结肠炎严重度诊断

基于深度学习的结肠炎严重度诊断 本文所涉及所有资源均在传知代码平台可获取 文章目录 基于深度学习的结肠炎严重度诊断1.概述1.1 数据集展示1.2 Resnet50介绍1.2.1结构与特点1.2.2关键优势1.2.3总结 2.创新点3.结果可视化展示结果展示4.核心逻辑5.部署及使用方式5.1 环境配置5…

彻底搞清楚SSR同构渲染的首屏

作为.NET技术栈的全干工程师&#xff0c;Blazor、Vue/Nuxt.js和React/Next.js都会接触到。它们&#xff08;准确的说是Blazor、Nuxt和Next&#xff09;&#xff0c;都实现了SSR同构渲染。要了解同构渲染&#xff0c;需要从服务端渲染开始。 传统的服务端渲染 如下图所示&…

开放式耳机什么牌子的好?看这6大品牌就够了

移动互联网时代&#xff0c;听歌、追剧、网课、短视频……这几年全球青年人对于耳机和耳朵的依赖程度&#xff0c;可谓前所未有的提升。但选择一款好的耳机&#xff0c;也不是一件容易的事&#xff0c;入耳式耳机戴久了耳道会疼&#xff0c;还可能引起一系列不必要的炎症&#…

【C语言】C语言期末突击/考研--数据的类型

目录 一、编程环境的搭建 二、数据的类型、数据的输入输出 2.1.数据类型 2.2.常量 2.3.变量 2.4.整型数据 2.4.1.符号常量 2.4.2.整型变量 2.5.浮点型数据 2.5.1.浮点型常量 2.5.2.浮点型变量 2.6.字符型数据 2.6.1字符型常量 2.6.2.字符数据在内存中的存储形式及…

Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 | 1/3(含分析过程)

Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 | 1/3&#xff08;含分析过程&#xff09; 目录 Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 | 1/3&#xff08;含分析过程&#xff09; 一、简单介绍 二、机器学习 1、为什么使用机器学习&a…

react antd upload custom request处理多个文件上传

react antd upload custom request处理多个文件上传的问题 背景&#xff1a;第一次请求需要请求后端返回aws 一个link&#xff0c;再往link push文件&#xff0c;再调用另一个接口告诉后端已经上传成功&#xff0c;拿到返回值。 再把返回值传给业务api... 多文件上传一直是循环…

字体表绘制的理解

下载字体到项目根目录下&#xff0c;我们通过一些在写预览本地字体的网站&#xff0c;简单看一下 通过图片不难看出阴书与原文的对应关系&#xff0c;接下来通过程序去完成这一过程&#xff0c;通过 fonttools 处理 ttf&#xff0c;然后获取字体和文字对应的 xml 文件 下面简单…

分布式SQL查询引擎之ByConity

ByConity 是字节跳动面向现代数据栈的一款开源数仓系统&#xff0c;应用了大量数据库成熟技术&#xff0c;如列存引擎&#xff0c;MPP 执行&#xff0c;智能查询优化&#xff0c;向量化执行&#xff0c;Codegen&#xff0c;indexing&#xff0c;数据压缩&#xff0c;适合用于 O…

线程池和进程池,输出有区别吗?

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor def fn(name):for i in range(1000):print(name,i)if __name__ __main__:with ThreadPoolExecutor(10) as t:for i in range(100):t.submit(fn,namef"线程{i}")with ProcessPoolExecutor(10…

艾体宝干货 | 如何分析关键网络性能指标?持续接收样品试用申请!

网络性能是企业顺利运营的重要基础&#xff0c;而Allegro流量分析仪作为一款强大的网络性能分析工具&#xff0c;为企业提供了深入了解网络运行状况的途径。在本文中&#xff0c;我们将探讨如何利用Allegro 流量分析仪分析关键网络性能指标&#xff0c;以优化网络性能、提高安全…

【综合案例】使用DevEco Studio编写京东登录界面

效果展示 模块拆分 布局容器 顶部 Logo输入框登录区域底部模块区域 知识点 复选框 Checkbox一段文本多个样式&#xff1a;Text 包裹 SpanRow 或 Column 空白区域填充&#xff1a;Blank线性渐变背景&#xff1a; .linearGradient({angle: 135, // 设置颜色渐变起始角度为顺时针…

BUGKU-WEB-文件包含

解题思路 你说啥我就干啥&#xff1a;点击一下试试你会想到PHP伪协议这方面去嘛&#xff0c;你有这方面的知识储备吗&#xff1f;看到?fileXXX.php&#xff0c;那不就是典型的文件包含吗&#xff1f;这里需要用的一个伪协议php://filter:是一种元封装器&#xff0c; 设计用于…