【玩转 Postman 接口测试与开发2_018】第14章:利用 Postman 初探 API 安全测试

book cover for the 2nd version

《API Testing and Development with Postman》最新第二版封面

文章目录

  • 第十四章 API 安全测试
    • 1 OWASP API 安全清单
      • 1.1 相关背景
      • 1.2 OWASP API 安全清单
      • 1.3 认证与授权
      • 1.4 破防的对象级授权(Broken object-level authorization)
      • 1.5 破防的属性级授权(Broken property-level authorization)
      • 1.6 不受控制的资源消耗(Unrestricted resource consumption)
      • 1.7 不受限制地访问业务流(Unrestricted access to business workflows)
      • 1.8 不安全地使用 API 接口(Unsafe consumption of APIs)
    • 2 模糊测试(Fuzzy)
      • 2.1 相关概念
      • 2.2 实战:在 Postman 中执行模糊测试
      • 2.3 绕开 Collection Runner 的测试方案
    • 3 利用 Postman 内置随机变量实现模糊测试
    • 4 小结

写在前面
本章为全书的倒数第二章,作者简要介绍了 API 安全测试的相关概念,并对 OWASP API 安全清单和模糊测试(Fuzzy Testing)进行了重点讲解;后半部分作者利用数据驱动测试演示了模糊测试在 Postman 中的具体应用,只可惜在实现方案和叙述的条理性上较为敷衍,导致我在实测过程中又踩了不少坑。特此梳理出来,既是对自己创新方案的复盘,也能让更多后来者少走弯路。

第十四章 API 安全测试

本章概要

  • OWASP API 安全清单
  • Postman 进行模糊测试(Fuzz testing)的方法

1 OWASP API 安全清单

1.1 相关背景

  • OWASP(Open Web Application Security Project)是一个非营利组织,专注于提高软件安全性;
  • 提供免费、开放的资源,如工具、文档、论坛等,帮助开发者和安全人员构建安全的应用程序;
  • 知名项目包括 OWASP Top 10(十大 Web 应用安全风险)和 API Security Top 10(API安全十大风险);
  • 相关资源:
    • 网站门户:https://owasp.org/;
    • 2023 最新版 API 安全清单:https://owasp.org/www-project-api-security/;

1.2 OWASP API 安全清单

  • API Security Top 10 是 OWASP 针对 API 安全的核心文档,罗列了 API 面临的十大安全风险。
  • 适用于开发者、安全工程师和架构师,帮助识别和缓解API安全威胁。

1.3 认证与授权

黑客最先尝试的攻击方式是身份验证,即通过用户名密码攻击。

最简单的方式是暴力破解(brute-force attack),因此 API 接口应采取在一定时间段内 限制登录次数 的防护措施。

具体实现:以 GitPod 演示项目为例,可利用 Postman 内置的随机数据变量高频多次调用登录接口(多次手动点击或 Collection Runner 设置迭代次数):

测试脚本:

for (let i = 0, len = 50; i < len; i++) {pm.sendRequest({url: pm.variables.replaceIn('{{base_url}}/token'),method: 'POST',header: { 'Content-Type': 'multipart/form-data'},body: {mode: "formdata",formdata: [{ key: 'username', value: pm.variables.replaceIn('{{$randomUserName}}') },{ key: 'password', value: pm.variables.replaceIn('{{$randomPassword}}') }]}},function (err, resp) {if (err) {console.error(JSON.parse(err));return;}pm.test(`${i + 1}: Response JSON have detail attribute`, function () {pm.expect(resp.json()).to.eql({ "detail": "Incorrect user name or password" });});})
}

实测结果(鉴权接口疑似不具备限制登录次数功能):

图 14.1 构造不同的用户名和密码,多次高频调用登录接口,模拟暴力破解过程

【图 14.1 构造不同的用户名和密码,多次高频调用登录接口,模拟暴力破解过程】

1.4 破防的对象级授权(Broken object-level authorization)

这是 2023 年十大安全清单排名第一的高风险议题:

  • 问题描述:未正确验证用户对对象的访问权限,导致越权访问。
  • 应对措施:实施严格的权限验证,确保用户只能访问授权资源。
  • 示例:用登录普通用户(user1/12345)的登录令牌去访问管理员帐号,看看示例项目是否会响应 403 错误。

实测结果:

图 14.2 用 user1 的登录令牌访问管理员 admin 的帐号信息,后台报 403 错误(符合预期)

【图 14.2 用 user1 的登录令牌访问管理员 admin 的帐号信息,后台报 403 错误(符合预期)】

1.5 破防的属性级授权(Broken property-level authorization)

示例:用 user1/12345 登录,先用 "user1" 为创建人(即 create_by 字段)添加一个正常的待办项,然后看看是否可以通过 PUT 请求篡改该创建人信息(例如改为 "user2")。

测试脚本:

// PUT request's Post-response
pm.collectionVariables.set('reqPut', pm.request);// Pre-request
pm.sendRequest(pm.collectionVariables.get('reqPut'),function(err, resp) {if(err) {console.error('Updated failure:', err);return;}pm.test('PUT req: status code should be 200', () => {console.log(resp)pm.expect(resp.code).to.eq(200);});}
)// Post-response
pm.test('The creator should not be updated (user1) after running PUT req',() => {const [{ created_by: creator }] = pm.response.json();pm.expect(creator).to.eql('user1')});

实测结果:

图 14.3 实测属性级授权漏洞(篡改失败,符合预期)

【图 14.3 实测属性级授权漏洞(篡改失败,符合预期)】

但实测发现,在创建待办项时人为设置创建人为 非当前登录用户(如 "user2"),最后仍然创建成功了,说明该接口还是有问题的:

图 14.4 创建任务时篡改创建人信息,最终任务创建成功,说明该接口仍然支持篡改信息

【图 14.4 创建任务时篡改创建人信息,最终任务创建成功,说明该接口仍然支持篡改信息】

1.6 不受控制的资源消耗(Unrestricted resource consumption)

方式一:通过耗尽所有系统资源来损害系统,导致系统瘫痪(denial-of-service attacks,拒绝服务攻击);

方式二:由于未对接口调用次数进行限制,大量请求将拖慢响应速度;如果接口调用了第三方付费资源,还会产生大量费用。

1.7 不受限制地访问业务流(Unrestricted access to business workflows)

攻击者可能会利用抓包、监控请求数据等方式获取系统内部通信 API,从而获悉内部工作流结构,给系统带来严重威胁(例如恶意逃票等)。

应对措施:仔细考虑暴露了哪些接口;严格控制接口访问权限。

1.8 不安全地使用 API 接口(Unsafe consumption of APIs)

典型案例:引用了被黑客攻击的第三方 API。

这类测试较为棘手,可能需要搭建一个模拟服务器,让从第三方 API 响应的数据在该模拟器上先行缓冲,或者模拟危险数据进行测试,看看本地后台是否能够鉴别该类数据。

2 模糊测试(Fuzzy)

2.1 相关概念

定义:模糊测试(Fuzzy) 是一种通过向程序提供无效、随机或意外的输入来发现漏洞和错误的测试技术。

主要特征:

  • 非通用测试技术
  • 有助于发现未考虑到、或未测试到的问题

具体的运行方式:

  • 手动执行:通过在 UI 中输入伪随机数据来完成(极少);
  • 编程执行:以程序的形式运行(绝大多数)。工具如 PeachFuzzer 或自定义输入集。

输入的生成:

  • 生成大量随机或半随机数据,可能包含格式错误或危险字符(如转义字符、引号等)。
  • 输入通常是随机的,但有一些边界限制(例如,字节 vs ASCII/Unicode 字符串)。
  • 输入可以偏向已知的“危险”字符(例如转义字符、引号等)。

输入内容的具体方式:

  • 通过命令行参数注入
  • 通过文件输入
  • 通过网络协议
  • 通过 API 输入(特别适合模糊测试,易于用程序控制)

模糊查询的应用场景:

  • 安全测试场景:揭示未能预见的攻击方向,发现错误处理机制中的薄弱环节等;
  • API 测试场景:具有易于访问且数量巨大的测试输入,尤其适合 API 测试。

2.2 实战:在 Postman 中执行模糊测试

GitHub 开源项目 Big List of Naughty StringsJSON 文件为数据源,利用 Collection RunnerGitPod 演示项目 ToDo List App 进行基于数据驱动的模糊测试,看看 POST /tasks 接口在大量随机输入下的响应情况。

首先从开源项目下载原始数据文件 blns.base64.json。该原始数据为 Base64 编码的字符串数组:

["", "dW5kZWZpbmVk", "dW5kZWY=", "bnVsbA==", ...
]

使用前需要先处理成如下格式(可使用 vim 宏的批量操作完成):

[{"naughtyString":""}, {"naughtyString":"dW5kZWZpbmVk"}, {"naughtyString":"dW5kZWY="}, {"naughtyString":"bnVsbA=="}, ...
]

然后创建测试集合 FuzzyTest 以及 POST 请求 Create a task,其 URL 设置为 {{base_url}}/tasks,其中 base_url 为集合变量,其值为 GitPod 演示项目的基础 URL(启动链接:https://gitpod.io/new/#https://github.com/djwester/todo-list-testing)。

接着,在测试请求的请求体中输入如下内容(这里有个大坑,后面会讲):

{"description": "{{naughtyString}}","status": "Draft"
}

上述代码中的 naughtyString 为数据驱动测试启动后、经 Pre-request 脚本处理得到的动态变量:

const atob = require('atob');
const encoded_string = pm.iterationData.get("naughtyString");
pm.collectionVariables.set('naughtyString', atob(encoded_string));

对应的 Post-response 响应后脚本如下:

pm.test("Status code is 201", function() {pm.response.to.have.status(201);
});

一切准备就绪后,启动 Collection Runner,加载 JSON 映射文件,执行模糊测试:

图 14.5 利用 Collection Runner,发起基于JSON 文件数据驱动的模糊测试

【图 14.5 利用 Collection Runner,发起基于JSON 文件数据驱动的模糊测试】

由于是分别读取每行数据并调用创建接口,整个过程需要多等些时间,最终得到结果:

图 14.6 Collection Runner 运行结束后的实测截图

【图 14.6 Collection Runner 运行结束后的实测截图】

由于作者演示时用的是本地部署的 ToDo List App,全套数据测完只用了 1'14",和我实测时的 23'56" 真是天壤之别(没办法,Python 基础有限,几次尝试本地部署都失败了)。但奇怪的是,除了 10 个请求因为网络原因发送失败,其余 666 个都成功了,并没有报错。

可高兴不到一分钟,我就知道出问题了。作者并没有交代要用 user2 登录,而我新增任务前压根儿就没登录,导致后续的数据清空全部失败了:

图 14.7 由于新增任务时没有登录,导致后续的批量删除全部失败(神坑)

【图 14.7 由于新增任务时没有登录,导致后续的批量删除全部失败(神坑)】

没办法,只能重置项目,重新来过……

Ctrl + C 中断 GitPod 项目,运行重置数据命令 poetry run python remove_tables.py,然后执行 make run-dev 重新启动。

这次先用 user2/12345 换取登录令牌,再到新增接口中完成鉴权:

图 14.8 用 user2 的登录令牌完成新增接口的鉴权设置

【图 14.8 用 user2 的登录令牌完成新增接口的鉴权设置】

然后只选取新增接口,再次上传 JSON 数据集运行 Collection Runner

图 14.9 重新运行 Collection Runner 的配置界面截图

【图 14.9 重新运行 Collection Runner 的配置界面截图】

也不知道是不是这份死磕精神感动了马克思,第二次运行居然全部成功了:

图 14.10 第二次运行结果截图(676 条数据全部新增成功)

【图 14.10 第二次运行结果截图(676 条数据全部新增成功)】

接着在浏览器查看项目首页,也没有书中说的 alert 注入问题(当真欧皇附体?):

img14.11

借着这波好运,赶紧再跑一遍数据批量清空。在 GET {{base_url}}/task 接口的响应后脚本中输入以下内容(直接用 JS 脚本批量删除,书中方法太过陈旧,还得再消耗一次 Collection Runner 免费额度,不知道作者怎么想的):

const tasks = pm.response.json();
const task_ids = tasks.map(t => t.id);const base_url = pm.collectionVariables.get('base_url');
const auth = {type: 'bearer',bearer: [{key: 'token',value: pm.collectionVariables.replaceIn('{{token}}'),type: 'string'}]
};
const getCallback = task_id => (err, response) => {if(err) {console.error(err);return;}pm.test(`Deleting id(${task_id}): Status should be OK`, function() {pm.expect(response.status).to.eql('OK');});
};for(const id of task_ids) {// Delete the task by idpm.sendRequest({url: `${base_url}/tasks/${id}`,method: 'DELETE',auth}, getCallback(id));
}

结果还是报错:

img14.12

仔细一想,真相大白了:新增接口必须在请求体中手动指定 created_by 字段,否则 一律按匿名处理(就当是作者故意挖的坑吧)。

只有再重来一遍了:

img14.13

再次执行批量删除,只有一次是后台原因删除失败,五次请求未发送,其余全部删除成功,终于熬出头了:

img14.14

2.3 绕开 Collection Runner 的测试方案

其实只要具备 JavaScript 基础,完全可以跳过 Collection Runner 的限制,在 Pre-request / Post-response 脚本中实现批量新增和删除。

批量新增的纯 JS 脚本实现:

  1. 将原始 JSON 数据集不经任何处理直接放到 Postman 的私有模块中,例如 my-blns

    const data = ["", "dW5kZWZpbmVk", "dW5kZWY=", "bnVsbA==", "TlVMTA==", "KG51bGwp", // ..."e3sgIiIuX19jbGFzc19fLl9fbXJvX19bMl0uX19zdWJjbGFzc2VzX18oKVs0MF0oIi9ldGMvcGFz","c3dkIikucmVhZCgpIH19"
    ];
    module.exports = {data
    };
    
  2. 新建请求 GET {{base_url}}/tasks,用于在批量新增后查询总的待办项列表:

    1. Pre-request 中输入以下脚本:
    const atob = require('atob');
    const { data } = pm.require('your/package/path/to/my-blns');
    // console.log(data.length);const postRequest = description => ({url: pm.collectionVariables.replaceIn('{{base_url}}/tasks'),method: 'POST',header: { 'Content-Type': 'application/json' },body: {mode: 'raw',raw: JSON.stringify({description,status: "Draft",created_by: "user2"})}
    });data.map(d => atob(d)).map((description, i) => pm.sendRequest(postRequest(description), (err, resp) => {if(err) {console.error(err);return;}pm.test(`Create task_${i+1} completed: the status code should be 201`,() => pm.expect(resp.code).to.eql(201));}));
    
    1. Post-response 输入以下脚本:

      pm.test('Task list length should be greater than 0', function () {pm.expect(pm.response.json()).to.be.an('array').and.to.have.lengthOf.at.least(1, "Task list length should be greater than 0");
      });
      
  3. 至于批量删除,刚才实测过程中已经看过脚本了,这里只说明一下实现逻辑。利用列表查询接口 GET {{base_url}}/tasks 获取到任务列表后,批量提取任务列表的 id;然后分别调用 DELETE 接口 POST {{base_url}}/tasks/:id 完成删除(注意:删除待办项时,别忘了在请求中带上鉴权配置对象 auth)。

这样就可以在 Postman 中随意批量新增和删除待办任务了,完全不受 Collection Runner 的制约。

3 利用 Postman 内置随机变量实现模糊测试

其实就是将上传的 JSON 文件内容用 Postman 内置的各种随机变量实现同样的模糊测试效果,例如将新增接口请求体中的 JSON 改为:

{"description": "{{$randomLoremSentence}}","status": "Draft","created_by": "user2"
}

或者借助测试脚本,引入 lodash 实现更多模糊测试效果:

const _ = require('lodash');
// _.sample(collection): Gets a random element from collection.
const description = _.sample(["{{$randomAbbreviation}}", "{{$randomAdjective}}"]);
const jsonBody = {description,"status", "Draft","created_by": "user2"
}

4 小结

本章内容整体感觉较敷衍,上半部分介绍相关概念几乎全是蜻蜓点水式的描述,后半段实战环节的条理性又明显不如前面的章节,漏掉很多关键细节,且在方案选择上过分依赖 Collection Runner,致使我在实测过程中浪费了不少免费额度。不过依次填完这些坑后,我也有机会用纯 JavaScript 脚本的方式实现待办任务的批量创建与删除,顺便学习了用 pm.sendRequest() 发送 POST 请求和 DELETE 请求的写法,也算是因祸得福了吧。

后记
最后再跟大家分享个操作技巧,遇到不会写的 Postman 脚本,可以利用其自带的 AI 机器人 Postbot 直接给答案。本章实测中几种 pm.sendRequest() 的写法就是这么来的,比查官方文档快多了。大家一定要学会使用 AI 工具,千万不要故步自封,成为 AI 时代的 “新文盲”。

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

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

相关文章

Spring @PropertySource:让你的应用配置更加模块化和可维护

PropertySource注解在Spring中的作用&#xff0c;就像是给Spring应用配了一个“外部配置箱”。 想象一下&#xff0c;你在开发一个Spring应用时&#xff0c;有很多配置信息需要设置&#xff0c;比如数据库的连接信息、应用的某些功能开关等。如果这些信息都硬编码在代码中&…

RK3576——USB3.2 OTG无法识别到USB设备

问题&#xff1a;使用硬盘接入到OTG接口无热插拔信息&#xff0c;接入DP显示屏无法正常识别到显示设备&#xff0c;但是能通过RKDdevTool工具烧录系统。 问题分析&#xff1a;由于热插拔功能实现是靠HUSB311芯片完成的&#xff0c;因此需要先确保HUSB311芯片驱动正常工作。 1. …

docker-compose 配置nginx

前言 前端打包的dist文件在宿主机&#xff0c;nginx运行在docker-compose 问题 nginx.conf 在本地配置可以生效&#xff0c;但是链接到容器就报错 基于本地的nginx运行&#xff0c;本地nginx.conf 如下 server {listen 8081;location / {root /usr/local/software/testweb/…

基于SpringBoot+ Vue的家教管理系统

随着互联网技术的发展&#xff0c;信息化管理已经深入到各个行业中。在教育领域&#xff0c;家教管理系统的需求日益增长。传统的手工管理方式在面对大量信息时&#xff0c;容易出现管理效率低下、数据错误率高、修改困难等问题。本文将介绍基于Spring Boot框架、MySQL数据库开…

【数据结构】树哈希

目录 一、树的同构1. 定义2. 具体理解(1) 结点对应(2) 孩子相同(3) 递归性质 3. 示例 二、树哈希1.定义2.哈希过程&#xff08;1&#xff09;叶节点哈希&#xff08;2&#xff09;非叶节点哈希&#xff08;3&#xff09;组合哈希值 3.性质&#xff08;1&#xff09; 唯一性 \re…

使用DeepSeek的技巧笔记

来源&#xff1a;新年逼自己一把&#xff0c;学会使用DeepSeek R1_哔哩哔哩_bilibili 前言 对于DeepSeek而言&#xff0c;我们不再需要那么多的提示词技巧&#xff0c;但还是要有两个注意点&#xff1a;你需要理解大语言模型的工作原理与局限,这能帮助你更好的知道AI可完成任务…

【工具篇】ChatGPT:开启人工智能新纪元

一、ChatGPT 是什么 最近,ChatGPT 可是火得一塌糊涂,不管是在科技圈、媒体界,还是咱们普通人的日常聊天里,都能听到它的大名。好多人都在讨论,这 ChatGPT 到底是个啥 “神器”,能让大家这么着迷?今天咱就好好唠唠。 ChatGPT,全称是 Chat Generative Pre-trained Trans…

【centOS】搭建公司内网git环境-GitLab 社区版(GitLab CE)

1. 安装必要的依赖 以 CentOS 7 系统为例&#xff0c;安装必要的依赖包&#xff1a; sudo yum install -y curl policycoreutils openssh-server openssh-clients postfix sudo systemctl start postfix sudo systemctl enable postfix2. 添加 GitLab 仓库 curl -sS https:/…

开源 GPU 集群管理器 GPUStack 轻松拉起deepseek各版本模型

GPUStack 是一个用于运行 AI 模型的开源 GPU 集群管理器。 项目地址&#xff1a;gpustack/gpustack: Manage GPU clusters for running AI modelshttps://github.com/gpustack/gpustackhttps://github.com/gpustack/gpustackhttps://github.com/gpustack/gpustackhttps://githu…

ESP32开发学习记录---》GPIO

she 2025年2月5日&#xff0c;新年后决定开始充电提升自己&#xff0c;故作此记,以前没有使用过IDF开发ESP32因此新年学习一下ESP32。 ESPIDF开发环境配置网上已经有很多的资料了&#xff0c;我就不再赘述&#xff0c;我这里只是对我的学习经历的一些记录。 首先学习一个…

3-kafka服务端之控制器

文章目录 概述控制器的选举与故障恢复控制器的选举故障恢复 优雅关闭分区leader的选举 概述 在Kafka集群中会有一个或多个broker&#xff0c;其中有一个broker会被选举为控制器&#xff08;Kafka Controler&#xff09;&#xff0c;它负责管理整个集群中所有分区和副本的状态。…

IDEA 中集成 Maven,配置环境、创建以及导入项目

目录 在 IntelliJ IDEA 中集成 Maven 并配置环境 1. 打开 IDEA 设置 2. 定位 Maven 配置选项 3. 配置 Maven 路径 4. 应用配置 创建 Maven 项目 1. 新建项目 2. 选择项目类型 3. 配置项目信息 4. 确认 Maven 设置 5. 完成项目创建 导入 Maven 项目 1. 打开导入窗口…

神经网络常见激活函数 1-sigmoid函数

sigmoid 1 函数求导 sigmoid函数 σ ( x ) 1 1 e ( − x ) \sigma(x) \frac{1}{1e^{(-x)}} σ(x)1e(−x)1​ sigmoid函数求导 d d x σ ( x ) d d x ( 1 1 e − x ) e − x ( 1 e − x ) 2 ( 1 e − x ) − 1 ( 1 e − x ) 2 1 1 e − x − 1 ( 1 e − x ) 2 …

穷举vs暴搜vs深搜vs回溯vs剪枝系列一>黄金矿工

目录 决策树&#xff1a;代码设计代码&#xff1a; 决策树&#xff1a; 代码设计 代码&#xff1a; class Solution {boolean[][] vis;int ret,m,n;public int getMaximumGold(int[][] grid) {m grid.length;n grid[0].length;vis new boolean[m][n]; for(int i 0; i <…

rabbitMQ消息转换器

消息转换器 Spring的消息发送代码接收的消息体是一个Object&#xff1a; 而在数据传输时&#xff0c;它会把你发送的消息序列化为字节发送给MQ&#xff0c;接收消息的时候&#xff0c;还会把字节反序列化为Java对象。 只不过&#xff0c;默认情况下Spring采用的序列化方式是J…

Java 如何覆盖第三方 jar 包中的类

目录 一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理 背景&#xff1a; 在我们日常的开发中&#xff0c;经常需要使用第三方的 jar 包&#xff0c;有时候我们会发现第三方的 jar 包中的某一个类有问题&#xff0c;或者我们需要定制化修改其中的逻辑&#xff0c…

CS 与 BS 架构的差异

在数字化的今天&#xff0c;选择软件架构模式对系统的性能、维护、安全和成本都有很大影响。BS架构和CS架构是最常见的两种模式&#xff0c;了解它们的区别和特点对开发人员和企业决策者都很重要。 CS架构最早出现&#xff0c;当时用户直接从主机获取数据。随着客户端和服务端…

区块链项目孵化与包装设计:从概念到市场的全流程指南

区块链技术的快速发展催生了大量创新项目&#xff0c;但如何将一个区块链项目从概念孵化成市场认可的产品&#xff0c;是许多团队面临的挑战。本文将从孵化策略、包装设计和市场落地三个维度&#xff0c;为你解析区块链项目成功的关键步骤。 一、区块链项目孵化的核心要素 明确…

【React】受控组件和非受控组件

目录 受控组件非受控组件基于ref获取DOM元素1、在标签中使用2、在组件中使用 受控组件 表单元素的状态&#xff08;值&#xff09;由 React 组件的 state 完全控制。组件的 state 保存了表单元素的值&#xff0c;并且每次用户输入时&#xff0c;React 通过事件处理程序来更新 …

C#开发的进销存管理系统软件

#### 介绍 进销存管理系统实现的功能及用途 含模块销售管理、采购管理、存货管理、库存管理、账款管理、用户管理、职员管理七个部分 进销存管理系统实现以下功能&#xff1a; 1. 库存管理 2. 应付账款和应收账款的统计 3. 对订单未结数量和采购单未结数量的统计 4. 权限的管理…