Aggregator
局域网监控软件WFilter ICF 鸡肋0day RCE漏洞挖掘
0x00关于本文逛freebuf的时候看到了这篇文章,关于PDD员工发帖溯源联想到的相关技术与实现,其中提到了一个叫做wfilter的局域网监控软件。这个软件官网提供了下载链接,支持10天实用,于是我就下下来研究一番。
作为一个搞安全的人,我最感兴趣的是这个软件本身是否存在缺陷,毕竟这类用于监控的安全软件自身出现漏洞是最有戏剧性的事情了
0x01不同寻常Web管理Wfilter通过串联/旁路的方式进行部署,同时提供一个Web管理接口供管理员进行远程管理。而作为一个Web狗,我自然把重心放在了这个Web管理接口上了。这个Web管理接口不同寻常的第一点就是登录之后不会返回任何Cookies,而且请求接口的时候也不会带Cookies或者任何特殊的Headers
那它是怎么进行鉴别的呢?测试了一番之后发现居然是根据IP地址来鉴别的,一旦管理者登录,wfilter就会记录下管理者的IP地址,然后允许其访问这些接口。这么诡异的鉴别方式大概率搭配了一个诡异的后端实现,结果我用火绒剑一看,发现后台是通过CGI运行的。也就是说想要审计这个软件还得去逆向这个EXE!算了,逆向就逆向吧,正好试试IDA7.5效果怎么样
0x01对于cgiproj.exe的逆向分析
这个Web接口的特点是通过/fcgi?cmd=【功能名】来调用,因此在IDA里面搜索相关的字符串就可以找到实现通过功能名来执行不同功能的函数了。
这个函数非常的大,以至于我需要修改IDA的hexrays支持的最大函数大小来让IDA能够成功反编译这个函数(Web狗实在是不想啃汇编了)。经过一番猜测和实验,我发现这个调用sub_BF65E0中的参数包含了功能名字,而v266[1]中存放的则是其对应的函数。通过这些信息我可以查看具体接口是如何实现。
0x02 思路1:找到无需鉴权的功能,直接拿下服务器!实现这个思路,简单来说就两个步骤,第一个是找到无需鉴权的功能,第二个是逆向分析然后找到有漏洞功能然后利用。为了找到无需鉴权就能使用的功能,简单起见,我搜集了那个大函数中的所有的字符串,然后拿到接口中去fuzz,接着发现viewHelp,helpSearch,login,mobileCode,mlogin,logout,remoteLogin,viewToolTip,ExecuteNoLogin,forgetPassword,resetPassword,viewHTML这些功能对应的接口无需登录。但是很快发现他们都无助于入侵。比如mobileCode功能虽然调用了system()但是所有的参数都是写死的,要么写死在程序里面,要么从配置文件里面读取
而viewHTML功能虽然可以玩路径穿越读取文件,但是后缀写死了htm,我尝试了00截断和Windows路径长度截断来绕过但是均未成功ExecuteNoLogin功能看名字很诱人,看样子似乎还可以直接给一个IP让它信任,但是我无论如何调用都显示错误,也没法直接RCE了
而其余的功能更是没法利用,因此目前看下来这条路就堵死了。
0x02 思路2:利用XSS/CSRF配合后台RCE ---XSS挖掘实现一键日天看起来是不现实了,但是XSS/CSRF配合后台RCE并不是没有可能的。首先我需要找一个靠谱的XSS漏洞。很快我就发现一个似乎可以利用的点,在“网页浏览”记录查看中,一旦请求带了User Agent,系统就会把User Agent放在前端
为了验证我这个猜测,我在安装了wfilter的机器上再装了个python requests库,以便于调试(简单起见,我并没有用串联/并联的方式将其接入我的网络),接着利用requests库构造一个包含XSS Payload的User-Agent,就像这样
接着再去看历史
0x03 思路2:利用XSS/CSRF配合后台RCE ---后台RCE挖掘
最开始我想的是直接搜system函数,查看引用,寻觅一波命令注入,但是并没有明显容易注入的点(主要是因为反编译出来的函数太大,而且一些不太清晰的反编译让我无法理清逻辑)。因此我采取另一种办法,即查看web界面有哪些有趣的功能,然后针对性地反编译。在翻找了一阵子后,我发现了这个系统包含了利用插件扩展的功能。
执行插件的时候抓个包,我发现了一个叫做runDll的功能,这不明显的让我为所欲为?
到IDA上一看,发现这个runDll的逻辑是根据接收到的dll参数加载dll
接着再去获取的proc参数作为函数名,通过GetProcAddress获取函数地址。最后再去把获取的para参数和另一个不知道干什么的v36传入到刚刚获取的函数中分析透了这个runDll之后,我们可以发现这里有执行shell的可能性。首先这个dll参数并没有任何过滤,这意味着我们可以穿越到任意目录中去调用任意位置的DLL,因此Windows的各种API我们都可以想办法去调用了。接着我需要找一个API函数,它接收两个参数,第一个参数是命令,第二个参数随便什么都可以,尽管看起来要满足这个条件相当的苛刻,但是真的有一个函数可以做到,那就是WinExec
而根据微软的描述,这个函数在Kernel32.dll中
于是构造如下Payload打过去
函数成功执行,但是并没有弹出计算器,不过这个执行的过程已经被火绒剑记录了下来,这证明我们成功地执行了shell
至此,整个利用链已经清晰了,就是通过XSS让管理员执行恶意JS,从而触发一个CSRF过去让服务器执行shell
0x04 完整的利用过程展示
首先用CS生成一个powershell的payload,这样就可以做到一执行命令就弹shell,接着编写CSRFPayload,用我写的CSRF 原理 利用 防御就可以了。
由于samesite Cookies机制,现在用POST来发CSRF包会导致Cookies不会发过去,但是现在是用IP来认证的,和Cookies无关,所以用POST来发是完全没问题的
接着用requests去插入一个带有XSS的访问记录,这个Payload的功能是去引入攻击机上面的JS来执行
再去访问历史记录查询页面,发现CS Shell弹回来了!,攻击成功!
在复现的时候发现有点坑,对于已经访问过的网站Wfilter似乎不是那么愿意再记录一次,并且有时候记录不下来,不知道原因是什么,猜测是软件自带的bug
0x04 结语这次就懒得写EXP出来了,毕竟这个漏洞用起来应该还是相对鸡肋的,写出来也没有什么用处。这个漏洞首先要求攻击者需要能让自己的流量被记录下来(所以需要能够进入内网中),而且还要管理员点击了历史记录查询页面才会触发,而且XSS Payload的引入过程还并不是那么的稳定因为有的时候还插入不了。因此想要成功利用这个漏洞进行攻击,需要天时(XSS 能够插入)地利(进入了内网)人和(管理员去查看历史访问记录)的配合,这个条件是相当苛刻的,在实战中也不太可能真正使用这么不稳定的攻击。不过逆向这么一个cgi程序还是挺有意思的,玩玩IDA陶冶身心也不错,权当是一次小练习吧
Using Direct Syscalls in Cobalt Strike's Artifact Kit
十分钟教会你MIPS编程入门
可以实现隐蔽持久性的Outlook后门
Newer Cryptographic Advances for the Domain Name System: NSEC5 and Tokenized Queries
This is the third in a multi-part blog series on cryptography and the Domain Name System (DNS). In my last post, I looked at what happens when a DNS query renders a “negative” response – i.e., when a domain name doesn’t exist. I then examined two cryptographic approaches to handling negative responses: NSEC and NSEC3. […]
The post Newer Cryptographic Advances for the Domain Name System: NSEC5 and Tokenized Queries appeared first on Verisign Blog.
XNU内核堆安全特性解读
Cryptographic Tools for Non-Existence in the Domain Name System: NSEC and NSEC3
This is the second in a multi-part blog series on cryptography and the Domain Name System (DNS). In my previous post, I described the first broad scale deployment of cryptography in the DNS, known as the Domain Name System Security Extensions (DNSSEC). I described how a name server can enable a requester to validate the […]
The post Cryptographic Tools for Non-Existence in the Domain Name System: NSEC and NSEC3 appeared first on Verisign Blog.
深度探索:解除文件占用那些坑
Can Bots Manipulate Data and Change Facts to Fiction?
Top Security Threats to Look Out for in 2021
Top Cyber Security Threats to Look Out for in 2021 2020 was unexpectedly defined by a global pandemic. Throughout the...
The post Top Security Threats to Look Out for in 2021 appeared first on McAfee Blog.
Security Update Guide Supports CVEs Assigned by Industry Partners
Security Update Guide Supports CVEs Assigned by Industry Partners
Internalship Program
The Story Of CVE-2021-1648
Author: k0shl of 360 Vulcan Team
SummaryIn January 2021 patch tuesday, MSRC patched a vulnerability in splwow64 service, assigned to CVE-2021-1648(also known as CVE-2020-17008), which merged my two interesting cases which bypass the patch of CVE-2020-0986, one of them also be found by Google Project Zero((https://bugs.chromium.org/p/project-zero/issues/detail?id=2096).actually this include one EoP and two info leak cases.
This vulnerability was planned to patch in October 2020, but MSRC seems found some other serious security problems in service, so they postpone the patch for four months.
BackgroundIn this blog, I don't want to talk more about the mechanism of splwow64, there are a lot of analysis of CVE-2020-0986 before, so let's focus on the vulnerability.
After CVE-2020-0986 had been patched, I make a quick bindiff on splwow64 and gdi32full, and found there are two check added after patch.
One is that Microsoft added two printer handle(or aka cookie?) check functions named "FindDriverForCookie" and "FindPrinterHandle", it will check printer driver handle which store in a global variable.
__int64 __fastcall FindDriverForCookie(__int64 a1) { v3 = qword_1800EABA0; if ( qword_1800EABA0 ) { do { if ( a1 == *(_QWORD *)(v3 + 56) ) //check driver index break; v3 = *(_QWORD *)(v3 + 8); } while ( v3 ); if ( v3 ) ++*(_DWORD *)(v3 + 44); } RtlLeaveCriticalSection(&semUMPD); return v3;// return driver heap } __int64 *__fastcall FindPrinterHandle(__int64 a1, int a2, int a3) { for ( i = *(__int64 **)(v3 + 64); i && (*((_DWORD *)i + 2) != v5 || *((_DWORD *)i + 3) != v4); i = (__int64 *)*i ) //check printer handle ; }Another is that MSRC added two pointer check functions "UMPDStringPointerFromOffset" and "UMPDPointerFromOffset" to check if pointer is validate.
FindDriverForCookie and FindPrinterHandle bypassFirst, I don't know the purpose that Microsoft add FindDriverForCookie and FindPrinterHandle, maybe it's not for mitigation? After quick review, I found there is a command named 0x6A that can set printer handle which the value we can controll in global variable of service to bypass this two check functions.
__int64 __fastcall bAddPrinterHandle(__int64 a1, int a2, int a3, __int64 a4) { v9 = RtlAllocateHeap(*(_QWORD *)(__readgsqword(0x60u) + 48), 0i64, 24i64); v10 = (_QWORD *)v9; if ( v9 ) { *(_DWORD *)(v9 + 8) = v6; *(_DWORD *)(v9 + 12) = v5; *(_QWORD *)(v9 + 16) = v8; RtlEnterCriticalSection(&semUMPD); *v10 = *(_QWORD *)(v4 + 0x40); v7 = 1; *(_QWORD *)(v4 + 0x40) = v10; //add print handle which can be controlled by user RtlLeaveCriticalSection(&semUMPD); } return v7; }By invoking command 0x6A, function bAddPrinterHandle will add print handle to driver heap which stored in global variable |qword_1800EABA0|.
//set print handle to 0xdeadbeef00006666 0:007> p gdi32full!bAddPrinterHandle+0x54: 00007ff8`380fc3bc 44897808 mov dword ptr [rax+8],r15d ds:00000000`0108a428=00000000 0:007> p gdi32full!bAddPrinterHandle+0x58: 00007ff8`380fc3c0 4489700c mov dword ptr [rax+0Ch],r14d ds:00000000`0108a42c=00000000 0:007> r r14d r14d=deadbeef 0:007> r r15d r15d=6666 //driver heap stored in global variable 0:007> dq gdi32full+0xEABA0 l1 00007ff8`381baba0 00000000`0108d000 0:007> dq 108d000+0x40 l1 00000000`0108d040 00000000`0108a420 0:007> dq 108a420+0x8 l1 00000000`0108a428 deadbeef`00006666So we can easy bypass printer handle check during invoking Command 0x6D, and hit the vulnerability code.
case 0x6Du: v31 = FindDriverForCookie(*(_QWORD *)(v6 + 24)); v32 = v31; if ( !v31 ) goto LABEL_137; v33 = FindPrinterHandle(v31, *(_DWORD *)(v6 + 32), *(_DWORD *)(v6 + 36)); ... [vulnerability code] CVE-2021-1648: arbitrary address readLet's talk about information disclosure, CVE-2020-1648 includes a arbitrary address read information disclosure.
if ( v51 != -1 ) { v57 = **(unsigned __int16 ***)(v6 + 0x50); //not check v57 if ( v57 ) { v58 = v57[34]; v59 = v58 + v57[35]; if ( (unsigned int)v59 >= v58 && (unsigned int)v59 <= 0x1FFFE ) memcpy_0(*(void **)(v6 + 88), v57, v59); //arbitrary address read } }The code of case Command 0x6D is too long, so I won't post all of them in my blog. In short, it will check destination address of memcpy if it's in "validate" range, the range of |v6+0x58|, but source address |v57| isn't checked, so we can read arbitrary address.
0:007> r rax=0000000000868a00 rbx=000000000001fffe rcx=0000000000000000 rdx=4141414141414141 rsi=0000000000150200 rdi=00000000008688d0 rip=00007ff9fc008403 rsp=000000000210f480 rbp=000000000210f4f9 r8=100297f000000002 r9=000000000022f000 r10=00000fff3c9c801d r11=000000000210f350 r12=0000000000868920 r13=0000000000868910 r14=0000000000000001 r15=0000000000461c50 iopl=0 nv up ei pl nz na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206 gdi32full!GdiPrinterThunk+0x1a73: 00007ff9fc008403 0fb74a44 movzx ecx,word ptr [rdx+44h] ds:4141414141414185=????Stack trace:
0:007> k Child-SP RetAddr Call Site 000000000210f480 00007ff7558e78ab gdi32full!GdiPrinterThunk+0x1a73 000000000210f560 00007ff7558e84de splwow64+0x78ab 000000000210f650 00007ff7558e9f28 splwow64+0x84de 000000000210f6b0 00007ff9fe3f2e93 splwow64+0x9f28 000000000210f6e0 00007ff9fe3f45b4 ntdll!RtlDeleteCriticalSection+0x363 000000000210f730 00007ff9fc487bd4 ntdll!RtlInitializeResource+0xce4 000000000210faf0 00007ff9fe42ce51 KERNEL32!BaseThreadInitThunk+0x14 000000000210fb20 0000000000000000 ntdll!RtlUserThreadStart+0x21 Another two cases of CVE-2021-1648Another two cases I reported to MSRC is about bypassing offset check functions "UMPDStringPointerFromOffset" and "UMPDPointerFromOffset", I think MSRC made a mistake in these two functions range check.
Splwow64 is a specail service which is compatible with x86 in x86-64 Windows OS, so it always allocate heap which is 32bits, but in CVE-2020-0986 patch, "UMPDStringPointerFromOffset" and "UMPDPointerFromOffset" only check if offset and |portview+offset| is less than 0x7fffffff.
signed __int64 __fastcall UMPDPointerFromOffset(unsigned __int64 *a1, __int64 a2, unsigned int a3) { [...] if ( v3 <= 0x7FFFFFFF && v3 + a3 <= 0x7FFFFFFF ) { *a1 = v3 + a2; return 1i64; } [...] } signed __int64 __fastcall UMPDStringPointerFromOffset(unsigned __int64 *a1, __int64 a2) { [...] if ( v3 > 0x7FFFFFFF ) goto LABEL_12; v4 = (0x7FFFFFFF - v3) >> 1; *a1 = v3 + a2; v5 = (unsigned int)v4; if ( v3 + a2 ) v2 = wcsnlen((const wchar_t *)(v3 + a2), (unsigned int)v4); [...] return result; }But in splwow64 service, so many heaps even stack is allocated in low address, like this:
0:004> pc splwow64!TLPCMgr::ProcessRequest+0x99: 00007ff6`846d7c71 e826490000 call splwow64!operator new[] (00007ff6`846dc59c) 0:004> p splwow64!TLPCMgr::ProcessRequest+0x9e: 00007ff6`846d7c76 488bf0 mov rsi,rax 0:004> r rax rax=00000000007d7c70 0:004> r rsp rsp=000000000217f400So it is possible to exploit through occupy to some important heaps or stack in splwow64 service, I suggest MSRC in my report to check range of pointer if it's in portview section instead of 0x7fffffff.
two cases crash dump:
0:006> r rax=0000000000000000 rbx=00000000012f8360 rcx=000000001363d9e0 rdx=00000000012f8360 rsi=0000000002d60200 rdi=000000001363d9d8 rip=00007fff728956d2 rsp=0000000002cdf230 rbp=0000000000000001 r8=0000000000000028 r9=0000000012345678 r10=000000007fffffff r11=2222222222222222 r12=00007fff57ea8fe0 r13=0000000001208210 r14=000000000120aa50 r15=00007fff72860000 iopl=0 nv up ei pl nz na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206 gdi32full!UMPDStringPointerFromOffset+0x12: 00007fff728956d2 4c8b09 mov r9,qword ptr [rcx] ds:000000001363d9e0=???????????????? 0:006> r rax=0000000000000001 rbx=0000000001628360 rcx=0000000042a3c4a1 rdx=0000000001628360 rsi=0000000000ff0200 rdi=0000000000000000 rip=00007fff7289568a rsp=0000000002ecf3d8 rbp=0000000000000001 r8=0000000000000028 r9=0000000041414141 r10=000000007fffffff r11=2222222222222222 r12=00007fff57ea8fe0 r13=0000000001407160 r14=000000000140a000 r15=00007fff72860000 iopl=0 nv up ei pl nz na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206 gdi32full!UMPDPointerFromOffset+0xa: 00007fff7289568a 4c8b09 mov r9,qword ptr [rcx] ds:0000000042a3c4a1=???????????????? The end of storyIt seems Microsoft redsigned splwow64 printer service, so they postponed the patch for four months, it's really a long time for me to wait a patch since I started my researching on Windows. Hope new printer service will be more secure:P.
Timeline
2020-07-27 Reported to MSRC.
2020-08-19 MSRC decided to put off patch.
2020-08-22 Bounty awarded
2021-01-13 Patch release
通过SMB进行横向移动
友情转载丨安全客2020年度第4季季刊发布
VIPKID SRC邀您共读安全客2020季刊—第4季!
通过 OpenVPN 实现流量审计
为了满足日益增长的渗透测试网络审计需要,特别是从普通的全流量记录,到进一步的HTTPS流量审计,特地设计了一套网络结构,满足简单的傻瓜化远程接入实验室,实现 http/https 全流量记录的功能。
更新记录:
2022年1月11日 更新:另一种流量分流的方式(进程分流)
0x01 流量镜像和审计系统早期的流量审计需求不高,抓下全流量就可以,直接买一个流量分光的设备,双网口,一个网口直接串联到设备中间,另一个网口接到抓流量的服务器上,不用改任何网络架构,就实现目的了。 但现在的要求越来越高,需要对HTTPS这样的加密流量也进行解密了,我们只能重新设计网络架构。
一、选择系统最开始的想法是选用市面上已经有的路由器系统,不是有不少打着名号说自己特别牛逼的全流量审计,解码等等一应俱全的网关,比如 PFsense,WFilter-NGF,RouterOS,openwrt之类的。
经过测试了好几个产品,基本不太能满足需求。 比如 PFsense: SSL/TLS 解码是通过 Squirt 实现的,仅仅能解包记录域名头,并不支持全流量抓包和明文记录,而且由于使用的是 bsd 系统,想自己装一些工具也遇到了比较多的兼容性问题,无奈只好放弃。
尝试了多种系统选择后,最终决定使用 debian + mitmproxy 从轮胎开始一步一步造一辆能跑起来的车。
二、物理架构图客户端接入有两条线路:
- 流量记录线路: Client (192.168.3.x) –> 路由器(192.168.3.1) –> VirtualBox debian (192.168.1.1) –> 4G sim 卡路由器(192.168.8.1) –> 外网
- 非流量记录线路: Client (10.90.45.x) –> 路由器(10.90.45.1) –> 外网
分成两条线路,保证工作和其他娱乐下载等流量分开,也避免造成不必要的流量浪费。
三、配置 VirtualBox debian 10 作为路由器虚拟机搭建步骤省略, 需要配置物理机的网卡 enp0s1 和 enp0s2通过桥接模式映射到虚拟机即可:
物理机 enp0s1 –> 虚拟机enp0s3
物理机 enp0s2 –> 虚拟机enp0s8
- 开启流量转发:
- 使用iptables 配置转发策略:
说明一下: nat 表里的规则是表示 80 443 8080 端口的流量转发到23333端口,实现透明代理,下文会解释 23333 端口的具体作用; filter 表的东西是一些转发和输入策略。
- iptables 规则持久化
使用 iptables-persistent 这个包,将上述规则保存到硬盘 /etc/iptables/rulesv4.save ,使用其他方式实现开机自动加载,方式自选,我使用的是 crontab 里的 @reboot 。
四、配置 mitmproxy 实现透明代理安装最新版 mitmproxy,使用如下命令抓取流量:
/usr/bin/mitmdump -q --mode transparent --showhost -p 23333 --ssl-insecure --cert=cert.pem -s mitm_parse.py -w /data/mitmproxy_traffic/mitm_files/mitm_raw_$(date +"%Y%m%d").mitm默认的 mitmdump 抓取的流量不是标准的通用格式,是 mitmdump 自定义的,为了保证可读性,这里我使用了 mitm_parse.py 这个脚本来对抓取的内容进行处理,脚本内容如下:
from mitmproxy import http import time,re import logging logfile = "/data/mitmproxy_traffic/logs/mitm_parsed_" + time.strftime("%Y%m%d") + ".log" logging.basicConfig(filename=logfile, filemode='a', format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s', datefmt='%H:%M:%S', level=logging.INFO) logger = logging.getLogger('mylogger') def response(flow): logtext = "\n" logtext += "="*50 + "\n" logtext += flow.request.url + "\n" logtext += "-"*25 + "request headers:" + "-"*25 + "\n" for k, v in flow.request.headers.items(): logtext += "%-20s: %s" % (k.upper(), v) + "\n" logtext += "-"*25 + "request content:" + "-"*25 + "\n" logtext += flow.response.content.decode('utf-8','ignore') + "\n" logtext += "-"*25 + "response headers:" + "-"*25 + "\n" for k, v in flow.response.headers.items(): logtext += "%-20s: %s" % (k.upper(), v) + "\n" logtext += "-"*25 + "response content:" + "-"*25 + "\n" logtext += flow.response.content.decode('utf-8','ignore') + "\n" logger.info(logtext)那么这里就有两份数据,一份数据是 mitmdump 抓取的原始数据,存放在 /data/mitmproxy_traffic/mitm_files/目录下,另一份经过脚本处理过的可读性较好的数据,存放在 /data/mitmproxy_traffic/logs/ 目录下。
使用 curl 访问 qq.com 的日志如下:
18:34:12,49 mylogger INFO ================================================== https://125.39.52.26/ -------------------------request headers:------------------------- :AUTHORITY : qq.com USER-AGENT : curl/7.72.0 ACCEPT : */* -------------------------request content:------------------------- <html> <head><title>302 Found</title></head> <body bgcolor="white"> <center><h1>302 Found</h1></center> <hr><center>nginx/1.6.0</center> </body> </html> -------------------------response headers:------------------------- DATE : Mon, 11 Jan 2021 10:34:12 GMT CONTENT-TYPE : text/html SERVER : squid/3.5.24 LOCATION : https://www.qq.com?fromdefault EXPIRES : Mon, 11 Jan 2021 10:35:11 GMT CACHE-CONTROL : max-age=60 VARY : Accept-Encoding X-CACHE : HIT from shenzhen.qq.com -------------------------response content:------------------------- <html> <head><title>302 Found</title></head> <body bgcolor="white"> <center><h1>302 Found</h1></center> <hr><center>nginx/1.6.0</center> </body> </html> 五、dumpcap 抓取全流量可以看到我们的 mitmproxy 的方案并不完美,只抓取了部分端口号的内容,并不能保证所有流量的记录,因此也十分有必要抓取全部的流量作为备份。
使用 dumpcap 抓取全流量:
#!/bin/bash dir=/data/net_traffic_storage/files/$(date +"%Y%m%d")/ if [ ! -d $dir ] then mkdir $dir fi dumpcap -i enp0s8 -w $dir/traffic.pcapng -b filesize:1024000使用 supervisor 或者其他类似的工具将上面的命令进行持久化(经常断电),保证每次开启都能自启动成功。
六、使用 NFS 存储数据到物理机虚拟机的硬盘容量较小,我们也没必要选择存在虚拟机上,所以选择存放在物理机上,使用NFS的方式。 这里也可以直接虚拟机映射硬盘,方式不局限,这里仅作我的选择的记录。
编辑 /etc/fstab,添加如下
192.168.1.128:/data/net_traffic_storage /data/net_traffic_storage nfs defaults 0 0 192.168.1.128:/data/mitmproxy_traffic /data/mitmproxy_traffic nfs defaults 0 0在重启过程中出现过一些因为先后顺序导致的挂载失败,因此写了个计划任务去判断:
0 */1 * * * cat /proc/mounts | grep -q 192.168.1.128 || systemctl restart remote-fs.target && systemctl restart supervisor 七、另一种流量分流的方式(进程分流)(2022年1月更新)上面的方式分流是因为 debian 虚拟机网关已经在流量记录的线路中了,对于网关自己来说就只有一条出口线路,直接设置就OK了。
那么更常见的情况,如网关直连两条线路,要求在网关上进行分流应该怎么操作? 最近遇到这样的需求,下面总结一下对应的方法。
大体思路是利用 cgroup 标记 mitmproxy 的流量,然后用 iptables 和 ip rule 控制其从流量记录的网卡出站,实现记录的目的。
网络介绍:
enp0s1 192.168.30.2 enp0s2 192.168.40.2两张网卡,默认路由走 enp0s1 出口,为非流量记录线路,enp0s2 为流量记录线路。
iptables 规则:
iptables -t nat -A PREROUTING -s 192.168.40.0/24 -p tcp -m tcp --dport 80 -j DNAT --to-destination 192.168.40.2:23333 iptables -t nat -A PREROUTING -s 192.168.40.0/24 -p tcp -m tcp --dport 443 -j DNAT --to-destination 192.168.40.2:23333 iptables -t nat -A PREROUTING -s 192.168.40.0/24 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 192.168.40.2:23333 iptables -t mangle -A OUTPUT -m cgroup --cgroup 0x00110011 -j MARK --set-mark 11 iptables -t nat -A POSTROUTING -m cgroup --cgroup 0x00110011 -o enp0s2 -j MASQUERADEPREROUTING 的规则和上面第一种方法一样,只不过指定了网卡的地址,效果没有区别。
mangle 和 POSTROUTING 的规则需要结合 cgroup 的规则一起理解,就是将其 mark ,然后将 mark 后的流量从 enp0s2 网卡出站。
下面设置 cgroup:
apt-get install cgroup-tools cgroup-bin mkdir /sys/fs/cgroup/net_cls/mitmproxy cd /sys/fs/cgroup/net_cls/mitmproxy echo 0x00110011 > net_cls.classid这里是创建了一个 cgroup 规则,然后创建了一个 mitmproxy 的组,classid 设置为 0x00110011。
然后设置路由
echo 11 mitmproxy >> /etc/iproute2/rt_tables ip rule add fwmark 11 table mitmproxy ip route add default via 192.168.40.1 table mitmproxy这些命令的作用在下文 OpenVPN Client as gateway 都有解释,这里就不详细多做解释了。
然后使用 cgroup 启动 mitmdump :
cgexec -g net_cls:mitmproxy mitmdump --mode transparent --showhost -p 23333这样 mitmdump 的所有流量都会从网卡 enp0s2 出站了,流量也会被记录下来,这样就完美实现需求了。
值得注意的是, ip route 和 cgroup 相关的规则是临时性的,和 iptables 一样,需要进行设置才能实现重启自动生效,这里就不展开了。
这里我们已经实现了流量审计的功能,接下来需要考虑的是如何远程接入审计系统。
0x02 OpenVPN 的网络架构网络流量走向为(在openvpn 接入的一台服务器上执行)
└─$ traceroute 8.8.8.8 traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets 1 192.168.2.189 (192.168.2.189) 2 192.168.2.200 (192.168.2.200) 3 192.168.1.1 (192.168.1.1) 4 192.168.8.1 (192.168.8.1) 5 * * * ...... 0x03 OpenVPN Client as gateway 实现这个需求出现的场景是,我们希望所有接入OpenVPN的客户端,不使用 Server(192.168.2.189) 作为最终的网关,而是使用某一个固定的客户端(192.168.2.200)作为最终的网关。 经过搜索,发现这个需求还比较小众,最终发现了某个帖子里提到了同样的需求,我们以这个帖子为参考。
一、OpenVPN 的 Bridge 和 Route 对比这篇帖子中提到仅有OpenVPN bridge 模式才能实现这样的功能。先解释一下 bridge 模式和 route 模式的区别(来自官方论坛):
TAP 设备(bridge 模式):
- 可以传输非 TCP/IP 层的流量,也就是第二层。
- 需要桥接网络
有下列需求的话,必须使用 bridge 模式
- 需要 LAN 和 VPN 客户端处在同一个广播域
- 需要 LAN 的 DHCP 服务器给 VPN 客户端提供 DHCP 地址
- 需要 Windows 服务器在 VPN 网络中,使用网络发现等功能
TAP 设备的优点:
- 类似一个真正的网络适配器
- 可以传输任何网络协议(IPv4, IPv6, Netalk, IPX, 等等)
- 工作在第二层
- 可以用来桥接网络
TAP 设备的缺点:
- VPN 传输更多广播流量
- VPN 传输更多以太网报文头
- 扩展型差
- 不能用在安卓或者 IOS 设备
相应的 route 模式,也就是 TUN 设备,工作在第三层,优势和劣势与 bridge 模式相反。
根据我们的需求,这里我们选择更底层的 bridge 模式,流量开销的缺点基本可以忽略不计。
二、搭建 OpenVPN (bridge 模式)基本的搭建我们就不赘述了,可以使用官方文档或者一键搭建脚本,需要注意的是,根据官方文档,我们搭建 bridge 模式的话,需要自己创建虚拟网卡 brxxx ,但是在云上一创建就会挂掉,这里我们需要使用其他方式来实现该功能,参考:is-it-possible-to-set-up-a-bridged-vpn-in-aws
在 Server 端配置中写入
dev tap # Define your IP address range within a valid subnet server-bridge 192.168.2.189 255.255.255.0 192.168.2.200 192.168.2.250 client-to-client # Set server tap0 ip address on startup up /etc/openvpn/server/openvpn-server-up.sh # Set client config dir client-config-dir /etc/openvpn/ccd其中 openvpn-server-up.sh 的内容为,并添加可执行权限。
#!/bin/sh ifconfig tap0 192.168.2.189 netmask 255.255.255.0 broadcast 192.168.2.255在 /etc/openvpn/ccd/ 目录下创建跟 Client 配置文件同名的文件,如 wangxiaoqing, 写入
ifconfig-push 192.168.2.211 255.255.255.0代表给 wangxiaoqing 这个用户固定 192.168.2.211 的 IP 地址,有多个用户就以此类推。
这样 Server 端启动时会自动启动 openvpn-server-up.sh 的脚本,给 tap0 的虚拟网卡指定网络配置。
最后的 Server 配置发出来大家参考:
port 1194 proto tcp dev tap ca ca.crt cert server.crt key server.key dh dh.pem auth SHA512 tls-crypt tc.key topology subnet script-security 2 server-bridge 192.168.2.189 255.255.255.0 192.168.2.200 192.168.2.250 push "dhcp-option DNS 192.168.8.1" push "redirect-gateway def1 bypass-dhcp" keepalive 10 120 cipher AES-256-CBC user nobody group nogroup persist-key persist-tun status openvpn-status.log verb 3 crl-verify crl.pem client-to-client up /etc/openvpn/server/openvpn-server-up.sh client-config-dir /etc/openvpn/ccd通过这样的配置,我们就可以实现 Client 接入 VPN 后,访问其他 Client ,或者通过 Server 端上网了。
三、配置 Server 端的路由通过上面的配置,我们实现了最基础也是最常用的 OpenVPN 架构,多 Client 通过 Server 上网。如果仅仅是这样的话,bridge 和 route 模式几乎没区别。 但接下来的设置,就只有 bridge 模式才能实现了。
为了让所有 Client 使用其中一个 Client (192.168.2.200)作为路由,我们需要在 Server 端进行如下设置。
- 先添加一个路由表
- 给 路由表 vpnroute 定义地址范围
- 给 vpnroute 添加路由
第一条为192.168.2.0/24 网段的地址走原始的 Server 网关 192.168.2.189,
第二条为其他的地址走默认的 192.168.2.200 的 Client 网关。
通过命令 ip route show table vpnroute 就能看到我们刚刚添加的路由:
root@openvpn-server:~# ip route show table vpnroute default via 192.168.2.200 dev tap0 192.168.2.0/24 via 192.168.2.189 dev tap0这种根据源地址来定义路由的方式,叫 policy based routing ,而直接通过 route 命令修改的系统路由表,只能根据目的地址的不同来定义路由策略,不能满足我们的需求。
四、配置 Client 端作为网关Client(192.168.2.200) 本来是一个普通的 Linux 服务器,想把它作为 gateway ,是需要一些额外配置的。
开启转发:
sysctl –w net.ipv4.ip_forward=1设置 SNAT
iptables -t nat -A POSTROUTING -s 192.168.2.0/24 ! -d 192.168.2.0/24 -j MASQUERADE -o eth0通过这样设置,如果 Client 只有一个网络出口的话,就已经可以实现我们需要的功能了。 之后会具体讲到一种多网络接口的特殊情况。
五、Client 多网络出口的情况下192.168.2.200 这台服务器,作为网关时本地有好几张网卡都可以出外网:
10.90.45.20/24 192.168.1.128/24 192.168.8.105/24默认的路由表是走 10.90.45.1 这个网关出去上网的,正常的情况下这个机器并不需要走流量审计的网络。这里就涉及到如何让来自 OpenVPN 的流量走 192.168.1.128/24 这个网卡的路由。( 考虑过使用 192.168.1.1 这台审计网关直接接入 OpenVPN ,但因为虚拟机的网卡是通过桥接模式映射的,再通过 bridge 接入 openvpn 冲突了(猜测),所以就选择直接在物理机上接入。)
这里我们还是使用上文提到的 policy-based route ,通过路由表规则来配置:
先确定通过其中一个网卡能顺利出网:
traceroute --interface=enp1s0f1 8.8.8.8然后配置路由规则:
echo 201 debian_lan >> /etc/iproute2/rt_tables ip route add default via 192.168.1.1 dev enp1s0f1 table debian_lan ip rule add from 192.168.1.0/24 table debian_lan ip rule add fwmark 0x1111 table debian_lan使用 iptables 转发
/sbin/iptables -t mangle -A PREROUTING -i tap0 -s 192.168.2.0/24 ! -d 192.168.2.0/24 -j MARK --set-mark 0x1111 /sbin/iptables -t nat -A POSTROUTING -s 192.168.2.0/24 ! -d 192.168.2.0/24 -j MASQUERADE -o enp1s0f1其他命令在前文已经解释过了,这里需要特别注意的就是如下两句:
ip rule add fwmark 0x1111 table debian_lan iptables -t mangle -A PREROUTING -i tap0 -s 192.168.2.0/24 ! -d 192.168.2.0/24 -j MARK --set-mark 0x1111第一条的意思是给 路由表 debian_lan 添加标记,第二条 iptables 的意思是在走路由策略之前,给流量包添加标记。符合这个标记的流量包就会走 debian_lan 这个路由表。 最开始我缺少这两条规则,NAT 一直不成功,但是如果尝试默认路由的话又可以 ,加上这两条规则做一下标记后就完美成功了。
0x04 一些不足以上这套方案,其实还是有很多问题的,比如结构比较复杂耦合程度较高,最严重的问题还是:
- 只能根据端口号判断 http(s) 流量,如果是非标准端口号,比如 8443 甚至 38443 这种端口,那就没有办法做证书劫持了,只能记录加密流量,同时其他协议流量也不能解密。所以也记录了全流量作为补充。
- 用户体验不太好,中间人劫持肯定是会弹出不信任的警告的,要么手动接受要么导入根证书,就算是根证书也因为 CN 和 SAN 的限制,无法让浏览器或者其他检验了该名称的工具实现完美信任。
能实现目前的程度我暂时觉得已经不错了,后续如果有其他思路的话再进一步改进。
0x05 参考链接Set up your Debian router / gateway in 10 minutes
Route the traffic over specific interface for a process in linux