测试设置用户ID set-user-id

本文探讨了进程的实际用户ID与有效用户ID的区别,并通过示例代码演示了如何利用set-user-id位使得普通用户能够以root权限打开特定文件。

有进程相关的id:
实际用户id,有效用户id,设置用户id
及相应组id;

一般情况下,有效用户id等于实际用户id;
特殊情况下,执行一个可执行文件,进程有效用户id可以设置为可执行文件的所有者id;

代码如下:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>


int main(int argc, char *argv[])
{

        if (argc < 2)
        {
                printf("usage: test <pathname>\n");
                return -1;
        }

        if (access(argv[1], R_OK) < 0)
        {
                printf("acess error for %s:", argv[1]);
                printf("%s\n", strerror(errno));
        }
        else
        {
                printf("read access OK\n");

        }

        if (open(argv[1], O_RDONLY) < 0)
        {
                printf("open error for %s:", argv[1]);
                printf("%s\n", strerror(errno));
        }
        else
        {
                printf("open for Reading OK\n");
        }


        return 0;
}

编译生成可执行文件test

[lanyang@localhost work]$ gcc test_access.c -o test
[lanyang@localhost work]$ ll
total 12
-rwxrwxr-x. 1 lanyang lanyang 7373 Jul 30 11:41 test

当前用户lanyang
测试文件/etc/passwd

[lanyang@localhost work]$ ll /etc/passwd
-rw-r--r--. 1 root root 1844 Jul 25 04:21 /etc/passwd

[lanyang@localhost work]$ ./test /etc/passwd
read access OK
open for Reading OK

测试文件/etc/shadow

[lanyang@localhost work]$ ll /etc/shadow
----------. 1 root root 1129 Jul 25 04:21 /etc/shadow

[lanyang@localhost work]$ ./test /etc/shadow
acess error for /etc/shadow:Permission denied
open error for /etc/shadow:Permission denied

切换到root用户

[lanyang@localhost work]$ su root
Password: 

设置文件所有者为root

[root@localhost work]# chown root test

设置set-user-id位

[root@localhost work]# chmod u+s test

查看test文件属性

[root@localhost work]# ll
total 12
-rwsrwxr-x. 1 root    lanyang 7373 Jul 30 11:50 test

退出root用户

[root@localhost work]# exit
exit

现在用户是lanyang
再次测试/etc/shadow

[lanyang@localhost work]$ ./test /etc/shadow
acess error for /etc/shadow:Permission denied
open for Reading OK

结论:

可以看到access测试失败,而open测试通过;
原因:
access的测试是按照进程实际用户ID(在这里是lanyang)进行权限测试,文件所有者为root,无法通过权限测试;

open以有效用户ID进行测试,由于test文件设置set-user-id位,当用户lanyang执行test时,进程的有效用户ID被设置成文件所有者ID,也就是root,通过权限测试;

<think>我们面对的问题是在缺少`setId`和`setOnline`方法的情况下,如何设置用户ID和在线状态。根据之前的讨论,用户状态更新是系统运行的关键环节,因此需要找到替代方案。 ### 解决方案分析 #### 1. **直接操作数据模型** 当缺少setter方法时,可以直接操作底层数据模型: ```java // 假设User类有id和online属性 User user = new User(); // 直接设置字段(需确保字段可见性) user.id = 123; // 不推荐,破坏封装性 ``` **风险**:破坏面向对象封装原则,且当字段为private时不可行[^1]。 #### 2. **通过构造函数初始化** 如果用户状态在创建时就确定,可通过构造函数设置: ```java public User(int id, boolean isOnline) { this.id = id; this.isOnline = isOnline; } // 使用构造函数 User user = new User(123, true); ``` **限制**:仅适用于对象创建时,无法用于更新[^2]。 #### 3. **反射机制(特殊场景)** 当必须修改已有对象且无setter时,可使用反射: ```java User user = new User(); Class<?> userClass = user.getClass(); // 设置ID Field idField = userClass.getDeclaredField("id"); idField.setAccessible(true); idField.set(user, 123); // 设置在线状态 Field onlineField = userClass.getDeclaredField("online"); onlineField.setAccessible(true); onlineField.set(user, true); ``` **注意**:反射会绕过访问控制,降低性能,且可能引发安全异常[^3]。 #### 4. **通过业务逻辑层更新** 设计专门的服务方法处理状态更新: ```java public class UserService { public void updateUserStatus(int userId, boolean isOnline) { // 1. 从数据库加载用户 User user = userRepository.findById(userId); // 2. 直接修改字段(需持久层支持) user.setOnline(isOnline); // 假设有setOnline,但问题中不存在 // 替代方案:使用SQL更新 jdbcTemplate.update("UPDATE users SET online=? WHERE id=?", isOnline, userId); } } ``` **优势**:符合分层架构,避免直接操作对象[^4]。 #### 5. **DTO模式传递数据** 使用数据传输对象(DTO)承载新状态: ```java public class UserStatusDTO { private int userId; private boolean online; // getters/setters省略 } // 在服务层解析DTO public void updateStatus(UserStatusDTO dto) { userRepository.updateOnlineStatus(dto.getUserId(), dto.isOnline()); } ``` **适用场景**:分布式系统或API接口[^5]。 ### 方案选择建议 | 场景 | 推荐方案 | 风险提示 | |------|----------|----------| | 对象初始化 | 构造函数初始化 | 无法修改已有对象 | | 紧急修复 | 反射 | 破坏封装,需异常处理 | | 生产系统 | 业务逻辑层更新 | 需持久层配合 | | 微服务架构 | DTO模式 | 增加数据传输开销 | ### 状态更新流程图 ```mermaid sequenceDiagram participant Client as 客户端 participant Service as 服务层 participant DAO as 数据层 Client->>Service: 提交用户ID和状态 alt 使用DTO Service->>Service: 解析UserStatusDTO else 直接操作 Service->>DAO: 调用更新方法 end DAO->>Database: 执行UPDATE语句 Database-->>DAO: 返回影响行数 DAO-->>Service: 返回结果 Service-->>Client: 操作成功/失败 ``` ### 相关问题 1. 如何在不破坏封装性的前提下动态更新对象状态?[^3] 2. 使用反射修改私有字段会导致哪些安全问题?[^3] 3. DTO模式在微服务架构中还有哪些应用场景?[^5] 4. 如何设计高并发的用户状态更新接口?[^4]</think>### 用户状态更新中缺失 setter 方法的解决方案 当系统中缺少 `setId` 和 `setOnline` 方法时,可通过以下方式设置用户 ID 和在线状态: --- #### 1. **通过构造函数初始化(推荐)** 在创建用户对象时直接初始化状态值: ```java // 用户实体类 public class User { private final int id; // final 确保 ID 不可变 private boolean online; // 通过构造函数设置 ID 和状态 public User(int id, boolean online) { this.id = id; this.online = online; } // 状态更新方法 public void updateOnlineStatus(boolean status) { this.online = status; } } // 使用示例 User user = new User(1001, true); // 创建 ID=1001 的在线用户 user.updateOnlineStatus(false); // 更新为离线状态 ``` **优势**: - 保证 ID 的不可变性(final 字段)[^1] - 状态更新通过专用方法控制 --- #### 2. **构建器模式(复杂对象)** 当需要多参数初始化时: ```java public class UserBuilder { private int id; private boolean online; public UserBuilder setId(int id) { this.id = id; return this; } public UserBuilder setOnline(boolean online) { this.online = online; return this; } public User build() { return new User(id, online); } } // 使用示例 User user = new UserBuilder() .setId(1002) .setOnline(true) .build(); ``` --- #### 3. **反射机制(谨慎使用)** 当无法修改源代码时: ```java User user = new User(); Class<?> userClass = user.getClass(); // 设置 ID Field idField = userClass.getDeclaredField("id"); idField.setAccessible(true); idField.set(user, 1003); // 设置在线状态 Field onlineField = userClass.getDeclaredField("online"); onlineField.setAccessible(true); onlineField.set(user, true); ``` **风险提示**: - 破坏封装性,违反面向对象原则 - 性能开销较大 - 需处理 IllegalAccessException 等异常[^3] --- #### 4. **数据库直接更新** 通过持久层绕过对象操作: ```sql /* SQL 直接更新 */ UPDATE users SET online_status = 1 WHERE user_id = 1004; ``` ```java // JDBC 示例 String sql = "UPDATE users SET online = ? WHERE id = ?"; try (PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setBoolean(1, true); // 在线状态 stmt.setInt(2, 1005); // 用户ID stmt.executeUpdate(); } ``` --- #### 5. **状态模式(行为管理)** 封装状态转换逻辑: ```java public interface UserState { void handleRequest(UserContext context); } public class OnlineState implements UserState { @Override public void handleRequest(UserContext context) { System.out.println("处理在线用户请求"); } } public class UserContext { private UserState state; public void setState(UserState state) { this.state = state; } public void request() { state.handleRequest(this); } } // 使用示例 UserContext user = new UserContext(); user.setState(new OnlineState()); // 设置在线状态 ``` --- ### 方案选择建议 | 场景 | 推荐方案 | 注意事项 | |------|----------|----------| | 新系统开发 | 构造函数初始化 | 确保 ID final 修饰 | | 复杂对象创建 | 构建器模式 | 适合多参数场景 | | 遗留系统改造 | 反射机制 | 添加异常处理 | | 批量状态更新 | 数据库操作 | 注意事务一致性[^4] | | 状态行为管理 | 状态模式 | 避免状态逻辑分散 | ```mermaid graph TD A[需要设置用户状态] --> B{能否修改源码?} B -->|是| C[使用构造函数/构建器] B -->|否| D[反射/数据库操作] C --> E[保证ID不可变] D --> F[添加异常处理] E --> G[正常业务流程] F --> G ``` ### 相关问题 1. 如何保证用户 ID 的不可变性?[^1] 2. 反射修改私有字段会引发哪些安全问题?[^3] 3. 数据库更新状态时如何保证事务一致性?[^4] 4. 状态模式在用户管理系统中的具体应用场景? 5. 构建器模式相比构造函数有哪些优势?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值