影视聚合站 科技 文章内容

企业FRP安全实践

发布时间:2021-01-07 20:58:45来源:FreeBuf

近期看到一则消息如下:

2020年11月17日上午11:56,某公司发出全员级别的通告。人工智能部AI实验室一名实习生私自将公司内网端口映射到公网,导致不法分子入侵公司服务器,违反《集团员工行为准则》和《员工信息安全规范》有关规定,解除其实习协议,并将相关涉案人员移送司法处理。对此,该公司客服工作人员表示:“针对此情况我们没有收到相关部门的通知。我们会尝试与相关部门沟通,有进展的话会予以答复。”看到的第一反应是无论此事真假,但如果发生在我司,安全部有没有能力去发现?于是,本着守望互助和发散思考的原则研究了一波内网端口映射到公网软件,恰好看到朋友圈已经有人总结了常见列表:

frp,proxychain,lcx,ngrok,regrok,ew,FPipe,Portmap,Termite,socat,natbypass,iox,abptts,Powercat,dnscat,reGeorg,tuna,reDuh,iodine,EarthWorm,sSocks,venom,s5.go本人能力有限,故仅挑选一款倍受喜爱的frp来浅析一二,希望可以抛砖引玉。

文章核心内容如下:

两种常用模式的示例

如何提取特征进行安全检测

绕过安全检测的思路

frp是一个专注于内网穿透的高性能的反向代理应用,支持TCP、UDP、HTTP、HTTPS等多种协议。可以将内网服务以安全、便捷的方式通过具有公网IP节点的中转暴露到公网。

为什么使用frp?通过在具有公网IP的节点上部署frp服务端,可以轻松地将内网服务穿透到公网,同时提供诸多专业的功能特性,这包括:

客户端服务端通信支持TCP、KCP以及Websocket等多种协议。

采用TCP连接流式复用,在单个连接间承载更多请求,节省连接建立时间。

代理组间的负载均衡。

端口复用,多个服务通过同一个服务端端口暴露。

多个原生支持的客户端插件(静态文件查看,HTTP、SOCK5代理等),便于独立使用frp客户端完成某些工作。

高度扩展性的服务端插件系统,方便结合自身需求进行功能扩展。

服务端和客户端UI页面。

1、图示

图示最能一眼直观看懂,奈何翻遍互联网没有找到让我满意的示意图,为清晰表达,故简单画图。

2、配置

frps配置

[common]bind_port=60000log_file=/solo/secsoft/frp_0.34.3_linux_amd64/frps.log

frpc配置

[common]server_addr=115.159.54.230server_port=60000[ssh]type=tcplocal_ip=127.0.0.1local_port=22remote_port=60001

客户端连接

ssh-oPort=60001root@115.159.54.230

3、日志

frps日志,包含:启动、连接成功、退出成功

#以下为启动2020/11/2415:24:37[I][service.go:190]frpstcplistenon0.0.0.0:600002020/11/2415:24:37[I][root.go:215]startfrpssuccess#以下为连接2020/11/2415:31:14[I][service.go:444][545f544dbae98e7d]clientlogininfo:ip[36.112.46.206:58090]version[0.34.3]hostname[]os[linux]arch[amd64]2020/11/2415:31:14[I][tcp.go:63][545f544dbae98e7d][ssh]tcpproxylistenport[60001]2020/11/2415:31:14[I][control.go:446][545f544dbae98e7d]newproxy[ssh]success2020/11/2415:31:18[I][proxy.go:103][545f544dbae98e7d][ssh]getanewworkconnection:[36.112.46.206:58090]2020/11/2415:32:19[I][proxy.go:103][545f544dbae98e7d][ssh]getanewworkconnection:[36.112.46.206:58090]#以下为断开客户端2020/11/2415:46:35[I][control.go:309][545f544dbae98e7d]controlwriterisclosing2020/11/2415:46:35[I][proxy.go:87][545f544dbae98e7d][ssh]proxyclosing2020/11/2415:46:35[I][proxy.go:159][545f544dbae98e7d][ssh]listenerisclosed2020/11/2415:46:35[I][control.go:384][545f544dbae98e7d]clientexitsuccessfrpc日志,只包含启动

[root@localhostfrp_0.34.3_linux_amd64]#./frpc-c./frpc.ini2020/11/2415:24:49[I][service.go:288][545f544dbae98e7d]logintoserversuccess,getrunid[545f544dbae98e7d],serverudpport[0]2020/11/2415:24:49[I][proxy_manager.go:144][545f544dbae98e7d]proxyadded:[ssh]2020/11/2415:24:49[I][control.go:180][545f544dbae98e7d][ssh]startproxysuccess

1、图示

2、配置

frps配置

[common]bind_port=60000log_file=/solo/secsoft/frp_SSH-2.0-OpenSSH_7.5_linux_amd64/frps.log#trace,debug,info,warn,error#这里开启trace模式信息最全面,有助于对照wireshark抓包分析log_level=trace

frpc内网服务器

[common]server_addr=115.159.54.230server_port=60000[secret_ssh]type=stcpsk=hahahahahause_encryption=trueuse_compression=truelocal_ip=127.0.0.1local_port=22

frpc客户端启动服务:

[common]server_addr=115.159.54.230server_port=60000[secret_ssh_visitor]type=stcprole=visitorserver_name=secret_sshsk=hahahahahabind_addr=127.0.0.1bind_port=7000

客户端连接

ssh-oPort=7000root@127.0.0.1

3、日志

这里针对frps开启了trace模式,可以收到全面的信息,对于下文wireshark抓包分析、对照数据包分析指令等有帮助。

frps:

2020/11/2519:30:28[I][service.go:190]frpstcplistenon0.0.0.0:600002020/11/2519:30:28[I][root.go:215]startfrpssuccess2020/11/2519:31:03[T][service.go:391]startcheckTLSconnection...2020/11/2519:31:03[T][service.go:399]successcheckTLSconnection2020/11/2519:31:03[I][service.go:444][bcae207f693a5e10]clientlogininfo:ip[36.112.46.206:40466]version[SSH-2.0-OpenSSH_7.5]hostname[]os[unknow]arch[solosec]2020/11/2519:31:03[I][stcp.go:34][bcae207f693a5e10][secret_ssh]stcpproxycustomlistensuccess2020/11/2519:31:03[I][control.go:446][bcae207f693a5e10]newproxy[secret_ssh]success2020/11/2519:31:03[D][control.go:219][bcae207f693a5e10]newworkconnectionregistered2020/11/2519:31:33[D][control.go:475][bcae207f693a5e10]receiveheartbeat2020/11/2519:32:03[D][control.go:475][bcae207f693a5e10]receiveheartbeat2020/11/2519:32:05[T][service.go:391]startcheckTLSconnection...2020/11/2519:32:05[T][service.go:399]successcheckTLSconnection2020/11/2519:32:05[I][service.go:444][4d8da059a1c4e710]clientlogininfo:ip[106.120.247.188:44408]version[SSH-2.0-OpenSSH_7.5]hostname[]os[unknow]arch[solosec]2020/11/2519:32:05[D][control.go:219][4d8da059a1c4e710]newworkconnectionregistered2020/11/2519:32:33[D][control.go:475][bcae207f693a5e10]receiveheartbeat2020/11/2519:32:35[D][control.go:475][4d8da059a1c4e710]receiveheartbeat

Frpc内网服务器:

2020/11/2519:24:37[I][service.go:290][bcae207f693a5e10]logintoserversuccess,getrunid[bcae207f693a5e10],serverudpport[0]2020/11/2519:24:37[I][proxy_manager.go:144][bcae207f693a5e10]proxyadded:[secret_ssh]2020/11/2519:24:37[I][control.go:180][bcae207f693a5e10][secret_ssh]startproxysuccess

frpc客户端启动服务:

2020/11/2519:32:05[I][service.go:290][4d8da059a1c4e710]logintoserversuccess,getrunid[4d8da059a1c4e710],serverudpport[0]2020/11/2519:32:05[I][visitor_manager.go:86][4d8da059a1c4e710]startvisitorsuccess2020/11/2519:32:05[I][visitor_manager.go:130][4d8da059a1c4e710]visitoradded:[secret_ssh_visitor]

1、特征提取

登陆后查看连接:

w查看有localhost登陆

确立连接中有到frps连接

确立连接中有到127.0.0.1:22的连接

根据端口反查:

[root@localhost~]#lsof-i:55728COMMANDPIDUSERFDTYPEDEVICESIZE/OFFNODENAMEfrpc27490root8uIPv4115183310t0TCPlocalhost:55728->localhost:ssh(ESTABLISHED)sshd30990root3uIPv4114830500t0TCPlocalhost:ssh->localhost:55728(ESTABLISHED)

断开连接后查看:

frpc关闭服务后查看:

综上总结:

程序运行时有127.0.0.1:22的监听和网络连接

会有一定特征进程启动

未编译版本可通过指纹(md5)匹配

2、检测

“君子生非异也,善假于物也”,故这里借助多种产品来协助安全检测。

产品一:

程序运行时刷新资产状态,可以查看到frpc进程:

通过网络连接查看:

selectdatetime,agent_ip,cmd,src_ip,src_port,dst_ip,dst_port,pnamefromqtevent_net_connectwheredatatime>"now-2h"wherepnamelike"frp"

通过进程查看:

selectpidas进程ID,processNameas进程名称,from_unixtime(startTime)asstart_time,startArgs,md5asMD5fromwx.wisteria_assets.linux_processwhereprocessNamelike"frp"orderbystart_timedesc

通过md5检查(这里偷懒就对比了一下值):

产品二:

通过网络连接查看:

__topic__:aegis-log-networkandip:172.23.250.213andfrpc

通过进程查看:

__topic__:aegis-log-processandip:172.23.250.213andfrpc

1、特征提取

这里只贴图,请按照上节内容自我思考。

2、检测

产品一:

通过网络连接查看:

selectdatetime,agent_ip,cmd,src_ip,src_port,dst_ip,dst_port,pnamefromqtevent_net_connectwheredatatime>"now-2h"wherepnamelike"frp"

通过进程查看:

selectpidas进程ID,processNameas进程名称,from_unixtime(startTime)asstart_time,startArgs,md5asMD5fromwx.wisteria_assets.linux_processwhereprocessNamelike"frp"orderbystart_timedesc

产品二:

通过网络连接查看:

__topic__:aegis-log-networkandip:172.23.250.213andfrpc

通过进程查看:

流量分析类安全产品可以根据特征检测。

传统的流量分析安全设备是根据规则来命中事件类型,那么我们来研究一下数据包有什么特征。

抓取了一次从建立连接到ssh登陆的数据包,干干净净的话大概20个。

找到第一个传输flag信息的数据包,右键Follow—TCPStream

先从数据包中分析有用的字段version,hostname,os,arch,user,privilege_key,runid,metas,接下来会从代码萌新层面讨论如何去掉特征。

git拉取源代码分析,gitclone

https://github.com/fatedier/frp

首先定位到pkg/msg/msg.go

typeLoginstruct{Versionstring`json:"version"`Hostnamestring`json:"hostname"`Osstring`json:"os"`Archstring`json:"arch"`Userstring`json:"user"`PrivilegeKeystring`json:"privilege_key"`Timestampint64`json:"timestamp"`RunIDstring`json:"run_id"`Metasmap[string]string`json:"metas"`//Someglobalconfigures.PoolCountint`json:"pool_count"`}typeLoginRespstruct{Versionstring`json:"version"`RunIDstring`json:"run_id"`ServerUDPPortint`json:"server_udp_port"`Errorstring`json:"error"`}

先拿version字段为例,第二三段均为frps端收到连接时打log用的,所以不用考虑。

我们打开第一段会定位到client/service.go,再次跟进到pkg/util/version/version.go,代码如下

varversionstring="0.34.3"funcFull()string{returnversion}

我这里修改版本号”0.34.3”为”SSH-2.0-OpenSSH_7.4”(一时脑洞,想着会不会被识别为ssh服务,哈哈)

这里还有个小插曲,就是修改了version后,建立连接时返回

"Pleaseupgradeyourfrpcversiontoatleast0.18.0"翻了一下代码定位到pkg/util/version/version.go中有如下:

funcCompat(clientstring)(okbool,msgstring){ifLessThan(client,"0.18.0"){returnfalse,"Pleaseupgradeyourfrpcversiontoatleast0.18.0"}returntrue,""}funcLessThan(clientstring,serverstring)bool{vc:=Proto(client)vs:=Proto(server)ifvc>vs{returnfalse}elseifvcvs{returnfalse}elseifvcvs{returnfalse}elseifvc

LessThan函数中会去做一堆版本号比对,检测什么呀,直接把Compat返回值returntrue,或者把LessThan返回值returnfalse都可以解决。

2020/11/2510:07:45[I][service.go:444][fd5d9a94278e35b7]clientlogininfo:ip[106.120.247.188:8103]version[0.34.3]hostname[]os[darwin]arch[amd64]

害,发现好多参数都是发了个寂寞~

既然如此,那么就顺便修改一下os和arch,用了个很low很简单方法

varnewArchstring="solosec"varnewOsstring="unknow"loginMsg:=&msg.Login{//Arch:runtime.GOARCH,Arch:newArch,//Os:runtime.GOOS,Os:newOs,PoolCount:svr.cfg.PoolCount,User:svr.cfg.User,Version:version.Full(),Timestamp:time.Now().Unix(),RunID:svr.runID,Metas:svr.cfg.Metas,}

好了,编译打包。frp里面集成了打包shell,去到对应目录下执行package.sh,结果如下:

(new-env)➜frpgit:(dev)✗./package.shgofmt./...envCGO_ENABLED=0gobuild-ldflags"-s-w"-obin/frps./cmd/frpsenvCGO_ENABLED=0gobuild-ldflags"-s-w"-obin/frpc./cmd/frpcbuildversion:SSH-2.0-OpenSSH_7.4envCGO_ENABLED=0GOOS=darwinGOARCH=amd64gobuild-ldflags"-s-w"-o./release/frpc_darwin_amd64./cmd/frpcenvCGO_ENABLED=0GOOS=darwinGOARCH=amd64gobuild-ldflags"-s-w"-o./release/frps_darwin_amd64./cmd/frpsenvCGO_ENABLED=0GOOS=freebsdGOARCH=386gobuild-ldflags"-s-w"-o./release/frpc_freebsd_386./cmd/frpcenvCGO_ENABLED=0GOOS=freebsdGOARCH=386gobuild-ldflags"-s-w"-o./release/frps_freebsd_386./cmd/frpsenvCGO_ENABLED=0GOOS=freebsdGOARCH=amd64gobuild-ldflags"-s-w"-o./release/frpc_freebsd_amd64./cmd/frpcenvCGO_ENABLED=0GOOS=freebsdGOARCH=amd64gobuild-ldflags"-s-w"-o./release/frps_freebsd_amd64./cmd/frpsenvCGO_ENABLED=0GOOS=linuxGOARCH=386gobuild-ldflags"-s-w"-o./release/frpc_linux_386./cmd/frpcenvCGO_ENABLED=0GOOS=linuxGOARCH=386gobuild-ldflags"-s-w"-o./release/frps_linux_386./cmd/frpsenvCGO_ENABLED=0GOOS=linuxGOARCH=amd64gobuild-ldflags"-s-w"-o./release/frpc_linux_amd64./cmd/frpcenvCGO_ENABLED=0GOOS=linuxGOARCH=amd64gobuild-ldflags"-s-w"-o./release/frps_linux_amd64./cmd/frpsenvCGO_ENABLED=0GOOS=linuxGOARCH=armgobuild-ldflags"-s-w"-o./release/frpc_linux_arm./cmd/frpcenvCGO_ENABLED=0GOOS=linuxGOARCH=armgobuild-ldflags"-s-w"-o./release/frps_linux_arm./cmd/frpsenvCGO_ENABLED=0GOOS=linuxGOARCH=arm64gobuild-ldflags"-s-w"-o./release/frpc_linux_arm64./cmd/frpcenvCGO_ENABLED=0GOOS=linuxGOARCH=arm64gobuild-ldflags"-s-w"-o./release/frps_linux_arm64./cmd/frpsenvCGO_ENABLED=0GOOS=windowsGOARCH=386gobuild-ldflags"-s-w"-o./release/frpc_windows_386.exe./cmd/frpcenvCGO_ENABLED=0GOOS=windowsGOARCH=386gobuild-ldflags"-s-w"-o./release/frps_windows_386.exe./cmd/frpsenvCGO_ENABLED=0GOOS=windowsGOARCH=amd64gobuild-ldflags"-s-w"-o./release/frpc_windows_amd64.exe./cmd/frpcenvCGO_ENABLED=0GOOS=windowsGOARCH=amd64gobuild-ldflags"-s-w"-o./release/frps_windows_amd64.exe./cmd/frpsenvCGO_ENABLED=0GOOS=linuxGOARCH=mips64gobuild-ldflags"-s-w"-o./release/frpc_linux_mips64./cmd/frpcenvCGO_ENABLED=0GOOS=linuxGOARCH=mips64gobuild-ldflags"-s-w"-o./release/frps_linux_mips64./cmd/frpsenvCGO_ENABLED=0GOOS=linuxGOARCH=mips64legobuild-ldflags"-s-w"-o./release/frpc_linux_mips64le./cmd/frpcenvCGO_ENABLED=0GOOS=linuxGOARCH=mips64legobuild-ldflags"-s-w"-o./release/frps_linux_mips64le./cmd/frpsenvCGO_ENABLED=0GOOS=linuxGOARCH=mipsGOMIPS=softfloatgobuild-ldflags"-s-w"-o./release/frpc_linux_mips./cmd/frpcenvCGO_ENABLED=0GOOS=linuxGOARCH=mipsGOMIPS=softfloatgobuild-ldflags"-s-w"-o./release/frps_linux_mips./cmd/frpsenvCGO_ENABLED=0GOOS=linuxGOARCH=mipsleGOMIPS=softfloatgobuild-ldflags"-s-w"-o./release/frpc_linux_mipsle./cmd/frpcenvCGO_ENABLED=0GOOS=linuxGOARCH=mipsleGOMIPS=softfloatgobuild-ldflags"-s-w"-o./release/frps_linux_mipsle./cmd/frps/Users/solo/PycharmProjects/frp/release/packages(new-env)➜frpgit:(dev)✗

好啦,这下子可以舒舒服服的绕过安全检测了吧

一边测试一边抓包看是否能绕过安全检测~

果不其然,还是检测出来了,打脸了

不哭不哭,我们来继续找原因,先看一下数据包

仔细翻了翻找到了刚刚没有看到的说明文档doc/server_plugin_zh.md,里面描述了用户登陆操作信息、创建代理的相关信息、心跳相关信息、新增frpc连接相关信息等数据字段格式,结合上图,突然有个扎眼的字段“privilege_key”,此时想了一下如果单独从前面几个字段的key去做特征误报率太高了,如果我设计就找几个特殊的key和几个特殊的value。心中定了想法,就去看一下这个key。

这里重点是第一个文件pkg/msg/msg.go,第二三个均是doc里面的内容,不用管。这里看了一下PrivilegeKey在pkg/auth/token.go和pkg/auth/oidc.go都有关联,但是字段名却只有这个文件包含了3次,全局替换一波字段名,老样子编译打包一波。

随手一测~(不要问,需要结果的同学自己动手试一下就知道了)

做一些发散思考和思路,时间原因,并未全部尝试;攻防本是对抗过程,文中方法可能随时失效,授人鱼不如授人以渔。

上文中绕过是在建立连接时用户发送登录信息给服务端时,那么如果针对此检测可以有两个思路:

1、提取登录信息的新特征,防止正则匹配绕过

2、对前置或后置阶段特征提取,如发送RPC请求建立连接数据包、建立连接后的心跳数据包(最近读安全客有感,可参照引用和致谢[2])

NetFilter可以针对Linux包过滤、网络地址转换(NAT)和基于协议类型的连接跟踪,可以做多个HOOK点,我们可以抽象流量转发模型有以下特征:

数据包内容相同,但五元组不同

数据包间隔时间短

会在127.0.0.1上面做些文章

以上特征用代码实现用作入侵检测取证时很棒,有魄力的也可以做成服务检测报送式,用salt或ansible推送到服务器检测。

设想,每次得手后都会在服务器留一个明文的.ini文件,应急响应和溯源时岂不是白白送上思路?

以上已有师傅实现,可参照引用和致谢[3]

frpc很多人会在BYOD终端启动,个人的测试中几款pc端的agent都无法从服务端采集到终端的frp软件信息和事件,故在没有更好的方法之前用一些手段去补偿控制,当然这些手段也需要不断的建设和运营,在阶段初期误报率和复杂度估计会很高。

服务器只允许堡垒机IP登录,其它ip做限制或登录告警(针对使用frp映射非ssh服务无法奏效)

抓取出互联网流量中心跳特征包(很多Saas服务会有心跳包,会产生大量误报,这个需要运营,如果流量分析产品可以实现则更佳)

零信任解决方案,基于账号、角色、权限、访问对象,结合微隔离、SDP,极端一些可以达到MAC(强制访问控制)模型

感谢引用中各位大佬和团队的分享精神!frp是一个免费开源且在开发中的软件,我相信它会越来越好;各安全检测方法和测试手法也仅限于本人研究过的产品和特定版本,不能以偏概全。如果文章中有任何描述不正确或引用不当的地方,辛苦大佬们指正。

也欢迎各位安全同僚拍砖、交流和技术分享!微信:solosec或freebuf留言交流~

引用来源:

[1]https://gofrp.org/docs/frp官方文档

[2]入侵检测系列1(上):基于私有协议的加密流量分析思路(Teamviewer篇)

[3]https://uknowsec.cn/posts/notes/FRP%E6%94%B9%E9%80%A0%E8%AE%A1%E5%88%92.htmlFRP改造计划

精彩推荐