当前位置: 首页 > news >正文

第30天:安全开发-JS 应用NodeJS 指南原型链污染Express 框架功能实现审计0

时间轴:

演示案例:

环境搭建-NodeJS-解析安装&库安装

功能实现-NodeJS-数据库&文件&执行

安全问题-NodeJS-注入&RCE&原型链

案例分析-NodeJS-CTF 题目&源码审计

开发指南-NodeJS-安全 SecGuide 项目、

环境搭建-NodeJS-解析安装&库安装

node.js是运行在服务端的JavaScript。

0 、文档参考:
https://www.w3cschool.cn/nodejs/
1 Nodejs 安装
https://nodejs.org/en

作者安装的是18.16.0.安装好后重启电脑,让环境进行配置。 (只有这样才能使npm,node这些生效)

2 、三方库安装
express
Express 是一个简洁而灵活的 node.js Web 应用框架
body-parser
node.js 中间件,用于处理 JSON, Raw, Text URL 编码的数据。
cookie-parser
这就是一个解析 Cookie 的工具。通过 req.cookies 可以取到传过来的 cookie ,并把
它们转成对象。
multer
node.js 中间件,用于处理 enctype="multipart/form-data" (设置表单的 MIME
编码)的表单数据。
mysql
Node.js 来连接 MySQL 专用库,并对数据库进行操作。
安装命令:
npm i express
npm i body-parser
npm i cookie-parser
npm i multer
npm i mysql

案例导入:

在js文件中创建一个sql.js,将端口创建为3000
//express_demo.js 文件
var express = require('express');
var app = express();app.get('/', function (req, res) {res.send('Hello World');
})var server = app.listen(3000, function () {var host = server.address().addressvar port = server.address().portconsole.log("应用实例,访问地址为 http://%s:%s", host, port)})

使用node .\sql.js进行运行:

无法运行时需要安装express库。

npm i express (会得到node modules)

运行结果:(文字路径不能太多)

网页源代码:

只显示了Hello World,没有显示visual studio中的代码

看到结果是运行结果而不是源代码

功能实现-NodeJS-数据库&文件&执行

登录操作

1 Express 开发
2 、实现用户登录
3 、加入数据库操作
1.先创建一个sql.html:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>后台登录</title><style>body {background-color: #f1f1f1;}.login {width: 400px;margin: 100px auto;background-color: #fff;border-radius: 5px;box-shadow: 0 0 10px rgba(0,0,0,0.3);padding: 30apx;}.login h2 {text-align: center;font-size: 2em;margin-bottom: 30px;}.login label {display: block;margin-bottom: 20px;font-size: 1.2em;}.login input[type="text"], .login input[type="password"] {width: 100%;padding: 10px;border: 1px solid #ccc;border-radius: 5px;font-size: 1.2em;margin-bottom: 20px;}.login input[type="submit"] {background-color: #2ecc71;color: #fff;border: none;padding: 10px 20px;border-radius: 5px;font-size: 1.2em;cursor: pointer;}.login input[type="submit"]:hover {background-color: #27ae60;}</style>
</head>
<body>
<div class="login" ><h2>后台登录</h2><form action="http://127.0.0.1:3000/login" method="POST"><label for="username">用户名:</label><input type="text" name="username" id="username" class="user" ><label for="password">密码:</label><input type="password" name="password" id="password" class="pass" ><button>登录</button></form>
</div>

2.创建一个sql.js(覆盖掉之前那一个)

sql.js:

const express= require('express');
const app=express();//get路由
app.get('/login',function(req,res){  //req访问,res结果res.send('<hr>登录页面</hr>');
})app.get('/',function(req,res){//res.send('<hr>首页页面</hr>');res.sendFile(__dirname+'/'+'sql.html');  //当前运行下的/sql.html(渲染为此页面)
})const server = app.listen(3000,function(){console.log('web的3000端口已启动!');
})

运行结果:

以get路由来传参:

对sql.html进行修改:

<div class="login" ><h2>后台登录</h2><form action="http://127.0.0.1:3000/login" method="GET"><label for="username">用户名:</label><input type="text" name="username" id="username" class="user" ><label for="password">密码:</label><input type="password" name="password" id="password" class="pass" ><button>登录</button></form>
</div>

对sql.js进行修改:

const express= require('express');
const app=express();//get路由
app.get('/login',function(req,res){const u = req.query.username   //获取sql.html中的usernameconst p = req.query.password   //获取sql.html中的passwordconsole.log(u);console.log(p)if(u == 'admin' && p == '123456'){res.send("欢迎进入后台管理页面")}else{res.send('登录用户或密码错误!');}
})app.get('/',function(req,res){//res.send('<hr>首页页面</hr>');res.sendFile(__dirname+'/'+'sql.html');
})const server = app.listen(3000,function(){console.log('web的3000端口已启动!');
})

运行结果:(使用console.log()来查看是否定义成功)

以post路由来传参:

修改后的sql.js:

const express= require('express');
const bodyParser = require('body-parser');
const app=express();
// 创建 application/x-www-form-urlencoded 编码解析
var urlencodedParser = bodyParser.urlencoded({ extended: false })//get路由
app.get('/login',function(req,res){const u = req.query.username   //query适用于getconst p = req.query.passwordconsole.log(u);console.log(p)if(u == 'admin' && p == '123456'){res.send("欢迎进入后台管理页面")}else{res.send('登录用户或密码错误!');}
})//post路由
app.post('/login',urlencodedParser,function(req,res){const u = req.body.username;  //body适用于postconst p = req.body.password;console.log(u);console.log(p);if(u == 'admin' && p == '123456'){res.send("欢迎进入后台管理页面")}else{res.send('登录用户或密码错误!');}
})
app.get('/',function(req,res){//res.send('<hr>首页页面</hr>');res.sendFile(__dirname+'/'+'sql.html');
})const server = app.listen(3000,function(){console.log('web的3000端口已启动!');
})

在前面安装一个const bodyParser = require('body-parser');

在终端使用npm i body-parser安装库

使用:

// 创建 application/x-www-form-urlencoded 编码解析

var urlencodedParser = bodyParser.urlencoded({ extended: false })是为了使得在network里看到的数据username =111&password =111有一个规范的格式。

数据库管理:

数据库连接:


mysql连接代码:

var connection = mysql.createConnection({host     : 'localhost',user     : 'root',password : 'root',database : 'demo01'
});connection.connect();
const sql = 'select * from admin ';
console.log(sql);
connection.query(sql,function(error,data){if(error){console.log('数据库连接失败!');}console.log(data);
})

效果展示:

与数据库相同

数据库与登录逻辑结合:
//post路由
app.post('/login',urlencodedParser,function(req,res){const u = req.body.username;const p = req.body.password;console.log(u);console.log(p);var connection = mysql.createConnection({host     : 'localhost',user     : 'root',password : 'root',database : 'demo01'});connection.connect();const sql = 'select * from admin where username="'+u+'" and password="'+p+'"';  console.log(sql);connection.query(sql,function(error,data){if(error){console.log('数据库连接失败!');}try{  //报错测试if(u==(data[0]['username']) && p==data[0]['password']){  
//data[0]的意思是取第一行数据['username']是取里面的username值res.send('欢迎进入后台管理页面');}}catch{res.send('错误');};})})app.get('/',function(req,res){//res.send('<hr>首页页面</hr>');res.sendFile(__dirname+'/'+'sql.html');
})const server = app.listen(3000,function(){console.log('web的3000端口已启动!');
})

效果展示:

使用永“真”语句注入的话,无论前面是什么都会全部回显。(or)

在页面注入:

测试下来后没有往后面运行:

证明前面的代码if(u==(data[0]['username']) && p==data[0]['password'])对其进行了过滤:

u==(data[0]['username']是用来判断键值是否相同,正常应该是数据库取出的行数进行判断,而不是data中取的值。

安全方法:

使用sql预编译进行写比较安全(secguide-main)

文件管理功能:

创建file.js:(记得npm i fs)

const fs=require('fs');
const express = require('express');
const app = express();app.get('/file', function (req, res) {const dir=req.query.dir;   //接受dirconsole.log(dir);          //调试dirfilemanage(dir);           //执行dir
})var server = app.listen(3000, function () {console.log('web应用3000端口已启动!')
})function filemanage(dir){fs.readdir(dir,function(error,files){console.log(files);
})};

运行结果:使用node .\file.js(在网址后面加上/file?dir=)

命令执行功能:

文件操作

1、Express开发
2、实现自录读取
3、加入传参接受
一命令执行(RCE)
1、eval
2、exec & spawnSyn

创建一个rce.js:(npm i child_process)

const rce=require('child_process');//nodejs 调用系统命令执行
//rce.exec('notepad');
//rce.spawnSync('calc');//nodejs 调用代码命令执行 把字符串当做代码解析
eval('require("child_process").exec("calc");');

node.js判断:


安全问题-NodeJs-注入&RCE&原型链


1、SQL注入&文件操作
2、RCE执行&原型链污染
2、NodeJS黑盒无代码分析

实战测试NodeJs安全
判断:参考前期的信息收集

黑盒:通过对各种功能和参数进行payload测试

白盒:通过对代码中写法安全进行审计分析


原型链污染


如果攻击者控制·并修改了一个对象的原型,(_proto_)那么将可以影响所有和这个对象来自同一个类、父祖类的对象。

// // foo是一个简单的JavaScript对象
// let foo = {bar: 1} //解释:1=1 0 __proto__= x
// // 原型链污染
// // foo.bar 此时为1
// console.log(foo.bar)// // 修改foo的原型(即Object)
// foo.__proto__.bar = 'x'// // // 由于查找顺序的原因,foo.bar仍然是1
// console.log(foo.bar)// // // 此时再用Object创建一个空的zoo对象
// let zoo = {}// // 查看zoo.bar,此时bar为2
// console.log(zoo.bar)let foo = {bar: 1};console.log(foo.bar);foo.__proto__.bar = 'require(\'child_process\').execSync(\'calc\');'console.log(foo.bar);let zoo = {};console.log(eval(zoo.bar));

运行结果:

案例分析-NodeJS-CTF题目&源码审计


1、CTFSHOW几个题日

https://ctfshow/Web334-344
https://f1veseven.github.io/2022/04/03/ctf-nodejs-zhi-yi-xie-xiao-zhi-shi/

CTFSHOW-334:

1.打开zip发现是pk开头:

证明是zip文件:使用winzip或者别的解压软件打开(Bandzip):

打开后由login.js和user.js:

(引用文章:Ctfshow web入门 nodejs篇 web334-web344-阿里云开发者社区)

login.js:

//login.jsvar express = require('express');              //引入各个模块
var router = express.Router();
var users = require('../modules/user').items;   //引入用户模块(user.js)var findUser = function(name, password){          //定义函数return users.find(function(item){return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;});       //如果name不等于CTFSHOW,并且将name都转为大写与item.name(CTFSHOW)相同,password=123456。则findUser返回true  //toUpperCase()是javascript中将小写转换成大写的函数。
};/* GET home page. */
router.post('/', function(req, res, next) {                 //POST请求的处理函数res.type('html');                                //设置响应(res)的内容类型为htmlvar 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});   //登录成功返回flag});}else{res.json({ret_code: 1, ret_msg: '账号或密码错误'});}  });module.exports = router;   //通过module.exports将该路由模块导出,以便在其他文件中引入和使用

user.js: 

//user.jsmodule.exports = {items: [{username: 'CTFSHOW', password: '123456'}]
};//这段代码是一个模块文件,通过`module.exports`将一个对象导出。
//在这个模块中,导出的对象是一个包含一个属性`items`的对象。`items`属性是一个数组,包含了一个用户对象。这个用户对象有两个属性:`username`表示用户名为"CTFSHOW",`password`表示密码为"123456"。//通过这种方式,其他文件可以引入该模块并访问`items`数组中的用户对象,用于验证用户的登录信息。

突破点:

1.2 nodejs语言的缺点

1.2.1 大小写特性

toUpperCase()
toLowerCase()

对于toUpperCase(): 字符"ı""ſ" 经过toUpperCase处理后结果为 "I""S"
对于toLowerCase(): 字符"K"经过toLowerCase处理后结果为"k"(这个K不是K)

var findUser = function(name, password){          //定义函数return users.find(function(item){return name!=='CTFSHOW' && item.username === name.toUpperCase() && item.password === password;});       //如果name不等于CTFSHOW,并且将name都转为大写与item.name(CTFSHOW)相同,password=123456。则findUser返回true  //toUpperCase()是javascript中将小写转换成大写的函数。
};

payload:

name:ctfshow

password:123456

result:


2、YApi管理平台漏洞
https://blog.csdn.net/weixin_42353842/article/details/127960229


开发指南-NodeJs-安全SecGuide项目:

https://github.com/Tencent/secguide

本文章由李豆豆喵和番薯小羊卷~共同完成。

相关文章:

第30天:安全开发-JS 应用NodeJS 指南原型链污染Express 框架功能实现审计0

时间轴&#xff1a; 演示案例&#xff1a; 环境搭建-NodeJS-解析安装&库安装 功能实现-NodeJS-数据库&文件&执行 安全问题-NodeJS-注入&RCE&原型链 案例分析-NodeJS-CTF 题目&源码审计 开发指南-NodeJS-安全 SecGuide 项目、 环境搭建-NodeJ…...

关于单片机的原理与应用!

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///计算机爱好者&#x1f60a;///目前正在学习C&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于单片…...

什么是节点嵌入向量

节点嵌入向量是图神经网络中对节点信息进行表示的一种方式。它是将节点的各种属性、特征以及其在图结构中的位置关系等信息&#xff0c;通过某种数学变换映射到一个低维向量空间中的向量。 在图神经网络中&#xff0c;节点通常具有多种属性&#xff0c;如在社交网络中用户节点可…...

青海摇摇了3天,技术退步明显.......

最近快手上的青海摇招聘活动非常火热&#xff0c;我已经在思考是否备战张诗尧的秋招活动。开个玩笑正片开始&#xff1a; 先说一下自己的情况&#xff0c;大专生&#xff0c;20年通过校招进入杭州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c…...

url_launcher三方包的用法

文章目录 1 概念介绍2 使用方法3 示例代码我们在上一章回中介绍了包管理相关的内容,本章回中将介绍如何使用url_launcher包.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 我们在这里介绍url_launcher包主要用来打开Url中的内容,Url可以是电话号码,网址,邮箱等内容。如…...

Python 【图像分类】之 PyTorch 进行猫狗分类功能的实现(Swanlab训练可视化/ Gradio 实现猫狗分类 Demo)

Python 【图像分类】之 PyTorch 进行猫狗分类功能的实现(Swanlab训练可视化/ Gradio 实现猫狗分类 Demo) 目录 Python 【图像分类】之 PyTorch 进行猫狗分类功能的实现(Swanlab训练可视化/ Gradio 实现猫狗分类 Demo) 一、简单介绍 二、PyTorch 三、CNN 1、神经网络 2、卷…...

springboot371高校实习管理系统(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;高校实习管理系统的设计与实现 摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解…...

Elasticsearch面试内容整理-面试注意事项

在准备 Elasticsearch 面试时,除了掌握技术知识外,还需要注意如何有效展示你的技能和经验。以下是一些 Elasticsearch 面试的注意事项和建议: 掌握基础概念 在面试中,面试官通常会首先评估你对 Elasticsearch 基础概念的理解,包括集群架构、分片、副本、节点类型等。这些是…...

Python学习第十五天--魔术方法

魔法方法就是可以给你的类增加魔力的特殊方法&#xff0c;它们总被双下划线所包围&#xff0c;像这种格式:"__方法名__"&#xff0c;这些方法很强大&#xff0c;充满魔力&#xff0c;可以让你实现很多功能。 使用dir()查看类的所有属性和方法 class A:passprint(di…...

计算机的错误计算(一百七十二)

摘要 探讨 MATLAB 对于算式 的计算误差。 例1. 在 MATLAB 中计算 的值。 直接贴图吧&#xff1a; 这样&#xff0c;MATLAB 的输出中只有3位正确数字&#xff0c;有效数字的错误率为 (16-3)/16 81.25% . 因为16位的正确输出为 0.2971242332737277e-18&#xff08;ISReals…...

C/C++每日一练:合并K个有序链表

本篇博客将探讨如何 “合并K个有序链表” 这一经典问题。本文将从题目要求、解题思路、过程解析和相关知识点逐步展开&#xff0c;同时提供详细注释的代码示例。 链表&#xff08;Linked List&#xff09; 链表是一种线性数据结构&#xff0c;由一系列节点&#xff08;Node&…...

STM32实现HC595控制三位数码管(内含程序,PCB原理图及相关资料)

目录 任务要求 一、595的作用 二、电路设计 三、STM32选型 四、cubeMX配置 五、代码实现 六、实现效果&#xff08;显示12.8&#xff09; 任务要求 使用两个595实现对三位数码管控制&#xff0c;实现三位值显示。 一、595的作用 74HC595的作用是将串行数据进行并行显示…...

《沉积与特提斯地质》

《沉积与特提斯地质》为中国地质调查局主管&#xff0c;中国地质调查局成都地质调查中心&#xff08;西南地质科技创新中心&#xff09;主办的地学类学术期刊。 《沉积与特提斯地质》创刊于1981年&#xff0c;创刊名为《岩相古地理研究与编图通讯》&#xff0c;后更名为《岩相…...

Android studio 签名加固后的apk文件

Android studio打包时&#xff0c;可以选择签名类型v1和v2&#xff0c;但是在经过加固后&#xff0c;签名就不在了&#xff0c;或者只有v1签名&#xff0c;这样是不安全的。 操作流程&#xff1a; 1、Android studio 对项目进行打包&#xff0c;生成有签名的apk文件&#xff…...

Brain.js(二):项目集成方式详解——npm、cdn、下载、源码构建

Brain.js 是一个强大且易用的 JavaScript 神经网络库&#xff0c;适用于前端和 Node.js 环境&#xff0c;帮助开发者轻松实现机器学习功能。 在前文Brain.js&#xff08;一&#xff09;&#xff1a;可以在浏览器运行的、默认GPU加速的神经网络库概要介绍-发展历程和使用场景中&…...

关于Vscode配置Unity环境时的一些报错问题(持续更新)

第一种报错&#xff1a; 下载net请求超时&#xff08;一般都会超时很正常的&#xff09; 实际时并不需要解决&#xff0c;它对你的项目毫无影响 第二种报错&#xff1a; .net版本不匹配 解决&#xff1a;&#xff08;由于造成问题不一样&#xff0c;所以建议都尝试一次&…...

MacOS 配置github密钥

MacOS 配置github密钥 1. 生成GitHub的SSH密钥对 ssh-keygen -t ed25519 -C "xxxxxxx.com" -f ~/.ssh/id_ed25519_github 其中 xxxxxxxxxxx.com 是注册github、gitee和gitlab的绑定账号的邮箱 -t ed25519:生成密钥的算法为ed25519&#xff08;ed25519比rsa速度快&…...

从0开始学PHP面向对象内容之常用设计模式(策略,观察者)

PHP设计模式——行为型模式 PHP 设计模式中的行为模式&#xff08;Behavioral Patterns&#xff09;主要关注对象之间的通信和交互。行为模式的目的是在不暴露对象之间的具体通信细节的情况下&#xff0c;定义对象的行为和职责。它们常用于解决对象如何协调工作的问题&#xff…...

前端 如何用 div 标签实现 步骤审批

在前端实现一个步骤审批流程&#xff0c;通常是通过 div 标签和 CSS 来构建一个可视化的流程图&#xff0c;结合 JavaScript 控制审批的状态变化。你可以使用 div 标签创建每一个步骤节点&#xff0c;通过不同的样式&#xff08;如颜色、边框等&#xff09;表示审批的不同状态&…...

【大数据技术基础 | 实验十四】Kafka实验:订阅推送示例

文章目录 一、实验目的二、实验要求三、实验原理&#xff08;一&#xff09;Kafka简介&#xff08;二&#xff09;Kafka使用场景 四、实验环境五、实验内容和步骤&#xff08;一&#xff09;配置各服务器之间的免密登录&#xff08;二&#xff09;安装ZooKeeper集群&#xff08…...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

C++_核心编程_多态案例二-制作饮品

#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为&#xff1a;煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例&#xff0c;提供抽象制作饮品基类&#xff0c;提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

边缘计算医疗风险自查APP开发方案

核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

华为OD机试-食堂供餐-二分法

import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...