mirror of
https://github.com/fatedier/frp.git
synced 2026-05-15 16:15:49 -06:00
[GH-ISSUE #1236] 服务端可以添加类似md5这种签名保护 frpc.ini中的配置项目不被篡改么 #975
Labels
No labels
In Progress
WIP
WaitingForInfo
bug
doc
duplicate
easy
enhancement
future
help wanted
invalid
lifecycle/stale
need-issue-template
need-usage-help
no plan
proposal
pull-request
question
todo
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: github-starred/frp#975
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @shoaly on GitHub (May 7, 2019).
Original GitHub issue: https://github.com/fatedier/frp/issues/1236
Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly.
(为了节约时间,提高处理问题的效率,不按照格式填写的 issue 将会直接关闭。)
Use the commands below to provide key information from your environment:
You do NOT have to include this information if this is a FEATURE REQUEST
What version of frp are you using (./frpc -v or ./frps -v)?
What operating system and processor architecture are you using (
go env)?Configures you used:
Steps to reproduce the issue:
1.
2.
3.
Describe the results you received:
Describe the results you expected:
frps.ini
[common]
bind_port = 7000
kcp_bind_port = 7000
token = 12345
md5_key = slkdjflkasdf # 这里是服务端配置的 md5 验签key
frpc.ini
[common]
....
[ssh]
sign=sign_md5(所有frpc的关键配置连接字符串+md5_key) # 通过预设签名保证 整个frpc.ini 中[ssh]的配置不被篡改
Additional information you deem important (e.g. issue happens only occasionally):
Can you point out what caused this issue (optional)
@shoaly commented on GitHub (May 7, 2019):
一直感觉 frp 客户端和 服务端的连接的机制上, "太过于信任" frpc.ini的配置
@fatedier commented on GitHub (May 7, 2019):
配置避免被修改,可以在客户端去做,比如你自己把 frpc.ini 文件加密。
服务端和客户端之间通过 token 做鉴权,已经足够,如果有人能随意修改你的文件,那么无论你做什么,其实意义都不大。
@shoaly commented on GitHub (May 8, 2019):
可能是我没表述清楚, 重新表述一次呢, 所有的frpc.ini都是 服务端下发的, 但是 frpc.ini现在是明文的所有有篡改的风险. 我把服务器端下发的配置:
[rdp100030]
type = tcp
local_ip = 127.0.0.1
local_port = 3389
remote_port = 13322
抽象成这个字符串: "rdp100030:local_ip=127.0.0.1&local_port=3389&remote_port=13322&type=tcp" , 如何保证他不被修改呢, 就是服务器端下发这个字符串之前 多加一个参数, 做md5签名, 追加到下发的配置当中:
md5("rdp100030:local_ip=127.0.0.1&local_port=3389&remote_port=13322&type=tcp&key=123456")
也就是等于: 3e575805c534dc81417f2b3d4eb5f03a
即是: rdp100030:local_ip=127.0.0.1&local_port=3389&remote_port=13322&type=tcp&md5_sign=3e575805c534dc81417f2b3d4eb5f03a
即是:
[rdp100030]
type = tcp
local_ip = 127.0.0.1
local_port = 3389
remote_port = 13322
md5_sign=3e575805c534dc81417f2b3d4eb5f03a
通过这个md5_sign, 如果客户端擅自把remote_port=13223, 因为他不知道服务端的key=123456, 所以无法算出正确的md5签名, 这样就保证了服务端同一个服务器给多个内网服务使用时 他们随意占用别人端口的问题, 为什么不全文加密, 因为加密了, 客户端就看不到真实的端口配置了呀....
@fatedier commented on GitHub (May 8, 2019):
你的这个思路和需求我理解了,只是这个实现方式看起来不是很优雅,需要再看看有没有更合适的实现方式。
@shoaly commented on GitHub (May 8, 2019):
这个实现方案是支付接口使用非常频繁的手段, 支付接口里面的金额, 或者敏感信息也是需要做签名去防止修改的, 所以很自然就想到 可以用一样的思路来签名 frpc.ini
参照微信支付签名的方式, 支付那边用的很频繁了, 只是还多做了一个按照键名排序, 这样可以保证字符串拼接的顺序
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
@lenghun commented on GitHub (May 9, 2019):
签名太麻烦了,建议做成服务端可以配置多个token,或者加个客户端id,针对每个客户端设置可用的端口与协议
@shoaly commented on GitHub (May 9, 2019):
看我上面的描述, 客户端配置 和服务端配置 都只添加一行即可, 签名机制并不会让正常使用变得麻烦
@almas-x commented on GitHub (Jun 7, 2019):
我觉得这个可以在admin dashboard增加一个注册通道的功能,就是把客户端配置的信息【就是题主所说的rdp100030:local_ip=127.0.0.1&local_port=3389&remote_port=13322&type=tcp】填写保存并生成对应的token,客户端配置中附带服务端生成的token,连接时进行验证
@ytpos commented on GitHub (Jul 15, 2019):
这个功能很实用,希望加上
@fatedier commented on GitHub (Aug 8, 2019):
提案
增加 config 文件签名机制,确保 frpc 端配置文件只能由 frps 端生成。
frps 改动
frps.ini 增加参数
config_sign_key,如果设置,会要求 frpc 客户端连接时必须使用配置文件启动,且同样会上报frpc.ini文件的 md5 值,以及 frps 端主动给出的签名。frps 新增命令
frps sign ./{frpc_file_path}给指定的 frpc 配置文件签名,会根据config_sign_key+ 文件 MD5 值做签名,生成一个 sign 值并写入指定的frpc.ini文件中。frpc 改动
将此文件下发给 frpc,通过此签名过的配置文件启动,会获取文件内容 MD5 以及 sign 值在登录时传给 frps。frps 校验通过,逻辑和之前一致。如果文件
frpc.ini一旦被修改,MD5 值会变化,对应 sign 不匹配,则无法通过校验。@shoaly commented on GitHub (Aug 8, 2019):
收到!
@fatedier commented on GitHub (Aug 8, 2019):
此 issue 用于追踪开发进度,完成后再关闭。
@lenghun commented on GitHub (Aug 8, 2019):
签名逻辑是不是错了,文件md5生成签名写入文件,文件不是被修改了么,MD5就已经对不上了,感觉要这么做要么只能将sign记录到frps.ini 并且要能记录多个,每个对应一个配置文件,这样的话记录文件的MD5不就够了么,不需要另外签名,使用文件的MD5做签名会导致必须先将本地需要用的ip端口先提交给服务端,涉及隐私并且每次修改本地设置都要重新提交,既然必须配置记录多个还不如用我之前说的方案,每客户端配置一个token,在服务的配置每一个token可用的远程端口,协议,域名
@ysc3839 commented on GitHub (Aug 8, 2019):
建议改用 SHA1 或者 SHA256,MD5 已经过时了。
@fatedier commented on GitHub (Aug 9, 2019):
@lenghun 签名参数是已知的,所以可以去掉,不影响之前的文件内容 MD5 值的计算,你可能没有理解我的意思。只记录文件 MD5 ,不签名,则文件内容无法保证不被修改。这里的需求是,服务端给出的配置,要求不能被变更,而不是让客户端随意指定配置,不是一个场景。
@ysc3839 MD5 或者其他都可以,综合考虑。
@recolic commented on GitHub (Aug 9, 2019):
不建议这么做。因为这是一个开源项目,客户端想要修改配置,他可以直接修改代码,我认为这种hash校验没能增加安全性,反而增加了复杂程度。
值得警惕的是,服务端设置者如果真的依赖这个被误认为是安全的机制,反而会露出安全漏洞。
对于一部分能够保护的参数,它的签名保护应该由一个单独的frp分支,用更严谨的密码学方法实现。frp是,也应该是一个单纯的网络工具。
@shoaly commented on GitHub (Aug 9, 2019):
@recolic
这里的初衷是客户端和服务端 并不是同一人, 服务端可以将frp的服务提供给多个客户端, 但是要防止客户端擅自改动配置, 并不是因为开源项目, 客户端就可以随便修改配置了啊
@fatedier commented on GitHub (Aug 9, 2019):
@recolic 你可能没有理解这里的签名逻辑,客户端想要连接上服务端,需要提供一些参数信息,比如你想要绑定在某一个端口,这些信息加上一个 sk 做一个签名,在服务端是可以做验证的。即使你修改代码,在不知道 sk 的情况下,也没办法自己正确签名。比如你想要绑定另外一个端口,就需要使用 sk 来重新签名。
这个 feature 是一个可选功能,默认关闭。目前有部分公司在使用时确实有需要这个功能的场景,比如边缘机房的节点,既要让对方能够连接到服务器使用,也需要限制对方节点,不能随意指定绑定的端口。
后续可能会针对一些对安全性要求较高的场景继续做优化。
@shoaly commented on GitHub (Aug 9, 2019):
@ysc3839
安全性上, 其实md5已经足够了, 因为要碰撞出一个修改的客户端配置, 还能通过服务端md5校验的, 不可能的事情. 并不是md5过时了就完全不能用在任何地方了
@shoaly commented on GitHub (Aug 9, 2019):
只是这个地方用配置文件的md5+服务端密钥一起做md5 确实有一点点不够稳妥, 主要是那个客户端配置文件太严格了, 不知道会不会因为 客户端无意多了一个空格, 或者windows linux 那种文本BOM切换之后 md5会不会改变, 之前提意用 键值对来拼接的话, 对客户端配置文件的内容要求要更松一些
@fatedier commented on GitHub (Aug 9, 2019):
@shoaly 主要是考虑易用性方面,如果每一个代理一个 sign ,是不是比较繁琐。还有如何简单的进行签名,这方面有什么建议?
@ytpos commented on GitHub (Aug 9, 2019):
[web02]
type = https
remote_port = 6004
subdomain = web01
local_ip = 127.0.0.1
local_port = 8000
sign=关键数据签名
每个节点一个签名,如 web02&type=https&remote_port=6004&subdomain=web01
就把这几个关键点签名就好了,其它的可以自己改
@recolic commented on GitHub (Aug 9, 2019):
理解了。这不是对配置文件frpc.ini进行签名,而是对部分参数,或者说在更多的情形下,是为每一个hostname:port提供一个密钥。
@shoaly commented on GitHub (Aug 9, 2019):
还是每一个 客户端配置 一个签名呀, 但是签名的时候 就麻烦循环一下 客户端配置里面所有的键值对, 然后键按照a-z排序, 首尾拼接成一个大的字符串, 然后做md5就可以了, 这样客户端配置, 多几个空格, 也不影响键值对, 甚至 客户端那边键值对配置 更换顺序也不影响签名的
@fatedier commented on GitHub (Aug 9, 2019):
@shoaly 还是比较麻烦的,因为登录和创建 proxy 是两个步骤。登录成功后是可以增加 proxy 的,如果只对初始文件签名通过了登录认证,则后续操作都是受信的。
如果对每一个 proxy 签名,则过于繁琐。
还是需要思考一下具体的实现方式。
@lenghun commented on GitHub (Aug 9, 2019):
只是为了限制客户端使用的端口,在服务端配置每个客户可用端口,只需要在服务端进行简单的匹配就行了,搞什么签名需要客户端服务的一起配合签名,使用时还得重新签名比较,感觉你们把问题想复杂了
@fatedier commented on GitHub (Aug 10, 2019):
@lenghun 不一定只是端口,签名的意图就是无论什么参数,都可以加入校验,不需要针对任何参数去特意增加配置,否则可能服务端就需要配置一堆参数来做这件事。
每个人使用的场景可能不一样,这个场景本来就是属于牺牲简单性,增加安全性,所以应该是一个可选方案,默认不开启。
@Hurricanezwf commented on GitHub (Aug 11, 2019):
个人觉得这样实现的复杂度会比较高,毕竟需要去识别每个类型中的关键字段;而且会增加后续的维护成本,比如某种代理类型新增了一个关键字段,或是新增了一种代理类型,那也必须去同步修改签名这块。
我的想法是,如果要对每个代理单独签名的话,个人建议所有代理的签名方式一致(与代理种类&配置细节解耦),可对所有非空字段拼接后签名。另外,可在frpc和frps命令行中新增一个签名的子命令,自动批量地对配置中的所有代理签名并写回配置,用以简化操作。
@deadlineOvO commented on GitHub (Aug 12, 2019):
我个人有一个建议,假设客户端不需要知道配置文件信息的话,是否可以有类似只指定服务端以及目标客户端配置文件的 profile_token 或者类似的东西?
下面是个简单的例子
服务端:
[client1_ssh]
type=
remote_port=
local_ip=127.0.0.1
local_port=22
profile_token=123456
[client1_web]
type=
remote_port=
local_ip=127.0.0.1
local_port=80
profilr_token=123456
客户端:
[common]
server_addr=
server_port=
token=none
profile_token=123456
log_level=info
tcp_mux=true
在客户端与服务端分别有这两个配置文件后,客户端不用增加任何条目就可以使用 ssh 与 web 了
不过在使用 profile_token 的时候不允许客户端定义任何 commom 之外的东西了
顺便说句闲话,为啥 server 是 addr 客户端则是 ip
算是历史遗留问题吗?
@fatedier commented on GitHub (Aug 12, 2019):
@funnypro 你这个示例就是很早以前的用法,但是不管是代码编写,还是实际使用上来说,都麻烦了很多,一份相关的配置在两个地方管理会带来比较大的复杂性,所以后来被废弃了。
@deadlineOvO commented on GitHub (Aug 12, 2019):
两个地方管理指的是?
@fatedier commented on GitHub (Aug 12, 2019):
@funnypro 换一个说法,这一份配置,客户端和服务端其实关心的是不同的部分。如果这么修改的话,需要完全修改整个交互的协议,把部分配置交给服务端来维护,而和客户端相关的部分需要下发给客户端。
这样,和现有的逻辑相结合的话,部分代理配置可能会存在于客户端或者服务端,这部分逻辑会相对复杂。当然,如果只提供这一种实现方式的话,也就没有这个问题了。
回到这个 issue 上来,目的是在不改变现有架构的基础上增加这个 feature。如果是需要大幅改动,增加复杂度的话,就不考虑了。
@ytpos commented on GitHub (Aug 12, 2019):
服务端要限制客户端的,也就是服务端的端口remote_port,subdomain,type,节点名称[web01],把这几项签名就差不多了
@ytpos commented on GitHub (Aug 12, 2019):
@deadlineOvO commented on GitHub (Aug 12, 2019):
也就是说代理端口这个问题假设服务端也要关心的话会很复杂?
@kasuganosoras commented on GitHub (Aug 21, 2019):
其实我很早就关注到了这个问题,关于如何限制用户的隧道是我一直在研究的事情。
各位都在想着如何如何加密配置文件、签名来防止用户更改自己的映射,那么为什么不换个思路,让服务端来进行验证呢?
正好我今天完成了 Frp 服务端改造工程,简单说一下我的实现原理:
|隔开,Frps 收到 User 信息后,将它提交给 API 进行验证,判断用户名和密码是否正确。ProxyType、ProxyName、CustomDomains、RemotePort等字段,因为考虑到用户可能需要进行配置调整,我没有限制 LocalService 部分的配置修改。NewProxy(ctl, pxyConf)等部分的代码。验证隧道是否存在的具体实现在
/server/control.go文件的RegisterProxy方法中,自行实现一个 CheckProxy 方法对隧道进行验证即可。验证用户名和密码的具体实现在
/server/service.go文件的RegisterControl方法中,具体要求怎么实现可以自己做,加密,散列,GET 还是 POST 都可以。感兴趣各位可以来测试下我做的:Sakura Frp,基于 Frp 0.17.0 版本二次开发。
@fatedier commented on GitHub (Aug 21, 2019):
@kasuganosoras 这是一种思路,但是添加复杂的管理能力不在目前的项目维护范围内。
近期也有一些类似的需求,例如 #1378 ,结合起来,考虑在之后的版本中构思一种类似的 plugin 机制。将部分逻辑判断和参数校验与修改的能力通过扩展的方式开放(RPC 或其他方式),比如你说的
RegisterProxy和RegisterControl等入口处。这样,项目本身还是维持核心功能的开发,通过搭配不同开发者提供的 plugin,可以实现各自的特殊需求。对于一些企业用户,也可以自行通过开发对接自己的鉴权系统等等,且不会影响到核心能力的迭代。
@shoaly commented on GitHub (Aug 21, 2019):
其实是这样的, 签名还是用服务端 将客户端配置读入进去结合key 做签名, 这种只是用来做示意的,或者单个配置签名的, 真正的场景比如要嵌入自己的系统的话, 都是重新实现一遍 服务端签名的算法, 然后通过网页或者后端管理的方式下发这个签名之后的客户端配置....
另外开启plugin 确实是一个好思路, gpc, 或者 restful的都可以, grpc 好像我印象中可以加一个插件就能同时导出一份restful的了 . 这样frp的定制性就强很多了...
@shoaly commented on GitHub (Aug 21, 2019):
这个思路可能 不适合官方自己去做, 这样会大大增加frp 和 php或者某一个实际业务的强相关...
frp我感觉官方定位还是专注于打洞, frp是我测试过性能最好的, 其他几个项目我都试过, 也是go写的 不知道为什么就是要弱一些, 无奈不太懂go语言, 所以无法从代码上去看原理了
@kasuganosoras commented on GitHub (Aug 21, 2019):
不一定要 PHP,你也可以使用其他语言,Frps 做的事情只是把你的隧道信息通过 Http 请求发送到了另一个 API 去处理,判断它是否是合法的,然后返回一个结果给 Frps,Frps 根据返回值决定是否启动这个隧道,目前来看这是最合理的方式了。
我这里提供了一个 API:https://github.com/ZeroDream-CN/simple-frp-api
另外我已经改好了 Frps 的代码,等会提 Pr
@fatedier commented on GitHub (Aug 21, 2019):
@kasuganosoras 暂时先不要提 PR,Plugin 的机制还需要先详细设计一下,具体的 API 规范,交互逻辑等等需要明确。
@kasuganosoras commented on GitHub (Aug 21, 2019):
刚点提交(
直接在 Pr 里讨论下 API 规范吧
(不过我感觉这个简单的功能直接用 GET 应该没啥问题了)
@kasuganosoras commented on GitHub (Aug 21, 2019):
另外的话我只是简单把这个功能实现了一下,可能还有些地方存在问题,还请dalao完善一下 😏😅
@fatedier commented on GitHub (Aug 21, 2019):
@kasuganosoras 这里会有很多应用场景,期望是通过一种机制来进行扩展,避免为了某一个功能或某一个需求而进行设计开发。比如在 #1378 中提到的,希望将随机分配端口尽可能持久化。
通常是先写好文档,描述清楚需求和场景,再进行设计,定义接口,最后才是编码开发。这个过程中可能会有很多需要讨论的地方,避免代码和预期有较大的差距。
列几点我的考虑:
还有一些细节没有列出来,这个 feature 还是需要一些完善的设计。
另外,近期会有一些代码结构的大的调整,会影响到大的 feature 的合并,所以开发工作会放在调整完成之后。
会另外开一个 issue #1403 来追踪这个问题。之后会将我的设计草案也放到那里。期待设计完成后你能继续参与开发。
@kasuganosoras commented on GitHub (Aug 21, 2019):
emmm...插件的话我不太了解,但是可以尝试下(虽然我学 Go 才一个月的时间
我还是先去研究下 GRPC 吧
@kasuganosoras commented on GitHub (Aug 21, 2019):
其实这个 API 也是我目前线上环境在用的,之前跟我朋友策划了很久,制定过很多种方案,甚至想过用 WebSocket 长连接之类的,结果越搞越复杂,最后还是选择了这种最简单的方式,HTTP GET
另外就是,有可能需要考虑大量连接的情况下的性能问题,例如用户一下子启动了上百个隧道,这时候对于 API 来说就跟 CC 攻击一样,如果 API 那边处理速度不够快的话可能会导致连接阻塞
@fatedier commented on GitHub (Aug 21, 2019):
@kasuganosoras 插件只是一种机制,RPC 是一种具体实现。
关于 Server Plugin 的设计,可以转到 #1403 讨论。
@wangrui027 commented on GitHub (Feb 2, 2021):
参照nps
@shukecho commented on GitHub (Jul 3, 2022):
可不可以这样:服务端单开(server plugin也可以)一个维护端口(或者端口复用)和维护服务;客户有一个类似守护进程(可单开或者frpc plugin实现),完成3个任务:1,对frpc的可执行文件和conf的唯一值计算和服务端存储校验,2,类似引导程序,客户填入管理员给的唯一值(token也可以),从服务器拉取frpc的可执行文件和配置文件,3,类似维护通道,当服务端对客户的配置conf更新,完成OTA,客户修改配置,输出提示联系管理员的提示信息。
简化下就是,frpc+plugin启动,用引导(token)拉取配置,frpc 用配置文件重启,frpc+plugin完成自我维护。
@kasuganosoras commented on GitHub (Jul 5, 2022):
没有必要,也不需要这样做,直接修改 Frps 服务端是最简单稳妥的选择,一切的鉴权都应该在服务端进行,而不是把主动权交给客户端。目前我已经有一套完善的解决方案,不过适配的 Frp 版本比较老(0.29.0),如有必要可以自行移植相关功能到新版本 Frp。当然如果作者原意增加原生的管理接口以及 API 的话那就最好了。
管理面板:https://github.com/ZeroDream-CN/SakuraPanel
改版 Frps:https://github.com/ZeroDream-CN/SakuraFrp
@Guation commented on GitHub (Dec 29, 2022):
有没有可能让服务端能够已知客户端行为
比如使用以下配置
frps:
frpc:
当客户端使用想要使用ssh这个标签将任意端口通过tcp转发到远端的6000端口上是被允许的 但是使用错误的标签比如ssh2或者使用了错误的协议比如udp或者使用了错误的远端端口比如6001那么这个转发将被拒绝 也可也从一定程度避免滥用 甚至服务端可以把local_ip和local_port配置也加上 这样只要客户端修改了任何参数都会导致无法连接
@fatedier commented on GitHub (Jun 29, 2023):
目前可以通过 Server Manager Plugin 机制对客户端的行为和配置进行控制,这个 issue 不再继续跟进。